2025-12-26 03:51:24 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"blazing/cool"
|
|
|
|
|
|
"blazing/modules/config/model"
|
2026-01-19 18:51:56 +08:00
|
|
|
|
"context"
|
2026-04-08 14:17:10 +08:00
|
|
|
|
"crypto/rand"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"math/big"
|
2026-04-08 15:49:03 +08:00
|
|
|
|
"time"
|
2026-01-19 18:51:56 +08:00
|
|
|
|
|
2026-04-08 14:17:10 +08:00
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
2026-01-19 18:51:56 +08:00
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
2026-04-10 19:36:59 +08:00
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
2026-01-19 18:51:56 +08:00
|
|
|
|
"github.com/google/uuid"
|
2026-04-06 02:51:13 +08:00
|
|
|
|
)
|
2026-01-19 18:51:56 +08:00
|
|
|
|
|
|
|
|
|
|
const charsetWithSymbol = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"
|
|
|
|
|
|
|
2026-04-08 14:17:10 +08:00
|
|
|
|
const (
|
2026-04-08 15:49:03 +08:00
|
|
|
|
CDKTypeReward uint32 = 0
|
|
|
|
|
|
CDKTypeServerNaming uint32 = 1
|
|
|
|
|
|
|
2026-04-08 14:17:10 +08:00
|
|
|
|
cdkCodeLength = 16
|
|
|
|
|
|
maxBatchGenerateCount = 5000
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var charsetWithSymbolSize = big.NewInt(int64(len(charsetWithSymbol)))
|
|
|
|
|
|
|
2026-01-19 18:51:56 +08:00
|
|
|
|
func Generate16CharSecure() string {
|
2026-04-08 14:17:10 +08:00
|
|
|
|
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()]
|
2026-01-19 18:51:56 +08:00
|
|
|
|
}
|
2026-04-08 14:17:10 +08:00
|
|
|
|
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
|
2026-01-19 18:51:56 +08:00
|
|
|
|
}
|
2025-12-26 03:51:24 +08:00
|
|
|
|
|
|
|
|
|
|
type CdkService struct {
|
|
|
|
|
|
*cool.Service
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewCdkService() *CdkService {
|
|
|
|
|
|
return &CdkService{
|
|
|
|
|
|
&cool.Service{
|
|
|
|
|
|
Model: model.NewCDKConfig(),
|
2026-01-19 18:51:56 +08:00
|
|
|
|
InsertParam: func(ctx context.Context) g.MapStrAny {
|
|
|
|
|
|
uuid.NewV7()
|
|
|
|
|
|
return g.MapStrAny{
|
|
|
|
|
|
"cdk_code": Generate16CharSecure(),
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-12-26 03:51:24 +08:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-06 02:51:13 +08:00
|
|
|
|
|
2026-01-19 18:51:56 +08:00
|
|
|
|
func (s *CdkService) Get(id string) *model.CDKConfig {
|
|
|
|
|
|
var item *model.CDKConfig
|
2026-02-14 23:14:43 +08:00
|
|
|
|
dbm_notenable(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Scan(&item)
|
2026-01-19 18:51:56 +08:00
|
|
|
|
return item
|
2026-04-06 02:51:13 +08:00
|
|
|
|
}
|
2026-01-19 18:51:56 +08:00
|
|
|
|
|
2026-04-06 02:51:13 +08:00
|
|
|
|
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
|
2026-03-23 22:59:29 +08:00
|
|
|
|
}
|
2026-04-06 02:51:13 +08:00
|
|
|
|
|
2026-03-23 22:59:29 +08:00
|
|
|
|
func (s *CdkService) All() []model.CDKConfig {
|
|
|
|
|
|
var item []model.CDKConfig
|
|
|
|
|
|
dbm_notenable(s.Model).WhereLT("exchange_remain_count", 0).Scan(&item)
|
|
|
|
|
|
return item
|
2026-01-19 18:51:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 02:51:13 +08:00
|
|
|
|
func (s *CdkService) Set(id string) bool {
|
2026-01-19 18:51:56 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-04-08 14:17:10 +08:00
|
|
|
|
|
2026-04-08 19:31:44 +08:00
|
|
|
|
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) {
|
2026-04-08 15:49:03 +08:00
|
|
|
|
if ctx == nil {
|
|
|
|
|
|
ctx = context.TODO()
|
|
|
|
|
|
}
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
serverService := NewServerService()
|
2026-04-10 19:36:59 +08:00
|
|
|
|
var updated model.ServerShow
|
2026-04-08 15:49:03 +08:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-04-10 19:36:59 +08:00
|
|
|
|
if err := tx.Model(model.NewServerList()).With(model.ServerShow{}).Where("online_id", serverID).Scan(&server); err != nil {
|
2026-04-08 15:49:03 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-04-08 19:31:44 +08:00
|
|
|
|
if server.OnlineID == 0 {
|
|
|
|
|
|
return gerror.New("服务器不存在")
|
|
|
|
|
|
}
|
2026-04-10 19:36:59 +08:00
|
|
|
|
if !serverService.CanUseDonationName(server, ownerID, now) {
|
2026-04-08 15:49:03 +08:00
|
|
|
|
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已被使用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 19:36:59 +08:00
|
|
|
|
var currentShow model.ServerShow
|
|
|
|
|
|
if server.ServerShow != nil {
|
|
|
|
|
|
currentShow = *server.ServerShow
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updated = currentShow
|
|
|
|
|
|
updated.ServerID = serverID
|
2026-04-08 15:49:03 +08:00
|
|
|
|
updated.Name = serverName
|
|
|
|
|
|
updated.Owner = ownerID
|
2026-04-10 19:36:59 +08:00
|
|
|
|
|
|
|
|
|
|
if currentShow.ServerID == 0 || !serverService.isActiveServerShow(¤tShow, now) || currentShow.Owner != ownerID {
|
|
|
|
|
|
updated.ExpireTime = now.AddDate(0, 1, 0)
|
2026-04-08 19:31:44 +08:00
|
|
|
|
} else {
|
2026-04-10 19:36:59 +08:00
|
|
|
|
baseTime := currentShow.ExpireTime
|
2026-04-08 19:31:44 +08:00
|
|
|
|
if baseTime.IsZero() {
|
|
|
|
|
|
baseTime = now
|
|
|
|
|
|
}
|
2026-04-10 19:36:59 +08:00
|
|
|
|
updated.ExpireTime = baseTime.AddDate(0, 1, 0)
|
2026-04-08 19:31:44 +08:00
|
|
|
|
}
|
2026-04-08 15:49:03 +08:00
|
|
|
|
|
2026-04-10 19:36:59 +08:00
|
|
|
|
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,
|
2026-04-08 15:49:03 +08:00
|
|
|
|
}).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())
|
2026-04-10 19:36:59 +08:00
|
|
|
|
g.DB(model.NewServerShow().GroupName()).GetCore().ClearCache(context.TODO(), model.NewServerShow().TableName())
|
2026-04-08 19:31:44 +08:00
|
|
|
|
return &ServerNamingCDKResult{
|
2026-04-10 19:36:59 +08:00
|
|
|
|
ServerID: updated.ServerID,
|
2026-04-08 19:31:44 +08:00
|
|
|
|
ServerName: updated.Name,
|
|
|
|
|
|
OwnerID: updated.Owner,
|
|
|
|
|
|
ServerExpireTime: updated.ExpireTime,
|
|
|
|
|
|
}, nil
|
2026-04-08 15:49:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 14:17:10 +08:00
|
|
|
|
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")
|
|
|
|
|
|
|
2026-04-10 19:36:59 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 14:17:10 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|