Files
junhong_cmp_fiber/openspec/changes/package-system-upgrade/tasks.md
2026-02-11 17:29:06 +08:00

17 KiB
Raw Blame History

实施任务清单: 套餐系统升级

1. 数据库迁移

  • 1.1 创建数据库迁移文件(make create-migration name=package_system_upgrade
  • 1.2 编写 Package 表扩展迁移(新增 3 个字段calendar_type, data_reset_cycle, enable_realname_activation
  • 1.3 编写 PackageUsage 表扩展迁移(扩展 status 枚举 0-4新增 7 个字段priority, master_usage_id, has_independent_expiry, pending_realname_activation, data_reset_cycle, last_reset_at, next_reset_at
  • 1.4 编写 IotCard 表扩展迁移(新增 3 个字段first_realname_at, stopped_at, resumed_at, stop_reason
  • 1.5 编写 Carrier 表扩展迁移(新增 1 个字段billing_day
  • 1.6 创建 PackageUsageDailyRecord 表迁移package_usage_id, date, daily_usage_mb, cumulative_usage_mb
  • 1.7 创建 CardDailyUsage 表迁移card_id, usage_date, total_data_usage, carrier_id
  • 1.8 创建索引迁移priority, master_usage_id, package_usage_id+date 联合唯一索引, card_id+usage_date 联合唯一索引)
  • 1.9 执行迁移(make migrate-up),验证表结构正确
  • 1.10 数据初始化:运营商 billing_day 字段(联通=27其他=1
  • 1.11 编写回滚迁移脚本(删除新表、删除新字段)

2. 常量定义

  • 2.1 在 pkg/constants/constants.go 新增套餐周期类型常量PackageCalendarTypeNaturalMonth, PackageCalendarTypeByDay
  • 2.2 新增套餐流量重置周期常量PackageDataResetDaily, PackageDataResetMonthly, PackageDataResetYearly, PackageDataResetNone
  • 2.3 新增套餐使用状态常量PackageUsageStatusPending=0, PackageUsageStatusActive=1, PackageUsageStatusDepleted=2, PackageUsageStatusExpired=3, PackageUsageStatusInvalidated=4
  • 2.4 新增任务类型常量TaskTypePackageFirstActivation, TaskTypePackageQueueActivation, TaskTypePackageDataReset
  • 2.5 新增 Redis 键函数RedisPackageActivationLockKey
  • 2.6 运行 lsp_diagnostics 验证编译通过

3. Model 层扩展

  • 3.1 扩展 Package 模型(新增 CalendarType, DataResetCycle, EnableRealnameActivation, DurationDays 字段)
  • 3.2 扩展 PackageUsage 模型(扩展 Status 注释,新增 Priority, MasterUsageID, HasIndependentExpiry, PendingRealnameActivation, DataResetCycle, LastResetAt, NextResetAt 字段)
  • 3.3 创建 PackageUsageDailyRecord 模型PackageUsageID, Date, DailyUsageMB, CumulativeUsageMB
  • 3.4 实现 PackageUsageDailyRecord.TableName() 方法
  • 3.5 运行 lsp_diagnostics 验证编译通过

4. DTO 扩展

  • 4.1 扩展 CreatePackageRequest DTO新增 CalendarType, DurationDays, DataResetCycle, EnableRealnameActivation 字段,添加 description 标签和验证标签)
  • 4.2 扩展 UpdatePackageRequest DTO新增 CalendarType, DurationDays, DataResetCycle, EnableRealnameActivation 字段)
  • 4.3 扩展 PackageResponse DTO新增 CalendarType, DurationDays, DataResetCycle, EnableRealnameActivation 字段)
  • 4.4 创建 PackageUsageCustomerViewResponse DTOmain_package, addon_packages, total 字段)
  • 4.5 创建 PackageUsageDailyRecordResponse DTOdate, daily_usage_mb, cumulative_usage_mb 字段)
  • 4.6 创建 PackageUsageDetailResponse DTOpackage_usage_id, package_name, records, total_usage_mb 字段)
  • 4.7 运行 lsp_diagnostics 验证编译通过

5. Store 层扩展

  • 5.1 扩展 PackageStore.Create 方法支持新字段calendar_type, data_reset_cycle, enable_realname_activation
  • 5.2 扩展 PackageStore.Update 方法支持新字段
  • 5.3 扩展 PackageStore.GetByID 查询返回新字段
  • 5.4 扩展 PackageUsageStore.Create 方法支持新字段priority, master_usage_id, has_independent_expiry, pending_realname_activation, data_reset_cycle
  • 5.5 新增 PackageUsageStore.GetActiveMainPackage 方法(查询生效中主套餐)
  • 5.6 新增 PackageUsageStore.GetNextPendingMainPackage 方法(查询下一个待生效主套餐,按 priority ASC
  • 5.7 新增 PackageUsageStore.GetActivePackages 方法(查询生效中的主套餐和加油包,按优先级排序)
  • 5.8 新增 PackageUsageStore.GetAddonsByMasterID 方法(查询主套餐下的所有加油包)
  • 5.9 新增 PackageUsageStore.BatchUpdateStatus 方法(批量更新加油包状态)
  • 5.10 新增 PackageUsageStore.UpdateDataUsage 方法(更新套餐流量使用,支持事务)
  • 5.11 新增 PackageUsageStore.GetPackagesForReset 方法查询需要重置的套餐WHERE next_reset_at <= NOW
  • 5.12 新增 PackageUsageStore.ResetDataUsage 方法(重置流量,更新 last_reset_at 和 next_reset_at
  • 5.13 创建 PackageUsageDailyRecordStoreNewPackageUsageDailyRecordStore 构造函数)
  • 5.14 实现 PackageUsageDailyRecordStore.CreateOrUpdate 方法(创建或更新日记录,使用 UPSERT
  • 5.15 实现 PackageUsageDailyRecordStore.GetByDateRange 方法(按日期范围查询)

6. 套餐有效期计算工具函数

  • 6.1 在 internal/service/package/utils.go 创建 CalculateExpiryTime 函数(根据 calendar_type 和 duration 计算过期时间)
  • 6.2 实现自然月套餐过期时间计算activated_at 月份 + N 个月,月末 23:59:59
  • 6.3 实现按天套餐过期时间计算activated_at + N 天23:59:59
  • 6.4 在 internal/service/package/utils.go 创建 CalculateNextResetTime 函数(根据 data_reset_cycle 计算下次重置时间)
  • 6.5 实现日重置计算(明天 00:00:00
  • 6.6 实现月重置计算联通27号 vs 其他1号下月 00:00:00
  • 6.7 实现年重置计算(明年 1 月 1 日 00:00:00

7. Package Service 改造

  • 7.1 扩展 PackageService.Create 方法支持新字段验证calendar_type=natural_month 时必须提供 duration_months
  • 7.2 扩展 PackageService.Update 方法支持新字段更新
  • 7.3 扩展 PackageService.GetByID 返回新字段

8. Order Service 改造(主套餐排队 + 加油包限制 + 混买限制)

  • 8.1 实现订单创建校验:禁止同订单混买正式套餐和加油包
  • 8.2 改造 OrderService.CreateOrder 方法,购买主套餐时检查是否有生效中主套餐
  • 8.3 实现主套餐排队逻辑(有生效中主套餐时,新套餐 status=0, priority=MAX(priority)+1
  • 8.4 实现首个主套餐立即激活逻辑无生效中主套餐时status=1, priority=1, 计算 activated_at 和 expires_at
  • 8.5 改造 OrderService.CreateOrder 方法购买加油包时检查是否有主套餐status IN (0,1) 的主套餐)
  • 8.6 实现加油包购买限制(无主套餐时返回错误 "必须有主套餐才能购买加油包"
  • 8.7 实现加油包创建逻辑master_usage_id=主套餐ID, status=1, priority=MAX(priority)+1, 根据 has_independent_expiry 计算 expires_at
  • 8.8 实现客户端未实名购买限制H5 端未实名时返回错误 403
  • 8.9 实现后台囤货场景enable_realname_activation=true 时status=0, pending_realname_activation=true

9. 套餐激活 Service首次实名激活 + 排队激活)

  • 9.1 创建 internal/service/package/activation_service.go
  • 9.2 实现 ActivationService.ActivateByRealname 方法(首次实名激活,查询 pending_realname_activation=true 的套餐)
  • 9.3 实现套餐激活逻辑(计算 activated_at 和 expires_at更新 status=1, pending_realname_activation=false
  • 9.4 实现 ActivationService.ActivateQueuedPackage 方法(主套餐排队激活,使用 Redis 分布式锁避免并发)
  • 9.5 实现过期主套餐检测逻辑(查询 status=1 AND expires_at <= NOW 的主套餐,更新 status=3
  • 9.6 实现下一个待生效主套餐查询WHERE status=0 AND master_usage_id IS NULL ORDER BY priority ASC LIMIT 1
  • 9.7 实现加油包级联失效逻辑(查询主套餐下的所有加油包,批量更新 status=4

10. 流量扣减优先级 Service

  • 10.1 创建 internal/service/package/usage_service.go
  • 10.2 实现 UsageService.DeductDataUsage 方法(按优先级扣减流量:加油包按 priority ASC → 主套餐)
  • 10.3 实现流量扣减逻辑FOR EACH 套餐,计算剩余额度,扣减流量,更新 data_usage_mb
  • 10.4 实现套餐流量用完标记data_usage_mb >= data_limit_mb 时,更新 status=2
  • 10.5 实现停机条件检查(所有套餐 status=2 时,触发停机操作)
  • 10.6 实现日记录写入(每次扣减后,创建或更新 PackageUsageDailyRecord

11. 流量重置 Service

  • 11.1 创建 internal/service/package/reset_service.go
  • 11.2 实现 ResetService.ResetDailyUsage 方法(查询 data_reset_cycle=daily 且 next_reset_at <= NOW 的套餐)
  • 11.3 实现日重置逻辑(批量更新 data_usage_mb=0, last_reset_at=NOW, next_reset_at=明天 00:00:00
  • 11.4 实现 ResetService.ResetMonthlyUsage 方法(查询 data_reset_cycle=monthly 且 next_reset_at <= NOW 的套餐)
  • 11.5 实现月重置逻辑区分联通27号 vs 其他1号批量更新流量和重置时间
  • 11.6 实现 ResetService.ResetYearlyUsage 方法(查询 data_reset_cycle=yearly 且 next_reset_at <= NOW 的套餐)
  • 11.7 实现年重置逻辑(批量更新 data_usage_mb=0, last_reset_at=NOW, next_reset_at=明年 1月 1日
  • 11.8 实现分批处理逻辑(每次最多处理 10000 条,避免长事务)

12. 客户视图流量查询 Service

  • 12.1 创建 internal/service/package/customer_view_service.go
  • 12.2 实现 CustomerViewService.GetMyUsage 方法(根据 user_id 获取载体信息)
  • 12.3 实现生效套餐查询逻辑WHERE status IN (1,2),区分主套餐和加油包)
  • 12.4 实现总计流量计算逻辑(主套餐 + 所有加油包的 used_mb 和 total_mb
  • 12.5 实现响应 DTO 组装main_package, addon_packages, total

13. 套餐流量详单 Service

  • 13.1 创建 internal/service/package/daily_record_service.go
  • 13.2 实现 DailyRecordService.GetDailyRecords 方法(查询 package_usage_id 的日记录)
  • 13.3 实现越权检查(使用 middleware.CanManageShop 或 middleware.CanManageEnterprise 验证权限)
  • 13.4 实现日记录查询逻辑WHERE package_usage_id=? AND date BETWEEN ? AND ?,按 date ASC 排序)
  • 13.5 实现响应 DTO 组装package_usage_id, package_name, records, total_usage_mb

14. Handler 层改造(套餐管理 API

  • 14.1 扩展 admin.PackageHandler.Create 方法支持新字段验证calendar_type, data_reset_cycle, enable_realname_activation
  • 14.2 扩展 admin.PackageHandler.Update 方法支持新字段更新
  • 14.3 扩展 admin.PackageHandler.GetByID 返回新字段

15. Handler 层改造(客户视图 API

  • 15.1 创建 internal/handler/h5/package_usage.go
  • 15.2 实现 PackageUsageHandler.GetMyUsage 方法GET /api/h5/packages/my-usage
  • 15.3 实现 JWT 认证和用户信息提取(从 context 获取 user_id 和载体信息)
  • 15.4 调用 CustomerViewService.GetMyUsage 获取流量数据
  • 15.5 返回 PackageUsageCustomerViewResponse 响应

16. Handler 层改造(套餐流量详单 API

  • 16.1 扩展 admin.PackageUsageHandler或创建新 Handler
  • 16.2 实现 PackageUsageHandler.GetDailyRecords 方法GET /api/admin/package-usage/:id/daily-records
  • 16.3 实现参数验证start_date, end_date 查询参数)
  • 16.4 调用 DailyRecordService.GetDailyRecords 获取日记录
  • 16.5 返回 PackageUsageDetailResponse 响应

17. 路由注册和文档生成器更新

  • 17.1 在 admin 路由组注册套餐管理 API支持新字段的 POST/PUT/GET
  • 17.2 在 h5 路由组注册客户视图 APIGET /api/h5/packages/my-usage
  • 17.3 在 admin 路由组注册套餐流量详单 APIGET /api/admin/package-usage/:id/daily-records
  • 17.4 更新 cmd/api/docs.go 文档生成器(添加新 Handler 到 handlers 结构体)
  • 17.5 更新 cmd/gendocs/main.go 文档生成器(添加新 Handler 到 handlers 结构体)
  • 17.6 运行 make docs 生成 OpenAPI 文档,验证新 API 出现在文档中

18. 轮询系统扩展(流量检查任务)

  • 18.1 扩展 internal/polling/carddata_handler.go
  • 18.2 改造 HandleCarddataCheck 方法支持流量扣减优先级(查询生效套餐,按优先级排序)
  • 18.3 实现流量扣减逻辑(调用 UsageService.DeductDataUsage 方法)
  • 18.4 改造停机条件检查(所有套餐流量用完才触发停机)

19. 轮询系统扩展(套餐激活检查任务)

  • 19.1 创建 internal/polling/package_activation_handler.go
  • 19.2 实现 HandlePackageActivation 方法查询已过期主套餐status=1 AND expires_at <= NOW
  • 19.3 实现过期主套餐状态更新(更新 status=3
  • 19.4 实现加油包级联失效(调用 ActivationService.CascadeInvalidateAddons
  • 19.5 实现下一个待生效主套餐查询和激活(提交 Asynq 任务 TaskTypePackageQueueActivation
  • 19.6 在 Scheduler.scheduleLoop 中注册套餐激活检查任务(每 10 秒调度一次)

20. 轮询系统扩展(流量重置调度任务)

  • 20.1 创建 internal/polling/data_reset_handler.go
  • 20.2 实现 HandleDataReset 方法(每 10 秒调度一次,检查需要重置的套餐)
  • 20.3 实现日重置调度(调用 ResetService.ResetDailyUsage
  • 20.4 实现月重置调度(调用 ResetService.ResetMonthlyUsage
  • 20.5 实现年重置调度(调用 ResetService.ResetYearlyUsage
  • 20.6 在 Scheduler.scheduleLoop 中注册流量重置调度任务

21. 轮询系统扩展(首次实名激活触发)

  • 21.1 扩展 internal/task/polling_handler.go实名检查部分
  • 21.2 改造 HandleRealnameCheck 方法检测到首次实名时realname_status: 0/1 → 2
  • 21.3 查询该卡/设备是否有待激活套餐WHERE pending_realname_activation=true AND status=0
  • 21.4 提交 Asynq 任务TaskTypePackageFirstActivation

22. Asynq Handler首次实名激活任务

  • 22.1 在 internal/polling/package_activation_handler.go 添加 HandlePackageFirstActivation 方法
  • 22.2 实现 HandlePackageFirstActivation 方法(解析任务 payload
  • 22.3 调用 ActivationService.ActivateByRealname 激活套餐
  • 22.4 实现幂等性保证(任务处理前检查 pending_realname_activation=false
  • 22.5 实现重试策略MaxRetry(3), Timeout(30s)
  • 22.6 在 pkg/queue/handler.go 注册任务 HandlerTaskTypePackageFirstActivation → HandlePackageFirstActivation

23. Asynq Handler主套餐排队激活任务

  • 23.1 在 internal/polling/package_activation_handler.go 添加 HandlePackageQueueActivation 方法
  • 23.2 实现 HandlePackageQueueActivation 方法(解析任务 payload
  • 23.3 调用 ActivationService.ActivateQueuedPackage 激活套餐
  • 23.4 实现幂等性保证(任务处理前检查 status=1
  • 23.5 实现重试策略MaxRetry(3), Timeout(30s)
  • 23.6 在 pkg/queue/handler.go 注册任务 HandlerTaskTypePackageQueueActivation → HandlePackageQueueActivation

24. 自动停复机功能(新增章节)

  • 24.1 扩展 IotCard Model新增 stopped_at, resumed_at, stop_reason 字段)
  • 24.2 创建 internal/service/iot_card/stop_resume_service.go
  • 24.3 实现 CheckAndStopCard 方法(检查流量耗尽并停机)
  • 24.4 实现 ResumeCardIfStopped 方法(购买套餐后自动复机)
  • 24.5 实现运营商停复机接口调用带重试机制最多3次
  • 24.6 在流量扣减Service中集成停机检查
  • 24.7 在套餐激活Service中集成复机触发

25. 错误码扩展

  • 25.1 在 pkg/errors/codes.go 新增错误码CodePackageActivationConflict - 套餐正在激活中)
  • 25.2 新增错误码CodeNoMainPackage - 必须有主套餐才能购买加油包)
  • 25.3 新增错误码CodeRealnameRequired - 设备/卡必须先完成实名认证才能购买套餐)
  • 25.4 新增错误码CodeMixedOrderForbidden - 同订单不能同时购买正式套餐和加油包)
  • 25.5 运行 lsp_diagnostics 验证编译通过

26. 最终检查

  • 26.1 运行 lsp_diagnostics确认无编译错误和类型错误
  • 26.2 生成 OpenAPI 文档,确认新 API 出现在文档中
  • 26.3 代码审查检查是否遵循分层架构、Go 惯用法、性能要求)

27. 文档更新

  • 27.1 更新 README.md新增套餐系统升级功能说明
  • 27.2 在 docs/package-system-upgrade/ 创建功能总结文档
  • 27.3 编写套餐系统升级用户指南(囤货、排队、加油包、流量查询)
  • 27.4 更新 API 文档(新增 API 端点和字段说明)

28. 部署准备

  • 28.1 编写数据库迁移回滚脚本
  • 28.2 配置监控指标Asynq 队列长度、套餐激活延迟、API 响应时间)
  • 28.3 配置告警规则(套餐激活延迟 > 1 分钟、队列堆积 > 1000 个任务)
  • 28.4 编写回滚预案(代码回滚、数据库回滚、数据修复脚本)