package service import ( "carrotskin/internal/model" "carrotskin/internal/repository" "carrotskin/pkg/redis" "carrotskin/pkg/utils" "context" "errors" "fmt" "net" "strings" "time" "go.uber.org/zap" "gorm.io/gorm" ) // SessionKeyPrefix Redis会话键前缀 const SessionKeyPrefix = "Join_" // SessionTTL 会话超时时间 - 增加到15分钟 const SessionTTL = 15 * time.Minute type SessionData struct { AccessToken string `json:"accessToken"` UserName string `json:"userName"` SelectedProfile string `json:"selectedProfile"` IP string `json:"ip"` } // GetUserIDByEmail 根据邮箱返回用户id func GetUserIDByEmail(db *gorm.DB, Identifier string) (int64, error) { user, err := repository.FindUserByEmail(Identifier) if err != nil { return 0, errors.New("用户不存在") } return user.ID, nil } // GetProfileByProfileName 根据用户名返回用户id func GetProfileByProfileName(db *gorm.DB, Identifier string) (*model.Profile, error) { profile, err := repository.FindProfileByName(Identifier) if err != nil { return nil, errors.New("用户角色未创建") } return profile, nil } // VerifyPassword 验证密码是否一致 func VerifyPassword(db *gorm.DB, password string, Id int64) error { passwordStore, err := repository.GetYggdrasilPasswordById(Id) if err != nil { return errors.New("未生成密码") } if passwordStore != password { return errors.New("密码错误") } return nil } func GetProfileByUserId(db *gorm.DB, userId int64) (*model.Profile, error) { profiles, err := repository.FindProfilesByUserID(userId) if err != nil { return nil, errors.New("角色查找失败") } if len(profiles) == 0 { return nil, errors.New("角色查找失败") } return profiles[0], nil } func GetPasswordByUserId(db *gorm.DB, userId int64) (string, error) { passwordStore, err := repository.GetYggdrasilPasswordById(userId) if err != nil { return "", errors.New("yggdrasil密码查找失败") } return passwordStore, nil } // ResetYggdrasilPassword 重置并返回新的Yggdrasil密码 func ResetYggdrasilPassword(db *gorm.DB, userId int64) (string, error) { // 生成新的16位随机密码 newPassword := model.GenerateRandomPassword(16) // 检查Yggdrasil记录是否存在 _, err := repository.GetYggdrasilPasswordById(userId) if err != nil { // 如果不存在,创建新记录 yggdrasil := model.Yggdrasil{ ID: userId, Password: newPassword, } if err := db.Create(&yggdrasil).Error; err != nil { return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err) } return newPassword, nil } // 如果存在,更新密码 if err := repository.ResetYggdrasilPassword(userId, newPassword); err != nil { return "", fmt.Errorf("重置Yggdrasil密码失败: %w", err) } return newPassword, nil } // JoinServer 记录玩家加入服务器的会话信息 func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serverId, accessToken, selectedProfile, ip string) error { // 输入验证 if serverId == "" || accessToken == "" || selectedProfile == "" { return errors.New("参数不能为空") } // 验证serverId格式,防止注入攻击 if len(serverId) > 100 || strings.ContainsAny(serverId, "<>\"'&") { return errors.New("服务器ID格式无效") } // 验证IP格式 if ip != "" { if net.ParseIP(ip) == nil { return errors.New("IP地址格式无效") } } // 获取和验证Token token, err := repository.GetTokenByAccessToken(accessToken) if err != nil { logger.Error( "验证Token失败", zap.Error(err), zap.String("accessToken", accessToken), ) return fmt.Errorf("验证Token失败: %w", err) } // 格式化UUID并验证与Token关联的配置文件 formattedProfile := utils.FormatUUID(selectedProfile) if token.ProfileId != formattedProfile { return errors.New("selectedProfile与Token不匹配") } profile, err := repository.FindProfileByUUID(formattedProfile) if err != nil { logger.Error( "获取Profile失败", zap.Error(err), zap.String("uuid", formattedProfile), ) return fmt.Errorf("获取Profile失败: %w", err) } // 创建会话数据 data := SessionData{ AccessToken: accessToken, UserName: profile.Name, SelectedProfile: formattedProfile, IP: ip, } // 序列化会话数据 marshaledData, err := json.Marshal(data) if err != nil { logger.Error( "[ERROR]序列化会话数据失败", zap.Error(err), ) return fmt.Errorf("序列化会话数据失败: %w", err) } // 存储会话数据到Redis sessionKey := SessionKeyPrefix + serverId ctx := context.Background() if err = redisClient.Set(ctx, sessionKey, marshaledData, SessionTTL); err != nil { logger.Error( "保存会话数据失败", zap.Error(err), zap.String("serverId", serverId), ) return fmt.Errorf("保存会话数据失败: %w", err) } logger.Info( "玩家成功加入服务器", zap.String("username", profile.Name), zap.String("serverId", serverId), ) return nil } // HasJoinedServer 验证玩家是否已经加入了服务器 func HasJoinedServer(logger *zap.Logger, redisClient *redis.Client, serverId, username, ip string) error { if serverId == "" || username == "" { return errors.New("服务器ID和用户名不能为空") } // 设置超时上下文 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() // 从Redis获取会话数据 sessionKey := SessionKeyPrefix + serverId data, err := redisClient.GetBytes(ctx, sessionKey) if err != nil { logger.Error("[ERROR] 获取会话数据失败:", zap.Error(err), zap.Any("serverId:", serverId)) return fmt.Errorf("获取会话数据失败: %w", err) } // 反序列化会话数据 var sessionData SessionData if err = json.Unmarshal(data, &sessionData); err != nil { logger.Error("[ERROR] 解析会话数据失败: ", zap.Error(err)) return fmt.Errorf("解析会话数据失败: %w", err) } // 验证用户名 if sessionData.UserName != username { return errors.New("用户名不匹配") } // 验证IP(如果提供) if ip != "" && sessionData.IP != ip { return errors.New("IP地址不匹配") } return nil }