Skip to content

管理端 · 外部聚合服务 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_KEY
    • AI_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>

2. 角色与权限建议

  • 平台管理员(UserRole.PLATFORM_ADMIN):
    • 允许创建/查看/禁用本地记录的外部 Key;
    • 允许调用外部管理员接口创建新的 Key;
    • 允许把 Key 分配给任意学校/用户;
    • 允许按管理员模式查询全站日志。
  • 学校管理员(UserRole.SCHOOL_ADMIN):
    • 无任何 Key 管理与分配权限;
    • 默认无权访问本文档相关接口(如后续仅开放日志只读,再行评估)。
  • 教师/学生:
    • 无管理权限,仅在调用 AI 功能时按生效规则使用被分配的 Key。

3. 环境变量(新增)

为对接外部“管理员接口”,需要新增四个可选配置(按服务分别配置):

  • NEW_API_ADMIN_ACCESS_TOKEN:外部 NEW_API 管理员 AccessToken
  • NEW_API_ADMIN_USER_ID:外部 NEW_API 管理员用户 ID(数值)
  • AI_INTENT_ADMIN_ACCESS_TOKEN:外部 AI_INTENT 管理员 AccessToken
  • AI_INTENT_ADMIN_USER_ID:外部 AI_INTENT 管理员用户 ID(数值)

说明:

  • 这四个变量仅用于服务端代表“平台管理员”调用外部管理员接口(创建 token / 查询全站日志等)。
  • 若未配置,则禁用相应管理员能力(仅允许使用本地已有的 Key 与按 Key 拉取日志)。

4. 数据模型(新增表)

为支持按学校/用户分配及审计,建议新增两张表:

  1. 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)
  1. 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 各自独立):

  1. 用户级分配(user→provider)
  2. 学校级默认(school→provider, is_default=true)
  3. 全局默认(回退到 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 字段;外加 providerfetched_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.pyapp/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. 里程碑与落地顺序

  1. 后端:新增数据表与加密工具;实现 Key 本地管理 CRUD;
  2. 后端:对接外部管理员接口(创建 Key、日志查询代理);
  3. 后端:实现 Key 解析器并接入 AI 调用链路;
  4. 前端管理端:Provider/Key/分配/日志四页; 5)(可选)日志归档与报表。

11. 已确认结论

  • 学校管理员不能创建外部 Key(仅平台管理员可创建/录入/修改/删除);
  • 学校管理员对 Key 无任何权限(含查看、分配);
  • 暂不实现“远端撤销/禁用”外部 token 的联动;
  • Key 明文仅在创建成功时返回一次(其后仅脱敏展示)满足需求;
  • 暂不做按班级/课程等更细粒度的分配与生效控制。