feat: add public model catalog discovery

This commit is contained in:
QiuSW
2026-07-04 10:14:16 +08:00
parent 3a661afaa5
commit 898329e047
16 changed files with 533 additions and 14 deletions
+2 -1
View File
@@ -38,7 +38,7 @@
## 当前阶段
当前项目处于:**Phase 5 后台与发布已完成计划内任务**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调、T-305 扫码充值下单 + 轮询与 T-306 对外 API 安全加固;Phase 4 已完成 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化;Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档。下一步是按 `deployment.md` 在 VPS 上配置真实邮件、支付、AI 模型和图片真实耗时验证,或从 Backlog 重新规划后续任务。
当前项目处于:**Phase 6 增强任务收尾期**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调、T-305 扫码充值下单 + 轮询与 T-306 对外 API 安全加固;Phase 4 已完成 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化;Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;Phase 6 已完成 T-601「可用别名发现」。当前暂无新的已拆 `TODO` 任务,同时生产侧仍需按 `deployment.md` 配置真实邮件、支付、AI 模型和图片真实耗时验证。
优先路径:
@@ -48,6 +48,7 @@
4. Phase 3:对外 API 与充值 —— T-301 Key 鉴权、T-302 生成接口、T-303 余额查询、T-304 充值回调、T-305 扫码下单与轮询、T-306 安全加固已完成。
5. Phase 4:用户端(Django 模板 SSR)—— T-501 注册登录、T-502 API Key 管理、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化已完成。
6. Phase 5:后台与发布 —— T-401 运营后台完善、T-402 完整验收 MVP、T-403 部署 / 运行文档已完成;计划内 MVP 任务已收尾。
7. Phase 6:增强(MVP 后)—— T-601 可用别名发现已完成,实现 `/api/v1/models` 与 portal 只读「可用模型」页;只展示能力别名、能力、是否需要原图和价格,不解密 provider key、不暴露具体 SKU / URL / key。
## 领取任务规则
+7
View File
@@ -75,6 +75,12 @@
| T-402 | 完整验收 MVP | T-401, T-505 | `02-requirements.md` 的 P0 验收全部通过(含用户端注册/充值/API Key/记录) | DONE |
| T-403 | 部署 / 运行文档 | T-402 | 新环境可按文档运行;明确图片接口超时配置;上线前必须引用一次真实图片生成耗时来设置 Gunicorn `--timeout`、Nginx `proxy_read_timeout`、客户端 read timeout;若尚无真实耗时,部署文档必须显式标注图片同步风险未退,不得声称已验证;生产必须配置真实 `DJANGO_EMAIL_BACKEND` / `DJANGO_DEFAULT_FROM_EMAIL`,否则 allauth 邮箱验证无法发信、用户无法完成登录;**生产静态文件 serving**:T-505 已把 Bootstrap/qrcode 自托管到 `apps/portal/static/`,但 `settings` 目前只有 `STATIC_URL`,`DEBUG=False` 下 Django 不发静态文件——必须配置 `STATIC_ROOT` + `collectstatic` + WhiteNoise 或 Nginx 托管 `/static/`,否则用户端 CSS 错版、充值二维码 404(P2-1 的国内可用性目标在生产失效);**DRF 限流依赖共享缓存**:多 Gunicorn worker 下默认 `LocMemCache` 是每进程的,会让 `generate`/`api_auth_failure` 限流按 worker 各算一份(实际速率≈worker 数×配置值),部署必须配置 Redis/Memcached 等共享 `CACHES` 后端并在文档说明,否则限流形同虚设 | DONE |
## Phase 6 · 增强(MVP 后)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-601 | 可用别名发现(portal 别名页 + `GET /api/v1/models`) | T-202, T-301, T-503 | 让调用方能自助发现「能填哪些 `model` 能力别名」。① 新增 `GET /api/v1/models`(**API Key 鉴权,继承 `ExternalApiView`,只复用认证失败限流;不要占用生成接口的 `GenerateRateThrottle` 额度**),仅返回 `ModelAlias.is_active=True` 且 `ai_model.is_active=True` 的可调用别名;② 响应每项只含对外安全字段:`alias`、`operation_type`、`capabilities`、`requires_image`(按 `api_type`/provider 规则推导,如 `images_edits=true`)、`pricing_status`、`prices[{resolution, points_cost}]`,缺 `PricingRule` 时标「暂未定价」而非报错;③ **实现时不得调用 `AiModel.to_resolved_model()` 或任何会解密 provider key 的路径**,列表接口不依赖 `AI_KEY_ENCRYPTION_KEY`,也不读取/输出 `api_key_encrypted`;④ **不暴露具体 SKU/模型名/url/key/provider 原始配置**,测试必须断言响应不含 `ai_model.model`、`ai_model.url`、`api_key`、`api_key_encrypted`、`extra_body` 等内部字段;⑤ portal 加只读「可用模型」页(session 鉴权),同字段、人类可读、单价以点数展示,并在导航中加入入口;⑥ 含测试:API Key 成功返回结构、缺/无效 Key 拒绝、Web session 不能调用外部 API、portal 未登录跳转、页面不泄露 SKU/URL/Key、缺定价别名显示暂未定价。**标注**:未来接 `account_alias_permission`(见 Backlog)后,本清单改为「只列该用户被授权的别名」,接口与页面均按 `user` 过滤 | DONE |
## 里程碑
- M1:Django + admin 骨架可运行、自定义 User 就位(T-003)。
@@ -82,6 +88,7 @@
- M3:计费 + 对外接口 + 充值闭环 + 上线前安全加固(T-306)。
- M4:用户端(注册/充值/API Key/记录)可用(T-504)。
- M5:运营后台 + MVP 验收 + 可部署(T-403)。
- M6:可用别名发现(T-601)。
## 待办池(Backlog)
+33
View File
@@ -23,6 +23,8 @@ T-302 已实现生成接口基线:`POST /api/v1/generate/title` 与 `POST /api
T-303 已实现余额查询基线:`GET /api/v1/balance` 已接入 API Key 鉴权,返回当前 `UserWallet.points_balance`;测试覆盖响应余额与 `PointsLedger.points_delta` 累加值一致的账务场景,并确认 Web session 不能调用该外部接口。
T-601 已实现可用别名发现:`GET /api/v1/models` 已接入 API Key 鉴权,只返回当前 active 且具备对应能力的公开别名、能力、是否需要原图和点数单价;接口不调用 `AiModel.to_resolved_model()`,不解密 provider key,不返回底层 SKU、模型 URL、`api_key_encrypted`、`extra_body` 等内部配置,且成功请求不占用生成接口限流额度。
T-304 已实现充值回调基线:`POST /api/v1/recharge/callback/wechat` 与 `/alipay` 已 `@csrf_exempt`,回调先验签(开发/测试可用明确 HMAC mock,生产 `PAYMENT_CALLBACK_MODE=sdk` 走支付 SDK),再按 `order_no` 锁定 `RechargeOrder` 幂等入账;金额或支付方式不一致不加点,重复回调不重复写充值流水。
T-305 已实现扫码充值下单与轮询基线:`POST /api/v1/recharge/create` 与 `GET /api/v1/recharge/status` 走用户端 `SessionAuthentication + CSRF`,不接受 API Key;下单创建 pending 订单并锁定汇率/点数,再返回微信 `code_url` 或支付宝 `qr_code`;状态查询只允许订单所属用户访问,并在 pending 时尝试主动查单补入账,查单不可用时保持 pending 等回调。
@@ -140,6 +142,37 @@ T-504/T-505 已实现用户端充值页基线:`/recharge` 走 Django session +
}
```
### `GET /api/v1/models`
查询当前可调用的能力别名。成功响应:
```json
{
"models": [
{
"alias": "title-standard",
"operation_type": "title",
"capabilities": ["text"],
"requires_image": false,
"pricing_status": "priced",
"prices": [
{"resolution": "default", "points_cost": 2}
]
},
{
"alias": "image-edit",
"operation_type": "image",
"capabilities": ["image", "vision"],
"requires_image": true,
"pricing_status": "unpriced",
"prices": []
}
]
}
```
要点:只列 `ModelAlias.is_active=True` 且 `ai_model.is_active=True`,并按操作类型校验模型声明能力。缺定价规则不报错,`pricing_status="unpriced"` 且 `prices=[]`,用户端页面显示「暂未定价」。该接口返回的是公开能力别名,不返回底层供应商模型名、模型 URL、provider key、`api_key_encrypted`、`extra_body` 或其他原始配置;实现不得依赖 `AI_KEY_ENCRYPTION_KEY`。未来接入 `account_alias_permission` 后,本接口应只列当前 API Key 所属用户被授权的别名。
## 计费模块合约(`apps.billing`)
T-202 后,计费计算已有独立模块;T-203 后,扣点/退点也收敛到 billing 层,供后续生成接口和充值下单调用:
+13 -10
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -13,8 +13,10 @@
| `/records/recharge` | GET | 充值记录 | session |
| `/records/usage` | GET | 点数使用(消费/调用)记录 | session |
| `/apikeys` | GET/POST | API Key 管理:列表 / 生成 / 删除(删除即吊销,明文只显示一次) | session |
| `/models` | GET | 可用模型:只读展示可调用能力别名、能力、是否需要原图和点数单价 | session |
T-501 已落地 `/signup`、`/login`、`/logout` 与最小 `/dashboard`。T-502 已落地 `/apikeys`:登录用户只能管理自己的 Key,生成后明文只显示一次,列表只显示 prefix,删除为吊销 `revoked`。T-503/T-505 已扩展 `/dashboard` 为个人中心汇总,并落地 `/records/recharge` 与 `/records/usage`:充值总额按已支付订单统计,入账 / 消费 / 退款点数按 `PointsLedger` 统计,记录页只查询当前登录用户数据并分页展示。T-504/T-505 已落地 `/recharge`:登录用户可选择金额和支付方式创建 pending 充值订单,页面用本地 static 自托管 qrcode.js 展示二维码票据并轮询 `/api/v1/recharge/status`,到账后刷新余额。
T-601 已落地 `/models`:登录用户可查看当前公开可调用别名、能力、是否需要原图和点数单价;页面不展示底层 SKU、模型 URL、provider key、`api_key_encrypted` 或 `extra_body`。
## API 路由(对外,DRF)
@@ -23,6 +25,7 @@ T-501 已落地 `/signup`、`/login`、`/logout` 与最小 `/dashboard`。T-502
| `/api/v1/generate/title` | POST | 生成标题 | API Key |
| `/api/v1/generate/image` | POST | 生成图片(同步) | API Key |
| `/api/v1/balance` | GET | 查询点数余额 | API Key |
| `/api/v1/models` | GET | 查询可调用能力别名、能力和点数单价 | API Key |
| `/api/v1/recharge/create` | POST | 用户端发起充值(weixin/alipay),下单取二维码 | Session(用户端) |
| `/api/v1/recharge/status` | GET | 轮询订单状态(前端每秒) | Session(用户端) |
| `/api/v1/recharge/callback/wechat` | POST | 微信 V3 异步回调 | 验签(`@csrf_exempt`) |