Files
bl/modules/player/service/info.go

315 lines
8.5 KiB
Go
Raw Normal View History

package service
import (
"blazing/common/data/share"
"blazing/common/utils"
"blazing/cool"
"blazing/modules/config/service"
"blazing/modules/player/model"
"context"
"encoding/hex"
2026-02-20 23:33:24 +08:00
"encoding/json"
2026-02-07 22:14:32 +08:00
"fmt"
2026-02-20 23:33:24 +08:00
"os"
"path/filepath"
2025-08-28 21:57:30 +00:00
"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"
)
2026-02-11 01:05:47 +08:00
// 是否注册,如果注册过,那么就会产生用户player信息
// 实现注册,id+昵称+颜色
func (s *InfoService) Reg(nick string, color uint32) *model.PlayerInfo {
2026-02-11 01:05:47 +08:00
var tt *model.Player
2026-02-13 22:57:05 +08:00
s.dbm_fix(s.Model).Scan(&tt)
2026-02-11 01:05:47 +08:00
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
}
2026-02-11 01:05:47 +08:00
}
2026-02-07 20:09:02 +08:00
func (s *InfoService) Person(userid uint32) (out *model.Player) {
2026-02-14 04:27:57 +08:00
cool.DBM(s.Model).Where("player_id", userid).Scan(&out)
return
}
func (s *InfoService) GetLogin() *model.PlayerInfo {
2026-02-07 20:09:02 +08:00
var tt *model.Player
2026-02-13 22:57:05 +08:00
s.dbm_fix(s.Model).Scan(&tt)
if tt == nil {
return nil
}
tt.Data.AllPetNumber = uint32(NewPetService(s.userid).PetCount(0))
if tt.Data.BackupPetList == nil {
tt.Data.BackupPetList = make([]model.PetInfo, 0)
``` feat(game): 实现扭蛋系统批量物品添加功能并优化地图逻辑 - 新增ItemAddBatch方法用于批量添加物品,支持普通道具和特殊道具的分别处理 - 优化扭蛋游戏玩法中的物品添加逻辑,使用新的批量接口提升性能 - 在扭蛋机器人命令中实现完整的物品检查和批量添加流程 refactor(map): 重构地图控制器代码结构并添加注释 - 为EnterMap、LeaveMap、GetMapPlayerList等方法添加中文注释 - 统一地图相关的命名规范,如enter map替换进入地图 - 调整地图玩家列表中BOSS广播命令ID,2021和2022进行对调 refactor(boss): 重构定时BOSS代码并优化注释 - 将原有的中文注释改为英文注释,统一代码风格 - 简化TimeBossRule结构体定义和相关配置 - 优化定时任务注册逻辑,去除冗余的注释和变量 refactor(space): 清理地图空间服务代码注释 - 移除多余的中文注释和说明文字 - 统一代码格式,移除不必要的空行和注释 - 保持原有的天气系统和地图刷怪逻辑不变 fix(role): 修复系统角色权限查询逻辑 - 修改BaseSysRoleService中的查询条件,正确处理管理员权限 - 使用Extend方法替代Where进行复杂的权限判断逻辑 - 确保超级管理员可以访问所有角色,其他用户受限于权限范围 refactor(dict): 添加字典服务批量查询方法 - 新增GetMaxMap方法用于批量获取物品最大持有上限 - 优化数据库查询,减少多次单个查询的开销 - 支持一次请求多个物品的最大数量限制 fix(player): 修复玩家信息保存异常处理 - 将panic方式改为错误日志记录,避免程序崩溃 - 优化Save方法的重试逻辑,统一错误处理方式 - 在本地文件回退时记录详细错误信息 feat(robot): 扩展扭蛋机器人功能 - 添加用户验证和角色创建检查 - 实现批量扭蛋的完整逻辑,支持1-10次抽取 - 集成物品数量检查和批量添加功能 ```
2026-04-02 02:33:05 +08:00
}
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
}
2026-02-16 09:44:05 +08:00
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()
2026-02-08 02:11:46 +08:00
uuidBytes := uuidV7[:] // UUID 类型底层是 [16]byte直接切片获取
// 移除UUID中的连字符便于后续处理
2026-02-08 02:11:46 +08:00
// 3. 计算 CRC32-IEEE 校验码最通用的CRC32标准
sessionID := hex.EncodeToString(uuidBytes)
2026-02-07 19:40:51 +08:00
2026-02-07 22:15:23 +08:00
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 || useid1 == 0 {
// 请求进入时已经离线,视为成功
return nil
}
cl, ok := cool.GetClientOnly(useid1)
if !ok || cl == nil {
// 目标服务器不在线,清理僵尸在线标记并视为成功
_ = share.ShareManager.DeleteUserOnline(id)
return nil
}
resultCh := make(chan error, 1)
go func() {
resultCh <- cl.KickPerson(id) // 实现指定服务器踢人
}()
select {
case callErr := <-resultCh:
if callErr == nil {
return nil
}
// 调用失败后兜底:若已离线/切服/目标服不在线则视为成功
useid2, err2 := share.ShareManager.GetUserOnline(id)
if err2 != nil || useid2 == 0 || useid2 != useid1 {
return nil
}
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
_ = share.ShareManager.DeleteUserOnline(id)
return nil
}
return callErr
case <-time.After(3 * time.Second):
// 防止异常场景下无限等待;超时不按成功处理
useid2, err2 := share.ShareManager.GetUserOnline(id)
if err2 != nil || useid2 == 0 || useid2 != useid1 {
return nil
}
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
_ = share.ShareManager.DeleteUserOnline(id)
return nil
}
return fmt.Errorf("kick timeout, user still online: uid=%d server=%d", id, useid2)
}
}
2026-02-20 23:33:24 +08:00
// saveToLocalFile 兜底保存将数据写入本地lose文件夹
func (s *InfoService) saveToLocalFile(player *model.PlayerInfo, err error) {
2026-02-20 23:33:24 +08:00
// 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"`
2026-02-20 23:33:24 +08:00
}
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字段根据实际调整
2026-02-20 23:33:24 +08:00
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
}
}
}
2025-11-16 20:30:17 +00:00
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"},
2025-11-16 20:30:17 +00:00
}},
},
}
}