Files
backend/internal/service/yggdrasil_serialization_service.go
lan 29f0bad2bc
All checks were successful
Build / build (push) Successful in 2m17s
Build / build-docker (push) Successful in 57s
feat(yggdrasil): implement standard error responses and UUID format improvements
- Add YggdrasilErrorResponse struct and standard error codes for protocol compliance
- Change UUID storage from varchar(36) to varchar(32) for unsigned format
- Add utility functions: GenerateUUID, FormatUUIDToNoDash, RandomHex
- Support unsigned query parameter in GetProfileByUUID endpoint
- Improve refresh token response with available profiles list
- Fix key pair retrieval to use correct database column (rsa_private_key)
- Update UUID validator to accept both 32-char and 36-char formats
- Add SignStringWithProfileRSA method for profile-specific signing
- Fix profile assignment validation in refresh token flow
2026-02-23 13:26:53 +08:00

174 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"carrotskin/internal/model"
"carrotskin/internal/repository"
"context"
"encoding/base64"
"time"
"go.uber.org/zap"
)
// SerializationService 序列化服务接口
type SerializationService interface {
// SerializeProfile 序列化档案为Yggdrasil格式
SerializeProfile(ctx context.Context, profile model.Profile) map[string]interface{}
// SerializeProfileWithUnsigned 序列化档案为Yggdrasil格式支持unsigned参数
SerializeProfileWithUnsigned(ctx context.Context, profile model.Profile, unsigned bool) map[string]interface{}
// SerializeUser 序列化用户为Yggdrasil格式
SerializeUser(ctx context.Context, user *model.User, uuid string) map[string]interface{}
}
// Property Yggdrasil属性
type Property struct {
Name string `json:"name"`
Value string `json:"value"`
Signature string `json:"signature,omitempty"`
}
// yggdrasilSerializationService 序列化服务实现
type yggdrasilSerializationService struct {
textureRepo repository.TextureRepository
signatureService *SignatureService
logger *zap.Logger
}
// NewSerializationService 创建序列化服务实例
func NewSerializationService(
textureRepo repository.TextureRepository,
signatureService *SignatureService,
logger *zap.Logger,
) SerializationService {
return &yggdrasilSerializationService{
textureRepo: textureRepo,
signatureService: signatureService,
logger: logger,
}
}
// SerializeProfile 序列化档案为Yggdrasil格式默认返回签名
func (s *yggdrasilSerializationService) SerializeProfile(ctx context.Context, profile model.Profile) map[string]interface{} {
return s.SerializeProfileWithUnsigned(ctx, profile, false)
}
// SerializeProfileWithUnsigned 序列化档案为Yggdrasil格式支持unsigned参数
func (s *yggdrasilSerializationService) SerializeProfileWithUnsigned(ctx context.Context, profile model.Profile, unsigned bool) 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(ctx, *profile.SkinID)
if err != nil {
s.logger.Error("获取皮肤失败",
zap.Error(err),
zap.Int64("skinID", *profile.SkinID),
)
} else if skin != nil {
texturesMap["SKIN"] = map[string]interface{}{
"url": skin.URL,
"metadata": skin.Size,
}
}
}
// 处理披风
if profile.CapeID != nil {
cape, err := s.textureRepo.FindByID(ctx, *profile.CapeID)
if err != nil {
s.logger.Error("获取披风失败",
zap.Error(err),
zap.Int64("capeID", *profile.CapeID),
)
} else if cape != nil {
texturesMap["CAPE"] = map[string]interface{}{
"url": cape.URL,
"metadata": cape.Size,
}
}
}
// 将textures编码为base64
bytes, err := json.Marshal(textures)
if err != nil {
s.logger.Error("序列化textures失败",
zap.Error(err),
zap.String("profileUUID", profile.UUID),
)
return nil
}
textureData := base64.StdEncoding.EncodeToString(bytes)
// 只有在 unsigned=false 时才签名
var signature string
if !unsigned {
signature, err = s.signatureService.SignStringWithSHA1withRSA(textureData)
if err != nil {
s.logger.Error("签名textures失败",
zap.Error(err),
zap.String("profileUUID", profile.UUID),
)
return nil
}
}
// 构建属性
property := Property{
Name: "textures",
Value: textureData,
}
// 只有在 unsigned=false 时才添加签名
if !unsigned {
property.Signature = signature
}
// 构建结果
data := map[string]interface{}{
"id": profile.UUID,
"name": profile.Name,
"properties": []Property{property},
}
return data
}
// SerializeUser 序列化用户为Yggdrasil格式
func (s *yggdrasilSerializationService) SerializeUser(ctx context.Context, user *model.User, uuid string) map[string]interface{} {
if user == nil {
s.logger.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("解析用户Properties失败使用空值",
zap.Error(err),
zap.Int64("userID", user.ID),
)
data["properties"] = nil
} else {
data["properties"] = propertiesValue
}
}
return data
}