Browse Source

add new_user_red_package

master
dengbiao 1 week ago
parent
commit
c27059345a
2 changed files with 289 additions and 0 deletions
  1. +222
    -0
      rule/new_user_red_package/new_user_red_package.go
  2. +67
    -0
      rule/new_user_red_package/util_test.go

+ 222
- 0
rule/new_user_red_package/new_user_red_package.go View File

@@ -0,0 +1,222 @@
package new_user_red_package

import (
"errors"
"fmt"
"math"
"math/rand"
)

// RedPacketDistributor 红包分发器,负责管理和分发红包
type RedPacketDistributor struct {
TotalAmount float64 // 总金额
TotalDays int // 总天数
RemainAmount float64 // 剩余金额
RemainDays int // 剩余天数
DailyAverage float64 // 每日平均金额 (TotalAmount / TotalDays)
DistributionHistory []float64 // 历史分发记录,用于调节分发策略
}

// DailyRedPacket 每日红包的详细信息
type DailyRedPacket struct {
Day int // 当前天数
BaseAmount float64 // 基础金额(未翻倍前)
IsMultiplied bool // 是否选择翻倍
Multiplier float64 // 翻倍倍数
FinalAmount float64 // 最终金额
RemainAmount float64 // 剩余金额
SafetyFactor float64 // 安全系数
}

// NewRedPacketDistributor 创建新的红包分发器
func NewRedPacketDistributor(totalAmount, remainAmount float64, totalDays, remainDays int, distributionHistory []float64) *RedPacketDistributor {
return &RedPacketDistributor{
TotalAmount: totalAmount,
TotalDays: totalDays,
RemainAmount: remainAmount,
RemainDays: remainDays,
DailyAverage: totalAmount / float64(totalDays),
DistributionHistory: distributionHistory,
}
}

// NewRedPacketDistributorForTest 创建新的红包分发器(TODO::测试使用)
func NewRedPacketDistributorForTest(totalAmount float64, totalDays int) *RedPacketDistributor {
return &RedPacketDistributor{
TotalAmount: totalAmount,
TotalDays: totalDays,
RemainAmount: totalAmount,
RemainDays: totalDays,
DailyAverage: totalAmount / float64(totalDays),
DistributionHistory: make([]float64, 0, totalDays),
}
}

// calculateDynamicSafetyFactor 计算动态安全系数
// 该系数用于控制红包金额的波动范围,确保资金分配的合理性
func (r *RedPacketDistributor) calculateDynamicSafetyFactor(currentDay int) float64 {
// 基础安全系数
baseSafety := 0.8

// 根据剩余金额与理想剩余金额的比率调整安全系数
remainingRatio := r.RemainAmount / (r.DailyAverage * float64(r.RemainDays))
if remainingRatio > 1.2 { // 剩余金额偏多,可以适当放宽限制
baseSafety += 0.1
} else if remainingRatio < 0.8 { // 剩余金额偏少,需要更谨慎
baseSafety -= 0.1
}

// 根据历史分发情况调整安全系数
if len(r.DistributionHistory) > 0 {
// 计算历史平均分发金额
avgDistributed := 0.0
for _, amount := range r.DistributionHistory {
avgDistributed += amount
}
avgDistributed /= float64(len(r.DistributionHistory))

// 如果历史平均值明显高于每日平均值,降低安全系数
if avgDistributed > r.DailyAverage*1.2 {
baseSafety -= 0.05
}
}

// 在后期(进度>70%)逐步降低安全系数,允许更大的波动
progress := float64(currentDay) / float64(r.TotalDays)
if progress > 0.7 {
baseSafety -= 0.1 * (progress - 0.7)
}

// 确保安全系数在合理范围内 [0.5, 0.95]
return math.Max(0.5, math.Min(0.95, baseSafety))
}

// calculateMultiplier 计算翻倍倍率
// 根据基础金额、每日平均值和剩余金额动态调整翻倍倍率
func (r *RedPacketDistributor) calculateMultiplier(baseAmount float64) float64 {
// 基础金额与每日平均值的比率
ratio := baseAmount / r.DailyAverage
// 基础倍率:金额越大,倍率越小
baseMultiplier := 2.5 - (1.5 * math.Min(ratio, 1.0))

// 添加随机浮动,使金额更自然 [0.95, 1.05]
randomFactor := 0.95 + rand.Float64()*0.1

// 根据剩余金额状况调整最大倍率
maxMultiplier := 3.0
if r.RemainAmount < r.DailyAverage*float64(r.RemainDays) {
// 剩余金额不足时,降低最大倍率
maxMultiplier = 2.0
}

// 确保最终倍率在合理范围内 [1.1, maxMultiplier]
return math.Max(1.1, math.Min(baseMultiplier*randomFactor, maxMultiplier))
}

// calculateSafeAmount 计算安全的基础金额
// 通过多个维度的约束确保金额分配的合理性
func (r *RedPacketDistributor) calculateSafeAmount(currentDay int) float64 {
// 最后一天直接返回剩余金额
if r.RemainDays <= 1 {
return r.RemainAmount
}

// 获取当前的安全系数
safetyFactor := r.calculateDynamicSafetyFactor(currentDay)
// 计算剩余天数的平均金额
avgRemaining := r.RemainAmount / float64(r.RemainDays)

// 根据进度调整金额范围
progress := float64(currentDay) / float64(r.TotalDays)
// 最小比率:随进度递减,确保后期有足够金额
minRatio := 0.4 + (0.2 * (1.0 - progress))
// 最大比率:随进度递增,允许更大波动
maxRatio := 1.3 + (0.4 * progress)

// 计算金额范围
minAmount := avgRemaining * minRatio
maxAmount := avgRemaining * maxRatio

// 预留安全金额,确保后续天数有基本保障
safeReserve := r.RemainAmount * 0.3 / float64(r.RemainDays)
maxAllowed := r.RemainAmount - (safeReserve * float64(r.RemainDays-1))

// 在允许范围内随机生成金额
randomRatio := rand.Float64() * safetyFactor
amount := minAmount + randomRatio*(maxAmount-minAmount)

// 确保不超过最大允许金额
return math.Min(amount, maxAllowed)
}

// DistributeRedPacket 分发红包
// 根据用户选择和各种约束条件计算最终的红包金额
func (r *RedPacketDistributor) DistributeRedPacket(day int, userChooseMultiply bool) (DailyRedPacket, error) {
// 检查是否还有红包可分发
if r.RemainDays <= 0 || r.RemainAmount <= 0 {
return DailyRedPacket{}, errors.New("当前红包已经发完!")
}

// 初始化结果
result := DailyRedPacket{
Day: day,
IsMultiplied: false,
Multiplier: 1.0,
}

// 计算安全系数
safetyFactor := r.calculateDynamicSafetyFactor(day)
result.SafetyFactor = safetyFactor

// 计算基础金额
if r.RemainDays == 1 {
// 最后一天,分配所有剩余金额
result.BaseAmount = r.RemainAmount
} else {
result.BaseAmount = r.calculateSafeAmount(day)
}

// 处理翻倍逻辑
if r.RemainDays > 1 && userChooseMultiply {
result.IsMultiplied = true
result.Multiplier = r.calculateMultiplier(result.BaseAmount)
result.FinalAmount = result.BaseAmount * result.Multiplier
} else {
result.FinalAmount = result.BaseAmount
}

// 最终安全检查
maxAllowed := r.RemainAmount
if r.RemainDays > 1 {
// 确保为后续天数预留足够金额
minFutureNeed := (r.RemainAmount * 0.2) / float64(r.RemainDays)
maxAllowed = r.RemainAmount - (minFutureNeed * float64(r.RemainDays-1))
}
result.FinalAmount = math.Min(result.FinalAmount, maxAllowed)

// 更新分发器状态
r.RemainAmount -= result.FinalAmount
r.RemainDays--
result.RemainAmount = r.RemainAmount

// 记录分发历史
r.DistributionHistory = append(r.DistributionHistory, result.FinalAmount)

return result, nil
}

// formatRedPacketInfo 格式化红包信息输出
func formatRedPacketInfo(packet DailyRedPacket) string {
multiplierInfo := "否"
if packet.IsMultiplied {
multiplierInfo = fmt.Sprintf("是 (%.2f倍)", packet.Multiplier)
}

return fmt.Sprintf(`第 %d 天红包详情:
基础金额: %.2f 元
是否翻倍: %s
安全系数: %.2f
最终金额: %.2f 元
剩余金额: %.2f 元
`, packet.Day, packet.BaseAmount, multiplierInfo, packet.SafetyFactor, packet.FinalAmount, packet.RemainAmount)
}

+ 67
- 0
rule/new_user_red_package/util_test.go View File

@@ -0,0 +1,67 @@
package new_user_red_package

import (
"fmt"
"math/rand"
"testing"
"time"
)

func TestDistributeRedPacket(t *testing.T) {
// 初始化随机数种子
rand.Seed(time.Now().UnixNano())
// 定义测试用例
testCases := []struct {
amount float64
days int
userChoices []bool // 每天用户是否选择翻倍
}{
{
amount: 10.0,
days: 5,
userChoices: []bool{true, false, true, false, false},
},
{
amount: 20.0,
days: 7,
userChoices: []bool{true, true, false, false, true, false, false},
},
}

// 运行测试用例
for _, tc := range testCases {
runTest(tc.amount, tc.days, tc.userChoices)
}
}

// runTest 运行测试用例
func runTest(totalAmount float64, totalDays int, userChoices []bool) {
fmt.Printf("\n测试: %.2f元 / %d天\n", totalAmount, totalDays)
fmt.Printf("每日平均金额: %.2f元\n", totalAmount/float64(totalDays))
fmt.Println("----------------------------------------")

distributor := NewRedPacketDistributorForTest(totalAmount, totalDays)
var totalDistributed float64

for day := 1; day <= totalDays; day++ {
// 获取用户选择
userChoice := false
if day <= len(userChoices) {
userChoice = userChoices[day-1]
}

// 分发红包并打印结果
packet, err := distributor.DistributeRedPacket(day, userChoice)
if err != nil {
println("err:::", err.Error())
break
}
totalDistributed += packet.FinalAmount
fmt.Println(formatRedPacketInfo(packet))
}

// 打印总结信息
fmt.Println("----------------------------------------")
fmt.Printf("总计分发: %.2f 元\n", totalDistributed)
fmt.Printf("剩余金额: %.2f 元\n", distributor.RemainAmount)
}

Loading…
Cancel
Save