Bug 修复
- textures metadata:SKIN 仅在 IsSlim 时输出 {"model":"slim"};CAPE 不带 metadata。
原实现误用文件字节数 skin.Size 作为 metadata,违反 Yggdrasil 协议。
- KeyPair 持久化不全:Profile 新增 rsa_public_key/public_key_signature/
public_key_signature_v2/key_expires_at/key_refresh_at 列;GetKeyPair/UpdateKeyPair
读写全部字段。AutoMigrate 自动加列。原实现每次请求都重新生成 RSA-4096。
- GetPlayerCertificates:无效 token 返回 401(先前误用 403 且未判 err)。
- HasJoinedServer:失败返回 204 无 body,符合 Yggdrasil 协议(原用 APIResponse
+ body 违反 204 规范)。
代码质量
- Authenticate 删除冗余 body 读取与回放。
- 证书服务引入具名结构 PlayerCertificate/PlayerKeyPair,替代 map[string]interface{}。
- CreateSession 用户名缺失改用 ErrUsernameRequired(新增)。
性能优化(签名一致性的回归测试已覆盖)
- SignatureService 缓存已解析的根私钥(sync.RWMutex 双重检查),
快路径仅一次 RLock + RSA 签名,免去每次签名 PEM 解析与 Redis 往返。
- GetOrCreateYggdrasilKeyPair 用 MGet 单次往返取三字段,缓存命中后零 Redis。
- NewKeyPair 消息构造用 strconv.AppendInt 避免 string+拼接分配;V2 复用 DER 字节。
- 序列化服务:texturesMap 预分配 cap(2);slim 分支直接构造完整 map。
- 会话服务 GetSession 移除重复的 ValidateServerID 校验。
死代码清理
- 删除 yggdrasil_validator.go(未被调用的 Validator)。
- 删除 signature_service.FormatPublicKey/SignStringWithProfileRSA。
- 删除 pkg/auth 中未被使用的 YggdrasilJWTManager 与 GenerateKeyPair/
EncodePrivateKeyToPEM/RedisClient/YggdrasilPrivateKeyRedisKey。
- 删除 yggdrasil_handler.go 中 25 个未使用常量、passwordRegex、
APIResponse/standardResponse。
- 删除 errors.go 中未被使用的 ErrYggForbiddenOperation/ErrYggIllegalArgument/
ErrInvalidSignature/ErrInvalidTextureType/ErrCertificateGenerate/ErrInvalidPassword。
测试
- 新增 signature_service_test.go(基于 miniredis):8 个测试 + 基准。
核心:SignString_MatchesReference 与重构前参考实现逐字节比对签名输出一致。
-race 检测通过;基准约 7.1ms/op(缓存命中路径)。
附带(与本次任务前已存在的未提交改动)
- handler/captcha_handler:将响应字段 msg 改为 message,与前端约定对齐。
136 lines
4.8 KiB
Go
136 lines
4.8 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
|
||
"carrotskin/internal/model"
|
||
"carrotskin/internal/repository"
|
||
"carrotskin/pkg/redis"
|
||
"carrotskin/pkg/utils"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// yggdrasilServiceComposite 组合服务,保持接口兼容性
|
||
// 将认证、会话、序列化、证书服务组合在一起
|
||
type yggdrasilServiceComposite struct {
|
||
authService *yggdrasilAuthService
|
||
sessionService SessionService
|
||
serializationService SerializationService
|
||
certificateService CertificateService
|
||
profileRepo repository.ProfileRepository
|
||
tokenService TokenService // 使用TokenService接口,不直接依赖TokenRepository
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewYggdrasilServiceComposite 创建组合服务实例
|
||
func NewYggdrasilServiceComposite(
|
||
userRepo repository.UserRepository,
|
||
profileRepo repository.ProfileRepository,
|
||
yggdrasilRepo repository.YggdrasilRepository,
|
||
textureRepo repository.TextureRepository,
|
||
signatureService *SignatureService,
|
||
redisClient *redis.Client,
|
||
logger *zap.Logger,
|
||
tokenService TokenService, // 新增:TokenService接口
|
||
) YggdrasilService {
|
||
// 创建各个专门的服务
|
||
authService := NewYggdrasilAuthService(userRepo, yggdrasilRepo, logger)
|
||
sessionService := NewSessionService(redisClient, logger)
|
||
serializationService := NewSerializationService(
|
||
textureRepo,
|
||
signatureService,
|
||
logger,
|
||
)
|
||
certificateService := NewCertificateService(profileRepo, signatureService, logger)
|
||
|
||
return &yggdrasilServiceComposite{
|
||
authService: authService,
|
||
sessionService: sessionService,
|
||
serializationService: serializationService,
|
||
certificateService: certificateService,
|
||
profileRepo: profileRepo,
|
||
tokenService: tokenService,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// GetUserIDByEmail 获取用户ID(通过邮箱)
|
||
func (s *yggdrasilServiceComposite) GetUserIDByEmail(ctx context.Context, email string) (int64, error) {
|
||
return s.authService.GetUserIDByEmail(ctx, email)
|
||
}
|
||
|
||
// VerifyPassword 验证密码
|
||
func (s *yggdrasilServiceComposite) VerifyPassword(ctx context.Context, password string, userID int64) error {
|
||
return s.authService.VerifyPassword(ctx, password, userID)
|
||
}
|
||
|
||
// ResetYggdrasilPassword 重置Yggdrasil密码
|
||
func (s *yggdrasilServiceComposite) ResetYggdrasilPassword(ctx context.Context, userID int64) (string, error) {
|
||
return s.authService.ResetYggdrasilPassword(ctx, userID)
|
||
}
|
||
|
||
// JoinServer 加入服务器
|
||
func (s *yggdrasilServiceComposite) JoinServer(ctx context.Context, serverID, accessToken, selectedProfile, ip string) error {
|
||
// 通过TokenService验证Token并获取UUID
|
||
uuid, err := s.tokenService.GetUUIDByAccessToken(ctx, accessToken)
|
||
if err != nil {
|
||
s.logger.Error("验证Token失败",
|
||
zap.Error(err),
|
||
zap.String("accessToken", accessToken),
|
||
)
|
||
return fmt.Errorf("验证Token失败: %w", err)
|
||
}
|
||
|
||
// 确保UUID是32位无符号格式(用于向后兼容)
|
||
formattedProfile := utils.FormatUUIDToNoDash(selectedProfile)
|
||
if uuid != formattedProfile {
|
||
return errors.New("selectedProfile与Token不匹配")
|
||
}
|
||
|
||
// 获取Profile以获取用户名
|
||
profile, err := s.profileRepo.FindByUUID(ctx, formattedProfile)
|
||
if err != nil {
|
||
s.logger.Error("获取Profile失败",
|
||
zap.Error(err),
|
||
zap.String("uuid", formattedProfile),
|
||
)
|
||
return fmt.Errorf("获取Profile失败: %w", err)
|
||
}
|
||
|
||
// 使用会话服务创建会话
|
||
return s.sessionService.CreateSession(ctx, serverID, accessToken, profile.Name, formattedProfile, ip)
|
||
}
|
||
|
||
// HasJoinedServer 验证玩家是否已加入服务器
|
||
func (s *yggdrasilServiceComposite) HasJoinedServer(ctx context.Context, serverID, username, ip string) error {
|
||
return s.sessionService.ValidateSession(ctx, serverID, username, ip)
|
||
}
|
||
|
||
// SerializeProfile 序列化档案
|
||
func (s *yggdrasilServiceComposite) SerializeProfile(ctx context.Context, profile model.Profile) map[string]interface{} {
|
||
return s.serializationService.SerializeProfile(ctx, profile)
|
||
}
|
||
|
||
// SerializeProfileWithUnsigned 序列化档案(支持unsigned参数)
|
||
func (s *yggdrasilServiceComposite) SerializeProfileWithUnsigned(ctx context.Context, profile model.Profile, unsigned bool) map[string]interface{} {
|
||
return s.serializationService.SerializeProfileWithUnsigned(ctx, profile, unsigned)
|
||
}
|
||
|
||
// SerializeUser 序列化用户
|
||
func (s *yggdrasilServiceComposite) SerializeUser(ctx context.Context, user *model.User, uuid string) map[string]interface{} {
|
||
return s.serializationService.SerializeUser(ctx, user, uuid)
|
||
}
|
||
|
||
// GeneratePlayerCertificate 生成玩家证书
|
||
func (s *yggdrasilServiceComposite) GeneratePlayerCertificate(ctx context.Context, uuid string) (*PlayerCertificate, error) {
|
||
return s.certificateService.GeneratePlayerCertificate(ctx, uuid)
|
||
}
|
||
|
||
// GetPublicKey 获取公钥
|
||
func (s *yggdrasilServiceComposite) GetPublicKey(ctx context.Context) (string, error) {
|
||
return s.certificateService.GetPublicKey(ctx)
|
||
}
|