Files
new-api-key-usage/docs/design/API契约-管理面.md
T
skycurtain 5f76bfc9d6 docs: 清除版本叙事并按开源文档体系整理
去掉初版/新版与分支指称,重写 README/AGENTS,删子包 README,对齐文档交叉引用与已废止测试脚本。
2026-10-10 07:56:07 +08:00

307 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API契约-管理面
> 管理面(作者,会话档)全部端点:引导与会话、引擎状态、全员总览、key 管理、日志明细(下钻)、订阅权益 / 充值入账、成员管理、审计日志。《API契约-公共约定》(定稿)全部条款在此适用不再重述——前缀 `/api/admin`、会话 cookie 鉴权、错误信封、ISO 8601 UTC 左闭右开、`quota_usd` 后端换算、snake_case 同构。
| 项目 | 内容 |
|------|------|
| 状态 | 待审阅(2026-10-09 支付拆表修订——§7) |
| 关联 | 《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 | 停用 / 启用 |
| 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
```json
{
"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)。
```json
{
"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`、`recent_failed=true`(引擎最近轮次失败)。排序默认数据截止降序(前端事)。
```json
{
"id": 5, "token_name": "小张-01", "status": "active",
"member_id": 1, "member_name": "小张",
"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 }`——登记四态:
| 结果 | 响应 |
|------|------|
| 成功 | `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` = 归还未归属)——部分更新,单字段或并存均可,审计按实际变更字段各落一行(§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 的另一半)。
```json
{
"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` 倒序。
```json
{
"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",
"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` · `remark`。美元双输入为前端换算,落库恒原始 quota。
### PUT `/api/admin/subscriptions/:id` / DELETE → 204
### GET `/api/admin/topups` → 200 `[topup]`
全量直返,`paid_at` 倒序。对齐上游充值单;**事件时刻单列**(上游 `complete_time`),**无 `quota_granted` 入库列**:
```json
{
"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",
"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` · `remark`。
### 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]`
全量直返(个位数,不分页)。
```json
{
"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?}`——开户即自动签发令牌,**响应附明文(仅此一次)**:
```json
{ "member": { "…": "如上实体" }, "token": "(明文令牌——关闭后不可再得,库里只有哈希)" }
```
### PATCH `/api/admin/members/:id` → 200 `member`
`{ "status": "disabled" }` / `active`——停用即成员视图访问立即失效(令牌校验含状态判断)。
### POST `/api/admin/members/:id/token` → 201
`{ "token": "…" }`——重发:旧令牌即刻失效(旧令牌的成员侧下一请求 `401`)。明文仅此响应一次。
## 9. 审计日志
### GET `/api/admin/audit-logs` → 200 `{items, total}`
分页;时间倒序;过滤 `domain`(`key` / `payment` / `member` / `account`)。
```json
{
"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.enable` / `member.disable` / `member.token_issue`(开户自动签发)/ `member.token_reissue` |
| 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. **令牌生成细节**(随机源 / 长度 / 前缀)——归技术稿。