bindbox-game/internal/service/activity/ichiban_slots_service.go
邹方成 45815bfb7d chore: 清理无用文件与优化代码结构
refactor(utils): 修复密码哈希比较逻辑错误
feat(user): 新增按状态筛选优惠券接口
docs: 添加虚拟发货与任务中心相关文档
fix(wechat): 修正Code2Session上下文传递问题
test: 补充订单折扣与积分转换测试用例
build: 更新配置文件与构建脚本
style: 清理多余的空行与注释
2025-12-18 17:35:55 +08:00

168 lines
4.8 KiB
Go

package activity
import (
"bindbox-game/internal/repository/mysql"
"bindbox-game/internal/repository/mysql/dao"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
)
type IchibanSlotsService struct {
read *dao.Query
repo mysql.Repo
}
func NewIchibanSlotsService(read *dao.Query, repo mysql.Repo) *IchibanSlotsService {
return &IchibanSlotsService{read: read, repo: repo}
}
func (s *IchibanSlotsService) buildMapping(ctx context.Context, activityID int64, issueID int64) ([]int64, error) {
var seedHex string
if err := s.repo.GetDbR().Raw("SELECT HEX(commitment_seed_master) FROM activities WHERE id=?", activityID).Scan(&seedHex).Error; err != nil || seedHex == "" {
return nil, errors.New("commitment not found")
}
seed, _ := hex.DecodeString(seedHex)
rewards, err := s.read.ActivityRewardSettings.WithContext(ctx).ReadDB().Where(s.read.ActivityRewardSettings.IssueID.Eq(issueID)).Order(
s.read.ActivityRewardSettings.Level.Desc(),
s.read.ActivityRewardSettings.Sort.Asc(),
s.read.ActivityRewardSettings.ID.Asc(),
).Find()
if err != nil || len(rewards) == 0 {
return nil, errors.New("no rewards")
}
var total int64
for _, r := range rewards {
if r.OriginalQty > 0 {
total += r.OriginalQty
}
}
if total <= 0 {
return nil, errors.New("no slots")
}
slots := make([]int64, 0, total)
for _, r := range rewards {
for i := int64(0); i < r.OriginalQty; i++ {
slots = append(slots, r.ID)
}
}
mac := hmac.New(sha256.New, seed)
for i := int(total - 1); i > 0; i-- {
mac.Reset()
mac.Write([]byte(fmt.Sprintf("shuffle:%d|issue:%d", i, issueID)))
sum := mac.Sum(nil)
rnd := int(binary.BigEndian.Uint64(sum[:8]) % uint64(i+1))
slots[i], slots[rnd] = slots[rnd], slots[i]
}
return slots, nil
}
type SlotItem struct {
SlotIndex int64
RewardID int64
RewardName string
Level int32
ProductImage string
OriginalQty int64
RemainingQty int64
Claimed bool
}
func (s *IchibanSlotsService) Page(ctx context.Context, activityID int64, issueID int64, page int, pageSize int, claimedFilter *bool) (int64, []SlotItem, error) {
mapping, err := s.buildMapping(ctx, activityID, issueID)
if err != nil {
return 0, nil, err
}
total := int64(len(mapping))
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 50
}
start := int64((page - 1) * pageSize)
end := start + int64(pageSize)
if start > total {
return total, []SlotItem{}, nil
}
if end > total {
end = total
}
var claimedIdx []int64
if err := s.repo.GetDbR().Raw("SELECT slot_index FROM issue_position_claims WHERE issue_id = ?", issueID).Scan(&claimedIdx).Error; err != nil {
claimedIdx = []int64{}
}
used := map[int64]struct{}{}
for _, x := range claimedIdx {
used[x] = struct{}{}
}
items := make([]SlotItem, 0, end-start)
for i := start; i < end; i++ {
rid := mapping[i]
rw, _ := s.read.ActivityRewardSettings.WithContext(ctx).Where(s.read.ActivityRewardSettings.ID.Eq(rid)).First()
if rw == nil {
continue
}
var img string
if rw.ProductID > 0 {
p, _ := s.read.Products.WithContext(ctx).Where(s.read.Products.ID.Eq(rw.ProductID)).First()
if p != nil && p.ImagesJSON != "" {
var arr []string
_ = json.Unmarshal([]byte(p.ImagesJSON), &arr)
if len(arr) > 0 {
img = arr[0]
}
}
}
c := false
if _, ok := used[i]; ok {
c = true
}
if claimedFilter != nil {
if *claimedFilter && !c {
continue
}
if !*claimedFilter && c {
continue
}
}
items = append(items, SlotItem{SlotIndex: i + 1, RewardID: rid, RewardName: rw.Name, Level: rw.Level, ProductImage: img, OriginalQty: rw.OriginalQty, RemainingQty: rw.Quantity, Claimed: c})
}
return total, items, nil
}
func (s *IchibanSlotsService) SlotDetail(ctx context.Context, activityID int64, issueID int64, slotIndex int64) (SlotItem, error) {
mapping, err := s.buildMapping(ctx, activityID, issueID)
if err != nil {
return SlotItem{}, err
}
idx0 := slotIndex - 1
if idx0 < 0 || idx0 >= int64(len(mapping)) {
return SlotItem{}, errors.New("out of range")
}
rid := mapping[idx0]
rw, _ := s.read.ActivityRewardSettings.WithContext(ctx).Where(s.read.ActivityRewardSettings.ID.Eq(rid)).First()
if rw == nil {
return SlotItem{}, errors.New("reward not found")
}
var img string
if rw.ProductID > 0 {
p, _ := s.read.Products.WithContext(ctx).Where(s.read.Products.ID.Eq(rw.ProductID)).First()
if p != nil && p.ImagesJSON != "" {
var arr []string
_ = json.Unmarshal([]byte(p.ImagesJSON), &arr)
if len(arr) > 0 {
img = arr[0]
}
}
}
var claimed int64
_ = s.repo.GetDbR().Raw("SELECT COUNT(1) FROM issue_position_claims WHERE issue_id=? AND slot_index=?", issueID, idx0).Scan(&claimed)
return SlotItem{SlotIndex: slotIndex, RewardID: rid, RewardName: rw.Name, Level: rw.Level, ProductImage: img, OriginalQty: rw.OriginalQty, RemainingQty: rw.Quantity, Claimed: claimed > 0}, nil
}