bindbox-game/internal/service/activity/ichiban_slots_service.go

169 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.IsBoss.Desc(),
s.read.ActivityRewardSettings.Level.Asc(),
s.read.ActivityRewardSettings.Sort.Asc(),
s.read.ActivityRewardSettings.ID.Asc(),
).Find()
if err != nil || len(rewards) == 0 {
return nil, errors.New("no rewards")
}
// 一番赏:每种奖品 = 1个格位
total := int64(len(rewards))
if total <= 0 {
return nil, errors.New("no slots")
}
slots := make([]int64, total)
for i, r := range rewards {
slots[i] = 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
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
var productName string
if rw.ProductID > 0 {
p, _ := s.read.Products.WithContext(ctx).Where(s.read.Products.ID.Eq(rw.ProductID)).First()
if p != nil {
productName = p.Name
if 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: productName, Level: rw.Level, ProductImage: img, 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
var productName string
if rw.ProductID > 0 {
p, _ := s.read.Products.WithContext(ctx).Where(s.read.Products.ID.Eq(rw.ProductID)).First()
if p != nil {
productName = p.Name
if 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: productName, Level: rw.Level, ProductImage: img, Claimed: claimed > 0}, nil
}