package service import ( "carrotskin/internal/model" "carrotskin/internal/repository" "carrotskin/pkg/auth" "carrotskin/pkg/redis" "carrotskin/pkg/utils" "context" "encoding/base64" "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"` } // yggdrasilService YggdrasilService的实现 type yggdrasilService struct { db *gorm.DB userRepo repository.UserRepository profileRepo repository.ProfileRepository textureRepo repository.TextureRepository tokenRepo repository.TokenRepository yggdrasilRepo repository.YggdrasilRepository signatureService *signatureService redis *redis.Client logger *zap.Logger } // NewYggdrasilService 创建YggdrasilService实例 func NewYggdrasilService( db *gorm.DB, userRepo repository.UserRepository, profileRepo repository.ProfileRepository, textureRepo repository.TextureRepository, tokenRepo repository.TokenRepository, yggdrasilRepo repository.YggdrasilRepository, signatureService *signatureService, redisClient *redis.Client, logger *zap.Logger, ) YggdrasilService { return &yggdrasilService{ db: db, userRepo: userRepo, profileRepo: profileRepo, textureRepo: textureRepo, tokenRepo: tokenRepo, yggdrasilRepo: yggdrasilRepo, signatureService: signatureService, redis: redisClient, logger: logger, } } func (s *yggdrasilService) GetUserIDByEmail(ctx context.Context, email string) (int64, error) { user, err := s.userRepo.FindByEmail(email) if err != nil { return 0, errors.New("用户不存在") } if user == nil { return 0, errors.New("用户不存在") } return user.ID, nil } func (s *yggdrasilService) VerifyPassword(ctx context.Context, password string, userID int64) error { passwordStore, err := s.yggdrasilRepo.GetPasswordByID(userID) if err != nil { return errors.New("未生成密码") } // 使用 bcrypt 验证密码 if !auth.CheckPassword(passwordStore, password) { return errors.New("密码错误") } return nil } func (s *yggdrasilService) ResetYggdrasilPassword(ctx context.Context, userID int64) (string, error) { // 生成新的16位随机密码(明文,返回给用户) plainPassword := model.GenerateRandomPassword(16) // 使用 bcrypt 加密密码后存储 hashedPassword, err := auth.HashPassword(plainPassword) if err != nil { return "", fmt.Errorf("密码加密失败: %w", err) } // 检查Yggdrasil记录是否存在 _, err = s.yggdrasilRepo.GetPasswordByID(userID) if err != nil { // 如果不存在,创建新记录 yggdrasil := model.Yggdrasil{ ID: userID, Password: hashedPassword, } if err := s.db.Create(&yggdrasil).Error; err != nil { return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err) } return plainPassword, nil } // 如果存在,更新密码(存储加密后的密码) if err := s.yggdrasilRepo.ResetPassword(userID, hashedPassword); err != nil { return "", fmt.Errorf("重置Yggdrasil密码失败: %w", err) } // 返回明文密码给用户 return plainPassword, nil } func (s *yggdrasilService) JoinServer(ctx context.Context, 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 := s.tokenRepo.FindByAccessToken(accessToken) if err != nil { s.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 := s.profileRepo.FindByUUID(formattedProfile) if err != nil { s.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 { s.logger.Error( "[ERROR]序列化会话数据失败", zap.Error(err), ) return fmt.Errorf("序列化会话数据失败: %w", err) } // 存储会话数据到Redis - 使用传入的 ctx sessionKey := SessionKeyPrefix + serverID if err = s.redis.Set(ctx, sessionKey, marshaledData, SessionTTL); err != nil { s.logger.Error( "保存会话数据失败", zap.Error(err), zap.String("serverId", serverID), ) return fmt.Errorf("保存会话数据失败: %w", err) } s.logger.Info( "玩家成功加入服务器", zap.String("username", profile.Name), zap.String("serverId", serverID), ) return nil } func (s *yggdrasilService) HasJoinedServer(ctx context.Context, serverID, username, ip string) error { if serverID == "" || username == "" { return errors.New("服务器ID和用户名不能为空") } // 从Redis获取会话数据 - 使用传入的 ctx sessionKey := SessionKeyPrefix + serverID data, err := s.redis.GetBytes(ctx, sessionKey) if err != nil { s.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 { s.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 } func (s *yggdrasilService) SerializeProfile(ctx context.Context, profile model.Profile) map[string]interface{} { // 创建基本材质数据 texturesMap := make(map[string]interface{}) textures := map[string]interface{}{ "timestamp": time.Now().UnixMilli(), "profileId": profile.UUID, "profileName": profile.Name, "textures": texturesMap, } // 处理皮肤 if profile.SkinID != nil { skin, err := s.textureRepo.FindByID(*profile.SkinID) if err != nil { s.logger.Error("[ERROR] 获取皮肤失败:", zap.Error(err), zap.Any("SkinID:", *profile.SkinID)) } else { texturesMap["SKIN"] = map[string]interface{}{ "url": skin.URL, "metadata": skin.Size, } } } // 处理披风 if profile.CapeID != nil { cape, err := s.textureRepo.FindByID(*profile.CapeID) if err != nil { s.logger.Error("[ERROR] 获取披风失败:", zap.Error(err), zap.Any("capeID:", *profile.CapeID)) } else { texturesMap["CAPE"] = map[string]interface{}{ "url": cape.URL, "metadata": cape.Size, } } } // 将textures编码为base64 bytes, err := json.Marshal(textures) if err != nil { s.logger.Error("[ERROR] 序列化textures失败: ", zap.Error(err)) return nil } textureData := base64.StdEncoding.EncodeToString(bytes) signature, err := s.signatureService.SignStringWithSHA1withRSA(textureData) if err != nil { s.logger.Error("[ERROR] 签名textures失败: ", zap.Error(err)) return nil } // 构建结果 data := map[string]interface{}{ "id": profile.UUID, "name": profile.Name, "properties": []Property{ { Name: "textures", Value: textureData, Signature: signature, }, }, } return data } func (s *yggdrasilService) SerializeUser(ctx context.Context, user *model.User, uuid string) map[string]interface{} { if user == nil { s.logger.Error("[ERROR] 尝试序列化空用户") return nil } data := map[string]interface{}{ "id": uuid, } // 正确处理 *datatypes.JSON 指针类型 // 如果 Properties 为 nil,则设置为 nil;否则解引用并解析为 JSON 值 if user.Properties == nil { data["properties"] = nil } else { // datatypes.JSON 是 []byte 类型,需要解析为实际的 JSON 值 var propertiesValue interface{} if err := json.Unmarshal(*user.Properties, &propertiesValue); err != nil { s.logger.Warn("[WARN] 解析用户Properties失败,使用空值", zap.Error(err)) data["properties"] = nil } else { data["properties"] = propertiesValue } } return data } func (s *yggdrasilService) GeneratePlayerCertificate(ctx context.Context, uuid string) (map[string]interface{}, error) { if uuid == "" { return nil, fmt.Errorf("UUID不能为空") } s.logger.Info("[INFO] 开始生成玩家证书,用户UUID: %s", zap.String("uuid", uuid)) keyPair, err := s.profileRepo.GetKeyPair(uuid) if err != nil { s.logger.Info("[INFO] 获取用户密钥对失败,将创建新密钥对: %v", zap.Error(err), zap.String("uuid", uuid), ) keyPair = nil } // 如果没有找到密钥对或密钥对已过期,创建一个新的 now := time.Now().UTC() if keyPair == nil || keyPair.Refresh.Before(now) || keyPair.PrivateKey == "" || keyPair.PublicKey == "" { s.logger.Info("[INFO] 为用户创建新的密钥对: %s", zap.String("uuid", uuid)) keyPair, err = s.signatureService.NewKeyPair() if err != nil { s.logger.Error("[ERROR] 生成玩家证书密钥对失败: %v", zap.Error(err), zap.String("uuid", uuid), ) return nil, fmt.Errorf("生成玩家证书密钥对失败: %w", err) } // 保存密钥对到数据库 err = s.profileRepo.UpdateKeyPair(uuid, keyPair) if err != nil { s.logger.Warn("[WARN] 更新用户密钥对失败: %v", zap.Error(err), zap.String("uuid", uuid), ) // 继续执行,即使保存失败 } } // 计算expiresAt的毫秒时间戳 expiresAtMillis := keyPair.Expiration.UnixMilli() // 返回玩家证书 certificate := map[string]interface{}{ "keyPair": map[string]interface{}{ "privateKey": keyPair.PrivateKey, "publicKey": keyPair.PublicKey, }, "publicKeySignature": keyPair.PublicKeySignature, "publicKeySignatureV2": keyPair.PublicKeySignatureV2, "expiresAt": expiresAtMillis, "refreshedAfter": keyPair.Refresh.UnixMilli(), } s.logger.Info("[INFO] 成功生成玩家证书", zap.String("uuid", uuid)) return certificate, nil } func (s *yggdrasilService) GetPublicKey(ctx context.Context) (string, error) { return s.signatureService.GetPublicKeyFromRedis() } type Property struct { Name string `json:"name"` Value string `json:"value"` Signature string `json:"signature,omitempty"` }