package service import ( "blazing/common/data/share" "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" ) func (s *InfoService) IsReg() bool { m := s.dbm_fix(s.Model) record, _ := m.Exist() return record } // 是否注册,如果注册过,那么就会产生用户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) SetLogin() *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 !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() { tt.Data.SetTask(int(v.TaskId), model.Unaccepted) } for i := 0; i < 50; i++ { //每日任务区段 tt.Data.DailyResArr[i] = 0 //重置每日任务 } //defer t.Service.Talk_Reset() _, err := s.dbm_fix(s.Model).Save(tt) if err != nil { panic(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.GetClient(useid1) if ok { err := cl.KickPerson(id) //实现指定服务器踢人 if err != nil { return err } } return nil } // saveToLocalFile 兜底保存:将数据写入本地lose文件夹 func (s *InfoService) saveToLocalFile(player *model.Player, 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.Player `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.PlayerID) // 假设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 } m := s.dbm_fix(s.Model) var tt *model.Player m.Scan(&tt) if tt == nil { return } tt.Data = data _, err := m.Save(tt) if err != nil { //todo 待实现兜底保存,现在有可能出错 s.saveToLocalFile(tt, err) panic(err) } } 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"}, }}, }, } }