Files
cmhub/docs/routes.md
T

128 lines
14 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.
# 路由与页面结构
> 本项目含三类界面:**用户端页面**(Django 模板 SSR,终端用户自助)、**对外 HTTP API**(DRF,程序调用)、**运营后台**(django-admin)。本文约定各自路由与页面职责。
> 接口请求/响应合约以 [`api.md`](api.md) 为准。
## 用户端页面(Django 模板 SSR,session 登录)
| 路由 | 方法 | 职责 | 鉴权 |
| --- | --- | --- | --- |
| `/` | GET | 公开首页:项目介绍、四步上手、客户端下载入口、导入模板下载入口、API/控制台入口 | 公开 |
| `/signup` `/login` `/logout` | GET/POST | 自助注册(**免邮箱验证、注册即可用**;注册成功一次性赠送 10 点试用点数)/登录/登出(Django auth/allauth) | 公开 |
| `/dashboard` | GET | 个人中心:剩余点数、充值总额、快捷入口 | session |
| `/recharge` | GET/POST | 发起充值:选金额→展示支付二维码→轮询到账 | session |
| `/records/recharge` | GET | 充值记录 | session |
| `/records/usage` | GET | 点数使用(消费/调用)记录 | session |
| `/apikeys` | GET/POST | API Key 管理:列表 / 生成 / 删除(删除即吊销,明文只显示一次) | session |
| `/models` | GET | 可用模型:只读展示可调用能力别名、能力、是否需要原图和点数单价 | session |
| `/migration/confirm/{request_id}` | GET/POST | 同账号确认当前设备的存量迁移 | session |
| `/migration/devices` | GET | 查看本人已确认设备与凭证状态 | session |
| `/migration/credentials/{id}/revoke` | POST | 自助解绑本人设备并吊销凭证 | session + CSRF |
T-501/T-608 已落地 `/signup`、`/login`、`/logout` 与 `/dashboard`:注册成功后经计费层一次性发放 10 点试用点数并写 `signup_bonus` 流水,注册限流由 allauth signup rate limit 执行。T-502 已落地 `/apikeys`:登录用户只能管理自己的 Key,生成后明文只显示一次,列表只显示 prefix,删除为吊销 `revoked`。T-503/T-505/T-608 已扩展 `/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`。
T-606 已落地 `/` 公开首页:匿名访问返回 200,不再重定向到 `/dashboard`;匿名用户看到注册 / 登录 / 下载入口,登录用户看到「进入控制台」。首页下载区读取 `DownloadRelease(platform=windows, is_current=True)`,优先使用 `external_url`,否则使用后台上传文件的 `file.url`;无当前版本时显示「暂未发布」。T-610 已在同一下载区“下载客户端”右侧增加“下载导入模板”,模板由后台配置当前版本,匿名可下载;模板本地文件 URL 会转成当前站点绝对 URL,页面不暴露本地文件路径。
## API 路由(对外,DRF)
| 路由 | 方法 | 职责 | 鉴权 |
| --- | --- | --- | --- |
| `/api/v1/generate/title` | POST | 生成标题 | API Key |
| `/api/v1/analyze/images` | POST | 理解一张或多张图片并同步返回文字 | API Key |
| `/api/v1/generate/image` | POST | 生成图片(同步,支持单图 / 多图) | API Key |
| `/api/v1/generate/image/tasks` | POST | 提交异步图片生成任务(支持单图 / 多图),返回 `task_id` | API Key |
| `/api/v1/generate/image/tasks/{task_id}` | GET | 轮询异步图片生成任务状态和结果 | API Key |
| `/api/v1/balance` | GET | 查询点数余额 | API Key |
| `/api/v1/models` | GET | 查询可调用能力别名、能力和点数单价 | API Key |
| `/api/v1/client/devices/register` | POST | 蝦皮圈设备登记,签发短期设备会话(仅观测) | API Key |
| `/api/v1/client/devices/heartbeat` | POST | 上报已登记设备活跃状态(仅观测) | Device session |
| `/api/v1/client/migration-requests` | POST | 存量客户端申请网页确认与一次性设备凭证 | API Key + Device session |
| `/api/v1/client/migration-requests/{request_id}` | GET | 轮询当前设备的迁移确认状态 | API Key + Device session |
| `/api/v1/client/releases/latest` | GET | 桌面端检查最新客户端版本(公开发布元数据) | 公开 |
| `/api/v1/recharge/create` | POST | 用户端发起充值(weixin/alipay),下单取二维码 | Session(用户端) |
| `/api/v1/recharge/status` | GET | 轮询订单状态(前端每秒) | Session(用户端) |
| `/api/v1/recharge/callback/wechat` | POST | 微信 V3 异步回调 | 验签(`@csrf_exempt`) |
| `/api/v1/recharge/callback/alipay` | POST | 支付宝异步回调 | 验签(`@csrf_exempt`) |
T-607/T-609/T-617 已落地 `/api/v1/client/releases/latest`:公开匿名可访问,不需要 API Key,不读取用户账本,只返回当前 `DownloadRelease` 的版本、下载 URL、SHA256、发布说明、强制更新标记、文件大小字节数和发布时间;无当前版本返回 `release:null`。
T-614 已落地 `/api/v1/generate/image/tasks` 与 `/api/v1/generate/image/tasks/{task_id}`:新版桌面端可先提交异步生图任务,再轮询状态;旧 `/api/v1/generate/image` 同步接口继续保留。异步查询只允许同一用户访问自己的任务,跨用户按不存在处理。
T-619 已落地 `/api/v1/analyze/images`:使用独立 `vision` 操作和能力别名,同步接收有序多图并返回文字;不复用标题接口语义,也不改变现有标题 / 生图路由。
T-620 已扩展两个图生图路由:旧 `image_url` / `image_base64` 单图字段保持兼容;新 `images` 为有序列表,每项必须且只能提供其中一种来源,且不能和旧字段混用。服务端固定把第 1 张作为主商品图、后续图作为参考图;异步任务在后台可查看有序输入文件,不保存 base64 原文。
T-624/T-625 已新增设备登记 / 心跳及调用关联:登记接口继续只认 API Key,返回仅本次展示的短期设备会话;心跳接口只认 `X-Device-Session`。四个生成提交路由可选带同一会话,服务端仅关联 `CallRecord` 与白名单遥测;无头旧客户端与异步任务轮询保持原鉴权、响应和计费语义,尚未进入订阅授权拦截。
T-627 已新增迁移申请 / 轮询和网页登录确认:客户端必须同时提供旧 API Key 与当前设备会话,申请得到短时确认链接及只出现一次的凭证明文;同账号 session 在 `/migration/confirm/{request_id}` 确认后才绑定席位。用户可在 `/migration/devices` 查看并自助解绑本人凭证;未新增生成授权拦截。
## 运营后台(django-admin,`/admin/`)
后台用 Django Session 登录,按模型注册 Admin:
| 管理项 | 对应模型 | 运营能做什么 |
| --- | --- | --- |
| 注册用户 | User | 查看/禁用注册用户;查看其钱包点数余额 |
| 点数钱包 | UserWallet | 查看余额;通过专用“手工调点”入口填写点数变动和原因,经计费层锁钱包并写 `adjust` 流水;余额字段本身只读 |
| API Key | ApiKey | 查看 / 吊销用户的 Key(只显示 prefix 和 hash 摘要,不回显明文) |
| 计费规则 | PricingRule | 配置「操作类型 × 能力别名(+ 可选分辨率)→ 点数单价」 |
| 汇率 | ExchangeRate | 配置金额→点数汇率 |
| 充值订单 | RechargeOrder | 检索订单、查看状态/金额/入账点数/支付流水号(只读) |
| 点数流水 | PointsLedger | 按账号、类型、时间检索充值/消费/调整/冲正流水(只读,对账用) |
| 调用记录 | CallRecord | 按账号、API Key、设备、时间、状态检索调用,查看别名/实际模型/消耗/错误(只读) |
| 图片生成任务 | ImageGenerationTask | 查看异步生图任务状态、公开 task_id、关联调用记录、worker 租约、结果 URL 与错误信息(只读排障) |
| 模型配置 | AiModel | 维护上游模型(url/model/api_key[加密脱敏]/api_type/capabilities/timeout) |
| 能力别名 | ModelAlias | 维护对外别名 → 具体模型的映射;换供应商在此改指向 |
| 配置审计 | AiConfigAuditLog | 只读查看 AiModel / ModelAlias / 密钥变更:谁、何时、改了什么 |
| 客户端下载版本 | DownloadRelease | 上传安装包或填写外部下载地址,标记每个平台当前版本、是否强制更新和文件大小字节数;新增 / 编辑时必须填写 SHA256 与文件大小字节数;展示版本、SHA256、强制更新标记与发布说明 |
| 导入模板 | ImportTemplate | 上传导入模板或填写外部下载地址,标记当前模板;展示模板名称、SHA256 与说明 |
| 会员套餐 | SoftwarePlan | 软件授权模块默认可见;维护蝦皮圈套餐的时长、价格、设备数、宽限期与启停状态,修改不回写已有权益快照 |
| 用户会员 | SoftwareEntitlement | 软件授权模块默认可见;只读查看用户权益和套餐快照,通过专用后台页面人工授予、续期或撤销,三种操作均必须填写原因并写授权事件 |
| 客户端设备(隐藏导航) | ClientDevice | 只读检索蝦皮圈设备平台、版本、状态和最近活跃时间;设备摘要仅脱敏显示 |
| 设备会话 / 审计(隐藏导航) | DeviceSession / DeviceBindingAudit | 只读查看会话有效期、吊销状态及登记/心跳审计;不展示会话令牌或安装公钥 |
| 软件套餐订单(隐藏导航) | SoftwareOrder | 只读检索套餐快照、金额、支付通道、交易号、状态、关联权益与发放时间;不允许后台直接修改订单、交易号或权益关联 |
| 授权席位 / 授权事件(隐藏导航) | LicenseSeat / LicenseEvent | 只读检索设备绑定、解绑时间和全部授权审计;不支持后台直接编辑席位或事件 |
| 存量迁移资格(隐藏导航) | LegacyMigrationGrant | 历史资格快照、关联权益和原因只读,新注册用户不会自动创建 |
| 迁移请求 / 设备凭证(隐藏导航) | MigrationRequest / DeviceCredential | 只读排查短时确认状态和凭证前缀 / 吊销时间;不展示令牌明文或 hash |
## 后台职责约定
### 用户与账号管理
- 用户自助注册;API Key 由用户在用户端自助生成(服务端生成、哈希存储、明文只显示一次),运营侧只能查看 prefix / 吊销,不回显明文。
- 禁用用户或吊销 Key 后,相关调用返回 403;无效、缺失或不存在的 Key 返回 401。
- T-401 已实现运营手工调点:后台从 `UserWallet` 进入专用表单,必须填写非 0 点数变动和原因;服务层 `adjust_wallet_points()` 在事务内锁 `UserWallet`、拒绝扣成负数,并写 `PointsLedger(change_type=adjust)`;不允许直接编辑 `points_balance` 字段。
### 模型与别名管理
- AiModel 的 `api_key` 加密存储,列表/详情脱敏显示,不回显明文。
- 换供应商:改 `ModelAlias` 的指向即可,对外别名与计费规则不变。
- AiModel / ModelAlias / 密钥的后台变更写入 `AiConfigAuditLog`(谁、何时、改了什么),日志只读;密钥变更只记录 empty/set 状态,不记录明文或密文。
### 流水 / 调用记录
- 只读列表,支持按账号、API Key、设备、时间范围、类型/状态筛选与搜索;设备关联为空表示存量无头调用,不代表调用失败。
### 软件授权
- T-632 后 django-admin 应用索引只展示“会员套餐”和“用户会员”。其他授权、订单、设备与审计模型仍注册且可通过直接管理地址访问,隐藏导航不等于删除数据或放宽权限。
- 开发测试阶段设置 `CMSHOPEE_SUBSCRIPTION_MODE=open`,运营无需给所有存量用户逐个创建长期权益;正式启用前按 `open` → `shadow` → `enforce` 顺序切换。
- T-626 的套餐、权益和席位只提供运营基础数据与人工操作,不新增公开接口、不接支付订单,也不改变通用生成 API 的授权结果。
- 授予 / 续期 / 撤销一律调用 `apps.licensing.services`;原因必填并产生 `LicenseEvent`。不得在 admin 表单中直接修改权益状态、到期时间、席位绑定或历史事件。
- 已授予权益读取自身套餐快照,不随 `SoftwarePlan` 后续价格、时长或设备数变化而变化;席位数量由授予时快照固定。
- 不允许在后台修改或删除流水与调用记录(账目不可篡改)。
### 列表与检索
- 充值订单、流水、调用记录默认按时间倒序,提供账号与状态筛选。
## 导航规则
- 对外只暴露 `/api/v1/*`;`/admin/` 仅限运营,生产环境建议限制来源或加额外保护。
- 未授权访问 admin 跳转登录;API 未授权返回 401,不静默失败。
- 用户端顶部导航的深色 / active 样式应表示“当前所在页面”,不能固定给某个入口。若保留“充值”作为常驻 CTA,应使用与当前页 active 明确区分的样式,避免用户误以为仍停留在充值页。
### 导航 active 状态
- 已修复:`apps/portal/templates/portal/base.html` 基于 `request.resolver_match.url_name` 判断当前页面,只给当前页面入口使用 `btn-primary` 与 `aria-current="page"`。
- 非当前页面入口使用 `btn-outline-secondary`;`/records/recharge` 与 `/records/usage` 分别高亮对应记录入口。
- 已补 portal 测试:登录后访问 `/models`、`/recharge`、`/apikeys`、`/records/recharge`、`/records/usage`、`/dashboard` 时,只有当前页面入口高亮;访问非充值页时「充值」不再固定高亮。