Files
bl/modules/player/service/info.go
昔念 5995f0670c
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat(game): 实现扭蛋系统批量物品添加功能并优化地图逻辑

- 新增ItemAddBatch方法用于批量添加物品,支持普通道具和特殊道具的分别处理
- 优化扭蛋游戏玩法中的物品添加逻辑,使用新的批量接口提升性能
- 在扭蛋机器人命令中实现完整的物品检查和批量添加流程

refactor(map): 重构地图控制器代码结构并添加注释

- 为EnterMap、LeaveMap、GetMapPlayerList等方法添加中文注释
- 统一地图相关的命名规范,如enter
2026-04-01 20:10:29 +08:00

280 lines
7.3 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/common/data/share"
"blazing/common/utils"
"blazing/cool"
"blazing/modules/config/service"
"blazing/modules/player/model"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtime"
"github.com/google/uuid"
csmap "github.com/mhmtszr/concurrent-swiss-map"
)
// 是否注册,如果注册过,那么就会产生用户player信息
// 实现注册,id+昵称+颜色
func (s *InfoService) Reg(nick string, color uint32) *model.PlayerInfo {
var tt *model.Player
s.dbm_fix(s.Model).Scan(&tt)
if tt == nil {
t := model.NewPlayer()
t.PlayerID = uint64(s.userid)
//设置用户信息
t.Data = model.NewPlayerInfo()
t.Data.Nick = nick
t.Data.UserID = s.userid
t.Data.Color = color
t.Data.RegisterTime = uint32(time.Now().Unix()) //写入注册时间
_, err := cool.DBM(s.Model).Data(t).FieldsEx("id").Insert()
if err != nil {
glog.Error(context.Background(), err)
}
return &t.Data
} else {
return &tt.Data
}
}
func (s *InfoService) Person(userid uint32) (out *model.Player) {
cool.DBM(s.Model).Where("player_id", userid).Scan(&out)
return
}
func (s *InfoService) GetLogin() *model.PlayerInfo {
var tt *model.Player
s.dbm_fix(s.Model).Scan(&tt)
if tt == nil {
return nil
}
tt.Data.AllPetNumber = uint32(NewPetService(s.userid).PetCount(0))
if tt.Data.MapID > 300 || tt.Data.MapID == 0 { //如果位于基地,就重置到传送仓
tt.Data.MapID = 1
}
if tt.Data.IsNewPlayer() { //重置新手地图,放到机械仓
tt.Data.SetTask(4, model.Completed) //设置新手任务默认完成
tt.Data.MapID = 8
if len(tt.Data.PetList) == 0 {
//这个是添加后防止卡死
rr := NewPetService(s.userid).PetInfo(0)
if len(rr) > 0 {
tt.Data.PetList = append(tt.Data.PetList, rr[0].Data)
}
}
}
if tt.Data.MaxPuniLv < 9 {
for i := 291; i < 299; i++ {
if tt.Data.GetTask(i) == model.Completed {
tt.Data.MaxPuniLv = uint32(i) - 290
}
}
}
if cool.Config.ServerInfo.IsVip == 0 {
if !utils.IsToday(tt.LastResetTime) { //判断是否是今天
//每天login时候检查重置时间然后把电池任务挖矿重置
//挖矿需要单独存,因为防止多开挖矿
tt.LastResetTime = gtime.Now()
//每天login时候检查重置时间然后把电池任务挖矿重置
//挖矿需要单独存,因为防止多开挖矿
tt.Data.TimeToday = 0 //重置电池
//tt.Data.FightTime = 60 * 60 * 2 //重置战斗次数
for _, v := range service.NewTaskService().GetDaily() {
if v.IsAcceptable == 1 {
tt.Data.SetTask(int(v.TaskId), model.Unaccepted)
} else {
tt.Data.SetTask(int(v.TaskId), model.Reserved)
}
}
// for i := 0; i < 50; i++ { //每日任务区段
// tt.Data.DailyResArr[i] = 0 //重置每日任务
// }
// //defer t.Service.Talk_Reset()
_, err := s.dbm_fix(s.Model).Data("last_reset_time", gtime.Now()).Update()
if err != nil {
cool.Logger.Error(context.TODO(), "update last_reset_time failed", s.userid, err)
}
}
if !utils.IsWEEK(tt.WeekLastResetTime) {
for _, v := range service.NewTaskService().GetWeek() {
if v.IsAcceptable == 1 {
tt.Data.SetTask(int(v.TaskId), model.Unaccepted)
} else {
tt.Data.SetTask(int(v.TaskId), model.Reserved)
}
}
_, err := s.dbm_fix(s.Model).Data("week_last_reset_time", gtime.Now()).Update()
if err != nil {
cool.Logger.Error(context.TODO(), "update week_last_reset_time failed", s.userid, err)
}
}
}
ret := tt.Data
return &ret
}
var User = csmap.New(
// set the number of map shards. the default value is 32.
csmap.WithShardCount[string, uint32](32),
// set the total capacity, every shard map has total capacity/shard count capacity. the default value is 0.
// csmap.WithSize[string, int](1000),
)
// 生成session
// GetSessionId 生成并返回会话ID、UUID字符串及可能的错误
// 会话ID由accountID(4字节) + UUID(16字节) + 随机数(4字节)组成,最终编码为十六进制字符串
func (s *InfoService) Gensession() string {
uuidV7, _ := uuid.NewV7()
uuidBytes := uuidV7[:] // UUID 类型底层是 [16]byte直接切片获取
// 移除UUID中的连字符便于后续处理
// 3. 计算 CRC32-IEEE 校验码最通用的CRC32标准
sessionID := hex.EncodeToString(uuidBytes)
cool.CacheManager.Set(context.Background(), fmt.Sprintf("session:%d", uint32(s.userid)), sessionID, 10*time.Minute)
// ///User.Store(string(uuidStr), uint32(s.userid))
// //share.ShareManager.SaveSession(string(uuidStr), uint32(s.userid))
return sessionID
}
func (s *InfoService) Kick(id uint32) error {
useid1, err := share.ShareManager.GetUserOnline(id)
if err != nil {
return err
}
cl, ok := cool.GetClientOnly(useid1)
if ok {
err := cl.KickPerson(id) //实现指定服务器踢人
if err != nil {
return err
}
}
return nil
}
// saveToLocalFile 兜底保存将数据写入本地lose文件夹
func (s *InfoService) saveToLocalFile(player *model.PlayerInfo, err error) {
// 1. 创建lose文件夹如果不存在
loseDir := "./lose"
if err := os.MkdirAll(loseDir, 0755); err != nil {
fmt.Printf("[ERROR] 创建lose文件夹失败: %v\n", err)
return
}
// 2. 构造保存的数据结构,包含错误信息和时间戳
type FallbackData struct {
PlayerData *model.PlayerInfo `json:"player_data"`
ErrorMsg string `json:"error_msg"`
SaveTime string `json:"save_time"`
ServerInfo string `json:"server_info"`
}
fallbackData := FallbackData{
PlayerData: player,
ErrorMsg: err.Error(),
SaveTime: time.Now().Format("20060102150405.000"), // 精确到毫秒的时间戳
ServerInfo: fmt.Sprintf("server_vip:%d", cool.Config.ServerInfo.IsVip),
}
// 3. 生成唯一的文件名(避免覆盖)
playerID := fmt.Sprintf("%d", player.UserID) // 假设Player有PlayerID字段根据实际调整
filename := fmt.Sprintf("player_%s_%s.json", playerID, fallbackData.SaveTime)
filePath := filepath.Join(loseDir, filename)
// 4. 将数据序列化为JSON并写入文件
file, err := os.Create(filePath)
if err != nil {
fmt.Printf("[ERROR] 创建兜底文件失败 %s: %v\n", filePath, err)
return
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ") // 格式化JSON方便查看
if err := encoder.Encode(fallbackData); err != nil {
fmt.Printf("[ERROR] 写入兜底文件失败 %s: %v\n", filePath, err)
return
}
// 5. 记录日志,方便排查
fmt.Printf("[INFO] 数据库保存失败,已将玩家[%s]数据兜底保存到: %s\n", playerID, filePath)
}
func (s *InfoService) Save(data model.PlayerInfo) {
if cool.Config.ServerInfo.IsVip != 0 {
return
}
for i := 0; i < 3; i++ {
_, err := s.dbm_fix(s.Model).Data("data", data).Update()
if err == nil {
return
}
if i == 2 {
cool.Logger.Error(context.TODO(), "player save failed after retries, fallback to local file", data.UserID, err)
s.saveToLocalFile(&data, err)
return
}
}
}
type InfoService struct {
BaseService
}
func NewInfoService(id uint32) *InfoService {
return &InfoService{
BaseService: BaseService{userid: id,
Service: &cool.Service{Model: model.NewPlayer(), UniqueKey: map[string]string{
"player_id": "角色名称不能重复",
}, PageQueryOp: &cool.QueryOp{
FieldEQ: []string{"player_id"},
}},
},
}
}