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 插件类型)
|
||||
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 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user