From ae66e4450bae71cbcfe7d25b2c8282e028adf710 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 28 Nov 2025 15:38:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86Dify=E8=B0=83?= =?UTF-8?q?=E7=94=A8API=E8=BF=94=E5=9B=9E403=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/service.yaml | 144 +++++++++++++++++++++++++++++ src/routes/v1/services/security.ts | 24 ++++- 2 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 docs/service.yaml diff --git a/docs/service.yaml b/docs/service.yaml new file mode 100644 index 0000000..0d805ed --- /dev/null +++ b/docs/service.yaml @@ -0,0 +1,144 @@ +openapi: 3.1.1 +info: + title: SaaS Platform Admin Query API + version: 1.0.0 + description: | + 通用数据查询接口,供系统管理员智能体(如 Dify Agent)使用。 + 接收自然语言问题,返回结构化平台数据。 + 所有查询自动应用租户隔离、权限控制和敏感字段脱敏。 + +servers: + - url: http://localhost:8080/api/v1 + description: Local Development Server + - url: https://service.aigc-quickapp.com/api/v1 + description: Internal Admin API Gateway (SaaS Platform) + +paths: + /admin/query: + post: + summary: 查询 SaaS 平台数据(自然语言接口) + description: | + 接收自然语言形式的问题,智能解析并返回平台相关数据。 + 支持订单、用户、商品、租户、GMV、日志等维度的查询。 + 示例问题: + - "商家 TechStore 昨天的 GMV 是多少?" + - "列出所有未发货的订单" + - "用户 12345 最近一次登录时间?" + + operationId: queryPlatformData + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QueryRequest' + + responses: + '200': + description: 查询成功,返回结构化数据 + content: + application/json: + schema: + $ref: '#/components/schemas/QueryResponse' + '400': + description: 请求参数无效(如缺少 question) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: 未授权(API Key 缺失或无效) + '403': + description: 权限不足(如越权查询其他租户) + '500': + description: 内部服务错误 + + security: + - AdminApiKey: [] + +components: + schemas: + QueryRequest: + type: object + required: + - question + properties: + question: + type: string + description: | + 用户提出的自然语言问题。 + 必须清晰、具体,包含必要上下文(如商家名、时间范围等)。 + example: "tenant-abc 上周的订单总量是多少?" + context: + type: object + description: | + 可选上下文信息,用于辅助解析(如当前用户所属租户)。 + 由调用方(如 Dify Agent)根据对话上下文填充。 + properties: + current_tenant_id: + type: string + description: 当前操作者所属租户 ID + example: "tenant-xyz" + operator_role: + type: string + description: 操作者角色(如 admin, support) + enum: [admin, support, super_admin] + additionalProperties: false + + QueryResponse: + type: object + required: + - result + properties: + result: + oneOf: + - type: object + description: 结构化数据对象(如单条记录) + - type: array + description: 数据列表(如订单列表) + items: + type: object + - type: string + description: 简单值(如数字、文本) + - type: null + description: 无结果 + example: + orders_count: 142 + gmv: "¥28,400.00" + return_rate: "2.1%" + metadata: + type: object + description: 查询元信息(调试用) + properties: + executed_query_type: + type: string + description: 实际执行的查询类型 + example: "sales_summary_by_tenant" + data_source: + type: string + description: 数据来源 + example: "orders_analytics_view" + processing_time_ms: + type: integer + additionalProperties: false + + ErrorResponse: + type: object + required: + - error + properties: + error: + type: string + example: "MISSING_REQUIRED_FIELD: 'question' is required" + details: + type: object + additionalProperties: true + + securitySchemes: + AdminApiKey: + type: http + scheme: bearer + bearerFormat: JWT + description: | + Bearer Token 格式的管理员 API Key。 + 示例:`Authorization: Bearer sk-admin-xxxxxxxxxxxx` \ No newline at end of file diff --git a/src/routes/v1/services/security.ts b/src/routes/v1/services/security.ts index 9b8dab7..9047e2a 100644 --- a/src/routes/v1/services/security.ts +++ b/src/routes/v1/services/security.ts @@ -72,11 +72,29 @@ export const sanitizeResult = (data: any): any => { // API Key 验证(修复:使用 Elysia 插件类型) export const verifyApiKey = (app: Elysia) => app.onBeforeHandle((ctx) => { - const authHeader = ctx.request.headers.get('authorization'); - if (!authHeader || !authHeader.startsWith('Bearer ')) { + // 检查所有可能的Authorization头大小写变体 + let authHeader: string | null = null; + for (const [key, value] of ctx.request.headers) { + if (key.toLowerCase() === 'authorization') { + authHeader = value; + break; + } + } + + // 检查Bearer格式 + if (!authHeader) { return new Response('Unauthorized', { status: 401 }); } - const token = authHeader.substring(7); + + const authHeaderLower = authHeader.toLowerCase(); + if (!authHeaderLower.startsWith('bearer ')) { + return new Response('Unauthorized', { status: 401 }); + } + + // 提取token(支持任意大小写的Bearer前缀) + const token = authHeader.substring(authHeader.indexOf(' ') + 1).trim(); + + // 验证token if (token !== process.env.ADMIN_API_KEY) { return new Response('Forbidden', { status: 403 }); }