Files
junhong_cmp_fiber/internal/service/package/utils.go
huang c665f32976
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
feat: 套餐系统升级 - Worker 重构、流量重置、文档与规范更新
- 重构 Worker 启动流程,引入 bootstrap 模块统一管理依赖注入
- 实现套餐流量重置服务(日/月/年周期重置)
- 新增套餐激活排队、加油包绑定、囤货待实名激活逻辑
- 新增订单创建幂等性防重(Redis 业务键 + 分布式锁)
- 更新 AGENTS.md/CLAUDE.md:新增注释规范、幂等性规范,移除测试要求
- 添加套餐系统升级完整文档(API文档、使用指南、功能总结、运维指南)
- 归档 OpenSpec package-system-upgrade 变更,同步 specs 到主目录
- 新增 queue types 抽象和 Redis 常量定义
2026-02-12 14:24:15 +08:00

117 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package packagepkg
import (
"time"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// CalculateExpiryTime 计算套餐过期时间
// calendarType: 套餐周期类型natural_month=自然月by_day=按天)
// activatedAt: 激活时间
// durationMonths: 套餐时长月数calendar_type=natural_month 时使用)
// durationDays: 套餐天数calendar_type=by_day 时使用)
// 返回:过期时间(当天 23:59:59
func CalculateExpiryTime(calendarType string, activatedAt time.Time, durationMonths, durationDays int) time.Time {
var expiryDate time.Time
if calendarType == constants.PackageCalendarTypeNaturalMonth {
// 自然月套餐activated_at 月份 + N 个月,月末 23:59:59
// 计算目标年月
targetYear := activatedAt.Year()
targetMonth := activatedAt.Month() + time.Month(durationMonths)
// 处理月份溢出
for targetMonth > 12 {
targetMonth -= 12
targetYear++
}
// 获取目标月份的最后一天下个月的第0天就是本月最后一天
expiryDate = time.Date(targetYear, targetMonth+1, 0, 23, 59, 59, 0, activatedAt.Location())
} else {
// 按天套餐activated_at + N 天23:59:59
expiryDate = activatedAt.AddDate(0, 0, durationDays)
expiryDate = time.Date(expiryDate.Year(), expiryDate.Month(), expiryDate.Day(), 23, 59, 59, 0, expiryDate.Location())
}
return expiryDate
}
// CalculateNextResetTime 计算下次流量重置时间
// dataResetCycle: 流量重置周期daily/monthly/yearly/none
// calendarType: 套餐周期类型natural_month/by_day影响月重置逻辑
// currentTime: 当前时间
// activatedAt: 套餐激活时间(按天套餐月重置时使用)
// 返回下次重置时间00:00:00
func CalculateNextResetTime(dataResetCycle, calendarType string, currentTime, activatedAt time.Time) *time.Time {
if dataResetCycle == constants.PackageDataResetNone {
// 不重置
return nil
}
var nextResetTime time.Time
switch dataResetCycle {
case constants.PackageDataResetDaily:
// 日重置:明天 00:00:00
nextResetTime = time.Date(
currentTime.Year(),
currentTime.Month(),
currentTime.Day()+1,
0, 0, 0, 0,
currentTime.Location(),
)
case constants.PackageDataResetMonthly:
if calendarType == constants.PackageCalendarTypeNaturalMonth {
// 自然月套餐每月1号 00:00:00 重置
year := currentTime.Year()
month := currentTime.Month()
// 如果当前日期 >= 1号即已过重置点则下次重置为下个月1号
if currentTime.Day() >= 1 {
month++
if month > 12 {
month = 1
year++
}
}
nextResetTime = time.Date(year, month, 1, 0, 0, 0, 0, currentTime.Location())
} else {
// 按天套餐从激活日期开始每30天重置一次
// 计算从激活到现在经过了多少个30天周期
daysSinceActivation := int(currentTime.Sub(activatedAt).Hours() / 24)
cyclesPassed := daysSinceActivation / 30
// 下次重置时间 = 激活时间 + (已过周期数+1) * 30天
nextResetTime = activatedAt.AddDate(0, 0, (cyclesPassed+1)*30)
nextResetTime = time.Date(
nextResetTime.Year(),
nextResetTime.Month(),
nextResetTime.Day(),
0, 0, 0, 0,
nextResetTime.Location(),
)
}
case constants.PackageDataResetYearly:
// 年重置每年1月1日 00:00:00
year := currentTime.Year()
// 如果当前日期已经过了1月1日则使用明年
jan1ThisYear := time.Date(year, 1, 1, 0, 0, 0, 0, currentTime.Location())
if currentTime.After(jan1ThisYear) || currentTime.Equal(jan1ThisYear) {
year++
}
nextResetTime = time.Date(year, 1, 1, 0, 0, 0, 0, currentTime.Location())
default:
return nil
}
return &nextResetTime
}