Files

18 KiB
Raw Permalink Blame History

API契约-管理面

管理面(作者,会话档)全部端点:引导与会话、引擎状态、全员总览、key 管理、日志明细(下钻)、订阅权益 / 充值入账、成员管理、审计日志。《API契约-公共约定》(定稿)全部条款在此适用不再重述——前缀 /api/admin、会话 cookie 鉴权、错误信封、ISO 8601 UTC 左闭右开、quota_usd 后端换算、snake_case 同构。

项目 内容
状态 定稿(2026-10-12,作者认可实现;含 10-09 支付拆表与 R16)
关联 《API契约-公共约定》(定稿——总纲)· 八篇交互设计 · api_keys / usage_logs / subscriptions / topups / qpu_history / members / users / sessions 表字段设计(DTO 字段权威)· 需求清单.md §9(访问主体模型)
产出 端点清单、请求 / 响应字段、错误码、本稿裁定项(§10)与遗留项(§11)

1. 端点总览

# 端点 方法 用途
1 /api/admin/setup-state GET 入口三态判定(登录页 / 引导页分流)
2 /api/admin/setup POST 引导页落库(一次性)
3 /api/admin/session POST / GET / DELETE 登录 / 当前会话 / 登出
4 /api/admin/password PUT 改密(删其他会话)
5 /api/admin/engine GET 引擎状态(最近轮次摘要)
6 /api/admin/overview GET 全员总览(健康带数据 + 三维聚合)
7 /api/admin/keys GET / POST key 清单 / 登记
8 /api/admin/keys/:id PATCH 启停 / 归属变更
9 /api/admin/logs GET 日志明细(下钻,分页)
10 /api/admin/subscriptions GET / POST 订阅权益清单 / 登记
11 /api/admin/subscriptions/:id PUT / DELETE 编辑 / 删除
12 /api/admin/topups GET / POST 充值入账清单 / 登记
13 /api/admin/topups/:id PUT / DELETE 编辑 / 删除
14 /api/admin/qpu/latest GET 当前换算率(表单美元双输入用)
15 /api/admin/members GET / POST 成员清单 / 开户(令牌明文仅一次)
16 /api/admin/members/:id PATCH / DELETE 档案修改(name/remark)与启停 / 空壳删除(仅 key_count = 0)
17 /api/admin/members/:id/token POST 令牌重发(明文仅一次)
18 /api/admin/audit-logs GET 审计日志(分页)

2. 引导与会话

GET /api/admin/setup-state → 200

{ "initialized": true }——入口三态判定端点(幂等):

  • users 非空 → true(登录页);空库 + env 凭证在 → 静默落库后 true;空库 + 无凭证 → false(引导页)
  • 「永不复现」保证:initialized 恒以库内事实为准

POST /api/admin/setup → 200 {username} + Set-Cookie

引导页提交 {username, password}(确认密码为前端校验)——落库 + 直接建立会话(免二次登录)。仅空库 + 无 env 凭证时有效,否则 409 already_initialized(与三态判定构成双保险)。

POST /api/admin/session → 200 {username} + Set-Cookie

登录 {username, password}。失败 401 invalid_credentials(通用文案,不区分用户名 / 密码错);连续失败触发 429(节流最小实现——失败延迟 / 计数,参数实现期定)。cookie 名 admin_session,形态见公共约定 §2。

GET /api/admin/session → 200 {username}

当前会话(刷新后恢复侧边栏用户名);无有效会话 → 401。

DELETE /api/admin/session → 204

登出(删会话行)。

PUT /api/admin/password → 204

{old_password, new_password}——成功后删除其他会话、保留当前。旧密码不正确 → 422(fields: {old_password: "旧密码不正确"})。

3. 引擎状态

GET /api/admin/engine → 200

{
  "running": true,
  "sync_interval_ms": 300000,
  "last_round": {
    "started_at": "2026-10-02T08:00:00Z",
    "keys_total": 18, "succeeded": 16, "failed": 2,
    "failed_api_key_ids": [3, 7]
  }
}

sync_interval_ms:当前同步周期(毫秒),只读观测,经 env 配置、无修改接口。
failed_api_key_ids 支撑 Key 管理「最近轮次失败」筛选与健康带失败数跳转;last_round 为 null(引擎尚未跑过)合法。状态存储为 engine_status 单行快照(《同步引擎设计》§5)。

4. 全员总览

GET /api/admin/overview?from&to → 200

from/to 必填(杜绝无口径数字;快捷项为前端预置)。聚合仅 type = 2(消费行,§10 裁定 1)。

{
  "data_cutoff": "2026-10-02T08:00:00Z",
  "unassigned_subscription": { "count": 0, "quota": 0, "quota_usd": "0.00" },
  "by_source": [
    { "billing_source": "subscription", "count": 420, "quota": 2100000, "quota_usd": "4.20" },
    { "billing_source": "wallet", "count": 80, "quota": 400000, "quota_usd": "0.80" }
  ],
  "by_member": [
    { "member_id": 1, "member_name": "小张", "key_count": 3,
      "data_cutoff": "2026-10-02T08:00:00Z",
      "usage": { "count": 300, "quota": 1500000, "quota_usd": "3.00" } },
    { "member_id": null, "member_name": null, "key_count": 2,
      "data_cutoff": "2026-10-01T06:00:00Z",
      "usage": { "count": 200, "quota": 1000000, "quota_usd": "2.00" } }
  ],
  "by_key": [
    { "key_id": 5, "token_name": "小张-01", "member_id": 1, "status": "active",
      "data_cutoff": "2026-10-02T08:00:00Z",
      "usage": { "count": 120, "quota": 600000, "quota_usd": "1.20" } }
  ]
}
  • data_cutoff:全池 log_synced_at 最慢;by_member 行为名下最慢、member_id: null 行为「未归属(归作者)」固定卡
  • unassigned_subscription:该时段 billing_source = subscription 且 subscription_id 缺失的量——正常恒为零(健康指示器,非零即异常信号);billing_source = wallet 的缺失属正常、不进此分组
  • 占全员比例条由前端现算(公共约定 §5)

5. Key 管理

GET /api/admin/keys → 200 [key]

全量直返(基数 10–25,不分页——§10 裁定 3)。筛选参数:member_id(id 或 unassigned)、status、billing_domain(值或 none = 未标注)、recent_failed=true(引擎最近轮次失败)。排序默认数据截止降序(前端事)。

{
  "id": 5, "token_name": "小张-01", "status": "active",
  "member_id": 1, "member_name": "小张",
  "billing_domain": "carpool",
  "registered_at": "2026-09-20T10:00:00Z",
  "log_synced_at": "2026-10-02T08:00:00Z", "usage_synced_at": "2026-10-02T08:00:00Z",
  "total_used": 600000, "total_available": 940000, "total_granted": 1540000,
  "unlimited_quota": false, "expires_at": null
}

key_hash / key_ciphertext 永不出 API(凭证形态);model_limits / model_limits_enabled 已入库暂无 UI 消费方,不出(随表设计 3.4 待 UI 稳定回审)。

POST /api/admin/keys → 201 key

{ "sk_key": "sk-…", "member_id": null, "billing_domain": null }——登记四态:

结果 响应
成功 201 + key 实体(首轮日志同步异步补,不阻塞响应——表设计 §4)
已登记(hash 命中,查库优先、先于上游验证) 409 key_already_registered,信封附注 key_id 定位既有行
上游拒绝(401) 422(fields: {sk_key: "上游拒绝了这枚 key——可能不存在或已被禁用"})
上游暂不可达 502 upstream_unreachable(「上游站点暂时不可达,请稍后重试」)

PATCH /api/admin/keys/:id → 200 key

{ "status": "disabled" }(启用 / 停用)· { "member_id": 3 }(归属变更;null = 归还未归属)或 { "billing_domain": "office" }(账本域变更;null = 未标域)——部分更新,单字段或并存均可,审计按实际变更字段各落一行(§9 枚举)。非法值 422;key 不存在 404;member_id 引用不存在成员 422(fields)。

6. 日志明细(管理面下钻)

GET /api/admin/logs?from&to → 200 {items, total, summary, models}

分页(page/page_size,公共约定 §6)。过滤:from/to(必填)、api_key_id、member_id(经 api_key_id → api_keys.member_id 联查当前归属——行级归属语义)、model_name、billing_source、subscription_id、type。默认全类型(错误行排查入口——§10 裁定 1 的另一半)。

响应附加块(汇总条与模型下拉的数据源;成员端 /api/member/logs 同构):

{
  "summary": {
    "hit_count": 131, "usage_count": 128, "error_count": 3,
    "quota": 1250000, "quota_usd": "2.50"
  },
  "models": ["claude-sonnet-4", "gpt-5"]
}
字段 口径
hit_count 当前筛选命中总数(= total;界面文案「请求次数」)
usage_count / error_count 同筛选下 type=2 / type≠2 条数
quota / quota_usd 同筛选下 type=2 行合计;美元按行 QPU 微分(公共约定 §5)
models 时间窗 + 主体/来源/订阅筛选下去重 model_name(不含 model_name / type),升序,供筛选下拉

裁定:条数跟全部筛选(含 type / model_name);用量字段恒 type = 2(即使请求 type=error)——界面标签「消费用量」。

{
  "id": 12345, "api_key_id": 5, "created_at": "2026-10-01T14:22:05Z",
  "token_name": "小张-01", "model_name": "gpt-5", "type": 2, "content": "",
  "quota": 250000, "quota_usd": "0.50",
  "prompt_tokens": 1200, "completion_tokens": 800,
  "use_time": 3, "is_stream": true,
  "billing_source": "subscription", "subscription_id": 3
}

token_name 取当前名(联查 api_keys,与「展示名以当前为准」对齐——§10 裁定 2);username / channel / ip / token_id / request_id / other 等其余表字段暂不出 API,界面需要时再扩。

界面落点:《交互设计-调用日志》——管理面独立查询页;总览/Key/成员筛选深链 + 行内回跳。

7. 订阅权益与充值入账

原 payments 单表已拆(payments建模评估.md 方案 B):订阅 = 权益(期间 + 额度预算 + 可选付款事实),充值 = 入账事件。UI 菜单仍叫「支付记录」,API 按资源分组。

GET /api/admin/subscriptions → 200 [subscription]

全量直返(年个位数,不分页),period_start 倒序。

{
  "id": 3, "subscription_id": 44,
  "period_start": "2026-09-01T00:00:00Z", "period_end": "2026-10-01T00:00:00Z",
  "quota_granted": 12500000, "quota_usd": "25.00",
  "paid_amount": 170, "paid_at": "2026-08-28T10:00:00Z",
  "billing_domain": "carpool",
  "remark": "九月拼车", "created_at": "2026-09-02T12:00:00Z"
}
  • quota_usd 换算锚点优先级:paid_at → 否则 created_at(§10 裁定 4 修订)
  • paid_amount / paid_at 可空(付款事实未知)

POST /api/admin/subscriptions → 201 subscription

必填:subscription_id · period_start · quota_granted;可空:period_end(=进行中)· paid_amount · paid_at · billing_domain · remark。美元双输入为前端换算,落库恒原始 quota。列表筛选支持 billing_domain(值或 none)。

PUT /api/admin/subscriptions/:id / DELETE → 204

GET /api/admin/topups → 200 [topup]

全量直返,paid_at 倒序。对齐上游充值单;事件时刻单列(上游 complete_time),无 quota_granted 入库列:

{
  "id": 1, "upstream_id": 17,
  "amount": 1, "money": 2.04,
  "trade_no": "USR6NOLqTPGa1791534153",
  "payment_method": "alipay", "payment_provider": "epay",
  "status": "success",
  "paid_at": "2026-10-09T12:22:56Z",
  "billing_domain": "carpool",
  "quota_derived": 500000, "quota_usd": "1.00",
  "remark": "", "created_at": "2026-10-09T13:00:00Z"
}

quota_derived / quota_usd 为派生(amount × qpu,锚点 paid_at),不入库。

POST /api/admin/topups → 201 topup

必填:paid_at · amount · money;可空:upstream_id · trade_no · payment_method · payment_provider · status · billing_domain · remark。列表筛选支持 billing_domain(值或 none)。

PUT /api/admin/topups/:id / DELETE → 204

平台辅助(登录代拉,密码不入库)

已有:POST /api/admin/platform/login · POST /api/admin/platform/subscriptions。
新增:POST /api/admin/platform/topups body { access_token, page, page_size } → { items, total, page, page_size }——透传 /api/user/topup/self 单页;前端传统分页、可翻至尽头、无时间参数、无页数硬上限(见交互篇 §4.1)。不做后台定时同步。

编辑全字段、删除有确认(前端);改错即修,均落审计。

GET /api/admin/qpu/latest → 200

{ "qpu": 500000, "effective_from": "2026-09-01T00:00:00Z" }——当前换算率(支付表单美元 ↔ quota 双输入换算用)。

8. 成员管理

GET /api/admin/members → 200 [member]

全量直返(个位数,不分页)。

{
  "id": 1, "name": "小张", "remark": "同事", "status": "active",
  "token_issued_at": "2026-09-18T10:00:00Z", "created_at": "2026-09-18T10:00:00Z",
  "key_count": 3
}

key_count 为派生聚合计数(当前归属名下 key 数);token_hash 永不出 API。名称唯一冲突 409 member_name_taken。

POST /api/admin/members → 201

{name, remark?}——开户即自动签发令牌,响应附明文(仅此一次):

{ "member": { "…": "如上实体" }, "token": "(明文令牌——关闭后不可再得,库里只有哈希)" }

PATCH /api/admin/members/:id → 200 member

部分更新,单字段或并存均可,审计按实际变更字段各落一行(§9):

  • { "status": "disabled" } / active——停用即成员视图访问立即失效(令牌校验含状态判断)
  • { "name": "小张" } / { "remark": "同事" }(R16 档案修改)——name 冲突 409 member_name_taken;不接受 token_hash / created_at / id

非法值 422;成员不存在 404。

DELETE /api/admin/members/:id → 204

空壳删除(R16):仅当该成员当前 key_count = 0(无任何 api_keys.member_id 指向)才物理删除行;否则 409 member_has_keys(fields 可附 key_count)。删除后名称 UNIQUE 释放、令牌一并作废。名下有 key 的清理路径 = 停用(软删除语义不变)。均落审计。

POST /api/admin/members/:id/token → 201

{ "token": "…" }——重发:旧令牌即刻失效(旧令牌的成员侧下一请求 401)。明文仅此响应一次。

9. 审计日志

GET /api/admin/audit-logs → 200 {items, total}

分页;时间倒序;过滤 domain(key / payment / member / account)。

{
  "id": 99, "created_at": "2026-10-02T09:15:00Z",
  "operator": "aaron", "domain": "key", "action": "key.disable",
  "summary": "key 小张-01 · 停用"
}

operator 为用户名;系统触发事件为 null(如空库静默重建 account.init)。操作类型枚举(审计域全集):

域 action 枚举
key key.register / key.enable / key.disable / key.reassign(归属变更)
payment subscription.create / subscription.update / subscription.delete / topup.create / topup.update / topup.delete(UI 菜单「支付记录」)
member member.create / member.update(档案改 name/remark)/ member.enable / member.disable / member.token_issue(开户自动签发)/ member.token_reissue / member.delete(空壳硬删)
account account.password_change / account.init(系统,operator null)

每个管理面写操作各落一行;查询类与成员侧动作不留痕。表结构归技术稿(members / api_keys 表设计既有标注),立稿时以本文 DTO 字段为对齐锚点(§11 遗留 2)。

10. 本稿裁定项(表设计 / 公共约定的待定落定,请作者重点审)

  1. 用量聚合口径仅 type = 2(消费行)——usage_logs §5 待定「type 过滤策略」落定:聚合与统计只计消费行(错误行入聚合即失真);明细查询默认全类型、可按 type 筛(错误排查入口);
  2. 日志明细 token_name 取当前名(联查 api_keys;快照名存库不出 API)——表设计 2.3 待定「展示口径」落定;
  3. 有限基数清单不分页:keys / members / subscriptions / topups 直返全量数组;公共约定 §6 的「列表类分页」指无界列表(logs / audit-logs);
  4. quota_usd 换算锚点:充值恒 paid_at(= 上游 complete_time);订阅优先 paid_at、否则 created_at——对齐「按日志时刻取值」的历史正确性原则;
  5. 公共约定随本稿与审阅增补四处:状态表加 429(登录节流)、错误信封允许端点附机器可读附注字段(409 key_already_registered 附 key_id)、/api 命名空间专属接口(页面路由永不进入)、端点面向交互设计不与数据表对齐。

11. 遗留项(归后续立稿或作者裁决)

  1. 管理面日志明细的界面落点——已裁决:实现期定(先做「跳 Key 管理定位到该行」,详情视图待真实数据再定);
  2. 审计日志表结构——归技术稿,以本文 §9 DTO 字段为对齐锚点;
  3. GET /api/admin/engine 的状态存储——已对齐《同步引擎设计》§5(engine_status 单行快照 + 只读 sync_interval_ms);
  4. 令牌生成细节(随机源 / 长度 / 前缀)——归技术稿。