Files
bl/modules/config/service/cdk.go
昔念 eca7dd86e1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
fix(fight): 修复单输入战斗中效果处理逻辑错误

- 在Effect201的OnSkill方法中调整了多输入战斗检查的位置,
  确保单输入战斗中的单目标效果被正确忽略

- 添加了针对单输入战斗中单目标效果的测试用例

- 移除了重复的多输入战斗检查代码

feat(fight): 添加战斗初始化时捕获标识设置功能

- 在initfightready函数中添加对CanCapture字段的处理
  将玩家的捕获能力信息传递到战斗准备信息中

- 在ReadyFightPetInfo结构体中添加IsCapture字段用于
  标识宠物是否为捕获类型

refactor(fight): 调整战斗初始化顺序确保数据一致性

- 将ReadyInfo初始化移到绑定输入上下文之后执行
  确保团队视图链接完成后再进行准备信息构建

fix(player): 增加宠物血量检查避免无效匹配

- 在玩家匹配检测中增加首只宠物血量检查
  当首只宠物血量为0时不参与匹配以防止异常情况
```
2026-04-13 10:21:13 +08:00

357 lines
8.9 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 service
import (
"blazing/cool"
"blazing/modules/config/model"
"context"
"crypto/rand"
"fmt"
"math/big"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/google/uuid"
)
const charsetWithSymbol = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"
const (
CDKTypeReward uint32 = 0
CDKTypeServerNaming uint32 = 1
cdkCodeLength = 16
maxBatchGenerateCount = 5000
)
var charsetWithSymbolSize = big.NewInt(int64(len(charsetWithSymbol)))
func Generate16CharSecure() string {
code, err := generateSecureCode(cdkCodeLength)
if err == nil {
return code
}
fallback := fmt.Sprintf("%x", uuid.New())
if len(fallback) >= cdkCodeLength {
return fallback[:cdkCodeLength]
}
return fmt.Sprintf("%-16s", fallback)
}
func generateSecureCode(length int) (string, error) {
if length <= 0 {
return "", nil
}
result := make([]byte, length)
for i := 0; i < length; i++ {
index, err := rand.Int(rand.Reader, charsetWithSymbolSize)
if err != nil {
return "", err
}
result[i] = charsetWithSymbol[index.Int64()]
}
return string(result), nil
}
func buildUniqueCodes(count int, generator func() (string, error), exists func([]string) (map[string]struct{}, error)) ([]string, error) {
if count <= 0 {
return nil, nil
}
codes := make([]string, 0, count)
selected := make(map[string]struct{}, count)
for len(codes) < count {
remaining := count - len(codes)
batchSize := remaining * 2
if batchSize < remaining+4 {
batchSize = remaining + 4
}
candidates := make([]string, 0, batchSize)
candidateSet := make(map[string]struct{}, batchSize)
for len(candidates) < batchSize {
code, err := generator()
if err != nil {
return nil, err
}
if _, ok := selected[code]; ok {
continue
}
if _, ok := candidateSet[code]; ok {
continue
}
candidateSet[code] = struct{}{}
candidates = append(candidates, code)
}
existing, err := exists(candidates)
if err != nil {
return nil, err
}
for _, code := range candidates {
if _, ok := existing[code]; ok {
continue
}
selected[code] = struct{}{}
codes = append(codes, code)
if len(codes) == count {
break
}
}
}
return codes, nil
}
type CdkService struct {
*cool.Service
}
func NewCdkService() *CdkService {
return &CdkService{
&cool.Service{
Model: model.NewCDKConfig(),
InsertParam: func(ctx context.Context) g.MapStrAny {
uuid.NewV7()
return g.MapStrAny{
"cdk_code": Generate16CharSecure(),
}
},
},
}
}
func (s *CdkService) Get(id string) *model.CDKConfig {
var item *model.CDKConfig
dbm_notenable(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Scan(&item)
return item
}
func (s *CdkService) GetByID(id uint32) *model.CDKConfig {
if id == 0 {
return nil
}
var item *model.CDKConfig
dbm_notenable(s.Model).Where("id", id).Scan(&item)
return item
}
func (s *CdkService) All() []model.CDKConfig {
var item []model.CDKConfig
dbm_notenable(s.Model).WhereLT("exchange_remain_count", 0).Scan(&item)
return item
}
func (s *CdkService) Set(id string) bool {
res, err := cool.DBM(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Decrement("exchange_remain_count", 1)
if err != nil {
return false
}
rows, _ := res.RowsAffected()
if rows == 0 {
return false
}
return true
}
type ServerNamingCDKResult struct {
ServerID uint32
ServerName string
OwnerID uint32
ServerExpireTime time.Time
}
// UseServerNamingCDK 使用服务器冠名类型CDK并原子化更新服务器归属和到期时间。
func (s *CdkService) UseServerNamingCDK(ctx context.Context, code string, ownerID, serverID uint32, serverName string) (*ServerNamingCDKResult, error) {
if ctx == nil {
ctx = context.TODO()
}
now := time.Now()
serverService := NewServerService()
var updated model.ServerShow
err := g.DB(s.Model.GroupName()).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
var cfg model.CDKConfig
if err := tx.Model(s.Model).Where("cdk_code", code).WhereNot("exchange_remain_count", 0).Scan(&cfg); err != nil {
return err
}
if cfg.ID == 0 {
return gerror.New("cdk不存在")
}
if cfg.CDKType != CDKTypeServerNaming {
return gerror.New("cdk类型不匹配")
}
if cfg.BindUserId != 0 && cfg.BindUserId != ownerID {
return gerror.New("cdk已绑定其他用户")
}
if !cfg.ValidEndTime.IsZero() && cfg.ValidEndTime.Before(now) {
return gerror.New("cdk已过期")
}
var server model.ServerList
if err := tx.Model(model.NewServerList()).Where("online_id", serverID).Scan(&server); err != nil {
return err
}
if server.OnlineID == 0 {
return gerror.New("服务器不存在")
}
if !serverService.CanUseDonationName(server, ownerID, now) {
return gerror.New("服务器不可冠名")
}
res, err := tx.Model(s.Model).Where("cdk_code", code).WhereNot("exchange_remain_count", 0).Decrement("exchange_remain_count", 1)
if err != nil {
return err
}
rows, _ := res.RowsAffected()
if rows == 0 {
return gerror.New("cdk已被使用")
}
var currentShow model.ServerShow
if err := tx.Model(model.NewServerShow()).
Where("server_id", serverID).
Where("owner", ownerID).
OrderDesc("id").
Limit(1).
Scan(&currentShow); err != nil {
return err
}
updated = currentShow
updated.ServerID = serverID
updated.Name = serverName
updated.Owner = ownerID
if currentShow.ServerID == 0 || !serverService.isActiveServerShow(&currentShow, now) {
updated.ExpireTime = now.AddDate(0, 1, 0)
} else {
baseTime := currentShow.ExpireTime
if baseTime.IsZero() {
baseTime = now
}
updated.ExpireTime = baseTime.AddDate(0, 1, 0)
}
if currentShow.ServerID == 0 {
_, err = tx.Model(model.NewServerShow()).Data(g.Map{
"server_id": updated.ServerID,
"name": updated.Name,
"owner": updated.Owner,
"expire_time": updated.ExpireTime,
}).Insert()
return err
}
_, err = tx.Model(model.NewServerShow()).Where("server_id", serverID).Data(g.Map{
"name": updated.Name,
"owner": updated.Owner,
"expire_time": updated.ExpireTime,
}).Update()
return err
})
if err != nil {
return nil, err
}
g.DB(s.Model.GroupName()).GetCore().ClearCache(context.TODO(), s.Model.TableName())
g.DB(model.NewServerList().GroupName()).GetCore().ClearCache(context.TODO(), model.NewServerList().TableName())
g.DB(model.NewServerShow().GroupName()).GetCore().ClearCache(context.TODO(), model.NewServerShow().TableName())
return &ServerNamingCDKResult{
ServerID: updated.ServerID,
ServerName: updated.Name,
OwnerID: updated.Owner,
ServerExpireTime: updated.ExpireTime,
}, nil
}
func (s *CdkService) BatchGenerate(ctx context.Context, count int) (interface{}, error) {
if count <= 0 {
return nil, gerror.New("生成数量必须大于0")
}
if count > maxBatchGenerateCount {
return nil, gerror.Newf("单次最多生成%d个CDK", maxBatchGenerateCount)
}
requestData := g.RequestFromCtx(ctx).GetMap()
delete(requestData, "count")
delete(requestData, "cdk_code")
delete(requestData, "id")
cdkType := gconv.Uint32(requestData["type"])
if cdkType != CDKTypeReward && cdkType != CDKTypeServerNaming {
return nil, gerror.New("CDK类型非法")
}
requestData["type"] = cdkType
if cdkType == CDKTypeServerNaming {
requestData["exchange_remain_count"] = int64(1)
requestData["item_reward_ids"] = []uint32{}
requestData["elf_reward_ids"] = []uint32{}
requestData["title_reward_ids"] = uint32(0)
}
codes, err := s.generateAvailableCodes(count)
if err != nil {
return nil, err
}
insertData := make(g.List, 0, len(codes))
for _, code := range codes {
row := g.Map{}
for key, value := range requestData {
row[key] = value
}
row["cdk_code"] = code
insertData = append(insertData, row)
}
err = g.DB(s.Model.GroupName()).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
_, err := tx.Model(s.Model).Data(insertData).Insert()
return err
})
if err != nil {
return nil, err
}
g.DB(s.Model.GroupName()).GetCore().ClearCache(context.TODO(), s.Model.TableName())
return g.Map{
"count": count,
"codes": codes,
}, nil
}
func (s *CdkService) generateAvailableCodes(count int) ([]string, error) {
return buildUniqueCodes(count, func() (string, error) {
return generateSecureCode(cdkCodeLength)
}, func(candidates []string) (map[string]struct{}, error) {
return s.findExistingCodeSet(candidates)
})
}
func (s *CdkService) findExistingCodeSet(candidates []string) (map[string]struct{}, error) {
existingSet := make(map[string]struct{})
if len(candidates) == 0 {
return existingSet, nil
}
var records []struct {
CDKCode string `json:"cdk_code"`
}
if err := dbm_nocache_noenable(s.Model).Fields("cdk_code").WhereIn("cdk_code", candidates).Scan(&records); err != nil {
return nil, err
}
for _, record := range records {
existingSet[record.CDKCode] = struct{}{}
}
return existingSet, nil
}