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

8.3 KiB
Raw Blame History

T-635 cmshopee 客户端启动订阅策略接口

背景

新版 cmshopee 客户端需要在启动时远程获知是否检查会员套餐、是否在 客户端侧展示会员门禁。客户端已经匿名请求 GET /api/v1/client/releases/latest?platform=windows,第一版应复用该公开 版本检查接口发布策略,避免新增启动请求。

现有订阅状态接口的 allowed 是当前服务端运行模式下的最终访问结果。 在 open 或 shadow 模式,无真实权益的账号仍可能得到 allowed=true。 客户端不能把该字段当作真实会员有效性,否则在未来切换客户端门禁时会误放行。

目标

  1. 版本检查响应顶层发布稳定、公开、无账号上下文的 client_policy。
  2. 用一个受校验的配置枚举表达客户端策略,输出兼容桌面端所需的两个布尔字段。
  3. 订阅状态接口明确返回真实权益是否可用,消除 open / shadow 的歧义。
  4. 策略仅影响客户端体验;服务端产品专属接口仍由 CMSHOPEE_SUBSCRIPTION_MODE 独立授权。

范围

公开版本检查接口

在 GET /api/v1/client/releases/latest?platform=windows 的顶层新增:

{
  "platform": "windows",
  "release": null,
  "message": "暂未发布",
  "client_policy": {
    "policy_version": 1,
    "subscription_check_enabled": true,
    "subscription_enforcement_enabled": false,
    "updated_at": "2026-07-28T10:00:00+08:00"
  }
}
  • client_policy 不得为 null,在有当前发布和 release:null 两条成功路径 都必须返回;release 的既有字段和语义不变。
  • policy_version 当前固定为整数 1;两个开关必须是 JSON 布尔值; updated_at 必须是带时区的 ISO 8601 字符串,代表配置最后变化时间,不能 使用每次请求的当前时间。
  • 该接口继续匿名、只读、无 API Key / Cookie / 用户 / 套餐 / 点数查询,不扣点, 不占用生成接口限流;响应不得含 API Key、用户、套餐、内部路径、后台 ID 或密钥。
  • 成功响应设置 Cache-Control: no-store,保证客户端紧急回退不会长期受 CDN 或浏览器缓存影响。

配置

  • 新增 CMSHOPEE_CLIENT_SUBSCRIPTION_POLICY=off|observe|enforce,默认 off。 非法值必须阻止 Django 启动,不能静默降级。
  • 输出映射固定为:off -> false/false、observe -> true/false、 enforce -> true/true。不使用两个独立环境变量,避免出现非法的 false/true 组合。
  • 新增 CMSHOPEE_CLIENT_SUBSCRIPTION_POLICY_UPDATED_AT,要求为带时区 ISO 8601 时间;每次把策略改为 observe 或 enforce 时必须同步显式更新。未配置时 只允许默认 off 使用仓库固定的基线时间;非 off 未配置或时间非法时阻止启动。
  • 该配置与已有 CMSHOPEE_SUBSCRIPTION_MODE=open|shadow|enforce 完全独立。 前者只控制新版客户端是否请求 / 展示门禁,后者才控制服务端专属生成接口的 最终放行。不得因客户端策略变更而创建、修改或删除权益、套餐、订单、点数、 API Key 或异步任务。

订阅状态真实权益语义

  • GET /api/v1/cmshopee/subscription/status 顶层新增 real_entitlement_allowed。 仅当 entitlement_status 为 active 或 grace 时为 true;该字段不受 open / shadow / enforce 的最终放行模式影响。
  • 保留现有 allowed、status、code、access_source 和 entitlement_status,不改变服务端当前授权或既有客户端兼容行为。
  • 文档删除未实现的 legacy / revoked 状态承诺:当前真实权益状态只以 active、grace、required、expired 为准;禁用账号 / API Key 仍由认证层 返回 HTTP 403 account_disabled,不是状态接口的成功 JSON 状态。

非目标与兼容

  • 不新增数据库模型、迁移、admin 配置页或运行时热修改能力;环境变量变更后 按既有部署方式重启 Web / Generate 服务生效。
  • 不改通用 /api/v1/generate/*、产品专属生成接口的授权逻辑、上游调用、 预扣 / 确认 / 退款、UserWallet、PointsLedger、充值支付或软件订单。
  • 旧客户端会忽略新增顶层字段,仍按自身内置逻辑工作;新客户端应只在 policy_version=1 且字段类型合法时采纳策略。无法获取策略时客户端应进入 “检测开启、强制关闭”的观察回退,不能提示用户重复付费。

验收条件

  1. off、observe、enforce 三种合法配置分别返回 false/false、true/false、true/true,类型均为 JSON boolean。
  2. 非法枚举、非 off 缺少更新时间、无时区或非法更新时间均阻止 Django 启动或 在配置读取处抛出明确 ImproperlyConfigured,不得向外发布矛盾策略。
  3. 有当前发布、没有当前发布、当前发布无下载地址三种成功响应均包含 client_policy;原有 release 字段与 force_update、size_bytes 不回归。
  4. 版本检查接口匿名可用,忽略 Session / API Key,响应为 Cache-Control: no-store, 且不访问或泄露用户、订阅、点数、模型、密钥、文件系统路径或后台 ID。
  5. open / shadow 下无权益账号仍可能保持现有 allowed=true,但 real_entitlement_allowed=false;真实 active / grace 权益为 true,过期 / 未开通 为 false。
  6. CMSHOPEE_SUBSCRIPTION_MODE 与客户端策略任意组合均不改变既有产品专属接口 的服务端授权结论;通用生成、扣点、退款、充值和软件订单回归通过。
  7. 同步更新 docs/api.md、docs/env.md、docs/deployment.md、 docs/04-architecture.md、docs/current-state.md、任务状态和 progress.md; 及 Obsidian 的客户端接口契约。
  8. manage.py check、目标 API / licensing 测试、迁移一致性、编译检查和 git diff --check 通过。

状态

DONE。

实施结果

  • 新增 config/client_subscription_policy.py,以单一 CMSHOPEE_CLIENT_SUBSCRIPTION_POLICY=off|observe|enforce 解析并发布策略, 固定输出 policy_version=1 与两个 JSON 布尔字段;非法模式、非 off 缺更新时间、 无时区或非法时间均抛出 ImproperlyConfigured。
  • GET /api/v1/client/releases/latest 的当前发布、无发布和无下载地址路径均返回 顶层 client_policy,所有响应设置 Cache-Control: no-store;接口仍只查询 DownloadRelease 和环境配置。
  • GET /api/v1/cmshopee/subscription/status 新增 real_entitlement_allowed,仅在真实权益为 active 或 grace 时为 true, 保留既有最终访问 allowed 兼容字段。
  • 未新增模型、迁移、admin 配置或运行时写入,不改通用 / 专属生成、点数账本、 充值支付、软件订单或服务端订阅强制模式。

验证结果

  • py -3.12 -m py_compile config\client_subscription_policy.py config\settings.py apps\api\views.py apps\api\tests.py apps\licensing\services.py apps\licensing\tests.py:通过。
  • py -3.12 manage.py check:通过,0 issues。
  • 隔离 SQLite 目标回归:ClientLatestReleaseApiTests、订阅状态核心场景和 SubscriptionModeUnitTests 共 27 tests OK,覆盖三种策略、无发布策略、 非法配置、no-store、真实权益 active / grace / required / expired 语义。
  • py -3.12 manage.py makemigrations --check --dry-run:No changes detected; 旧本地 MySQL 地址拒绝连接,仅产生迁移历史检查 warning,本任务无迁移。
  • ./init.ps1 和 git diff --check:通过。

生产部署

  • 2026-07-28 已部署到 185.216.248.75,线上 .deploy_revision 为 1c100c5。
  • 部署前备份位于 /root/cmhub_deploy_backups/cmhub-before-t635-20260728_151900,包含发布前应用代码和 .env。
  • 线上保持 CMSHOPEE_SUBSCRIPTION_MODE=enforce,新增客户端策略明确设置为 CMSHOPEE_CLIENT_SUBSCRIPTION_POLICY=off,更新时间为 2026-07-28T15:19:19+08:00;不改变服务端授权。
  • cmhub-web、cmhub-generate、Nginx、MySQL 8.4 均为 active;66 个图片 worker 未重启且均 active,failed units 为 0。
  • 公网 GET /api/v1/client/releases/latest?platform=windows 返回 HTTP 200、 Cache-Control: no-store 和 client_policy=false/false。