feat: Enhance dependency injection and service integration

- Updated main.go to initialize email service and include it in the dependency injection container.
- Refactored handlers to utilize context in service method calls, improving consistency and error handling.
- Introduced new service options for upload, security, and captcha services, enhancing modularity and testability.
- Removed unused repository implementations to streamline the codebase.

This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
This commit is contained in:
lan
2025-12-02 22:52:33 +08:00
parent 792e96b238
commit 034e02e93a
54 changed files with 2305 additions and 2708 deletions

View File

@@ -7,6 +7,7 @@ import (
"carrotskin/pkg/redis"
"carrotskin/pkg/utils"
"context"
"encoding/base64"
"errors"
"fmt"
"net"
@@ -31,27 +32,57 @@ type SessionData struct {
IP string `json:"ip"`
}
// GetUserIDByEmail 根据邮箱返回用户id
func GetUserIDByEmail(db *gorm.DB, Identifier string) (int64, error) {
user, err := repository.FindUserByEmail(Identifier)
// 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
}
// 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)
func (s *yggdrasilService) VerifyPassword(ctx context.Context, password string, userID int64) error {
passwordStore, err := s.yggdrasilRepo.GetPasswordByID(userID)
if err != nil {
return errors.New("未生成密码")
}
@@ -62,27 +93,7 @@ func VerifyPassword(db *gorm.DB, password string, Id int64) error {
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) {
func (s *yggdrasilService) ResetYggdrasilPassword(ctx context.Context, userID int64) (string, error) {
// 生成新的16位随机密码明文返回给用户
plainPassword := model.GenerateRandomPassword(16)
@@ -93,21 +104,21 @@ func ResetYggdrasilPassword(db *gorm.DB, userId int64) (string, error) {
}
// 检查Yggdrasil记录是否存在
_, err = repository.GetYggdrasilPasswordById(userId)
_, err = s.yggdrasilRepo.GetPasswordByID(userID)
if err != nil {
// 如果不存在,创建新记录
yggdrasil := model.Yggdrasil{
ID: userId,
ID: userID,
Password: hashedPassword,
}
if err := db.Create(&yggdrasil).Error; err != nil {
if err := s.db.Create(&yggdrasil).Error; err != nil {
return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err)
}
return plainPassword, nil
}
// 如果存在,更新密码(存储加密后的密码)
if err := repository.ResetYggdrasilPassword(userId, hashedPassword); err != nil {
if err := s.yggdrasilRepo.ResetPassword(userID, hashedPassword); err != nil {
return "", fmt.Errorf("重置Yggdrasil密码失败: %w", err)
}
@@ -115,15 +126,14 @@ func ResetYggdrasilPassword(db *gorm.DB, userId int64) (string, error) {
return plainPassword, nil
}
// JoinServer 记录玩家加入服务器的会话信息
func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serverId, accessToken, selectedProfile, ip string) error {
func (s *yggdrasilService) JoinServer(ctx context.Context, serverID, accessToken, selectedProfile, ip string) error {
// 输入验证
if serverId == "" || accessToken == "" || selectedProfile == "" {
if serverID == "" || accessToken == "" || selectedProfile == "" {
return errors.New("参数不能为空")
}
// 验证serverId格式防止注入攻击
if len(serverId) > 100 || strings.ContainsAny(serverId, "<>\"'&") {
if len(serverID) > 100 || strings.ContainsAny(serverID, "<>\"'&") {
return errors.New("服务器ID格式无效")
}
@@ -135,9 +145,9 @@ func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serv
}
// 获取和验证Token
token, err := repository.GetTokenByAccessToken(accessToken)
token, err := s.tokenRepo.FindByAccessToken(accessToken)
if err != nil {
logger.Error(
s.logger.Error(
"验证Token失败",
zap.Error(err),
zap.String("accessToken", accessToken),
@@ -151,9 +161,9 @@ func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serv
return errors.New("selectedProfile与Token不匹配")
}
profile, err := repository.FindProfileByUUID(formattedProfile)
profile, err := s.profileRepo.FindByUUID(formattedProfile)
if err != nil {
logger.Error(
s.logger.Error(
"获取Profile失败",
zap.Error(err),
zap.String("uuid", formattedProfile),
@@ -172,55 +182,49 @@ func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serv
// 序列化会话数据
marshaledData, err := json.Marshal(data)
if err != nil {
logger.Error(
s.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(
// 存储会话数据到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),
zap.String("serverId", serverID),
)
return fmt.Errorf("保存会话数据失败: %w", err)
}
logger.Info(
s.logger.Info(
"玩家成功加入服务器",
zap.String("username", profile.Name),
zap.String("serverId", serverId),
zap.String("serverId", serverID),
)
return nil
}
// HasJoinedServer 验证玩家是否已经加入了服务器
func HasJoinedServer(logger *zap.Logger, redisClient *redis.Client, serverId, username, ip string) error {
if serverId == "" || username == "" {
func (s *yggdrasilService) HasJoinedServer(ctx context.Context, 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)
// 从Redis获取会话数据 - 使用传入的 ctx
sessionKey := SessionKeyPrefix + serverID
data, err := s.redis.GetBytes(ctx, sessionKey)
if err != nil {
logger.Error("[ERROR] 获取会话数据失败:", zap.Error(err), zap.Any("serverId:", serverId))
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 {
logger.Error("[ERROR] 解析会话数据失败: ", zap.Error(err))
s.logger.Error("[ERROR] 解析会话数据失败: ", zap.Error(err))
return fmt.Errorf("解析会话数据失败: %w", err)
}
@@ -236,3 +240,163 @@ func HasJoinedServer(logger *zap.Logger, redisClient *redis.Client, serverId, us
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"`
}