All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
- 重构 Worker 启动流程,引入 bootstrap 模块统一管理依赖注入 - 实现套餐流量重置服务(日/月/年周期重置) - 新增套餐激活排队、加油包绑定、囤货待实名激活逻辑 - 新增订单创建幂等性防重(Redis 业务键 + 分布式锁) - 更新 AGENTS.md/CLAUDE.md:新增注释规范、幂等性规范,移除测试要求 - 添加套餐系统升级完整文档(API文档、使用指南、功能总结、运维指南) - 归档 OpenSpec package-system-upgrade 变更,同步 specs 到主目录 - 新增 queue types 抽象和 Redis 常量定义
5.8 KiB
5.8 KiB
套餐系统升级 - 功能总结
概述
本次升级实现了完整的套餐生命周期管理,支持主套餐排队激活、加油包绑定主套餐、囤货待实名激活、流量按优先级扣减等核心功能。
核心功能
1. 套餐有效期计算
| 类型 | 计算方式 | 示例 |
|---|---|---|
| 自然月 | 激活月份 + N 个月,月末 23:59:59 | 2月15日激活3个月 → 5月31日 23:59:59 过期 |
| 按天 | 激活日期 + N 天,当天 23:59:59 | 2月15日激活30天 → 3月16日 23:59:59 过期 |
2. 主套餐排队机制
卡/设备 购买主套餐 A → 立即激活(status=1, priority=1)
购买主套餐 B → 排队等待(status=0, priority=2)
主套餐 A 过期 → 自动激活主套餐 B
- 同一卡/设备同时只能有一个生效中的主套餐
- 新购买的主套餐自动进入排队状态
- 过期检查每 10 秒执行一次
3. 加油包绑定主套餐
加油包必须绑定到当前生效的主套餐(master_usage_id)
├── 加油包与主套餐同时生效
├── 主套餐过期时,加油包自动失效(status=4)
└── 流量扣减时,先扣加油包,再扣主套餐
- 购买加油包时必须有生效中或待生效的主套餐
- 加油包可设置独立有效期(
has_independent_expiry=true)
4. 囤货待实名激活
后台为未实名卡/设备购买套餐
├── 套餐 status=0, pending_realname_activation=true
├── 用户完成实名
├── 轮询系统检测到实名状态变更
└── 自动激活套餐(status=1)
- 仅当套餐
enable_realname_activation=true时触发此机制 - H5 端未实名用户无法直接购买套餐
5. 流量扣减优先级
扣减顺序:加油包(按 priority ASC)→ 主套餐
// 示例:卡有 3 个生效套餐
主套餐:1000MB,已用 500MB
加油包1:100MB,已用 0MB,priority=2
加油包2:200MB,已用 50MB,priority=3
// 本次使用 180MB
扣减顺序:
1. 加油包1 扣 100MB(用完,status=2)
2. 加油包2 扣 80MB
3. 主套餐不变
6. 流量重置周期
| 周期 | 套餐类型 | 重置时间 | 说明 |
|---|---|---|---|
| 日重置 | 所有 | 每天 00:00:00 | data_reset_cycle=daily |
| 月重置 | 自然月 | 每月1号 00:00:00 | calendar_type=natural_month |
| 月重置 | 按天 | 从激活日期起每30天 | calendar_type=by_day |
| 年重置 | 所有 | 每年1月1日 00:00:00 | data_reset_cycle=yearly |
7. 停复机机制
- 停机条件:所有生效套餐流量用完(主套餐 + 所有加油包 status=2)
- 复机条件:购买新套餐或套餐激活后自动复机
数据库变更
新增表
| 表名 | 说明 |
|---|---|
tb_package_usage_daily_record |
套餐流量日记录 |
tb_card_daily_usage |
卡每日流量使用汇总 |
扩展字段
tb_package 表:
calendar_type: 有效期类型(natural_month/by_day)data_reset_cycle: 流量重置周期(daily/monthly/yearly/none)enable_realname_activation: 是否需要实名后激活duration_days: 按天套餐的有效天数
tb_package_usage 表:
priority: 套餐优先级master_usage_id: 主套餐 ID(加油包使用)has_independent_expiry: 加油包是否有独立有效期pending_realname_activation: 是否待实名激活data_reset_cycle: 流量重置周期last_reset_at: 上次重置时间next_reset_at: 下次重置时间
tb_iot_card 表:
stopped_at: 停机时间resumed_at: 复机时间stop_reason: 停机原因
tb_carrier 表:
billing_day: 运营商计费日(用于流量查询接口的计费周期计算,联通=27,其他=1)
API 端点
新增端点
| 端点 | 方法 | 说明 |
|---|---|---|
/api/h5/packages/my-usage |
GET | 客户端查询我的流量使用情况 |
/api/admin/package-usage/:id/daily-records |
GET | 查询套餐流量详单 |
扩展端点
套餐管理 API 支持新字段:
calendar_type: 有效期类型duration_days: 有效天数data_reset_cycle: 重置周期enable_realname_activation: 实名激活开关
轮询任务
| 任务 | 调度频率 | 说明 |
|---|---|---|
| 套餐激活检查 | 每 10 秒 | 检查过期主套餐,激活排队套餐 |
| 流量重置调度 | 每 10 秒 | 执行日/月/年流量重置 |
| 实名状态检查 | 配置化 | 检测首次实名,触发套餐激活 |
Asynq 任务
| 任务类型 | 说明 |
|---|---|
task:package:first_activation |
首次实名激活套餐 |
task:package:queue_activation |
排队主套餐激活 |
错误码
| 错误码 | 说明 |
|---|---|
CodePackageActivationConflict |
套餐正在激活中 |
CodeNoMainPackage |
必须有主套餐才能购买加油包 |
CodeRealnameRequired |
必须先完成实名认证才能购买套餐 |
CodeMixedOrderForbidden |
同订单不能同时购买正式套餐和加油包 |
技术实现
Service 层
| 服务 | 文件 | 职责 |
|---|---|---|
| ActivationService | activation_service.go |
套餐激活(实名激活、排队激活) |
| UsageService | usage_service.go |
流量扣减、停机检查 |
| ResetService | reset_service.go |
流量重置(日/月/年) |
| CustomerViewService | customer_view_service.go |
客户端流量查询 |
| DailyRecordService | daily_record_service.go |
套餐流量详单 |
| StopResumeService | stop_resume_service.go |
停复机操作 |
工具函数
| 函数 | 说明 |
|---|---|
CalculateExpiryTime() |
计算套餐过期时间 |
CalculateNextResetTime() |
计算下次重置时间 |
性能优化
- 流量重置分批处理:每批最多 10000 条
- 使用 Redis 分布式锁避免套餐激活并发问题
- Asynq 任务重试策略:MaxRetry(3), Timeout(30s)