Files

5.8 KiB
Raw Permalink Blame History

T-633 存量用户批量授予过渡测试套餐

背景

线上已创建用于开发和联调阶段的“测试”会员套餐。当前订阅仍运行在 shadow 模式,存量用户不会被强制拦截,但在正式切换到 enforce 前,需要用真实 SoftwareEntitlement 验证桌面端会员状态、到期时间和后续套餐切换流程。

直接在数据库批量插入权益会绕过套餐快照、席位和 LicenseEvent,逐个在 admin 操作又容易漏发或重复发放。因此需要一个默认只预演、显式确认后才执行的批量命令。

目标

  1. 给执行时已有的启用非后台账号批量授予指定测试套餐。
  2. 复用现有授权服务,完整生成权益快照、席位和授权事件。
  3. 提供预演、人数校验、事务回滚和重复执行保护,降低生产批处理风险。
  4. 不影响已有正式会员、API Key、点数、充值、软件订单和生成任务。

范围

批量命令

  • 新增 grant_existing_users_plan management command。
  • 套餐必须通过数据库 plan_id 精确指定,且执行时仍为启用状态;不按可能重名的套餐名称模糊匹配。
  • 默认只输出预演统计,不写数据库。
  • 实际执行必须同时传入 --execute 和预演得到的 --expected-grant-count;人数不一致时整批拒绝。
  • --reason 必填并写入每一条授权事件,不能生成无原因的批量权益。

用户范围和幂等

  • 默认只处理 is_active=True、is_staff=False、is_superuser=False 的账号。
  • 对指定套餐所属产品,用户只要已有仍处于有效期或宽限期的权益,就跳过本次授予;不得用测试套餐覆盖或制造与正式套餐并存的有效权益。
  • 已过期或已撤销权益不阻止重新授予。
  • 执行阶段锁定套餐和目标用户,并在同一数据库事务内重新计算范围;任何一名用户授予失败都必须整批回滚。

授权与留痕

  • 每名实际授予用户必须复用 grant_software_entitlement(),不得直接创建 SoftwareEntitlement。
  • 权益继续复制套餐名称、价格、时长、设备数和宽限期快照,并按既有服务生成席位。
  • 每项授予写 LicenseEvent(granted),记录统一的非空批处理原因。
  • 命令输出只给出符合条件、跳过和实际授予数量,不输出邮箱、API Key 或其他敏感数据。

实施约束

  • 不修改 CMSHOPEE_SUBSCRIPTION_MODE,本任务执行期间线上继续保持 shadow。
  • 不撤销、续期或改写用户已有权益。
  • 不修改 UserWallet、PointsLedger、RechargeOrder、SoftwareOrder 或生成记录。
  • 不新增数据迁移,不直接运行原始 SQL。
  • 本任务只完成测试套餐的安全批量授予;测试结束后的正式套餐切换另行落任务,不通过编辑套餐反向修改权益快照。

验收条件

  1. 默认预演不创建权益、席位或授权事件,并返回待授予和跳过数量。
  2. 未提供 --execute 时不能写数据;执行时缺少或错误的预期授予人数必须拒绝且不产生部分数据。
  3. 启用的普通用户获得指定套餐;后台账号、停用账号和已有同产品有效权益用户不受影响。
  4. 每个新权益具有正确套餐快照、到期时间和宽限截止时间,并存在对应 granted 授权事件。
  5. 同一命令重复执行时已获有效权益用户被跳过,不重复授予。
  6. 任一授予失败时整批回滚。
  7. manage.py check、目标测试、迁移一致性、编译检查和 git diff --check 通过。
  8. 线上执行前完成数据库备份;预演人数与实际执行人数一致;执行后权益数、事件数和订阅状态接口抽样验收通过。

状态

DONE。

实施结果

  • 新增 grant_plan_to_existing_users() 批量服务:执行时锁定套餐和启用的非后台用户,按同产品仍有效或处于宽限期的权益跳过已有会员,并在单事务中复用 grant_software_entitlement()。
  • 新增 grant_existing_users_plan management command。默认只预演;实际执行必须同时提供 --execute 和与当前范围一致的 --expected-grant-count。
  • 命令按套餐 ID 精确选择启用套餐,统一要求非空原因;输出只包含套餐、产品和数量统计。
  • 新增 5 条专项测试,覆盖只读预演、普通用户范围、正式权益保护、重复执行、人数不匹配拒绝和异常整批回滚。

验证结果

  • manage.py check:通过,0 issues。
  • manage.py makemigrations --check --dry-run:No changes detected;本任务无迁移。
  • manage.py test apps.licensing.tests.ExistingUserPlanGrantCommandTests:5 tests OK。
  • SQLite 完整 apps.licensing 回归共 35 条,其中 33 条通过;2 条既有多连接并发测试因 SQLite 表级锁返回 database table is locked,与本任务断言无关,未误记为全量通过。
  • compileall apps/licensing 与 git diff --check:通过。
  • 生产 MySQL 8.4 执行前备份:/root/cmhub-t633-backup-20260722_164956/cmhub-before-t633.sql.gz,SHA256 为 ae53d3edd329a3ed16e55958f2a93453f420b5026bbb3294edb6b4e91ad17162,本地副本校验一致。
  • 线上预演:eligible=30、skipped_existing=0、grant_count=30;按预期人数执行成功。
  • 执行后:测试套餐权益 30 条、不同用户 30 个、granted 事件 30 条、席位 3030 条;后台账号权益 0,缺少有效权益的目标用户 0,重复有效权益用户 0。
  • 反向预演:eligible=30、skipped_existing=30、grant_count=0,证明重复执行不会重复授予。
  • 真实 API Key 抽样返回 access_source=entitlement、entitlement_status=active、套餐“测试”,点数余额保持 148。
  • 执行期间线上有 4 个生图任务运行,聚合点数同期减少 2 点并新增一条关联调用的 consume -2 流水;这是并发生成业务变化,不是授权命令写账。