Appearance
管理端 · 外部聚合服务 API Key 与日志管理(提案)
本提案用于确定平台管理端如何对接两类外部聚合服务:
- NEW_API(
NEW_API_BASE_URL) - AI_INTENT(
AI_INTENT_BASE_URL)
目标:
- 平台管理员可在本系统内直接调用外部管理员接口创建不同的 API Key;
- 将 Key 分配给不同“作用域”(学校/用户),支持同校共用或按教师单独配置;
- 支持调用外部日志接口,按全站/个人/按 Key 维度查询使用日志;
- 业务侧调用 AI 时,按“用户 → 学校 → 全局默认”的优先级解析生效 Key。
1. 现状与依赖
- 现有配置(见
app/core/config.py)已包含:NEW_API_BASE_URL,NEW_API_KEYAI_INTENT_BASE_URL,AI_INTENT_KEY
- 业务调用目前使用全局 Key(例如
app/api/ai.py中通过settings.NEW_API_KEY/settings.AI_INTENT_KEY透传到上游)。 - 提供的 Windows 测试脚本与文档(
docs/test-create-token.ps1,docs/test-fetch-logs.ps1,docs/windows-api-tests.md)展示了外部管理员接口形态:- 创建 Token:
POST /api/token/,随后通过GET /api/token/search?keyword=...获取sk-...样式的 key; - 查询日志:
- 全站(管理员):
GET /api/log/ - 当前用户:
GET /api/log/self - 按 Key:
GET /api/log/token?key=sk-xxx | raw_key
- 全站(管理员):
- 管理员接口要求请求头同时携带:
Authorization: <AccessToken>与New-Api-User: <AdminUserId>。
- 创建 Token:
2. 角色与权限建议
- 平台管理员(
UserRole.PLATFORM_ADMIN):- 允许创建/查看/禁用本地记录的外部 Key;
- 允许调用外部管理员接口创建新的 Key;
- 允许把 Key 分配给任意学校/用户;
- 允许按管理员模式查询全站日志。
- 学校管理员(
UserRole.SCHOOL_ADMIN):- 无任何 Key 管理与分配权限;
- 默认无权访问本文档相关接口(如后续仅开放日志只读,再行评估)。
- 教师/学生:
- 无管理权限,仅在调用 AI 功能时按生效规则使用被分配的 Key。
3. 环境变量(新增)
为对接外部“管理员接口”,需要新增四个可选配置(按服务分别配置):
NEW_API_ADMIN_ACCESS_TOKEN:外部 NEW_API 管理员 AccessTokenNEW_API_ADMIN_USER_ID:外部 NEW_API 管理员用户 ID(数值)AI_INTENT_ADMIN_ACCESS_TOKEN:外部 AI_INTENT 管理员 AccessTokenAI_INTENT_ADMIN_USER_ID:外部 AI_INTENT 管理员用户 ID(数值)
说明:
- 这四个变量仅用于服务端代表“平台管理员”调用外部管理员接口(创建 token / 查询全站日志等)。
- 若未配置,则禁用相应管理员能力(仅允许使用本地已有的 Key 与按 Key 拉取日志)。
4. 数据模型(新增表)
为支持按学校/用户分配及审计,建议新增两张表:
- external_api_keys(外部 Key 主表)
- id (pk)
- provider (enum:
new_api|ai_intent) - name (str) 外部 token 名称(创建时自定义,如
cli-token-2025...) - remote_token_id (str | null) 外部返回的 token id
- key_encrypted (text) 加密存储的密钥值(建议使用应用级对称加密,如 Fernet/AES)
- key_masked (str) 脱敏展示(如
sk-xxxx...abcd) - status (enum:
active|disabled|revoked) - metadata (jsonb) 额度/组别/备注等(如 remain_quota/unlimited_quota/group)
- created_by_user_id (fk -> users.id)
- created_at (timestamp)
- external_api_key_assignments(Key 分配表)
- id (pk)
- provider (enum:
new_api|ai_intent) - api_key_id (fk -> external_api_keys.id)
- scope_type (enum:
school|user) - scope_id (int) 当 scope_type=school 时为 schools.id;当 scope_type=user 时为 users.id
- is_default (bool) 该作用域默认 Key(同一作用域与 provider 仅允许 0/1 个默认)
- created_by_user_id (fk -> users.id)
- created_at (timestamp)
生效与回收策略:
- 删除 assignment 仅影响生效映射,不影响 Key 主表;
- 禁用 Key(status=disabled)将导致所有分配该 Key 的作用域失效;
- 若外部支持撤销/禁用 token,可扩展“远端撤销”动作,当前先不强制。 当前不实现,可后续扩展。
5. 生效规则(Key 解析优先级)
当用户在本系统调用 AI 时,按以下顺序解析生效 Key(按 provider 各自独立):
- 用户级分配(user→provider)
- 学校级默认(school→provider, is_default=true)
- 全局默认(回退到
settings.NEW_API_KEY/settings.AI_INTENT_KEY,若也未配置则报 503/配置缺失)
为便于排错,提供调试接口:
GET /admin/integrations/resolve-key?provider=new_api&user_id=...返回解析路径与最终使用的 Key(仅脱敏展示)。
6. 管理端 API 设计(后端)
路由前缀建议:/admin/integrations
6.1 Provider 列表
GET /admin/integrations/providers- 返回可用 provider 及配置状态(是否已配置管理员凭据)。
响应示例:
json
{
"providers": [
{"id": "new_api", "base_url": "https://www.api.moduoduo.cn", "admin_configured": true},
{"id": "ai_intent", "base_url": "https://www.moduoduo.pro", "admin_configured": false}
]
}6.2 Key 管理(本地)
GET /admin/integrations/{provider}/keys- 分页列出本地 Key(含分配计数、状态、创建人、创建时间)。
GET /admin/integrations/{provider}/keys/{id}POST /admin/integrations/{provider}/keys- 手工录入一个已有外部 Key(若企业预分配的情况)。
PATCH /admin/integrations/{provider}/keys/{id}- 更新名称、备注、状态(支持禁用)。
DELETE /admin/integrations/{provider}/keys/{id}- 软删除本地记录;若需要可扩展“远端撤销”。
权限说明:
- 所有操作(
GET/POST/PATCH/DELETE)仅平台管理员; - 学校管理员无权访问该组接口(含只读)。
请求示例(手工录入):
json
{
"name": "school-001-default",
"key": "sk-xxxxxxxx",
"metadata": {"group": "auto", "unlimited_quota": true}
}6.3 Key 管理(通过外部管理员接口创建)
POST /admin/integrations/{provider}/keys/create-remote- 仅平台管理员;需要已配置对应的
*_ADMIN_ACCESS_TOKEN与*_ADMIN_USER_ID。 - 由后端代理调用:
POST {BASE_URL}/api/token/创建;GET {BASE_URL}/api/token/search?keyword=...获取 key(sk-...)。
- 后端将完整明文 Key 使用应用密钥加密后保存,仅首次在响应中明示返回一次给前端;其后仅返回
key_masked。
- 仅平台管理员;需要已配置对应的
请求示例:
json
{
"name": "cli-token-20250925-120000",
"expired_time": -1,
"remain_quota": 500000,
"unlimited_quota": true,
"model_limits_enabled": false,
"group": "auto"
}响应示例:
json
{
"id": 101,
"provider": "new_api",
"name": "cli-token-20250925-120000",
"key": "sk-xxxxxxxxxxxxxxxx", // 仅首次返回
"key_masked": "sk-xxxx...abcd",
"status": "active",
"metadata": {"group": "auto", "unlimited_quota": true},
"created_at": "2025-09-25T04:00:00Z"
}6.4 Key 分配(学校/用户)
GET /admin/integrations/{provider}/assignments- 支持按 scope_type/scope_id 过滤;返回 Key 脱敏信息与状态。
POST /admin/integrations/{provider}/assignments- 创建分配,若
is_default=true则清理同作用域同 provider 的其他默认标记。
- 创建分配,若
DELETE /admin/integrations/{provider}/assignments/{id}
权限说明:仅平台管理员;学校管理员无权分配。
请求示例:
json
{
"api_key_id": 101,
"scope_type": "school",
"scope_id": 12,
"is_default": true
}6.5 Key 解析调试
GET /admin/integrations/resolve-key?provider=new_api&user_id=...- 返回解析步骤(是否命中用户级/学校级/全局)与
key_masked。
- 返回解析步骤(是否命中用户级/学校级/全局)与
7. 日志查询(后端代理外部接口 + 学校管理员只读)
路由前缀建议:/admin/integrations/{provider}/logs
管理员全站日志:
GET /admin/integrations/{provider}/logs/admin- 查询参数保持与外部一致:
page, page_size, type, start_timestamp, end_timestamp, username, token_name, model_name, channel, group - 由后端携带管理员头:
Authorization: <*_ADMIN_ACCESS_TOKEN>New-Api-User: <*_ADMIN_USER_ID>
- 仅平台管理员可用。
- 查询参数保持与外部一致:
当前用户日志(管理员):
GET /admin/integrations/{provider}/logs/self- 后端仍使用管理员头代理调用外部
/api/log/self;用户信息由外部据头部判断(若外部要求)。 - 仅平台管理员可用。
- 后端仍使用管理员头代理调用外部
按 Key 查询(管理员):
GET /admin/integrations/{provider}/logs/by-token?key=sk-xxx- 直连外部
/api/log/token,无需管理员头;支持sk-前缀或裸 key。 - 仅平台管理员可用。
- 直连外部
新增(学校管理员只读):
- 查看本校被分配的 Key(脱敏):
GET /admin/integrations/{provider}/school/keys - 直接查询本校日志(自动选择 Key):
GET /admin/integrations/{provider}/school/logs- 优先使用默认分配(is_default=true);若无默认且仅有一把则使用该把;若多把且未设置默认,返回 400
返回格式:
- 透传外部响应中的
data字段;外加provider与fetched_at作为本系统补充信息。
是否需要本地落库?
- 第一阶段:不落库,只做透传;
- 第二阶段(可选):增加“拉取并归档”任务(定时/手动),写入
external_usage_logs以便做报表。
8. 服务实现要点
- Key 安全存储:
- 新增应用级对称加密密钥(例如
.env配置EXTERNAL_KEY_ENCRYPT_SECRET),使用 Fernet/AES 加密key_encrypted; - 仅在服务端使用时解密,不向前端返回明文(除首次创建返回)。
- 新增应用级对称加密密钥(例如
- 统一 Key 解析:
- 新增
ApiKeyResolver服务(例如app/services/api_key_resolver.py),对外暴露resolve(provider, user); - 改造
app/api/ai.py与app/services/ai/new_api_client.py,在发起上游请求时,优先调用解析器获取覆盖用 Key; NewApiClient已支持api_key参数,可直接注入覆盖值。
- 新增
- 权限拦截:
- 平台管理员接口使用
require_platform_admin - 学校管理员只读接口使用
require_school_admin
- 平台管理员接口使用
9. 前端管理端页面(简述)
- Provider 概览:显示配置状态与基础信息;
- Key 列表:搜索/分页/禁用;支持“创建远端 Key”“手工录入 Key”;
- 分配管理:
- 选择 provider 与 Key;
- 选择作用域(学校/用户),可设为默认;
- 显示当前作用域下的“最终生效 Key”;
- 日志查询:提供三个标签页(全站/个人/按 Key),参数与脚本一致;支持导出 JSON。
10. 里程碑与落地顺序
- 后端:新增数据表与加密工具;实现 Key 本地管理 CRUD;
- 后端:对接外部管理员接口(创建 Key、日志查询代理);
- 后端:实现 Key 解析器并接入 AI 调用链路;
- 前端管理端:Provider/Key/分配/日志四页; 5)(可选)日志归档与报表。
11. 已确认结论
- 学校管理员不能创建外部 Key(仅平台管理员可创建/录入/修改/删除);
- 学校管理员对 Key 无任何权限(含查看、分配);
- 暂不实现“远端撤销/禁用”外部 token 的联动;
- Key 明文仅在创建成功时返回一次(其后仅脱敏展示)满足需求;
- 暂不做按班级/课程等更细粒度的分配与生效控制。