feat: hide legacy licensing admin models

This commit is contained in:
QiuSW
2026-07-21 18:08:19 +08:00
parent 7bd4f3f125
commit 3130da6929
6 changed files with 56 additions and 6 deletions
+11 -4
View File
@@ -334,8 +334,15 @@ class ReadOnlyLicenseAdmin(admin.ModelAdmin):
return False return False
class HiddenLegacyLicenseAdmin(ReadOnlyLicenseAdmin):
"""Keep legacy records addressable without advertising them in admin."""
def get_model_perms(self, request):
return {}
@admin.register(LicenseSeat) @admin.register(LicenseSeat)
class LicenseSeatAdmin(ReadOnlyLicenseAdmin): class LicenseSeatAdmin(HiddenLegacyLicenseAdmin):
list_display = ("entitlement", "seat_number", "device", "bound_at", "released_at") list_display = ("entitlement", "seat_number", "device", "bound_at", "released_at")
list_filter = ("entitlement__product_code", "bound_at", "released_at") list_filter = ("entitlement__product_code", "bound_at", "released_at")
search_fields = ( search_fields = (
@@ -363,7 +370,7 @@ class LegacyMigrationGrantForm(EntitlementGrantForm):
@admin.register(LegacyMigrationGrant) @admin.register(LegacyMigrationGrant)
class LegacyMigrationGrantAdmin(ReadOnlyLicenseAdmin): class LegacyMigrationGrantAdmin(HiddenLegacyLicenseAdmin):
list_display = ("user", "product_code", "entitlement", "status", "actor", "created_at") list_display = ("user", "product_code", "entitlement", "status", "actor", "created_at")
list_filter = ("product_code", "status", "created_at") list_filter = ("product_code", "status", "created_at")
search_fields = ("user__username", "user__email", "reason") search_fields = ("user__username", "user__email", "reason")
@@ -410,7 +417,7 @@ class LegacyMigrationGrantAdmin(ReadOnlyLicenseAdmin):
@admin.register(MigrationRequest) @admin.register(MigrationRequest)
class MigrationRequestAdmin(ReadOnlyLicenseAdmin): class MigrationRequestAdmin(HiddenLegacyLicenseAdmin):
list_display = ("request_id", "user", "device", "migration_grant", "status", "expires_at", "confirmed_at") list_display = ("request_id", "user", "device", "migration_grant", "status", "expires_at", "confirmed_at")
list_filter = ("status", "device__product_code", "expires_at") list_filter = ("status", "device__product_code", "expires_at")
search_fields = ("=request_id", "user__username", "user__email") search_fields = ("=request_id", "user__username", "user__email")
@@ -418,7 +425,7 @@ class MigrationRequestAdmin(ReadOnlyLicenseAdmin):
@admin.register(DeviceCredential) @admin.register(DeviceCredential)
class DeviceCredentialAdmin(ReadOnlyLicenseAdmin): class DeviceCredentialAdmin(HiddenLegacyLicenseAdmin):
list_display = ("token_prefix", "user", "product_code", "device", "entitlement", "expires_at", "revoked_at") list_display = ("token_prefix", "user", "product_code", "device", "entitlement", "expires_at", "revoked_at")
list_filter = ("product_code", "revoked_at", "expires_at") list_filter = ("product_code", "revoked_at", "expires_at")
search_fields = ("token_prefix", "user__username", "user__email") search_fields = ("token_prefix", "user__username", "user__email")
+29
View File
@@ -1,7 +1,9 @@
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from types import SimpleNamespace
from django.contrib import admin
from django.db import close_old_connections from django.db import close_old_connections
from django.test import TestCase, TransactionTestCase, override_settings from django.test import TestCase, TransactionTestCase, override_settings
from django.urls import reverse from django.urls import reverse
@@ -505,6 +507,7 @@ class SoftwareEntitlementAdminTests(TestCase):
email="licensing-admin@example.com", email="licensing-admin@example.com",
password="test-password", password="test-password",
is_staff=True, is_staff=True,
is_superuser=True,
) )
self.user = User.objects.create_user( self.user = User.objects.create_user(
username="licensing-target", username="licensing-target",
@@ -547,6 +550,32 @@ class SoftwareEntitlementAdminTests(TestCase):
).exists() ).exists()
) )
def test_admin_hides_legacy_license_models_but_keeps_current_models_visible(self):
hidden_models = (
LicenseSeat,
LegacyMigrationGrant,
MigrationRequest,
DeviceCredential,
)
visible_models = (
ClientDevice,
DeviceSession,
DeviceBindingAudit,
SoftwarePlan,
SoftwareEntitlement,
SoftwareOrder,
LicenseEvent,
)
request = SimpleNamespace(user=self.operator)
for model in hidden_models:
self.assertEqual(admin.site._registry[model].get_model_perms(request), {})
for model in visible_models:
self.assertTrue(admin.site._registry[model].get_model_perms(request))
self.assertTrue(LicenseSeat.objects.model._meta.db_table)
self.assertTrue(DeviceCredential.objects.model._meta.db_table)
class LicenseSeatConcurrencyTests(TransactionTestCase): class LicenseSeatConcurrencyTests(TransactionTestCase):
def setUp(self): def setUp(self):
+1 -1
View File
@@ -109,7 +109,7 @@
| 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-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-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/*`、充值、点数和历史迁移接口不变。保留旧设备/卡密数据只读,不删除历史记录,不再签发新设备凭证;过期设备会话也不会阻断账号订阅授权。已补账号无订阅/有效订阅状态、强制拦截和多设备上下文回归测试;`check`、编译检查和目标 API 测试通过。 | 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/*`、充值、点数和历史迁移接口不变。保留旧设备/卡密数据只读,不删除历史记录,不再签发新设备凭证;过期设备会话也不会阻断账号订阅授权。已补账号无订阅/有效订阅状态、强制拦截和多设备上下文回归测试;`check`、编译检查和目标 API 测试通过。 | DONE |
| T-631 | 隐藏旧设备授权模型的 admin 菜单入口 | T-630 | 待实现:从 django-admin 应用索引隐藏 `LicenseSeat`、`LegacyMigrationGrant`、`MigrationRequest`、`DeviceCredential` 四个旧方案模型;保留数据库表、历史数据、只读模型和当前订阅/审计/设备观测模型,不改变任何 API、授权和账务行为。详见 [`tasks/T-631.md`](tasks/T-631.md)。 | TODO | | T-631 | 隐藏旧设备授权模型的 admin 菜单入口 | T-630 | 已完成:从 django-admin 应用索引隐藏 `LicenseSeat`、`LegacyMigrationGrant`、`MigrationRequest`、`DeviceCredential` 四个旧方案模型;保留数据库表、历史数据和只读模型。当前订阅、订单、授权事件、设备和会话观测模型继续可见,不改变 API、授权和账务行为。定向 admin 测试、`check`、迁移一致性和编译检查通过。详见 [`tasks/T-631.md`](tasks/T-631.md)。 | DONE |
## 里程碑 ## 里程碑
+1 -1
View File
@@ -75,7 +75,7 @@
任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史执行记录见 [`../progress.md`](../progress.md)。 任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史执行记录见 [`../progress.md`](../progress.md)。
- 已完成: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-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-630 账号订阅授权与默认多设备模式。 - 已完成补充:T-619 多张图片理解并返回文字;T-620 图生图支持单图 / 多图主图与参考图;T-622 图片生成任务后台图片缩略预览;T-623 图片生成任务单图 / 多图筛选;T-624 蝦皮圈设备登记与会话观测;T-625 蝦皮圈设备使用关联与迁移观测;T-626 软件套餐、权益与设备席位基础模型及凭证续期/撤销清理;T-627 存量用户迁移权益、网页确认与设备凭证;T-628 蝦皮圈专属授权入口与影子校验;T-629 软件套餐购买、续订订单与权益入账;T-630 账号订阅授权与默认多设备模式;T-631 隐藏旧设备授权模型的 admin 菜单入口。
- 正在进行:无。 - 正在进行:无。
- T-621 注册赠点运营后台配置继续留在 Backlog。真实支付回调到账闭环、客户端发布、生产多图理解模型配置和线上旧同步接口用量观察仍可继续拆任务。 - T-621 注册赠点运营后台配置继续留在 Backlog。真实支付回调到账闭环、客户端发布、生产多图理解模型配置和线上旧同步接口用量观察仍可继续拆任务。
- 当前 blocker:支付商户真实密钥/证书与生产 SDK 依赖仍待提供;微信回调到账闭环仍需真实支付验收;真实 AI 标题生成已在线上跑通,图片生成慢 / 504 / 客户端超时风险已拆为 T-612~T-616 并完成工程侧处理。 - 当前 blocker:支付商户真实密钥/证书与生产 SDK 依赖仍待提供;微信回调到账闭环仍需真实支付验收;真实 AI 标题生成已在线上跑通,图片生成慢 / 504 / 客户端超时风险已拆为 T-612~T-616 并完成工程侧处理。
+7
View File
@@ -34,6 +34,10 @@
- 不改变账号订阅授权、API Key、点数、充值和生成接口。 - 不改变账号订阅授权、API Key、点数、充值和生成接口。
- `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT` 行为不变。 - `CMSHOPEE_SUBSCRIPTION_ENFORCEMENT` 行为不变。
## 实施结果
已通过 `HiddenLegacyLicenseAdmin.get_model_perms()` 将四个旧方案模型从 admin 应用索引隐藏;模型仍注册为只读 admin,数据库表和历史数据未改变。当前订阅、订单、授权事件和设备观测模型保持可见。
## 验收条件 ## 验收条件
1. admin 软件授权列表不再显示上述四个旧方案模型。 1. admin 软件授权列表不再显示上述四个旧方案模型。
@@ -42,3 +46,6 @@
4. 非 staff 用户仍不能访问 admin。 4. 非 staff 用户仍不能访问 admin。
5. 通过 admin 相关测试、`manage.py check`、迁移一致性和编译检查。 5. 通过 admin 相关测试、`manage.py check`、迁移一致性和编译检查。
## 状态
DONE。定向 admin 测试 2 条通过;`manage.py check`、`makemigrations --check --dry-run`、`compileall` 和 `git diff --check` 通过。测试保留既有 MySQL 条件唯一约束 `models.W036` 警告。
+7
View File
@@ -2124,3 +2124,10 @@
- 多设备:设备登记、设备会话、旧凭证和席位不再参与账号订阅授权;设备数据继续保留为可选观测,过期设备会话不会阻断有效订阅账号。 - 多设备:设备登记、设备会话、旧凭证和席位不再参与账号订阅授权;设备数据继续保留为可选观测,过期设备会话不会阻断有效订阅账号。
- 验证:目标 API 测试通过(无订阅状态、有效订阅状态、强制拒绝、多设备上下文),`manage.py check`、`compileall` 通过。测试环境仍提示 MySQL 不支持条件唯一约束的既有 warning。 - 验证:目标 API 测试通过(无订阅状态、有效订阅状态、强制拒绝、多设备上下文),`manage.py check`、`compileall` 通过。测试环境仍提示 MySQL 不支持条件唯一约束的既有 warning。
- 下一步:T-621 继续留在 Backlog;按业务优先级拆分真实支付回调验收、客户端发布和生产观察任务。 - 下一步:T-621 继续留在 Backlog;按业务优先级拆分真实支付回调验收、客户端发布和生产观察任务。
## 2026-07-21 完成:T-631 隐藏旧设备授权模型的 admin 菜单入口
- 实现:旧方案的 `LicenseSeat`、`LegacyMigrationGrant`、`MigrationRequest`、`DeviceCredential` 从 django-admin 应用索引隐藏;保留模型注册、只读访问、数据库表和历史数据。`SoftwarePlan`、`SoftwareEntitlement`、`SoftwareOrder`、`LicenseEvent` 及设备观测模型继续可见。
- 兼容:不修改账号订阅授权、设备观测、API、点数、充值或迁移数据;不删除任何旧数据。
- 验证:`py -3.12 manage.py test apps.licensing.tests.SoftwareEntitlementAdminTests --keepdb --noinput` 通过(2 tests);`manage.py check`、`makemigrations --check --dry-run`、`compileall`、`git diff --check` 通过。保留既有 MySQL `models.W036` warning。
- 下一步:T-621 继续留在 Backlog;按业务优先级拆分后续真实支付验收、客户端发布或生产观察任务。