Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
@@ -0,0 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据源模块
"""
from core.data_source.api import router
__all__ = ['router']
+419
View File
@@ -0,0 +1,419 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Data Source API - 数据源接口
提供数据源的增删改查、执行、测试等功能
"""
import json
import logging
from typing import List, Any, Dict, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import StreamingResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from core.application.model import Application
from app.config import settings
from app.base_schema import PaginatedResponse, ResponseModel
from core.data_source.model import DataSource
from core.data_source.schema import (
DataSourceCreate,
DataSourceUpdate,
DataSourceResponse,
DataSourceSimpleOut,
DataSourcePreviewRequest,
DataSourceExecuteRequest,
DataSourceTestRequest,
DataSourceCopyRequest,
DataSourceImportCheckIn,
DataSourceImportCheckOut,
DataSourceImportIn,
AIGenerateSqlRequest,
AIGenerateSqlResponse,
)
from core.data_source.service import DataSourceService
from core.data_source.import_export import (
DataSourceImportExportException,
export_config as export_data_source_config,
check_import as check_data_source_import,
import_config as import_data_source_config,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/data-source", tags=["数据源管理"])
# ============ 静态路径接口(必须放在动态路径之前) ============
@router.get("", response_model=PaginatedResponse[DataSourceResponse], summary="获取数据源列表")
async def list_data_source(
page: int = Query(default=1, ge=1, description="页码"),
page_size: int = Query(default=settings.PAGE_SIZE, ge=1, le=settings.PAGE_MAX_SIZE, alias="pageSize", description="每页数量"),
application_id: Optional[str] = Query(default=None, alias="applicationId", description="所属应用ID"),
name: str = Query(default=None, description="名称(模糊查询)"),
code: str = Query(default=None, description="编码(模糊查询)"),
source_type: str = Query(default=None, alias="sourceType", description="类型"),
status: bool = Query(default=None, description="状态"),
db: AsyncSession = Depends(get_db),
):
"""获取数据源列表(分页,自动应用数据权限)"""
items, total = await DataSourceService.get_list_with_data_scope(
db, page=page, page_size=page_size,
application_id=application_id,
name=name, code=code, source_type=source_type, status=status,
)
# 批量查询应用名称
app_ids = list({item.application_id for item in items if item.application_id})
app_name_map = {}
if app_ids:
app_result = await db.execute(
select(Application.id, Application.name).where(Application.id.in_(app_ids))
)
app_name_map = {row.id: row.name for row in app_result}
# 构建响应,附加 application_name
result_items = []
for item in items:
item_dict = DataSourceResponse.model_validate(item).model_dump()
item_dict["application_name"] = app_name_map.get(item.application_id, "")
result_items.append(item_dict)
return PaginatedResponse(items=result_items, total=total)
@router.post("", response_model=DataSourceResponse, summary="创建数据源")
async def create_data_source(
data: DataSourceCreate,
db: AsyncSession = Depends(get_db),
):
"""创建数据源"""
# 检查编码是否已存在
if await DataSourceService.check_code_exists(db, data.code):
raise HTTPException(status_code=400, detail=f"编码已存在: {data.code}")
source = await DataSourceService.create(db, data.model_dump())
await db.commit()
logger.info(f"数据源已创建: {source.code}")
return source
@router.get("/get/all", response_model=List[DataSourceSimpleOut], summary="获取所有数据源")
async def list_all_data_source(
application_id: Optional[str] = Query(default=None, alias="applicationId", description="所属应用ID"),
db: AsyncSession = Depends(get_db),
):
"""获取所有数据源(不分页,用于下拉选择)"""
items = await DataSourceService.get_all(db, application_id=application_id)
return items
@router.post("/ai/generate-sql", response_model=AIGenerateSqlResponse, summary="AI 生成 SQL")
async def ai_generate_sql(
body: AIGenerateSqlRequest,
db: AsyncSession = Depends(get_db),
):
"""
使用 AI 生成 SQL 语句
根据用户的自然语言描述,结合选中的表结构和表关系,自动生成 SQL 查询语句。
"""
try:
# 将表关系转换为字典格式
table_relations = [rel.model_dump() for rel in body.table_relations] if body.table_relations else []
result = await DataSourceService.ai_generate_sql(
db=db,
user_question=body.user_question,
db_connection=body.db_connection,
database=body.database,
schema_name=body.schema_name,
selected_tables=body.selected_tables,
table_fields=body.table_fields,
table_relations=table_relations,
include_table_relations=body.include_table_relations,
model_id=body.model_id,
)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"AI 生成 SQL 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}")
@router.post("/test", summary="测试数据源配置")
async def test_data_source(
body: DataSourceTestRequest,
db: AsyncSession = Depends(get_db),
):
"""测试数据源配置(不保存,直接执行)"""
try:
config = {
'source_type': body.source_type,
'api_url': body.api_url,
'api_method': body.api_method,
'api_headers': body.api_headers,
'api_query_params': body.api_query_params,
'api_body_type': body.api_body_type,
'api_body': body.api_body,
'api_content_type': body.api_content_type,
'api_timeout': body.api_timeout,
'api_data_path': body.api_data_path,
'api_auth_type': body.api_auth_type,
'api_auth_config': body.api_auth_config,
'api_retry_count': body.api_retry_count,
'api_retry_interval': body.api_retry_interval,
'api_success_condition': body.api_success_condition,
'api_proxy': body.api_proxy,
'api_follow_redirects': body.api_follow_redirects,
'api_verify_ssl': body.api_verify_ssl,
'sql_content': body.sql_content,
'db_connection': body.db_connection,
'static_data': body.static_data,
'params_def': body.params_def,
'result_type': body.result_type,
'tree_config': body.tree_config,
'field_mapping': body.field_mapping,
'chart_config': body.chart_config,
}
data = await DataSourceService.execute_temp(db, config, body.params)
total = len(data) if isinstance(data, list) else 1
return {
'data': data,
'total': total,
'limited': DataSourceService.MAX_ROWS_TEST,
'success': True
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"数据源测试失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"测试失败: {str(e)}")
@router.get("/check-code/{code}", summary="检查编码是否可用")
async def check_code_available(
code: str,
db: AsyncSession = Depends(get_db),
):
"""检查数据源编码是否可用"""
exists = await DataSourceService.check_code_exists(db, code)
return {'available': not exists}
@router.post("/import/check", response_model=DataSourceImportCheckOut, summary="导入预检查")
async def check_import_data_source(
data: DataSourceImportCheckIn,
db: AsyncSession = Depends(get_db),
):
"""导入预检查:检查数据源编码是否冲突"""
try:
return await check_data_source_import(db, data.code)
except DataSourceImportExportException as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/import", response_model=DataSourceResponse, summary="导入数据源配置")
async def import_data_source(
data: DataSourceImportIn,
db: AsyncSession = Depends(get_db),
):
"""导入数据源配置"""
try:
source = await import_data_source_config(db, data.model_dump())
await db.commit()
return source
except DataSourceImportExportException as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/code/{code}", response_model=DataSourceResponse, summary="根据编码获取数据源详情")
async def get_data_source_by_code(
code: str,
db: AsyncSession = Depends(get_db),
):
"""根据编码获取数据源详情(包含参数定义)"""
source = await DataSourceService.get_by_code(db, code)
if not source:
raise HTTPException(status_code=404, detail=f"数据源不存在: {code}")
return source
# ============ 执行接口 ============
@router.get("/execute/{code}", summary="执行数据源(GET")
async def execute_data_source_get(
request: Request,
code: str,
db: AsyncSession = Depends(get_db),
):
"""根据编码执行数据源获取数据(GET 方式)"""
# 获取所有查询参数
params = dict(request.query_params)
try:
data = await DataSourceService.execute(db, code, params)
return {'data': data}
except ValueError as e:
if "不存在" in str(e):
raise HTTPException(status_code=404, detail=str(e))
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"数据源执行失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"执行失败: {str(e)}")
@router.post("/execute/{code}", summary="执行数据源(POST")
async def execute_data_source_post(
code: str,
body: DataSourceExecuteRequest,
db: AsyncSession = Depends(get_db),
):
"""根据编码执行数据源获取数据(POST 方式)"""
try:
data = await DataSourceService.execute(db, code, body.params)
return {'data': data}
except ValueError as e:
if "不存在" in str(e):
raise HTTPException(status_code=404, detail=str(e))
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"数据源执行失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"执行失败: {str(e)}")
# ============ 动态路径接口(必须放在静态路径之后) ============
@router.get("/{source_id}", response_model=DataSourceResponse, summary="获取数据源详情")
async def get_data_source(
source_id: str,
db: AsyncSession = Depends(get_db),
):
"""获取数据源详情"""
source = await DataSourceService.get_by_id(db, source_id)
if not source:
raise HTTPException(status_code=404, detail="数据源不存在")
return source
@router.get("/{source_id}/export", summary="导出数据源配置")
async def export_data_source(
source_id: str,
db: AsyncSession = Depends(get_db),
):
"""导出数据源配置为 JSON"""
try:
config = await export_data_source_config(db, source_id)
content = json.dumps(config, ensure_ascii=False, indent=2)
return StreamingResponse(
iter([content]),
media_type="application/json",
headers={
"Content-Disposition": f'attachment; filename="{config["code"]}.json"'
},
)
except DataSourceImportExportException as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/{source_id}", response_model=DataSourceResponse, summary="更新数据源")
async def update_data_source(
source_id: str,
data: DataSourceUpdate,
db: AsyncSession = Depends(get_db),
):
"""更新数据源"""
# 如果更新了编码,检查新编码是否已存在
if data.code:
if await DataSourceService.check_code_exists(db, data.code, exclude_id=source_id):
raise HTTPException(status_code=400, detail=f"编码已存在: {data.code}")
source = await DataSourceService.update(db, source_id, data.model_dump(exclude_unset=True))
if not source:
raise HTTPException(status_code=404, detail="数据源不存在")
await db.commit()
logger.info(f"数据源已更新: {source.code}")
return source
@router.delete("/{source_id}", response_model=ResponseModel, summary="删除数据源")
async def delete_data_source(
source_id: str,
db: AsyncSession = Depends(get_db),
):
"""删除数据源"""
success = await DataSourceService.delete(db, source_id)
if not success:
raise HTTPException(status_code=404, detail="数据源不存在")
await db.commit()
return ResponseModel(message="删除成功")
# ============ 预览和其他接口 ============
@router.post("/{source_id}/preview", summary="预览数据源数据")
async def preview_data_source(
source_id: str,
body: DataSourcePreviewRequest,
db: AsyncSession = Depends(get_db),
):
"""预览数据源数据(用于调试)"""
try:
data = await DataSourceService.execute_by_id(db, source_id, body.params)
# 限制返回数量
if isinstance(data, list) and len(data) > body.limit:
data = data[:body.limit]
return {
'data': data,
'total': len(data) if isinstance(data, list) else 1,
'limited': body.limit
}
except ValueError as e:
if "不存在" in str(e):
raise HTTPException(status_code=404, detail=str(e))
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"数据源预览失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"预览失败: {str(e)}")
@router.post("/{source_id}/copy", response_model=DataSourceResponse, summary="复制数据源")
async def copy_data_source(
source_id: str,
body: DataSourceCopyRequest,
db: AsyncSession = Depends(get_db),
):
"""复制数据源"""
# 检查新编码是否已存在
if await DataSourceService.check_code_exists(db, body.new_code):
raise HTTPException(status_code=400, detail=f"编码已存在: {body.new_code}")
source = await DataSourceService.copy(db, source_id, body.new_code, body.new_name)
if not source:
raise HTTPException(status_code=404, detail="数据源不存在")
await db.commit()
logger.info(f"数据源已复制: {body.new_code}")
return source
@router.post("/{source_id}/clear-cache", response_model=ResponseModel, summary="清除数据源缓存")
async def clear_data_source_cache(
source_id: str,
db: AsyncSession = Depends(get_db),
):
"""清除数据源缓存"""
source = await DataSourceService.get_by_id(db, source_id)
if not source:
raise HTTPException(status_code=404, detail="数据源不存在")
await DataSourceService.clear_cache(source.code)
return ResponseModel(message=f"缓存已清除: {source.code}")
@@ -0,0 +1,149 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据源导入/导出
"""
import logging
from typing import Any, Dict
from sqlalchemy.ext.asyncio import AsyncSession
from core.data_source.model import DataSource
from core.data_source.service import DataSourceService
logger = logging.getLogger(__name__)
EXPORT_FIELD_KEYS = [
"name", "code", "source_type", "description", "status",
"api_url", "api_method", "api_headers", "api_query_params", "api_body_type",
"api_body", "api_content_type", "api_timeout", "api_data_path",
"api_auth_type", "api_auth_config", "api_retry_count", "api_retry_interval",
"api_success_condition", "api_proxy", "api_follow_redirects", "api_verify_ssl",
"sql_content", "db_connection", "static_data", "params",
"result_type", "tree_config", "field_mapping", "chart_config",
"cache_enabled", "cache_ttl",
]
class DataSourceImportExportException(Exception):
"""数据源导入导出异常"""
pass
def _source_to_export_dict(source: DataSource) -> Dict[str, Any]:
"""将数据源转为可导出配置(不含 id、统计字段)"""
return {
"name": source.name,
"code": source.code,
"source_type": source.source_type or "static",
"description": source.description or "",
"status": source.status if source.status is not None else True,
"api_url": source.api_url or "",
"api_method": source.api_method or "GET",
"api_headers": source.api_headers or {},
"api_query_params": source.api_query_params or [],
"api_body_type": source.api_body_type or "none",
"api_body": source.api_body or {},
"api_content_type": source.api_content_type or "",
"api_timeout": source.api_timeout or 30,
"api_data_path": source.api_data_path or "",
"api_auth_type": source.api_auth_type or "none",
"api_auth_config": source.api_auth_config or {},
"api_retry_count": source.api_retry_count or 0,
"api_retry_interval": source.api_retry_interval or 1,
"api_success_condition": source.api_success_condition or {},
"api_proxy": source.api_proxy or "",
"api_follow_redirects": (
source.api_follow_redirects if source.api_follow_redirects is not None else True
),
"api_verify_ssl": (
source.api_verify_ssl if source.api_verify_ssl is not None else True
),
"sql_content": source.sql_content or "",
"db_connection": source.db_connection or "default",
"static_data": source.static_data or [],
"params": source.params or [],
"result_type": source.result_type or "list",
"tree_config": source.tree_config or {},
"field_mapping": source.field_mapping or {},
"chart_config": source.chart_config or {},
"cache_enabled": source.cache_enabled or False,
"cache_ttl": source.cache_ttl or 300,
}
def _build_import_payload(data: Dict[str, Any]) -> Dict[str, Any]:
"""从导入 JSON 构建创建参数"""
defaults = {
"source_type": "static",
"description": "",
"status": True,
"api_url": "",
"api_method": "GET",
"api_headers": {},
"api_query_params": [],
"api_body_type": "none",
"api_body": {},
"api_content_type": "",
"api_timeout": 30,
"api_data_path": "",
"api_auth_type": "none",
"api_auth_config": {},
"api_retry_count": 0,
"api_retry_interval": 1,
"api_success_condition": {},
"api_proxy": "",
"api_follow_redirects": True,
"api_verify_ssl": True,
"sql_content": "",
"db_connection": "default",
"static_data": [],
"params": [],
"result_type": "list",
"tree_config": {},
"field_mapping": {},
"chart_config": {},
"cache_enabled": False,
"cache_ttl": 300,
}
payload = {**defaults}
for key in EXPORT_FIELD_KEYS:
if key in data and data[key] is not None:
payload[key] = data[key]
payload["application_id"] = data.get("application_id")
return payload
async def export_config(db: AsyncSession, source_id: str) -> Dict[str, Any]:
"""导出数据源配置"""
source = await DataSourceService.get_by_id(db, source_id)
if not source:
raise DataSourceImportExportException("数据源不存在")
return _source_to_export_dict(source)
async def check_import(db: AsyncSession, code: str) -> Dict[str, Any]:
"""导入预检查:编码是否冲突"""
code_exists = False
if code:
code_exists = await DataSourceService.check_code_exists(db, code)
return {
"code_exists": code_exists,
"can_import": not code_exists,
}
async def import_config(db: AsyncSession, data: Dict[str, Any]) -> DataSource:
"""导入数据源配置"""
if not data.get("name"):
raise DataSourceImportExportException("缺少必要字段: name")
if not data.get("code"):
raise DataSourceImportExportException("缺少必要字段: code")
if await DataSourceService.check_code_exists(db, data["code"]):
raise DataSourceImportExportException(f"数据源编码已存在: {data['code']}")
create_data = _build_import_payload(data)
source = await DataSourceService.create(db, create_data)
logger.info("数据源导入成功: %s", source.code)
return source
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Data Source Model - 数据源模型
用于管理系统数据源配置,支持 API、SQL、静态数据等多种类型
"""
from sqlalchemy import Column, String, Text, Boolean, Integer, JSON
from app.base_model import BaseModel
class DataSource(BaseModel):
"""
数据源模型
支持三种数据源类型:
1. API - 调用外部或内部 API 接口
2. SQL - 执行 SQL 查询(只读)
3. Static - 静态数据
"""
__tablename__ = "core_data_source"
# 所属应用(逻辑外键关联 core_application
application_id = Column(String(21), nullable=True, index=True, comment="所属应用ID")
# 基本信息
name = Column(String(100), nullable=False, comment="数据源名称")
code = Column(String(50), unique=True, index=True, nullable=False, comment="数据源编码(唯一标识)")
source_type = Column(String(20), default='static', comment="数据源类型: api/sql/static")
description = Column(Text, default='', comment="描述说明")
status = Column(Boolean, default=True, comment="是否启用")
# ===== API 配置 =====
api_url = Column(String(500), default='', comment="API地址,支持 {param} 占位符")
api_method = Column(String(10), default='GET', comment="请求方法: GET/POST/PUT/DELETE/PATCH")
api_headers = Column(JSON, default=dict, comment="请求头配置")
api_query_params = Column(JSON, default=list, comment="Query参数列表 [{key, value, description, enabled}]")
api_body_type = Column(String(20), default='none', comment="请求体类型: none/json/form-data/x-www-form-urlencoded/raw")
api_body = Column(JSON, default=dict, comment="请求体模板")
api_content_type = Column(String(50), default='', comment="Content-Typeraw模式时使用")
api_timeout = Column(Integer, default=30, comment="请求超时时间(秒)")
api_data_path = Column(String(100), default='', comment="响应数据路径,如 data.list")
# ===== API 认证配置 =====
api_auth_type = Column(String(20), default='none', comment="认证类型: none/bearer_token/basic_auth/api_key")
api_auth_config = Column(JSON, default=dict, comment="认证配置 {token, username, password, key_name, key_value, key_position}")
# ===== API 高级配置 =====
api_retry_count = Column(Integer, default=0, comment="重试次数(0表示不重试)")
api_retry_interval = Column(Integer, default=1, comment="重试间隔(秒)")
api_success_condition = Column(JSON, default=dict, comment="成功条件 {status_codes, field_path, field_value}")
api_proxy = Column(String(200), default='', comment="代理地址")
api_follow_redirects = Column(Boolean, default=True, comment="是否跟随重定向")
api_verify_ssl = Column(Boolean, default=True, comment="是否验证SSL证书")
# ===== SQL 配置 =====
sql_content = Column(Text, default='', comment="SQL语句,使用 :param 作为参数占位符")
db_connection = Column(String(50), default='default', comment="数据库连接名称")
# ===== 静态数据 =====
static_data = Column(JSON, default=list, comment="静态数据(JSON 数组)")
# ===== 参数定义 =====
params = Column(JSON, default=list, comment="参数定义列表")
# ===== 结果处理 =====
result_type = Column(String(20), default='list', comment="结果类型: list/tree/object/value/chart-*")
tree_config = Column(JSON, default=dict, comment="树形转换配置")
field_mapping = Column(JSON, default=dict, comment="字段映射配置")
chart_config = Column(JSON, default=dict, comment="图表配置")
# ===== 缓存配置 =====
cache_enabled = Column(Boolean, default=False, comment="是否启用缓存")
cache_ttl = Column(Integer, default=300, comment="缓存时间(秒)")
def __repr__(self):
return f"<DataSource {self.name} ({self.code})>"
+238
View File
@@ -0,0 +1,238 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Data Source Schema - 数据源数据验证模式
"""
from datetime import datetime
from typing import Optional, List, Any, Dict
from pydantic import BaseModel, ConfigDict, Field
from app.base_schema import CSTDatetime
class DataSourceBase(BaseModel):
"""数据源基础Schema"""
application_id: Optional[str] = Field(None, description="所属应用ID")
name: str = Field(..., description="数据源名称")
code: str = Field(..., pattern=r"^[a-zA-Z][a-zA-Z0-9_]*$", description="数据源编码(字母开头,只能包含字母、数字和下划线)")
source_type: str = Field(default='static', description="数据源类型: api/sql/static")
description: str = Field(default='', description="描述说明")
status: bool = Field(default=True, description="是否启用")
# API 配置
api_url: str = Field(default='', description="API地址")
api_method: str = Field(default='GET', description="请求方法: GET/POST/PUT/DELETE/PATCH")
api_headers: Dict[str, str] = Field(default_factory=dict, description="请求头")
api_query_params: List[Dict[str, Any]] = Field(default_factory=list, description="Query参数列表")
api_body_type: str = Field(default='none', description="请求体类型: none/json/form-data/x-www-form-urlencoded/raw")
api_body: Dict[str, Any] = Field(default_factory=dict, description="请求体模板")
api_content_type: str = Field(default='', description="Content-Typeraw模式时使用")
api_timeout: int = Field(default=30, description="超时时间")
api_data_path: str = Field(default='', description="响应数据路径")
# API 认证配置
api_auth_type: str = Field(default='none', description="认证类型: none/bearer_token/basic_auth/api_key")
api_auth_config: Dict[str, Any] = Field(default_factory=dict, description="认证配置")
# API 高级配置
api_retry_count: int = Field(default=0, description="重试次数")
api_retry_interval: int = Field(default=1, description="重试间隔(秒)")
api_success_condition: Dict[str, Any] = Field(default_factory=dict, description="成功条件")
api_proxy: str = Field(default='', description="代理地址")
api_follow_redirects: bool = Field(default=True, description="是否跟随重定向")
api_verify_ssl: bool = Field(default=True, description="是否验证SSL证书")
# SQL 配置
sql_content: str = Field(default='', description="SQL语句")
db_connection: str = Field(default='default', description="数据库连接")
# 静态数据
static_data: List[Any] = Field(default_factory=list, description="静态数据")
# 参数定义
params: List[Dict[str, Any]] = Field(default_factory=list, description="参数定义")
# 结果处理
result_type: str = Field(default='list', description="结果类型")
tree_config: Dict[str, Any] = Field(default_factory=dict, description="树形配置")
field_mapping: Dict[str, str] = Field(default_factory=dict, description="字段映射")
chart_config: Dict[str, Any] = Field(default_factory=dict, description="图表配置")
# 缓存配置
cache_enabled: bool = Field(default=False, description="是否启用缓存")
cache_ttl: int = Field(default=300, description="缓存时间")
class DataSourceCreate(DataSourceBase):
"""数据源创建Schema"""
pass
class DataSourceUpdate(BaseModel):
"""数据源更新Schema"""
application_id: Optional[str] = None
name: Optional[str] = None
code: Optional[str] = Field(None, pattern=r"^[a-zA-Z][a-zA-Z0-9_]*$", description="数据源编码(字母开头,只能包含字母、数字和下划线)")
source_type: Optional[str] = None
description: Optional[str] = None
status: Optional[bool] = None
api_url: Optional[str] = None
api_method: Optional[str] = None
api_headers: Optional[Dict[str, str]] = None
api_query_params: Optional[List[Dict[str, Any]]] = None
api_body_type: Optional[str] = None
api_body: Optional[Dict[str, Any]] = None
api_content_type: Optional[str] = None
api_timeout: Optional[int] = None
api_data_path: Optional[str] = None
api_auth_type: Optional[str] = None
api_auth_config: Optional[Dict[str, Any]] = None
api_retry_count: Optional[int] = None
api_retry_interval: Optional[int] = None
api_success_condition: Optional[Dict[str, Any]] = None
api_proxy: Optional[str] = None
api_follow_redirects: Optional[bool] = None
api_verify_ssl: Optional[bool] = None
sql_content: Optional[str] = None
db_connection: Optional[str] = None
static_data: Optional[List[Any]] = None
params: Optional[List[Dict[str, Any]]] = None
result_type: Optional[str] = None
tree_config: Optional[Dict[str, Any]] = None
field_mapping: Optional[Dict[str, str]] = None
chart_config: Optional[Dict[str, Any]] = None
cache_enabled: Optional[bool] = None
cache_ttl: Optional[int] = None
class DataSourceResponse(DataSourceBase):
"""数据源响应Schema"""
id: str
sort: int = 0
is_deleted: bool = False
sys_create_datetime: Optional[CSTDatetime] = None
sys_update_datetime: Optional[CSTDatetime] = None
model_config = ConfigDict(from_attributes=True)
class DataSourceSimpleOut(BaseModel):
"""数据源简单输出(用于下拉选择)"""
id: str
application_id: Optional[str] = None
name: str
code: str
source_type: str
result_type: str = "list"
description: str = ""
model_config = ConfigDict(from_attributes=True)
class DataSourcePreviewRequest(BaseModel):
"""数据源预览请求"""
params: Dict[str, Any] = Field(default_factory=dict)
limit: int = Field(default=100, ge=1, le=1000)
class DataSourceExecuteRequest(BaseModel):
"""数据源执行请求"""
params: Dict[str, Any] = Field(default_factory=dict)
class DataSourceTestRequest(BaseModel):
"""数据源测试请求(临时配置)"""
source_type: str
# API 配置
api_url: str = ""
api_method: str = "GET"
api_headers: Dict[str, str] = Field(default_factory=dict)
api_query_params: List[Dict[str, Any]] = Field(default_factory=list)
api_body_type: str = "none"
api_body: Dict[str, Any] = Field(default_factory=dict)
api_content_type: str = ""
api_timeout: int = 30
api_data_path: str = ""
# API 认证
api_auth_type: str = "none"
api_auth_config: Dict[str, Any] = Field(default_factory=dict)
# API 高级
api_retry_count: int = 0
api_retry_interval: int = 1
api_success_condition: Dict[str, Any] = Field(default_factory=dict)
api_proxy: str = ""
api_follow_redirects: bool = True
api_verify_ssl: bool = True
# SQL 配置
sql_content: str = ""
db_connection: str = "default"
# 静态数据
static_data: List[Any] = Field(default_factory=list)
# 参数
params_def: List[Dict[str, Any]] = Field(default_factory=list)
params: Dict[str, Any] = Field(default_factory=dict)
# 结果处理
result_type: str = "list"
tree_config: Dict[str, Any] = Field(default_factory=dict)
field_mapping: Dict[str, str] = Field(default_factory=dict)
chart_config: Dict[str, Any] = Field(default_factory=dict)
class DataSourceCopyRequest(BaseModel):
"""数据源复制请求"""
new_code: str = Field(..., description="新编码")
new_name: str = Field(default="", description="新名称")
class DataSourceImportCheckIn(BaseModel):
"""数据源导入预检查请求"""
code: str = Field(..., description="数据源编码")
class DataSourceImportCheckOut(BaseModel):
"""数据源导入预检查结果"""
code_exists: bool = Field(..., description="数据源编码是否已存在")
can_import: bool = Field(..., description="是否可以直接导入(编码不冲突)")
class DataSourceImportIn(DataSourceBase):
"""数据源配置导入"""
pass
class TableRelation(BaseModel):
"""表关系定义"""
id: str = Field(default="", description="关系ID")
sourceTable: str = Field(..., description="源表名")
sourceField: str = Field(..., description="源字段名")
targetTable: str = Field(..., description="目标表名")
targetField: str = Field(..., description="目标字段名")
relationType: str = Field(default="many-to-one", description="关系类型")
class AIGenerateSqlRequest(BaseModel):
"""AI 生成 SQL 请求"""
model_config = ConfigDict(protected_namespaces=())
user_question: str = Field(..., description="用户问题(自然语言描述)")
db_connection: str = Field(default="default", description="数据库连接名称")
database: str = Field(default="", description="数据库名")
schema_name: str = Field(default="", description="Schema 名称")
selected_tables: List[str] = Field(default_factory=list, description="选中的表名列表")
table_fields: Dict[str, List[Dict[str, Any]]] = Field(default_factory=dict, description="表字段信息")
table_relations: List[TableRelation] = Field(default_factory=list, description="表关系列表")
include_table_relations: bool = Field(default=True, description="是否包含表关系信息")
model_id: str = Field(..., description="LLM 模型 ID")
class AIGenerateSqlResponse(BaseModel):
"""AI 生成 SQL 响应"""
sql: str = Field(..., description="生成的 SQL 语句")
thought: str = Field(default="", description="生成思路")
params: List[Dict[str, Any]] = Field(default_factory=list, description="推荐的参数定义")
File diff suppressed because it is too large Load Diff