fix: 修复了Dify调用API返回403错误的问题
This commit is contained in:
144
docs/service.yaml
Normal file
144
docs/service.yaml
Normal 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`
|
||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user