# 套餐系统升级 - 功能总结 ## 概述 本次升级实现了完整的套餐生命周期管理,支持主套餐排队激活、加油包绑定主套餐、囤货待实名激活、流量按优先级扣减等核心功能。 ## 核心功能 ### 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)→ 主套餐** ```go // 示例:卡有 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)