Preserve local user table for superDream-specific features while syncing user lifecycle, API key CRUD and usage queries through sub2api. Admin token handles reads and user lifecycle; per-user tokens (Fernet-encrypted in DB) handle key writes that admin endpoints do not expose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
sub2api 接口对接规范
本文档记录 superDreamFront 与 sub2api 对接所使用的全部 HTTP 接口,以及 BFF 层(superDreamFront FastAPI)各服务的调用策略。
对应的 sub2api 源码位置:
- 路由:
sub2api/backend/internal/server/routes/{auth,user,admin}.go- 用户侧 handler:
sub2api/backend/internal/handler/{auth_handler,api_key_handler,usage_handler,user_handler}.go- 管理员 handler:
sub2api/backend/internal/handler/admin/{user_handler,apikey_handler}.go- DTO 类型:
sub2api/backend/internal/handler/dto/types.go
1. 通用约定
1.1 Base URL
所有接口以 /api/v1 为前缀,完整地址由环境变量 SD_SUB2API_BASE_URL 决定,例如:
SD_SUB2API_BASE_URL = http://127.0.0.1:8080
→ 实际请求:http://127.0.0.1:8080/api/v1/...
1.2 响应 envelope
所有响应统一包一层(来源 internal/pkg/response/response.go):
{
"code": 0, // 0 表示成功;非 0 为 HTTP 状态码
"message": "success",
"reason": "", // 仅错误时出现
"metadata": null, // 错误附加信息
"data": { ... } // 业务载荷
}
分页响应的 data 形状固定为:
{
"items": [...],
"total": 1234,
"page": 1,
"page_size": 20,
"pages": 62
}
BFF 层建议在 sub2api.client.request() 中统一解 envelope:
code == 0→ 返回datacode != 0→ 抛自定义异常Sub2APIError(code, message, reason, metadata)
1.3 认证方式
sub2api 有三类鉴权:
| 鉴权类型 | 携带方式 | 用途 |
|---|---|---|
| 公开 | 无 | 注册、登录、忘记密码、验证码 |
| 用户 JWT | Authorization: Bearer <access_token> |
/keys、/usage、/user/* |
| 管理员 | x-api-key: <admin_api_key> 或 Authorization: Bearer <admin_jwt> |
/admin/* |
管理员 API Key 在 sub2api 后台
Admin 设置 → Admin API Key → 生成后拿到,写入SD_SUB2API_ADMIN_TOKEN。
1.4 Turnstile
部分公开接口会校验 Cloudflare Turnstile。sub2api 管理员可在设置里关闭;本 BFF 暂不处理 Turnstile,若 sub2api 启用会 400。
2. 认证(公开接口)
2.1 发送邮箱验证码
POST /api/v1/auth/send-verify-code
请求:
{ "email": "user@example.com", "turnstile_token": "" }
响应:
{ "message": "Verification code sent successfully", "countdown": 60 }
2.2 注册
POST /api/v1/auth/register
请求:
{
"email": "user@example.com",
"password": "******", // min 6
"verify_code": "123456", // 若 sub2api 开启邮箱验证码注册
"turnstile_token": "",
"promo_code": "", // 可选注册优惠码
"invitation_code": "" // 可选邀请码
}
响应(同 AuthResponse):
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 3600, // access 有效期(秒)
"token_type": "Bearer",
"user": { ...dto.User }
}
2.3 登录
POST /api/v1/auth/login
请求:
{ "email": "user@example.com", "password": "******", "turnstile_token": "" }
若用户未开 2FA,响应同注册。 若开启 2FA,响应:
{ "requires_2fa": true, "temp_token": "...", "user_email_masked": "u**@e**.com" }
此时前端需再调 POST /api/v1/auth/login/2fa:
{ "temp_token": "...", "totp_code": "123456" }
2.4 刷新 Token
POST /api/v1/auth/refresh
// 请求
{ "refresh_token": "..." }
// 响应
{ "access_token": "...", "refresh_token": "...", "expires_in": 3600, "token_type": "Bearer" }
注意:sub2api 的 refresh_token 一次性使用、每次刷新会 rotate,BFF 取到新 refresh_token 要覆盖旧值。
2.5 登出
POST /api/v1/auth/logout body 可选 {"refresh_token": "..."},服务端会撤销该 token。
2.6 忘记 / 重置密码
POST /api/v1/auth/forgot-password{"email", "turnstile_token"}POST /api/v1/auth/reset-password{"email", "token", "new_password"}
2.7 当前用户
GET /api/v1/auth/me 需要用户 JWT,响应 dto.User + {run_mode}。
3. API Key(用户 JWT)
路由前缀 /api/v1/keys(注意不是 /api-keys,handler 注释里旧路径是误导)。
3.1 列表
GET /api/v1/keys?page=1&page_size=20&sort_by=created_at&sort_order=desc&search=&status=&group_id=
响应 data(分页 envelope):
{
"items": [ { ...dto.APIKey } ],
"total": 5, "page": 1, "page_size": 20, "pages": 1
}
dto.APIKey 关键字段(完整字段见 types.go):
{
id: number,
user_id: number,
key: string, // 完整 key(注意:sub2api 确实返回明文完整 key,不做截断)
name: string,
group_id: number | null,
status: "active" | "inactive",
ip_whitelist: string[], ip_blacklist: string[],
last_used_at: string | null,
quota: number, // USD,0 = 无限
quota_used: number,
expires_at: string | null,
rate_limit_5h: number, rate_limit_1d: number, rate_limit_7d: number, // 0=不限
usage_5h: number, usage_1d: number, usage_7d: number,
reset_5h_at, reset_1d_at, reset_7d_at: string | null,
created_at, updated_at: string,
group?: dto.Group
}
3.2 单个查询
GET /api/v1/keys/:id → dto.APIKey
3.3 新建
POST /api/v1/keys
{
"name": "my-key",
"group_id": 1, // 可选
"custom_key": null, // 可选自定义 key
"ip_whitelist": [], "ip_blacklist": [],
"quota": 0, // 0=无限
"expires_in_days": null, // null=不过期
"rate_limit_5h": 0, "rate_limit_1d": 0, "rate_limit_7d": 0
}
响应:dto.APIKey。
3.4 更新
PUT /api/v1/keys/:id(字段同上,全部可选;特殊:expires_at="" 清除过期,reset_quota=true 重置已用,reset_rate_limit_usage=true 重置限速计数)
3.5 删除
DELETE /api/v1/keys/:id → { "message": "API key deleted successfully" }
3.6 用户可用分组 / 专属倍率
GET /api/v1/groups/available→dto.Group[]GET /api/v1/groups/rates→{ [groupID: number]: number }
4. 用量(用户 JWT)
4.1 列表
GET /api/v1/usage?page=&page_size=&api_key_id=&model=&request_type=&stream=&billing_type=&start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&timezone=Asia/Shanghai
响应 items: dto.UsageLog[](分页)。
dto.UsageLog 关键字段:
{
id, user_id, api_key_id, account_id: number,
request_id, model: string,
input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost,
request_type: string, // "chat"/"responses"/"messages"/"image" 等
stream: boolean,
duration_ms, first_token_ms: number | null,
image_count: number,
created_at: string
}
4.2 详情
GET /api/v1/usage/:id → dto.UsageLog
4.3 统计
GET /api/v1/usage/stats?period=today|week|month 或 start_date&end_date
→ 返回 service.UsageStats(求和 + tokens/requests/cost 各纬度汇总;字段见 sub2api UsageStats 定义)。
4.4 Dashboard
GET /api/v1/usage/dashboard/statsGET /api/v1/usage/dashboard/trend?granularity=day&start_date&end_dateGET /api/v1/usage/dashboard/models?start_date&end_datePOST /api/v1/usage/dashboard/api-keys-usagebody{ "api_key_ids": [1,2,3] }
5. 管理员接口(x-api-key)
superDream BFF 仅用到以下 admin 接口:
5.1 用户
POST /api/v1/admin/users— 注册同步→{ "email", "password", "username", "notes": "from superDream", "balance": 0, "concurrency": 0, "allowed_groups": [] }dto.AdminUserPUT /api/v1/admin/users/:id— 改密/改状态同步(字段均为可选 PATCH 语义)DELETE /api/v1/admin/users/:id— 删除同步GET /api/v1/admin/users?search=email@x.com— 查找用户(拿id用于后续调用)GET /api/v1/admin/users/:id— 详情
5.2 查用户 API Keys / 用量
GET /api/v1/admin/users/:id/api-keys?page=&page_size=&sort_by=&sort_order=→dto.APIKey[](分页)GET /api/v1/admin/users/:id/usage?period=today|week|month→UsageStats
5.3 能力缺口
sub2api admin 路径不提供 代用户创建/删除/修改 Key 的接口(registerAdminAPIKeyRoutes 仅 PUT /admin/api-keys/:id 改分组)。
→ Key 的写操作必须使用用户 JWT。
6. BFF 对接策略
6.1 本地 User 表扩展字段
| 字段 | 类型 | 说明 |
|---|---|---|
sub2api_user_id |
BIGINT |
对应 sub2api 的 dto.User.id |
sub2api_refresh_token_enc |
TEXT |
Fernet 加密后的 refresh_token |
sub2api_access_token |
TEXT |
当前有效的 access_token(内存级缓存即可,但写库便于重启复用) |
sub2api_access_expires_at |
DATETIME |
access_token 过期时间(UTC) |
6.2 鉴权 → Token 获取顺序
每次 BFF 需要代理到 sub2api 用户接口 时:
- 如本地缓存 access_token 未过期(预留 60s 余量),直接用
- 否则用 Fernet 解密 refresh_token,调
/auth/refresh换新(rotate 后写回) - 若 refresh 也 401(token 被撤销)→ 返回前端
401 sub2api_reauth_required,前端弹窗让用户重新输密码,后端重调/auth/login重新拿到 token
6.3 各业务同步矩阵
| 动作 | 本地 | sub2api(admin token) | sub2api(用户 token) |
|---|---|---|---|
| 注册 | ①写 user(pending) | ②POST /admin/users 取回 id |
③POST /auth/login 拿首对 tokens④成功后 commit 本地;任一失败整体回滚 |
| 登录 | ①校验本地密码 → 签本地 JWT | — | ②同步调 /auth/login 刷新存储 refresh_token |
| 改密 | ①更新本地 hash | ②PUT /admin/users/:id |
③用新密码调 /auth/login 替换 token |
| 删用户 | ①级联删 | ②DELETE /admin/users/:id |
— |
GET /keys |
— | GET /admin/users/:id/api-keys |
— |
POST/PUT/DELETE /keys |
— | — | /keys 系列 |
GET /usage/* |
— | GET /admin/users/:id/usage |
兜底:若 admin 接口粒度不够,回退到用户 token 调 /usage |
| Dashboard 统计 | 本地聚合 sub2api 返回 | /admin/users/:id/usage |
/usage/dashboard/* |
6.4 前端字段替换
替换 frontend/src/services/ 下的 TS interface,使其与 dto.APIKey / dto.UsageLog / dto.User 完全一致(字段名用 snake_case 保留,不转 camelCase,避免映射层)。具体列在实现阶段列出 diff。
6.5 错误映射
| sub2api 返回 | BFF 抛出 | 前端显示 |
|---|---|---|
code=400 |
HTTPException 400 + reason |
表单校验提示 |
code=401 INVALID_TOKEN/TOKEN_EXPIRED |
触发 refresh 流;失败抛 401 sub2api_reauth_required |
弹框"请重新登录" |
code=403 |
403 |
"没有权限" |
code=404 |
404 |
"资源不存在" |
code>=500 |
502 upstream_error + 日志 |
"上游服务异常" |
| 网络异常 | 504 upstream_timeout |
"请求超时" |
7. 新增配置项
写入 app/config/settings.py:
sub2api_base_url: str = "http://127.0.0.1:8080"
sub2api_admin_token: str = "" # sub2api Admin API Key
sub2api_request_timeout: float = 10.0 # 秒
sub2api_turnstile_bypass: bool = True # 是否绕过 turnstile(需 sub2api 端也关)
token_encryption_key: str = "" # Fernet key(44 字节 urlsafe base64)
生成 Fernet key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
requirements.txt 增加:httpx>=0.27 cryptography>=42