1. 修复 SDK 文档 API 路由顺序问题
- 将静态路由 /sdk-docs, /test-script, /secrets 移到动态路由 /{task_id} 之前
- 解决 "请求参数验证失败" 错误
2. 优化错误页面体验
- 使用 sessionStorage 传递错误信息,URL 保持干净
- 使用 router.replace 替代 push,浏览器返回不会停留在错误页
- 记录来源页面,支持正确返回
3. 增强网络错误处理
- 区分超时、网络错误、服务不可用
- 后端未启动时显示友好的 "服务暂时不可用" 提示
4. 添加定时任务模块文档
This commit is contained in:
@@ -75,6 +75,191 @@ class TestScriptRequest(BaseModel):
|
||||
params: Optional[dict] = None
|
||||
|
||||
|
||||
# ==================== Static Routes (must be before dynamic routes) ====================
|
||||
|
||||
@router.get("/sdk-docs")
|
||||
async def get_sdk_docs():
|
||||
"""获取SDK文档"""
|
||||
return {
|
||||
"functions": [
|
||||
{
|
||||
"name": "log",
|
||||
"signature": "log(message: str, level: str = 'INFO')",
|
||||
"description": "记录日志",
|
||||
"example": "log('处理完成', 'INFO')"
|
||||
},
|
||||
{
|
||||
"name": "print",
|
||||
"signature": "print(*args)",
|
||||
"description": "打印输出",
|
||||
"example": "print('Hello', 'World')"
|
||||
},
|
||||
{
|
||||
"name": "ai",
|
||||
"signature": "ai(prompt: str, system: str = None, model: str = None, temperature: float = 0.7)",
|
||||
"description": "调用AI模型",
|
||||
"example": "result = ai('生成一段问候语', system='你是友善的助手')"
|
||||
},
|
||||
{
|
||||
"name": "dingtalk",
|
||||
"signature": "dingtalk(webhook: str, content: str, title: str = None, at_all: bool = False)",
|
||||
"description": "发送钉钉消息",
|
||||
"example": "dingtalk(webhook_url, '# 标题\\n内容')"
|
||||
},
|
||||
{
|
||||
"name": "wecom",
|
||||
"signature": "wecom(webhook: str, content: str, msg_type: str = 'markdown')",
|
||||
"description": "发送企微消息",
|
||||
"example": "wecom(webhook_url, '消息内容')"
|
||||
},
|
||||
{
|
||||
"name": "http_get",
|
||||
"signature": "http_get(url: str, headers: dict = None, params: dict = None)",
|
||||
"description": "发起GET请求",
|
||||
"example": "resp = http_get('https://api.example.com/data')"
|
||||
},
|
||||
{
|
||||
"name": "http_post",
|
||||
"signature": "http_post(url: str, data: any = None, headers: dict = None)",
|
||||
"description": "发起POST请求",
|
||||
"example": "resp = http_post('https://api.example.com/submit', {'key': 'value'})"
|
||||
},
|
||||
{
|
||||
"name": "db_query",
|
||||
"signature": "db_query(sql: str, params: dict = None)",
|
||||
"description": "执行只读SQL查询",
|
||||
"example": "rows = db_query('SELECT * FROM users WHERE status = :status', {'status': 1})"
|
||||
},
|
||||
{
|
||||
"name": "get_var",
|
||||
"signature": "get_var(key: str, default: any = None)",
|
||||
"description": "获取持久化变量",
|
||||
"example": "counter = get_var('counter', 0)"
|
||||
},
|
||||
{
|
||||
"name": "set_var",
|
||||
"signature": "set_var(key: str, value: any)",
|
||||
"description": "设置持久化变量",
|
||||
"example": "set_var('counter', counter + 1)"
|
||||
},
|
||||
{
|
||||
"name": "del_var",
|
||||
"signature": "del_var(key: str)",
|
||||
"description": "删除持久化变量",
|
||||
"example": "del_var('temp_data')"
|
||||
},
|
||||
{
|
||||
"name": "get_param",
|
||||
"signature": "get_param(key: str, default: any = None)",
|
||||
"description": "获取任务参数",
|
||||
"example": "prompt = get_param('prompt', '默认提示词')"
|
||||
},
|
||||
{
|
||||
"name": "get_params",
|
||||
"signature": "get_params()",
|
||||
"description": "获取所有任务参数",
|
||||
"example": "params = get_params()"
|
||||
},
|
||||
{
|
||||
"name": "get_tenants",
|
||||
"signature": "get_tenants(app_code: str = None)",
|
||||
"description": "获取租户列表",
|
||||
"example": "tenants = get_tenants('notification-service')"
|
||||
},
|
||||
{
|
||||
"name": "get_tenant_config",
|
||||
"signature": "get_tenant_config(tenant_id: str, app_code: str, key: str = None)",
|
||||
"description": "获取租户的应用配置",
|
||||
"example": "webhook = get_tenant_config('tenant1', 'notification-service', 'dingtalk_webhook')"
|
||||
},
|
||||
{
|
||||
"name": "get_all_tenant_configs",
|
||||
"signature": "get_all_tenant_configs(app_code: str)",
|
||||
"description": "获取所有租户的应用配置",
|
||||
"example": "configs = get_all_tenant_configs('notification-service')"
|
||||
},
|
||||
{
|
||||
"name": "get_secret",
|
||||
"signature": "get_secret(key: str)",
|
||||
"description": "获取密钥(优先租户级)",
|
||||
"example": "api_key = get_secret('api_key')"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{"name": "task_id", "description": "当前任务ID"},
|
||||
{"name": "tenant_id", "description": "当前租户ID"},
|
||||
{"name": "trace_id", "description": "当前执行追踪ID"}
|
||||
],
|
||||
"libraries": [
|
||||
{"name": "json", "description": "JSON处理"},
|
||||
{"name": "re", "description": "正则表达式"},
|
||||
{"name": "math", "description": "数学函数"},
|
||||
{"name": "random", "description": "随机数"},
|
||||
{"name": "hashlib", "description": "哈希函数"},
|
||||
{"name": "base64", "description": "Base64编解码"},
|
||||
{"name": "datetime", "description": "日期时间处理"},
|
||||
{"name": "timedelta", "description": "时间差"},
|
||||
{"name": "urlencode/quote/unquote", "description": "URL编码"}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/test-script")
|
||||
async def test_script(data: TestScriptRequest, db: Session = Depends(get_db)):
|
||||
"""测试脚本执行"""
|
||||
executor = ScriptExecutor(db)
|
||||
result = executor.test_script(
|
||||
script_content=data.script_content,
|
||||
task_id=0,
|
||||
tenant_id=data.tenant_id,
|
||||
params=data.params
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/secrets")
|
||||
async def list_secrets(
|
||||
tenant_id: Optional[str] = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取密钥列表"""
|
||||
query = db.query(Secret)
|
||||
if tenant_id:
|
||||
query = query.filter(Secret.tenant_id == tenant_id)
|
||||
|
||||
items = query.order_by(desc(Secret.created_at)).all()
|
||||
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"id": s.id,
|
||||
"tenant_id": s.tenant_id,
|
||||
"secret_key": s.secret_key,
|
||||
"description": s.description,
|
||||
"created_at": s.created_at,
|
||||
"updated_at": s.updated_at
|
||||
}
|
||||
for s in items
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/secrets")
|
||||
async def create_secret(data: SecretCreate, db: Session = Depends(get_db)):
|
||||
"""创建密钥"""
|
||||
secret = Secret(
|
||||
tenant_id=data.tenant_id,
|
||||
secret_key=data.secret_key,
|
||||
secret_value=data.secret_value,
|
||||
description=data.description
|
||||
)
|
||||
db.add(secret)
|
||||
db.commit()
|
||||
db.refresh(secret)
|
||||
|
||||
return {"success": True, "id": secret.id}
|
||||
|
||||
|
||||
# ==================== Task CRUD ====================
|
||||
|
||||
@router.get("")
|
||||
@@ -272,194 +457,7 @@ async def get_task_logs(
|
||||
}
|
||||
|
||||
|
||||
# ==================== Script Testing ====================
|
||||
|
||||
@router.post("/test-script")
|
||||
async def test_script(data: TestScriptRequest, db: Session = Depends(get_db)):
|
||||
"""测试脚本执行"""
|
||||
executor = ScriptExecutor(db)
|
||||
result = executor.test_script(
|
||||
script_content=data.script_content,
|
||||
task_id=0,
|
||||
tenant_id=data.tenant_id,
|
||||
params=data.params
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
# ==================== SDK Documentation ====================
|
||||
|
||||
@router.get("/sdk-docs")
|
||||
async def get_sdk_docs():
|
||||
"""获取SDK文档"""
|
||||
return {
|
||||
"functions": [
|
||||
{
|
||||
"name": "log",
|
||||
"signature": "log(message: str, level: str = 'INFO')",
|
||||
"description": "记录日志",
|
||||
"example": "log('处理完成', 'INFO')"
|
||||
},
|
||||
{
|
||||
"name": "print",
|
||||
"signature": "print(*args)",
|
||||
"description": "打印输出",
|
||||
"example": "print('Hello', 'World')"
|
||||
},
|
||||
{
|
||||
"name": "ai",
|
||||
"signature": "ai(prompt: str, system: str = None, model: str = None, temperature: float = 0.7)",
|
||||
"description": "调用AI模型",
|
||||
"example": "result = ai('生成一段问候语', system='你是友善的助手')"
|
||||
},
|
||||
{
|
||||
"name": "dingtalk",
|
||||
"signature": "dingtalk(webhook: str, content: str, title: str = None, at_all: bool = False)",
|
||||
"description": "发送钉钉消息",
|
||||
"example": "dingtalk(webhook_url, '# 标题\\n内容')"
|
||||
},
|
||||
{
|
||||
"name": "wecom",
|
||||
"signature": "wecom(webhook: str, content: str, msg_type: str = 'markdown')",
|
||||
"description": "发送企微消息",
|
||||
"example": "wecom(webhook_url, '消息内容')"
|
||||
},
|
||||
{
|
||||
"name": "http_get",
|
||||
"signature": "http_get(url: str, headers: dict = None, params: dict = None)",
|
||||
"description": "发起GET请求",
|
||||
"example": "resp = http_get('https://api.example.com/data')"
|
||||
},
|
||||
{
|
||||
"name": "http_post",
|
||||
"signature": "http_post(url: str, data: any = None, headers: dict = None)",
|
||||
"description": "发起POST请求",
|
||||
"example": "resp = http_post('https://api.example.com/submit', {'key': 'value'})"
|
||||
},
|
||||
{
|
||||
"name": "db_query",
|
||||
"signature": "db_query(sql: str, params: dict = None)",
|
||||
"description": "执行只读SQL查询",
|
||||
"example": "rows = db_query('SELECT * FROM users WHERE status = :status', {'status': 1})"
|
||||
},
|
||||
{
|
||||
"name": "get_var",
|
||||
"signature": "get_var(key: str, default: any = None)",
|
||||
"description": "获取持久化变量",
|
||||
"example": "counter = get_var('counter', 0)"
|
||||
},
|
||||
{
|
||||
"name": "set_var",
|
||||
"signature": "set_var(key: str, value: any)",
|
||||
"description": "设置持久化变量",
|
||||
"example": "set_var('counter', counter + 1)"
|
||||
},
|
||||
{
|
||||
"name": "del_var",
|
||||
"signature": "del_var(key: str)",
|
||||
"description": "删除持久化变量",
|
||||
"example": "del_var('temp_data')"
|
||||
},
|
||||
{
|
||||
"name": "get_param",
|
||||
"signature": "get_param(key: str, default: any = None)",
|
||||
"description": "获取任务参数",
|
||||
"example": "prompt = get_param('prompt', '默认提示词')"
|
||||
},
|
||||
{
|
||||
"name": "get_params",
|
||||
"signature": "get_params()",
|
||||
"description": "获取所有任务参数",
|
||||
"example": "params = get_params()"
|
||||
},
|
||||
{
|
||||
"name": "get_tenants",
|
||||
"signature": "get_tenants(app_code: str = None)",
|
||||
"description": "获取租户列表",
|
||||
"example": "tenants = get_tenants('notification-service')"
|
||||
},
|
||||
{
|
||||
"name": "get_tenant_config",
|
||||
"signature": "get_tenant_config(tenant_id: str, app_code: str, key: str = None)",
|
||||
"description": "获取租户的应用配置",
|
||||
"example": "webhook = get_tenant_config('tenant1', 'notification-service', 'dingtalk_webhook')"
|
||||
},
|
||||
{
|
||||
"name": "get_all_tenant_configs",
|
||||
"signature": "get_all_tenant_configs(app_code: str)",
|
||||
"description": "获取所有租户的应用配置",
|
||||
"example": "configs = get_all_tenant_configs('notification-service')"
|
||||
},
|
||||
{
|
||||
"name": "get_secret",
|
||||
"signature": "get_secret(key: str)",
|
||||
"description": "获取密钥(优先租户级)",
|
||||
"example": "api_key = get_secret('api_key')"
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{"name": "task_id", "description": "当前任务ID"},
|
||||
{"name": "tenant_id", "description": "当前租户ID"},
|
||||
{"name": "trace_id", "description": "当前执行追踪ID"}
|
||||
],
|
||||
"libraries": [
|
||||
{"name": "json", "description": "JSON处理"},
|
||||
{"name": "re", "description": "正则表达式"},
|
||||
{"name": "math", "description": "数学函数"},
|
||||
{"name": "random", "description": "随机数"},
|
||||
{"name": "hashlib", "description": "哈希函数"},
|
||||
{"name": "base64", "description": "Base64编解码"},
|
||||
{"name": "datetime", "description": "日期时间处理"},
|
||||
{"name": "timedelta", "description": "时间差"},
|
||||
{"name": "urlencode/quote/unquote", "description": "URL编码"}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# ==================== Secrets ====================
|
||||
|
||||
@router.get("/secrets")
|
||||
async def list_secrets(
|
||||
tenant_id: Optional[str] = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取密钥列表"""
|
||||
query = db.query(Secret)
|
||||
if tenant_id:
|
||||
query = query.filter(Secret.tenant_id == tenant_id)
|
||||
|
||||
items = query.order_by(desc(Secret.created_at)).all()
|
||||
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"id": s.id,
|
||||
"tenant_id": s.tenant_id,
|
||||
"secret_key": s.secret_key,
|
||||
"description": s.description,
|
||||
"created_at": s.created_at,
|
||||
"updated_at": s.updated_at
|
||||
}
|
||||
for s in items
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/secrets")
|
||||
async def create_secret(data: SecretCreate, db: Session = Depends(get_db)):
|
||||
"""创建密钥"""
|
||||
secret = Secret(
|
||||
tenant_id=data.tenant_id,
|
||||
secret_key=data.secret_key,
|
||||
secret_value=data.secret_value,
|
||||
description=data.description
|
||||
)
|
||||
db.add(secret)
|
||||
db.commit()
|
||||
db.refresh(secret)
|
||||
|
||||
return {"success": True, "id": secret.id}
|
||||
|
||||
# ==================== Secrets (dynamic routes) ====================
|
||||
|
||||
@router.put("/secrets/{secret_id}")
|
||||
async def update_secret(secret_id: int, data: SecretUpdate, db: Session = Depends(get_db)):
|
||||
|
||||
Reference in New Issue
Block a user