Files
bl/modules/config/service/cdk.go
昔念 f95fd49efd
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat(fight): 新增疲惫状态并优化睡眠状态机制

- 实现疲惫状态(StatusTired),仅限制攻击技能,允许属性技能正常使用
- 重构睡眠状态,改为在被攻击且未miss时立即解除,而非技能使用后
- 修复寄生种子效果触发时机,改为回合开始时触发
- 调整寄生效果的目标为技能施放者而非
2026-04-13 21:06:45 +08:00

366 lines
9.2 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/base/service"
"blazing/modules/config/model"
"context"
"crypto/rand"
"database/sql"
"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) {
execCtx := context.Background()
if ctx != nil && ctx.Err() != nil {
ctx = nil
}
now := time.Now()
serverService := NewServerService()
var updated model.ServerShow
err := g.DB(s.Model.GroupName()).Transaction(execCtx, 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.Type != 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 {
if err == sql.ErrNoRows {
err = nil
}
}
if 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())
service.NewBaseSysUserService().UpdateGold(updated.Owner, int64(200*100))
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
}