feat: add legacy device migration flow

This commit is contained in:
QiuSW
2026-07-21 10:06:51 +08:00
parent 7b80ae3a7a
commit 1af47d043e
20 changed files with 1030 additions and 6 deletions
+5
View File
@@ -44,6 +44,8 @@ T-624/T-625 已新增 `apps.licensing`:`POST /api/v1/client/devices/register`
T-626 已在同一 app 建立授权基础:`SoftwarePlan` 可由运营维护产品、时长、价格、设备数、宽限期和状态;`SoftwareEntitlement` 在人工授予时复制套餐名称、价格、时长、设备数和宽限期快照,之后套餐改动不回写历史权益。服务层在事务内预创建固定数量 `LicenseSeat`,绑定时先锁权益和全部席位,避免并发超售;人工授予、续期、撤销、绑定和解绑都必须给出原因并写只读 `LicenseEvent`。续期从 `max(now, expires_at)` 起算,且全程不读取或修改点数钱包。django-admin 通过专用操作页处理授予 / 续期 / 撤销,权益、席位和事件不允许直接篡改。此阶段没有购买入口、支付订单、迁移凭证或生成授权拦截。
T-627 已建立存量迁移闭环:运营在 admin 显式创建 `LegacyMigrationGrant`,它关联一项套餐快照权益及资格快照,新注册用户不会自动创建。客户端必须以旧 API Key 加服务端验证的当前设备会话申请 `MigrationRequest`;请求只短时有效,生成的设备凭证明文只在该 API 响应出现一次。portal 仅允许请求所属账号确认,确认事务内分配席位、按请求中已存 token hash 签发 `DeviceCredential`,重复确认只返回既有结果。用户自助解绑会吊销凭证、写授权事件并释放对应席位。原始凭证、机器标识、公钥和 API Key 都不写入迁移记录 / 事件;T-628 才会用凭证参与专属接口影子校验。
T-302 已实现 `/api/v1/generate/title` 与 `/api/v1/generate/image`:API 层只做鉴权、参数校验和编排;别名解析、Provider 选择、计费计算、预扣、成功确认、失败退点分别调用 `apps.ai` / `apps.billing` 既有模块。T-613 已把生成链路抽为 `apps.api.generation` 的核心阶段:`prepare_generation()` 负责审核、图片输入、别名、Provider 与计费准备;`precharge_generation()` 只调用 billing 预扣;`execute_precharged_generation()` 复用已预扣 `CallRecord` 调上游并成功确认或失败退点,供旧同步接口和后续异步 worker 共用。T-620 将图生图输入扩展为有序集合:兼容旧单图字段,新 `images[0]` 为主商品图、`images[1:]` 为参考图;服务端在审核用户 prompt 后固定追加角色规则,再将完整顺序交给 Provider。图片结果 MVP 先用本地 `default_storage` 保存到 `MEDIA_ROOT/generated/images/...` 并返回 `image_url`;核心阶段通过 URL 构建器生成外部 URL,不依赖 DRF `Request`;`CallRecord` 只写 URL / 摘要,不保存 provider `raw` 或 base64。
T-619 已在同一生成核心增加 `vision` 分支和 `/api/v1/analyze/images`:API 接收有序的 `images[]`,每项二选一提供 `image_url` / `image_base64`;prompt 审核通过后才下载或解码图片,随后校验同时具备 `text + vision` 的模型与 Provider、按 `vision + alias` 默认价格预扣一次、同步调用 Chat Completions / Gemini 多模态接口并返回完整文字。输入图片只在请求内存中使用,不落库;`CallRecord` 只保存最多 500 字结果摘要。
@@ -123,6 +125,9 @@ T-619 多图理解继续复用上述 URL 下载器和逐跳 SSRF 校验,并额
| SoftwareEntitlement | 授权服务 | 用户、产品、来源套餐(可空)及购买/授予时套餐快照、状态、开始/到期/宽限截止/撤销时间;不从套餐反向同步 |
| LicenseSeat | 授权服务 | 每项权益按序号预创建的固定席位,可空关联已绑定设备及绑定 / 解绑时间;`(entitlement, seat_number)` 唯一 |
| LicenseEvent | 授权服务 | 授予、续期、撤销、绑定、解绑等不可变事件;保存权益、可选席位/设备、操作人、必填原因和非敏感元数据 |
| LegacyMigrationGrant | 运营迁移 | 显式授予的存量用户资格、资格快照、关联权益、操作人和原因;`(user, product_code)` 唯一,禁止注册自动创建 |
| MigrationRequest | 客户端迁移 | 公开 UUID、用户、当前设备、资格、待签发凭证 hash/前缀、短时状态和确认时间;不存凭证明文 |
| DeviceCredential | 产品凭证 | 用户、产品、设备、权益、席位、来源迁移请求、token hash/前缀、到期/吊销信息;明文只在请求 API 返回一次 |
关键事实: