117 lines
8.4 KiB
Markdown
117 lines
8.4 KiB
Markdown
---
|
||
id: T-705
|
||
title: cmhub 远程订阅策略开关与分类门禁提示
|
||
phase: 8
|
||
deps: [T-700, T-701]
|
||
status: DONE
|
||
created: 2026-07-28
|
||
---
|
||
|
||
# T-705 cmhub 远程订阅策略开关与分类门禁提示
|
||
|
||
## 问题 / 背景
|
||
|
||
当前客户端通过本地常量控制启动订阅检测和强制门禁,cmhub 无法在灰度上线、接口维护或异常期间远程切换。每次调整都需要重新修改并打包客户端。现有强制模式已经能限制业务 Tab、拦截新提交,并对缺少 Key 和套餐过期提供窗口,但未开通套餐、套餐撤销、账号禁用、Key 无效和接口异常仍缺少与原因匹配的独立窗口。
|
||
|
||
不能把远程开关放进订阅状态接口,否则客户端必须先请求该接口才能知道是否应跳过它。客户端门禁也不能成为服务端授权的唯一安全边界,cmhub 产品接口仍必须独立校验 API Key 和套餐。
|
||
|
||
## 外部契约(已交付)
|
||
|
||
cmhub 已按 Obsidian《cmhub-cmshopee客户端订阅策略接口契约》完成 T-635。客户端复用现有 HTTPS 启动版本接口:
|
||
|
||
最低要求是在客户端启动时已经请求的 HTTPS 版本接口中增加可选 `client_policy`:
|
||
|
||
```json
|
||
{
|
||
"client_policy": {
|
||
"policy_version": 1,
|
||
"subscription_check_enabled": true,
|
||
"subscription_enforcement_enabled": true,
|
||
"updated_at": "2026-07-28T10:00:00+08:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
cmhub 内部只配置 `CMSHOPEE_CLIENT_SUBSCRIPTION_POLICY=off|observe|enforce`,对客户端仍输出上述两个布尔字段。订阅状态成功响应另提供顶层严格布尔字段 `real_entitlement_allowed`;强制模式必须只按该字段放行,不得使用展示用 `status` 或兼容 `allowed` 推断。当前成功状态为 `active/grace/required/expired`,账号禁用使用 HTTP 403;`revoked` 只保留客户端兼容处理,不属于当前服务端承诺状态。
|
||
|
||
cmhub 保证无论客户端门禁是否开启,付费产品接口仍独立执行服务端授权;客户端开关不是授权凭据。
|
||
|
||
## 方案
|
||
|
||
### 1. 远程双开关
|
||
|
||
- `check=false`:跳过 `SubscriptionCheckWorker`,不显示会员激活/过期/异常窗口,不锁定业务 Tab;生成前订阅预检直接放行,设置页重新检测入口显示“服务端暂未启用会员检测”并禁用。
|
||
- `check=true, enforcement=false`:执行真实查询并展示账号、套餐、有效期和观察状态,但不禁用业务功能、不拦截新提交、不弹强制处理窗口。
|
||
- `check=true, enforcement=true`:保持现有强制门禁;仅 `real_entitlement_allowed=true` 放行业务功能,该值只能与 `active/grace` 同时出现。
|
||
- `check=false, enforcement=true` 属于非法组合,客户端归一化为两者均关闭并写脱敏诊断,不允许在没有检测结果时强制锁定。
|
||
|
||
### 2. 启动策略获取与兼容
|
||
|
||
- 优先复用启动版本接口返回的 `client_policy`,不得为了读取开关再调用订阅状态接口,也不增加独立同步阻塞请求。
|
||
- 缓存最近一次结构合法的非敏感策略和取得时间到 `data/config/` 独立文件;不包含 API Key、账号、套餐或订阅结果。
|
||
- 当前响应有效时覆盖缓存;接口不可达、字段缺失或非法时使用最近一次有效缓存。缓存额外记录本地取得时间。无有效缓存时回退为“检测开启、强制关闭”的观察模式,避免控制面异常锁死正常用户;cmhub 产品接口继续作为最终授权边界。
|
||
- 旧 cmhub 响应不含 `client_policy` 时按上述兼容规则处理,不影响启动版本检查和强制升级。
|
||
- 只在启动时确定本次运行策略;服务端开关变更下一次启动生效。设置保存和“重新检测会员状态”只重查套餐,不偷偷改变本次策略。
|
||
|
||
### 3. 分类窗口
|
||
|
||
- `not_configured`:复用会员 API Key 激活窗口。
|
||
- `required`:新增“尚未开通会员套餐”,提供“前往会员中心”“重新检测”“退出程序”。
|
||
- `expired`:保留现有“会员套餐已过期”窗口及会员中心入口。
|
||
- `revoked`:保留兼容窗口“会员套餐已失效”,不得使用含糊的技术错误;当前 cmhub 不承诺返回该状态。
|
||
- `account_disabled`:新增“会员账号当前不可用”,提示前往会员中心或联系处理。
|
||
- `key_invalid`:打开可重新填写 API Key 的激活窗口并聚焦输入框,不使用泛化订阅窗口。
|
||
- `unavailable`:显示“暂时无法验证会员状态”,提供“重新检测”“打开设置”“退出程序”;不得误导用户未付费或要求重复订阅。
|
||
- 同一运行期间同一状态只显示一次;状态恢复为允许后再次进入无效状态才可重新提示。重复检测、保存设置和陈旧 worker 结果不得造成弹窗循环。
|
||
- 会员中心地址继续使用 `subscription.safe_manage_url()` 的 HTTPS 安全结果;无有效地址时禁用入口并显示中文原因。
|
||
|
||
### 4. 门禁边界
|
||
|
||
- 强制模式查询期间和明确无效状态只保留“设置”Tab;AI生成、AI帮写和商品套图的新提交继续经过主窗口统一预检。
|
||
- 不改变已经运行任务的协作取消策略,不删除本地结果、不修改 SQLite 业务数据、不关闭 Chrome。
|
||
- cmhub 托管生成接口仍必须服务端校验套餐;自定义网关和客户端本地功能是否受门禁影响沿用当前产品规则,本任务不扩张授权范围。
|
||
|
||
## 验收要点
|
||
|
||
- [x] 服务端双开关三种合法组合分别得到关闭、观察、强制三种稳定行为,非法组合不会锁死客户端。
|
||
- [x] 明确关闭时启动不请求订阅状态;观察模式查询但不限制;强制模式保持现有门禁。
|
||
- [x] 策略响应、缓存、旧响应、接口失败和无缓存回退均有测试,不记录敏感信息。
|
||
- [x] `required/expired/revoked/account_disabled/key_invalid/unavailable/not_configured` 使用对应中文窗口或激活流程,不把技术异常说成未订阅。
|
||
- [x] 分类窗口按状态转换去重,会员中心 URL 安全校验、重新检测、设置和退出操作可用。
|
||
- [x] 服务端仍是授权最终来源;修改客户端开关不能绕过 cmhub 生成接口套餐校验。
|
||
- [x] 不修改 Chrome、CDP、蝦皮采集、封面上传、拖拽或线上更新逻辑。
|
||
|
||
## 测试与文档
|
||
|
||
- 扩展 `tests/test_update_check.py`:策略字段解析、非法组合、旧响应和策略透传。
|
||
- 新增或扩展订阅策略缓存测试:有效缓存、损坏缓存、接口失败和无缓存观察回退。
|
||
- 扩展 `tests/test_gui.py`:关闭/观察/强制模式、七类状态窗口、去重、重检和门禁。
|
||
- 扩展 `tests/test_subscription.py`:状态与窗口动作映射所需的纯逻辑契约。
|
||
- 更新 `docs/04-architecture.md`、`docs/api.md`、`docs/routes.md` 和打包/发布说明。
|
||
|
||
## 验证
|
||
|
||
```bash
|
||
py -3.10 -m unittest tests.test_update_check tests.test_subscription tests.test_gui
|
||
py -3.10 -m unittest discover -s tests
|
||
py -3.10 -m ruff check app tests main.py
|
||
py -3.10 -m compileall app main.py
|
||
git diff --check
|
||
```
|
||
|
||
## 边界(不改什么)
|
||
|
||
- 不把客户端开关当成服务端授权凭据。
|
||
- 不在订阅状态接口中返回“是否调用本接口”的开关。
|
||
- 不新增同步启动阻塞请求,不把网络故障等同于未付费。
|
||
- 不提交 API Key、账号、套餐响应、配置、数据库、日志或业务数据。
|
||
|
||
## 执行记录
|
||
|
||
- 2026-07-28:完成客户端任务定义;等待 cmhub 启动策略接口契约与测试响应交付后解除阻塞。
|
||
- 2026-07-28:cmhub T-635 与线上契约已交付,确认 `client_policy`、服务端单枚举配置及 `real_entitlement_allowed` 语义,任务解除阻塞并开始实现。
|
||
- 2026-07-28:新增 `app/client_policy.py`,接入版本响应策略解析、非法组合归一化、`data/config/client_policy.json` 非敏感原子缓存和无缓存观察回退;启动流程把本轮策略传入主窗口。
|
||
- 2026-07-28:订阅状态改为只按 `real_entitlement_allowed` 放行;完成关闭/观察/强制三模式、设置页关闭态、七类中文处理流程和恢复前按状态去重,不修改 Shopee/CDP 链路。
|
||
- 2026-07-28:线上版本接口实测 HTTP 200、`Cache-Control: no-store`、策略 `off`;线上订阅接口脱敏实测返回 `active` 且真实权益允许。
|
||
- 2026-07-28:验证通过:策略/版本/订阅/GUI 定向测试 260 项;全量 `unittest discover` 717 项;`ruff check app tests main.py`、`compileall app main.py`、`git diff --check` 全部通过。
|