Files
cmhub/docs/tasks/T-632.md
T

94 lines
6.0 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.
# T-632 会员订阅三阶段启用与简化运营
## 背景
cmhub 已具备软件套餐、用户权益、软件订单、支付回调、授权事件和设备观测能力,但开发与线上测试阶段若要求运营逐个给存量用户绑定长期套餐,操作成本高且容易误伤已有用户。
当前 `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=false` 只保证产品专属生成接口不阻断;订阅状态接口仍返回真实的 `required` / `expired`,且响应缺少桌面端已依赖的账号、套餐代码、顶层有效期、会员中心和通知字段,因此桌面端可能把有效 API Key 误判为“需要配置会员账号”。
## 目标
1. 用 `open`、`shadow`、`enforce` 三种明确模式分阶段启用账号订阅。
2. 开发测试阶段无需批量创建虚假 `SoftwareEntitlement`,存量有效 API Key 可继续使用。
3. 订阅状态接口返回桌面端可直接消费的完整、向后兼容结构。
4. django-admin 软件授权首页只展示“会员套餐”和“用户会员”两个日常入口,其他模型保留数据与直接管理地址。
## 范围
### 配置
- 新增 `CMSHOPEE_SUBSCRIPTION_MODE`,仅接受 `open` / `shadow` / `enforce`。
- 新配置缺失时兼容旧 `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT`:旧值 `true` 等价于 `enforce`,否则等价于 `open`。
- 非法模式必须在 Django 启动配置阶段明确报错,不能静默回退。
### 授权
- `open`:有效 API Key 统一获得开发测试访问,不要求数据库权益,不写入虚假权益。
- `shadow`:有真实有效权益时返回真实套餐;无权益或过期时继续放行并返回兼容访问,同时记录真实权益状态。
- `enforce`:只有真实有效或仍处于宽限期的权益可提交产品专属任务。
- 通用 `/api/v1/generate/*` 不应用软件订阅限制。
- 已接受的异步任务详情、轮询和下载不追加订阅拦截。
### 状态接口
`GET /api/v1/cmshopee/subscription/status` 保留现有 `product_code`、`status`、`allowed`、`code`,并补齐:
- `account.display_name` / `account.username`
- `plan.code` / `plan.display_name`
- 顶层 `expires_at` / `grace_expires_at`
- `manage_url`
- `notice_id`
- `access_source`
- `entitlement_status`
其中 `status` 表示当前运行模式下的最终访问状态,`entitlement_status` 表示数据库真实权益状态;两者在 `open` / `shadow` 下允许不同。
### admin
- 将 `SoftwarePlan` 展示名改为“会员套餐”。
- 将 `SoftwareEntitlement` 展示名改为“用户会员”。
- admin 应用索引只展示上述两个模型。
- `SoftwareOrder`、`LicenseEvent`、`ClientDevice`、`DeviceSession`、`DeviceBindingAudit` 以及 T-631 已隐藏的历史授权模型继续注册,保留权限控制、历史数据和直接管理地址。
## 实施约束
- 模式切换不得创建、续期、撤销或批量修改用户权益。
- 不修改用户 API Key、钱包、点数流水、充值订单或软件订单状态。
- 不删除授权、订单、设备或审计表。
- 日志不得记录 API Key、prompt、原始设备标识或图片。
- 响应新增字段保持向后兼容,不能删除 T-630 已发布字段。
- `manage_url` 只能由服务端 `PUBLIC_BASE_URL` 构造。
## 验收条件
1. `open` 下无真实权益用户的状态接口返回有效访问,产品专属 submit 可用,数据库不新增权益。
2. `shadow` 下无真实权益用户继续可用,响应和结构化日志能区分最终放行与真实权益缺失。
3. `enforce` 下无权益与过期用户分别返回 `subscription_required` / `subscription_expired`,且拒绝发生在预扣与上游调用前。
4. 有效与宽限期权益返回真实套餐、稳定套餐代码和正确顶层有效期。
5. 状态接口包含桌面端要求的账号、套餐、有效期、会员中心和通知字段;旧字段仍存在。
6. 新配置缺失时兼容旧布尔开关;非法模式不能通过配置检查。
7. admin 首页仅显示“会员套餐”和“用户会员”;隐藏模型的直接管理地址和历史数据仍可访问。
8. 通用生成、点数、充值、软件订单和异步任务读取回归通过。
9. `manage.py check`、目标测试、迁移一致性、编译检查和 `git diff --check` 通过。
## 状态
DONE。
## 实施结果
- 配置层新增 `CMSHOPEE_SUBSCRIPTION_MODE` 严格枚举校验;新配置缺失时按旧 `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT` 映射,运行时代码统一读取有效模式。
- 授权服务将数据库真实权益判定与最终访问判定分离;`open` / `shadow` 不写 `SoftwareEntitlement`,`enforce` 才在产品专属 submit 预扣前拒绝。
- 订阅状态接口已补齐账号、套餐代码/显示名、顶层有效期、会员中心、通知 ID、访问来源和真实权益状态;保留 T-630 的 `plan.name` 与套餐内有效期字段。
- 授权日志同时记录真实 `would_reject`、最终 `final_allowed`、模式、来源和真实权益状态,不记录 API Key、prompt 或图片。
- admin 应用索引只保留“会员套餐”和“用户会员”;其他模型仍注册并可通过直接管理地址访问。新增 `licensing.0005_alter_softwareentitlement_options_and_more`,仅修改模型显示名称。
## 验证结果
- `manage.py check`:通过,0 issues。
- `manage.py test apps.licensing.tests.SubscriptionModeUnitTests --noinput --verbosity 2`:5 tests OK,覆盖旧开关映射、open 合约、shadow 观测、真实宽限期和 admin 导航。
- `manage.py makemigrations --check --dry-run`:No changes detected;检查迁移历史时旧远程 MySQL `43.128.3.240` 拒绝连接并给出 RuntimeWarning,不影响迁移文件一致性结果。
- `compileall config apps/licensing apps/api`:通过。
- 非法 `CMSHOPEE_SUBSCRIPTION_MODE` 启动检查:按预期抛 `ImproperlyConfigured`。
- 依赖数据库的 70 条 API/admin 定向测试已尝试,但本地 `.env` 指向的 `43.128.3.240` 拒绝连接;改用本机 MySQL 时项目账号无本机权限,因此本轮未取得数据库集成测试通过结果,未将其误记为通过。