17 KiB
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}
分页(page/page_size,公共约定 §6)。过滤:from/to(必填)、api_key_id、member_id(经 api_key_id → api_keys.member_id 联查当前归属——行级归属语义)、billing_source、subscription_id、type。默认全类型(错误行排查入口——§10 裁定 1 的另一半)。
{
"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. 本稿裁定项(表设计 / 公共约定的待定落定,请作者重点审)
- 用量聚合口径仅
type = 2(消费行)——usage_logs §5 待定「type 过滤策略」落定:聚合与统计只计消费行(错误行入聚合即失真);明细查询默认全类型、可按type筛(错误排查入口); - 日志明细
token_name取当前名(联查api_keys;快照名存库不出 API)——表设计 2.3 待定「展示口径」落定; - 有限基数清单不分页:
keys/members/subscriptions/topups直返全量数组;公共约定 §6 的「列表类分页」指无界列表(logs/audit-logs); quota_usd换算锚点:充值恒paid_at(= 上游complete_time);订阅优先paid_at、否则created_at——对齐「按日志时刻取值」的历史正确性原则;- 公共约定随本稿与审阅增补四处:状态表加
429(登录节流)、错误信封允许端点附机器可读附注字段(409 key_already_registered附key_id)、/api命名空间专属接口(页面路由永不进入)、端点面向交互设计不与数据表对齐。
11. 遗留项(归后续立稿或作者裁决)
- 管理面日志明细的界面落点——已裁决:实现期定(先做「跳 Key 管理定位到该行」,详情视图待真实数据再定);
- 审计日志表结构——归技术稿,以本文 §9 DTO 字段为对齐锚点;
GET /api/admin/engine的状态存储——已对齐《同步引擎设计》§5(engine_status单行快照 + 只读sync_interval_ms);- 令牌生成细节(随机源 / 长度 / 前缀)——归技术稿。