diff --git a/docs/04-architecture.md b/docs/04-architecture.md index 446324c..46f97a8 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -46,6 +46,8 @@ T-626 已在同一 app 建立授权基础:`SoftwarePlan` 可由运营维护产 T-629 已完成:`SoftwareOrder` 属于 `apps.licensing`,与 `RechargeOrder`、`PointsLedger` 完全隔离。订单在创建时锁定产品、套餐、金额、时长、设备数和宽限期快照;支付成功后锁订单,校验金额/通道/交易号,首次购买创建权益,续订延长同套餐权益,并记录一项可反查订单的 `LicenseEvent(order_fulfilled)`。`(pay_method, payment_txn_no)` 唯一且已支付订单只接受原交易号,防止跨订单复用交易号;任何路径不得调用充值入账服务或修改钱包。微信下单、验签与查单只复用通用网关适配,订阅使用独立 notify URL、回调与状态查询路由。第一版不做自动代扣、退款发起或自动回收权益;已支付退款及权益撤销由运营按订单人工处理。 +T-630 规划:蝦皮圈产品授权从设备凭证改为账号订阅。`evaluate_account_authorization(user, product_code)` 只查询用户有效 `SoftwareEntitlement`,不读取 `DeviceCredential`、`LicenseSeat` 或 `DeviceSession`;同一用户可从多台设备调用。`ClientDevice` / `DeviceSession` 仍可作为可选观测关联到调用记录,但不参与授权结果。产品专属入口默认只记录 `would_reject` 账号订阅日志,配置 `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=true` 后才将无订阅/过期结果映射为 403;通用生成入口继续保持原 API Key + 点数语义。 + 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。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index ea1a249..75d7e32 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -108,6 +108,7 @@ | T-627 | 存量用户迁移权益、网页确认与设备凭证 | T-624, T-626, T-501 | 已完成存量迁移闭环:新增 `LegacyMigrationGrant`、短时 `MigrationRequest` 和 `DeviceCredential`,均只保存 hash/摘要;admin 通过显式用户+套餐授予迁移资格和快照,新注册用户无自动路径。客户端用旧 API Key + 当前设备会话申请迁移并一次性取得凭证明文;同账号 portal 确认后事务内绑定席位、按 hash 签发凭证,重复确认/轮询不重复占位或签发。用户可自助解绑,凭证吊销并释放席位且写审计。新增迁移 API、portal 确认/设备页;旧生成 API 完全不变。已覆盖资格、缺会话、跨账号、过期、重复确认、凭证 hash、状态轮询与解绑;迁移 / `check` / 迁移一致性通过。 | DONE | | T-628 | 蝦皮圈专属授权入口与影子校验 | T-625, T-626, T-627, T-613, T-614 | 已完成:新增 `/api/v1/cmshopee/` 的 title、vision、异步图片提交和已接受任务读取,均复用原 API / 计费 core。统一授权判定检查设备会话、凭证、用户、产品、席位和权益,返回 `device_not_bound` / `device_mismatch` / `license_required` / `license_expired`;当前仅写脱敏 `would_reject` 日志,不阻断专属或通用调用。通用 `/api/v1/generate/*` 未改,任务读取不追加订阅校验;`CMSHOPEE_AUTHORIZATION_SHADOW_MODE=true` 为部署口径。已验证正确 / 缺失 / 过期凭证判定、迁移流程回归、`check` 和迁移一致性。**评审说明(2026-07-21)**:`CMSHOPEE_AUTHORIZATION_SHADOW_MODE` 当前仅被写入日志事件,没有任何分支依据它决定放行或拒绝,属阶段 2 预期行为;须留档提醒——切换强制拦截不是改环境变量即可,需要在专属入口显式加入拒绝分支并同步部署说明,避免运维误判为纯配置开关。 | DONE | | T-629 | 软件套餐购买、续订订单与权益入账 | T-626, T-627, T-628, T-304, T-305 | **已完成。** 新增与 `RechargeOrder`、`PointsLedger` 完全分离的 `SoftwareOrder` 和 `licensing.0004_alter_licenseevent_action_softwareorder`。订单锁定产品、套餐、金额、币种、时长、设备数、宽限期和通道快照,保存关联权益、唯一 `order_fulfilled` 事件和发放时间;`order_no` 与 `(pay_method, payment_txn_no)` 受数据库唯一约束,已支付订单收到不同交易号一律拒绝。微信支付仅复用通用验签、下单、查单适配,新增独立 `SOFTWARE_WECHAT_PAY_NOTIFY_URL`、`POST /api/v1/software-orders/callback/wechat` 与 `GET /api/v1/software-orders/status`,未调用 `apply_recharge_payment()`、未写 `UserWallet` / `PointsLedger`。portal 新增 `/subscription` 套餐购买/续订、状态和最近订单,admin 新增只读软件套餐订单检索。成功回调/查单锁订单后首次创建权益或续订同套餐权益;同订单重复/并发回调最多发放一次,金额/通道/验签不一致不发权益。支付票据到期转 `expired`,下单失败转 `failed`。第一版仅微信主动月度续订,不做自动代扣、退款发起或自动退款回收;已支付退款与权益撤销由运营按订单人工处理。**同时修复 T-626 遗留**:续期同步刷新未吊销凭证到期时间,撤销权益逐项吊销凭证、释放席位并写审计。已通过 12 条 licensing/API 目标测试(含双连接并发回调)和 31 条既有充值回归;`check`、`makemigrations --check --dry-run`、编译检查通过。全量测试在远端 MySQL 测试库运行 10 分钟未返回且无失败输出,未将其记为通过。 | DONE | +| T-630 | 账号订阅授权与默认多设备模式 | T-628, T-629 | 将蝦皮圈产品专属授权从“设备会话 + 设备凭证”调整为“API Key 对应账号 + 软件订阅权益”。同一账号默认允许多台电脑使用,不设置 `LicenseSeat` 设备数量硬限制;`ClientDevice` / `DeviceSession` 仅保留为可选观测和风控数据。新增 `GET /api/v1/cmshopee/subscription/status`,返回账号对应产品的订阅状态、套餐和到期时间;新增 `subscription_required` / `subscription_expired` 错误码。授权服务只查询用户 `SoftwareEntitlement`,不再读取 `DeviceCredential`。`CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=false` 时只写账号订阅影子日志并保持兼容,开启后产品专属生成入口才拒绝无订阅用户;通用 `/api/v1/generate/*`、充值、点数和历史迁移接口不变。保留旧设备/卡密数据只读,不删除历史记录,不再签发新设备凭证。测试覆盖多设备同账号、无订阅、过期、有效订阅、开关强制拦截和通用接口回归。 | TODO | ## 里程碑 diff --git a/docs/api.md b/docs/api.md index 71cdb99..620e3ba 100644 --- a/docs/api.md +++ b/docs/api.md @@ -41,6 +41,8 @@ T-305 已实现扫码充值下单与轮询基线:`POST /api/v1/recharge/create T-629 已完成:软件套餐订单使用独立 `SoftwareOrder`、`POST /api/v1/software-orders/callback/wechat` 与 `GET /api/v1/software-orders/status`,只发放/续订软件权益,绝不兑换点数或触碰 `RechargeOrder` / `PointsLedger`。订阅微信下单必须配置独立 `SOFTWARE_WECHAT_PAY_NOTIFY_URL`,不得复用充值回调地址。 +T-630 规划:蝦皮圈授权改为账号订阅模式。产品专属接口按 API Key 对应用户查询 `SoftwareEntitlement`,默认允许同一账号在多台电脑使用;设备登记、设备会话和历史凭证不再作为授权前置条件。 + T-306 已实现对外 API 安全加固:`image_url` 下载只允许 `http` / `https` 公网地址,拒绝私有/回环/链路本地/保留等地址,重定向后重新校验并限制响应大小;全局 DRF 默认认证为空且默认权限为 `IsAuthenticated`,外部 API 与用户端 API 必须显式 opt-in 认证类;生成接口按 API Key / 用户限流,认证失败按 IP 限流;充值创建有 `RECHARGE_MAX_AMOUNT_CNY` 单笔上限。 T-604 目标口径:生成接口在 serializer 基础校验后先执行 prompt 本地敏感词检查;命中返回 `400 content_blocked`,且不得下载 `image_url`、不得预扣点、不得写 `CallRecord` / `PointsLedger`、不得调用上游。T-604 不启用输出审核和图片审核;详细规则见 [`moderation.md`](moderation.md)。 @@ -95,6 +97,8 @@ T-607/T-609/T-617 已实现 `GET /api/v1/client/releases/latest?platform=windows | `device_session_invalid` | 缺失、无效或过期设备会话 | 401 | | `device_revoked` | 客户端设备已被吊销 | 403 | | `device_mismatch` | 设备会话不属于当前 API Key 所属账号 | 403 | +| `subscription_required` | 当前账号没有该产品的有效订阅 | 403 | +| `subscription_expired` | 当前账号订阅已到期或超过宽限期 | 403 | | `device_session_required` | 存量迁移申请缺少当前设备会话 | 401 | | `migration_not_eligible` | 当前账号没有有效存量迁移资格 | 403 | | `migration_request_not_found` | 迁移请求不存在或不属于当前设备 | 404 | diff --git a/docs/current-state.md b/docs/current-state.md index 05a6f32..fa5659e 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -77,9 +77,9 @@ - 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补;T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型;T-202 PricingRule / ExchangeRate 模型 + 计费计算;T-203 并发安全扣点 / 退点;T-204 Phase 2 计费核心审核加固;T-301 API Key 鉴权;T-302 生成标题 / 图片接口;T-303 余额查询接口;T-304 充值回调;T-305 扫码充值下单 + 轮询;T-306 Phase 3 对外 API 安全加固;T-501 注册 / 登录(allauth);T-502 API Key 自助管理页;T-503 个人中心 / 记录页;T-504 充值页(扫码 + 轮询到账);T-505 Phase 4 用户端审核优化;T-401 运营后台完善;T-402 完整验收 MVP;T-403 部署 / 运行文档;T-601 可用别名发现;T-602 django-admin 中文化(第 1-3 层);T-603 django-admin 中文化(第 4 层·字段级);T-604 中文敏感词本地过滤;T-605 免邮箱验证策略落地;T-606 公开首页 + 客户端下载入口;T-607 桌面端最新版本检查接口;T-608 新用户注册赠送试用点数(当前 10 点);T-609 桌面端版本检查接口增加强制更新标记;T-610 首页导入模板下载入口;T-611 用户端品牌名统一为虾皮圈;T-612 生图同步接口止血(上游硬截止 + 长请求池校准);T-613 抽生成核心 service(计费+审核+上游共享 core);T-614 生图异步任务化接口(提交+轮询,新增不动旧接口);T-615 旧同步生图接口用量遥测 + 弃用口径;T-616 生图失败自动重试 2 次;T-617 桌面端版本检查接口增加文件大小字段;T-618 客户端发布版本后台必填文件校验元数据。 - 已完成补充:T-619 多张图片理解并返回文字;T-620 图生图支持单图 / 多图主图与参考图;T-622 图片生成任务后台图片缩略预览;T-623 图片生成任务单图 / 多图筛选;T-624 蝦皮圈设备登记与会话观测;T-625 蝦皮圈设备使用关联与迁移观测;T-626 软件套餐、权益与设备席位基础模型及凭证续期/撤销清理;T-627 存量用户迁移权益、网页确认与设备凭证;T-628 蝦皮圈专属授权入口与影子校验;T-629 软件套餐购买、续订订单与权益入账。 - 正在进行:无。 -- 待开始:T-621 注册赠点运营后台配置继续留在 Backlog。真实支付回调到账闭环、客户端发布、生产多图理解模型配置和线上旧同步接口用量观察仍可继续拆任务。 +- 待开始:T-630 账号订阅授权与默认多设备模式;T-621 注册赠点运营后台配置继续留在 Backlog。真实支付回调到账闭环、客户端发布、生产多图理解模型配置和线上旧同步接口用量观察仍可继续拆任务。 - 当前 blocker:支付商户真实密钥/证书与生产 SDK 依赖仍待提供;微信回调到账闭环仍需真实支付验收;真实 AI 标题生成已在线上跑通,图片生成慢 / 504 / 客户端超时风险已拆为 T-612~T-616 并完成工程侧处理。 -- 下一个可领取任务:无。T-621 保留在 Backlog,真实微信商户支付验收仍是线上 blocker;软件订阅订单已完成代码与 mock/SDK 契约验证,但未进行真实付款验收。具体范围见 [`06-tasks.md`](06-tasks.md)。 +- 下一个可领取任务:T-630 账号订阅授权与默认多设备模式。T-621 保留在 Backlog,真实微信商户支付验收仍是线上 blocker;软件订阅订单已完成代码与 mock/SDK 契约验证,但未进行真实付款验收。具体范围见 [`06-tasks.md`](06-tasks.md)。 ## 当前可运行内容 diff --git a/docs/env.md b/docs/env.md index 0923c01..f1c0f7f 100644 --- a/docs/env.md +++ b/docs/env.md @@ -55,6 +55,7 @@ | `DEVICE_SESSION_TTL_SECONDS` | 否 | `3600` | T-624 设备会话令牌有效秒数,默认 1 小时;过期后客户端重新登记刷新令牌 | | `DEVICE_ACTIVITY_UPDATE_SECONDS` | 否 | `86400` | T-624 同一设备更新最后活跃时间的最小间隔秒数,默认每日一次,避免每次生成写库 | | `MIGRATION_REQUEST_TTL_SECONDS` | 否 | `900` | T-627 存量迁移网页确认请求有效秒数,默认 15 分钟;过期后客户端必须重新申请,凭证明文不在服务端恢复 | +| `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT` | 否 | `false` | T-630 账号订阅强制开关;`false` 只记录影子结果,`true` 才拦截蝦皮圈产品专属接口的无订阅/过期账号 | | `AI_IMAGE_UPSTREAM_DEADLINE_SECONDS` | 否 | `180` | T-612 生图上游读取硬截止秒数;只作用于 `generate_image` 的上游请求和上游返回图片 URL 下载,Provider 实际读取超时取 `min(AiModel.timeout_seconds 或分辨率默认值, 本值)`;生产按真实图片 smoke 耗时校准,外层 Gunicorn / Nginx / 客户端超时必须大于该值 | | `PUBLIC_BASE_URL` | 否 | `https://cm.833729.com` | 站点公开基础 URL;异步 worker 没有 request 时可用它生成绝对媒体 URL | | `MEDIA_PUBLIC_BASE_URL` | 否 | `https://cm.833729.com` | 媒体文件公开基础 URL;优先于 `PUBLIC_BASE_URL`,用于 T-614 异步生图 worker 返回 `result.image_url` | diff --git a/progress.md b/progress.md index 302e656..59f281d 100644 --- a/progress.md +++ b/progress.md @@ -2111,3 +2111,9 @@ - 支付:支付网关抽取无业务副作用的 `PaymentReceipt`,充值继续将其交给 `apply_recharge_payment()`;订阅改用独立的微信回调/状态路由和 `SOFTWARE_WECHAT_PAY_NOTIFY_URL`,避免软件支付进入点数充值回调。portal 新增 `/subscription`,admin 新增只读软件套餐订单。 - T-626 修复:续期在事务内刷新全部未吊销设备凭证到新的宽限截止;撤销权益会吊销未吊销凭证、释放已占席位并分别写审计事件。 - 验证:`py -3.12 manage.py test apps.licensing.tests.SoftwareOrderServiceTests apps.licensing.tests.SoftwareOrderConcurrencyTests apps.licensing.tests.LegacyMigrationFlowTests apps.api.tests.SoftwareOrderCallbackApiTests --keepdb` 通过(12 tests);`py -3.12 manage.py test apps.billing.tests.BillingServiceTests apps.api.tests.RechargeCallbackApiTests apps.api.tests.RechargeCreateStatusApiTests --keepdb` 通过(31 tests);`py -3.12 manage.py check`、`py -3.12 manage.py makemigrations --check --dry-run` 和 `compileall` 通过。两组均仅有既有 allauth MySQL 条件唯一约束 `models.W036` 警告。完整 `manage.py test --keepdb` 在远端 MySQL 测试库运行 10 分钟无失败输出后超时,未记为通过;线上真实微信付款验收仍未执行。 + +## 2026-07-21 规划:T-630 账号订阅授权与默认多设备模式 + +- 需求变更:保留软件会员订阅,取消卡密和固定电脑绑定。同一账号默认允许多台设备使用。 +- 任务边界:授权只检查 API Key 对应用户的 `SoftwareEntitlement`;设备登记/心跳/调用关联仅作观测和风控,历史设备凭证与迁移记录保留只读,不删除、不再作为新授权依据。 +- 发布策略:新增 `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=false`,先记录账号订阅影子结果;完成多设备与存量用户回归后,才在产品专属入口开启实际拒绝。