fix: 修复了Dify调用API返回403错误的问题

This commit is contained in:
2025-11-28 15:38:53 +08:00
parent 97c0c7c0fc
commit ae66e4450b
2 changed files with 165 additions and 3 deletions

144
docs/service.yaml Normal file
View File

@@ -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`

View File

@@ -72,11 +72,29 @@ export const sanitizeResult = (data: any): any => {
// API Key 验证(修复:使用 Elysia 插件类型) // API Key 验证(修复:使用 Elysia 插件类型)
export const verifyApiKey = (app: Elysia) => app.onBeforeHandle((ctx) => { export const verifyApiKey = (app: Elysia) => app.onBeforeHandle((ctx) => {
const authHeader = ctx.request.headers.get('authorization'); // 检查所有可能的Authorization头大小写变体
if (!authHeader || !authHeader.startsWith('Bearer ')) { 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 }); 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) { if (token !== process.env.ADMIN_API_KEY) {
return new Response('Forbidden', { status: 403 }); return new Response('Forbidden', { status: 403 });
} }