邹方成 81e2fb5a75
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
feat(activity): 实现抽奖随机承诺与验证功能
新增随机种子生成与验证逻辑,包括:
1. 添加随机承诺生成接口
2. 实现抽奖执行与验证流程
3. 新增批量用户创建与删除功能
4. 添加抽奖收据记录表
5. 完善配置管理与错误码

新增测试用例验证随机算法正确性
2025-11-15 20:39:13 +08:00

147 lines
4.5 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 activity
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"errors"
"sort"
"bindbox-game/internal/repository/mysql/model"
"gorm.io/gorm"
)
var algoVersion = "v1-hmac-256"
func (s *service) CommitIssueRandom(ctx context.Context, issueID int64) (*IssueRandomCommitment, error) {
// 检查活动期数状态
issue, err := s.readDB.ActivityIssues.WithContext(ctx).
Where(s.readDB.ActivityIssues.ID.Eq(issueID)).
First()
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New("活动期数不存在")
}
return nil, err
}
// 只允许在未开始状态(3)生成承诺
if issue.Status != 3 {
return nil, errors.New("只能在期数未开始状态下生成随机承诺")
}
// 检查是否已存在承诺(防止重复生成)
existing, _ := s.readDB.IssueRandomCommitments.WithContext(ctx).
Where(s.readDB.IssueRandomCommitments.IssueID.Eq(issueID)).
First()
if existing != nil {
return nil, errors.New("该期数已存在随机承诺,不可重复生成")
}
items, err := s.readDB.ActivityRewardSettings.WithContext(ctx).
Where(s.readDB.ActivityRewardSettings.IssueID.Eq(issueID)).
Order(s.readDB.ActivityRewardSettings.ID).
Find()
if err != nil {
return nil, err
}
if len(items) == 0 {
return nil, errors.New("该期数未配置奖励,无法生成随机承诺")
}
canonical := make([]ReceiptItem, 0, len(items))
var weightsTotal int64
for _, it := range items {
canonical = append(canonical, ReceiptItem{ID: it.ID, Name: it.Name, Weight: it.Weight, QuantityBefore: it.Quantity})
if it.Weight > 0 && (it.Quantity == -1 || it.Quantity > 0) {
weightsTotal += int64(it.Weight)
}
}
// 检查奖池权重是否有效
if weightsTotal <= 0 {
return nil, errors.New("奖池配置无效总权重必须大于0")
}
sort.Slice(canonical, func(i, j int) bool { return canonical[i].ID < canonical[j].ID })
b, _ := json.Marshal(canonical)
itemsRoot := sha256.Sum256(b)
master := make([]byte, 32)
_, _ = rand.Read(master)
serverHash := sha256.Sum256(master)
nextVer := int32(1)
enc, _ := maskSeed(master, issueID, nextVer)
rec := &model.IssueRandomCommitments{
IssueID: issueID,
AlgoVersion: algoVersion,
ServerSeedMaster: enc,
ServerSeedHash: serverHash[:],
ItemsRoot: itemsRoot[:],
WeightsTotal: weightsTotal,
StateVersion: nextVer,
}
if err := s.writeDB.IssueRandomCommitments.WithContext(ctx).Create(rec); err != nil {
return nil, err
}
return &IssueRandomCommitment{
AlgoVersion: rec.AlgoVersion,
IssueID: rec.IssueID,
ServerSeedMaster: rec.ServerSeedMaster,
ServerSeedHash: rec.ServerSeedHash,
ItemsRoot: rec.ItemsRoot,
WeightsTotal: rec.WeightsTotal,
StateVersion: rec.StateVersion,
}, nil
}
func (s *service) GetIssueRandomCommit(ctx context.Context, issueID int64) (*IssueRandomCommitment, error) {
latest, err := s.readDB.IssueRandomCommitments.WithContext(ctx).
Where(s.readDB.IssueRandomCommitments.IssueID.Eq(issueID)).
Order(s.readDB.IssueRandomCommitments.StateVersion.Desc()).
Take()
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil // 没有找到记录是正常的,表示尚未生成承诺
}
return nil, err
}
return &IssueRandomCommitment{
AlgoVersion: latest.AlgoVersion,
IssueID: latest.IssueID,
ServerSeedMaster: latest.ServerSeedMaster,
ServerSeedHash: latest.ServerSeedHash,
ItemsRoot: latest.ItemsRoot,
WeightsTotal: latest.WeightsTotal,
StateVersion: latest.StateVersion,
}, nil
}
func (s *service) GetIssueRandomCommitHistory(ctx context.Context, issueID int64) ([]*IssueRandomCommitment, error) {
commitments, err := s.readDB.IssueRandomCommitments.WithContext(ctx).
Where(s.readDB.IssueRandomCommitments.IssueID.Eq(issueID)).
Order(s.readDB.IssueRandomCommitments.StateVersion.Desc()).
Find()
if err != nil {
if err == gorm.ErrRecordNotFound {
return []*IssueRandomCommitment{}, nil // 返回空数组而不是错误
}
return nil, err
}
result := make([]*IssueRandomCommitment, 0, len(commitments))
for _, commit := range commitments {
result = append(result, &IssueRandomCommitment{
AlgoVersion: commit.AlgoVersion,
IssueID: commit.IssueID,
ServerSeedMaster: commit.ServerSeedMaster,
ServerSeedHash: commit.ServerSeedHash,
ItemsRoot: commit.ItemsRoot,
WeightsTotal: commit.WeightsTotal,
StateVersion: commit.StateVersion,
})
}
return result, nil
}