refactor: 移除全局单例、修复分层违规、统一错误与响应
This commit is contained in:
@@ -72,13 +72,15 @@ type RedisData struct {
|
||||
|
||||
// captchaService CaptchaService的实现
|
||||
type captchaService struct {
|
||||
cfg *config.Config
|
||||
redis *redis.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCaptchaService 创建CaptchaService实例
|
||||
func NewCaptchaService(redisClient *redis.Client, logger *zap.Logger) CaptchaService {
|
||||
func NewCaptchaService(cfg *config.Config, redisClient *redis.Client, logger *zap.Logger) CaptchaService {
|
||||
return &captchaService{
|
||||
cfg: cfg,
|
||||
redis: redisClient,
|
||||
logger: logger,
|
||||
}
|
||||
@@ -157,8 +159,7 @@ func (s *captchaService) Generate(ctx context.Context) (masterImg, tileImg, capt
|
||||
// Verify 验证验证码
|
||||
func (s *captchaService) Verify(ctx context.Context, dx int, captchaID string) (bool, error) {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
if s.cfg.IsTestEnvironment() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -197,8 +198,7 @@ func (s *captchaService) Verify(ctx context.Context, dx int, captchaID string) (
|
||||
// CheckVerified 检查验证码是否已验证(仅检查captcha_id)
|
||||
func (s *captchaService) CheckVerified(ctx context.Context, captchaID string) (bool, error) {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
if s.cfg.IsTestEnvironment() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -216,8 +216,7 @@ func (s *captchaService) CheckVerified(ctx context.Context, captchaID string) (b
|
||||
// ConsumeVerified 消耗已验证的验证码(注册成功后调用)
|
||||
func (s *captchaService) ConsumeVerified(ctx context.Context, captchaID string) error {
|
||||
// 测试环境下直接返回成功
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
if s.cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// 通用错误
|
||||
var (
|
||||
ErrProfileNotFound = errors.New("档案不存在")
|
||||
ErrProfileNoPermission = errors.New("无权操作此档案")
|
||||
ErrTextureNotFound = errors.New("材质不存在")
|
||||
ErrTextureNoPermission = errors.New("无权操作此材质")
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
)
|
||||
// 通用辅助函数。
|
||||
//
|
||||
// 注意:错误定义已统一到 internal/errors 包。
|
||||
// 原先在此文件重复定义的 ErrProfileNotFound/ErrProfileNoPermission/
|
||||
// ErrTextureNotFound/ErrTextureNoPermission/ErrUserNotFound 已删除,
|
||||
// 请使用 internal/errors(别名 apperrors)中的对应定义。
|
||||
|
||||
// NormalizePagination 规范化分页参数
|
||||
func NormalizePagination(page, pageSize int) (int, int) {
|
||||
@@ -27,11 +20,3 @@ func NormalizePagination(page, pageSize int) (int, int) {
|
||||
}
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
// WrapError 包装错误,添加上下文信息
|
||||
func WrapError(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s: %w", message, err)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -30,21 +29,3 @@ func TestNormalizePagination_Basic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWrapError 覆盖 WrapError 的 nil 与非 nil 分支
|
||||
func TestWrapError(t *testing.T) {
|
||||
if err := WrapError(nil, "msg"); err != nil {
|
||||
t.Fatalf("WrapError(nil, ...) 应返回 nil, got=%v", err)
|
||||
}
|
||||
|
||||
orig := errors.New("orig")
|
||||
wrapped := WrapError(orig, "context")
|
||||
if wrapped == nil {
|
||||
t.Fatalf("WrapError 应返回非 nil 错误")
|
||||
}
|
||||
if wrapped.Error() == orig.Error() {
|
||||
t.Fatalf("WrapError 应添加上下文信息, got=%v", wrapped)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ type UserService interface {
|
||||
// 配置获取
|
||||
GetMaxProfilesPerUser() int
|
||||
GetMaxTexturesPerUser() int
|
||||
|
||||
// 管理员操作
|
||||
ListUsers(ctx context.Context, page, pageSize int) ([]*model.User, int64, error)
|
||||
SetRole(ctx context.Context, userID int64, role string) error
|
||||
SetStatus(ctx context.Context, userID int64, status int16) error
|
||||
}
|
||||
|
||||
// ProfileService 档案服务接口
|
||||
@@ -73,6 +78,11 @@ type TextureService interface {
|
||||
|
||||
// 限制检查
|
||||
CheckUploadLimit(ctx context.Context, uploaderID int64, maxTextures int) error
|
||||
|
||||
// 管理员操作
|
||||
ListForAdmin(ctx context.Context, page, pageSize int) ([]*model.Texture, int64, error)
|
||||
AdminDelete(ctx context.Context, textureID int64) error
|
||||
IncrementDownload(ctx context.Context, textureID int64) error
|
||||
}
|
||||
|
||||
// TokenService 令牌服务接口
|
||||
|
||||
@@ -130,6 +130,24 @@ func (m *MockUserRepository) FindByIDs(ctx context.Context, ids []int64) ([]*mod
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// List 分页查询用户列表(管理员,mock 实现)
|
||||
func (m *MockUserRepository) List(ctx context.Context, page, pageSize int) ([]*model.User, int64, error) {
|
||||
var all []*model.User
|
||||
for _, u := range m.users {
|
||||
all = append(all, u)
|
||||
}
|
||||
total := int64(len(all))
|
||||
offset := (page - 1) * pageSize
|
||||
if offset >= len(all) {
|
||||
return []*model.User{}, total, nil
|
||||
}
|
||||
end := offset + pageSize
|
||||
if end > len(all) {
|
||||
end = len(all)
|
||||
}
|
||||
return all[offset:end], total, nil
|
||||
}
|
||||
|
||||
// MockProfileRepository 模拟ProfileRepository
|
||||
type MockProfileRepository struct {
|
||||
profiles map[string]*model.Profile
|
||||
@@ -474,6 +492,32 @@ func (m *MockTextureRepository) BatchDelete(ctx context.Context, ids []int64) (i
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
// ListForAdmin 管理员列表(mock 实现)
|
||||
func (m *MockTextureRepository) ListForAdmin(ctx context.Context, page, pageSize int) ([]*model.Texture, int64, error) {
|
||||
var all []*model.Texture
|
||||
for _, t := range m.textures {
|
||||
all = append(all, t)
|
||||
}
|
||||
total := int64(len(all))
|
||||
offset := (page - 1) * pageSize
|
||||
if offset >= len(all) {
|
||||
return []*model.Texture{}, total, nil
|
||||
}
|
||||
end := offset + pageSize
|
||||
if end > len(all) {
|
||||
end = len(all)
|
||||
}
|
||||
return all[offset:end], total, nil
|
||||
}
|
||||
|
||||
// FindByIDForAdmin 管理员查询(mock 实现,无权限过滤)
|
||||
func (m *MockTextureRepository) FindByIDForAdmin(ctx context.Context, id int64) (*model.Texture, error) {
|
||||
if t, ok := m.textures[id]; ok {
|
||||
return t, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Service Mocks
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
apperrors "carrotskin/internal/errors"
|
||||
"carrotskin/internal/model"
|
||||
"carrotskin/internal/repository"
|
||||
"carrotskin/pkg/database"
|
||||
@@ -100,7 +101,7 @@ func (s *profileService) GetByUUID(ctx context.Context, uuid string) (*model.Pro
|
||||
profile2, err := s.profileRepo.FindByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrProfileNotFound
|
||||
return nil, apperrors.ErrProfileNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("查询档案失败: %w", err)
|
||||
}
|
||||
@@ -140,13 +141,13 @@ func (s *profileService) Update(ctx context.Context, uuid string, userID int64,
|
||||
profile, err := s.profileRepo.FindByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrProfileNotFound
|
||||
return nil, apperrors.ErrProfileNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("查询档案失败: %w", err)
|
||||
}
|
||||
|
||||
if profile.UserID != userID {
|
||||
return nil, ErrProfileNoPermission
|
||||
return nil, apperrors.ErrProfileNoPermission
|
||||
}
|
||||
|
||||
// 检查角色名是否重复
|
||||
@@ -187,13 +188,13 @@ func (s *profileService) Delete(ctx context.Context, uuid string, userID int64)
|
||||
profile, err := s.profileRepo.FindByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrProfileNotFound
|
||||
return apperrors.ErrProfileNotFound
|
||||
}
|
||||
return fmt.Errorf("查询档案失败: %w", err)
|
||||
}
|
||||
|
||||
if profile.UserID != userID {
|
||||
return ErrProfileNoPermission
|
||||
return apperrors.ErrProfileNoPermission
|
||||
}
|
||||
|
||||
if err := s.profileRepo.Delete(ctx, uuid); err != nil {
|
||||
|
||||
@@ -53,7 +53,7 @@ func NewSignatureService(
|
||||
}
|
||||
|
||||
// NewKeyPair 生成新的RSA密钥对
|
||||
func (s *SignatureService) NewKeyPair() (*model.KeyPair, error) {
|
||||
func (s *SignatureService) NewKeyPair(ctx context.Context) (*model.KeyPair, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, KeySize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成RSA密钥对失败: %w", err)
|
||||
@@ -85,7 +85,7 @@ func (s *SignatureService) NewKeyPair() (*model.KeyPair, error) {
|
||||
refresh := now.AddDate(0, 0, RefreshDays)
|
||||
|
||||
// 获取Yggdrasil根密钥并签名公钥
|
||||
yggPublicKey, yggPrivateKey, err := s.GetOrCreateYggdrasilKeyPair()
|
||||
yggPublicKey, yggPrivateKey, err := s.GetOrCreateYggdrasilKeyPair(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取Yggdrasil根密钥失败: %w", err)
|
||||
}
|
||||
@@ -132,9 +132,7 @@ func (s *SignatureService) NewKeyPair() (*model.KeyPair, error) {
|
||||
}
|
||||
|
||||
// GetOrCreateYggdrasilKeyPair 获取或创建Yggdrasil根密钥对
|
||||
func (s *SignatureService) GetOrCreateYggdrasilKeyPair() (string, *rsa.PrivateKey, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
func (s *SignatureService) GetOrCreateYggdrasilKeyPair(ctx context.Context) (string, *rsa.PrivateKey, error) {
|
||||
// 尝试从Redis获取密钥
|
||||
publicKeyPEM, err := s.redis.Get(ctx, PublicKeyRedisKey)
|
||||
if err == nil && publicKeyPEM != "" {
|
||||
@@ -201,15 +199,14 @@ func (s *SignatureService) GetOrCreateYggdrasilKeyPair() (string, *rsa.PrivateKe
|
||||
}
|
||||
|
||||
// GetPublicKeyFromRedis 从Redis获取公钥
|
||||
func (s *SignatureService) GetPublicKeyFromRedis() (string, error) {
|
||||
ctx := context.Background()
|
||||
func (s *SignatureService) GetPublicKeyFromRedis(ctx context.Context) (string, error) {
|
||||
publicKey, err := s.redis.Get(ctx, PublicKeyRedisKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("从Redis获取公钥失败: %w", err)
|
||||
}
|
||||
if publicKey == "" {
|
||||
// 如果Redis中没有,创建新的密钥对
|
||||
publicKey, _, err = s.GetOrCreateYggdrasilKeyPair()
|
||||
publicKey, _, err = s.GetOrCreateYggdrasilKeyPair(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建新密钥对失败: %w", err)
|
||||
}
|
||||
@@ -218,14 +215,13 @@ func (s *SignatureService) GetPublicKeyFromRedis() (string, error) {
|
||||
}
|
||||
|
||||
// SignStringWithSHA1withRSA 使用SHA1withRSA签名字符串
|
||||
func (s *SignatureService) SignStringWithSHA1withRSA(data string) (string, error) {
|
||||
ctx := context.Background()
|
||||
func (s *SignatureService) SignStringWithSHA1withRSA(ctx context.Context, data string) (string, error) {
|
||||
|
||||
// 从Redis获取私钥
|
||||
privateKeyPEM, err := s.redis.Get(ctx, PrivateKeyRedisKey)
|
||||
if err != nil || privateKeyPEM == "" {
|
||||
// 如果没有私钥,创建新的密钥对
|
||||
_, privateKey, err := s.GetOrCreateYggdrasilKeyPair()
|
||||
_, privateKey, err := s.GetOrCreateYggdrasilKeyPair(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取私钥失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
apperrors "carrotskin/internal/errors"
|
||||
"carrotskin/internal/model"
|
||||
"carrotskin/internal/repository"
|
||||
"carrotskin/pkg/database"
|
||||
@@ -13,8 +14,10 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// textureService TextureService的实现
|
||||
@@ -26,6 +29,7 @@ type textureService struct {
|
||||
cacheKeys *database.CacheKeyBuilder
|
||||
cacheInv *database.CacheInvalidator
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewTextureService 创建TextureService实例
|
||||
@@ -35,6 +39,7 @@ func NewTextureService(
|
||||
storageClient *storage.StorageClient,
|
||||
cacheManager *database.CacheManager,
|
||||
logger *zap.Logger,
|
||||
db *gorm.DB,
|
||||
) TextureService {
|
||||
return &textureService{
|
||||
textureRepo: textureRepo,
|
||||
@@ -44,6 +49,7 @@ func NewTextureService(
|
||||
cacheKeys: database.NewCacheKeyBuilder(""),
|
||||
cacheInv: database.NewCacheInvalidator(cacheManager),
|
||||
logger: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +68,7 @@ func (s *textureService) GetByID(ctx context.Context, id int64) (*model.Texture,
|
||||
return nil, err
|
||||
}
|
||||
if texture2 == nil {
|
||||
return nil, ErrTextureNotFound
|
||||
return nil, apperrors.ErrTextureNotFound
|
||||
}
|
||||
if texture2.Status == -1 {
|
||||
return nil, errors.New("材质已删除")
|
||||
@@ -80,7 +86,7 @@ func (s *textureService) GetByID(ctx context.Context, id int64) (*model.Texture,
|
||||
return nil, err
|
||||
}
|
||||
if texture2 == nil {
|
||||
return nil, ErrTextureNotFound
|
||||
return nil, apperrors.ErrTextureNotFound
|
||||
}
|
||||
if texture2.Status == -1 {
|
||||
return nil, errors.New("材质已删除")
|
||||
@@ -109,7 +115,7 @@ func (s *textureService) GetByHash(ctx context.Context, hash string) (*model.Tex
|
||||
return nil, err
|
||||
}
|
||||
if texture2 == nil {
|
||||
return nil, ErrTextureNotFound
|
||||
return nil, apperrors.ErrTextureNotFound
|
||||
}
|
||||
if texture2.Status == -1 {
|
||||
return nil, errors.New("材质已删除")
|
||||
@@ -162,10 +168,10 @@ func (s *textureService) Update(ctx context.Context, textureID, uploaderID int64
|
||||
return nil, err
|
||||
}
|
||||
if texture == nil {
|
||||
return nil, ErrTextureNotFound
|
||||
return nil, apperrors.ErrTextureNotFound
|
||||
}
|
||||
if texture.UploaderID != uploaderID {
|
||||
return nil, ErrTextureNoPermission
|
||||
return nil, apperrors.ErrTextureNoPermission
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
@@ -200,10 +206,10 @@ func (s *textureService) Delete(ctx context.Context, textureID, uploaderID int64
|
||||
return err
|
||||
}
|
||||
if texture == nil {
|
||||
return ErrTextureNotFound
|
||||
return apperrors.ErrTextureNotFound
|
||||
}
|
||||
if texture.UploaderID != uploaderID {
|
||||
return ErrTextureNoPermission
|
||||
return apperrors.ErrTextureNoPermission
|
||||
}
|
||||
|
||||
err = s.textureRepo.Delete(ctx, textureID)
|
||||
@@ -225,33 +231,41 @@ func (s *textureService) ToggleFavorite(ctx context.Context, userID, textureID i
|
||||
return false, err
|
||||
}
|
||||
if texture == nil {
|
||||
return false, ErrTextureNotFound
|
||||
return false, apperrors.ErrTextureNotFound
|
||||
}
|
||||
|
||||
isFavorited, err := s.textureRepo.IsFavorited(ctx, userID, textureID)
|
||||
// 在事务中执行"切换收藏 + 增减计数"两步,保证数据一致性
|
||||
var favorited bool
|
||||
err = database.TransactionWithTimeout(ctx, s.db, 5*time.Second, func(tx *gorm.DB) error {
|
||||
isFav, err := s.textureRepo.IsFavorited(ctx, userID, textureID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isFav {
|
||||
// 已收藏 -> 取消收藏
|
||||
if err := s.textureRepo.RemoveFavorite(ctx, userID, textureID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.textureRepo.DecrementFavoriteCount(ctx, textureID); err != nil {
|
||||
return err
|
||||
}
|
||||
favorited = false
|
||||
return nil
|
||||
}
|
||||
// 未收藏 -> 添加收藏
|
||||
if err := s.textureRepo.AddFavorite(ctx, userID, textureID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.textureRepo.IncrementFavoriteCount(ctx, textureID); err != nil {
|
||||
return err
|
||||
}
|
||||
favorited = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if isFavorited {
|
||||
// 已收藏 -> 取消收藏
|
||||
if err := s.textureRepo.RemoveFavorite(ctx, userID, textureID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := s.textureRepo.DecrementFavoriteCount(ctx, textureID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 未收藏 -> 添加收藏
|
||||
if err := s.textureRepo.AddFavorite(ctx, userID, textureID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := s.textureRepo.IncrementFavoriteCount(ctx, textureID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
return favorited, nil
|
||||
}
|
||||
|
||||
func (s *textureService) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) {
|
||||
@@ -277,7 +291,7 @@ func (s *textureService) UploadTexture(ctx context.Context, uploaderID int64, na
|
||||
// 验证用户存在
|
||||
user, err := s.userRepo.FindByID(ctx, uploaderID)
|
||||
if err != nil || user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
return nil, apperrors.ErrUserNotFound
|
||||
}
|
||||
|
||||
// 验证文件大小和扩展名
|
||||
@@ -403,3 +417,28 @@ func parseTextureTypeInternal(textureType string) (model.TextureType, error) {
|
||||
return "", errors.New("无效的材质类型")
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 管理员操作 ====================
|
||||
|
||||
// ListForAdmin 分页查询材质列表(管理员,含 Uploader 预加载)
|
||||
func (s *textureService) ListForAdmin(ctx context.Context, page, pageSize int) ([]*model.Texture, int64, error) {
|
||||
page, pageSize = NormalizePagination(page, pageSize)
|
||||
return s.textureRepo.ListForAdmin(ctx, page, pageSize)
|
||||
}
|
||||
|
||||
// AdminDelete 管理员删除材质(无权限校验)
|
||||
func (s *textureService) AdminDelete(ctx context.Context, textureID int64) error {
|
||||
texture, err := s.textureRepo.FindByIDForAdmin(ctx, textureID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if texture == nil {
|
||||
return apperrors.ErrTextureNotFound
|
||||
}
|
||||
return s.textureRepo.Delete(ctx, textureID)
|
||||
}
|
||||
|
||||
// IncrementDownload 增加下载计数(CustomSkinAPI 等场景使用)
|
||||
func (s *textureService) IncrementDownload(ctx context.Context, textureID int64) error {
|
||||
return s.textureRepo.IncrementDownloadCount(ctx, textureID)
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ func TestTextureServiceImpl_Create(t *testing.T) {
|
||||
_ = userRepo.Create(context.Background(), testUser)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger)
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger, nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -617,7 +617,7 @@ func TestTextureServiceImpl_GetByID(t *testing.T) {
|
||||
_ = textureRepo.Create(context.Background(), testTexture)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger)
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger, nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -675,7 +675,7 @@ func TestTextureServiceImpl_GetByUserID_And_Search(t *testing.T) {
|
||||
}
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger)
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -714,7 +714,7 @@ func TestTextureServiceImpl_Update_And_Delete(t *testing.T) {
|
||||
_ = textureRepo.Create(context.Background(), texture)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger)
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -764,7 +764,7 @@ func TestTextureServiceImpl_FavoritesAndLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger)
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -807,7 +807,7 @@ func TestTextureServiceImpl_ToggleFavorite(t *testing.T) {
|
||||
_ = textureRepo.Create(context.Background(), testTexture)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger)
|
||||
textureService := NewTextureService(textureRepo, userRepo, nil, cacheManager, logger, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -173,8 +172,9 @@ func (s *tokenServiceRedis) Create(ctx context.Context, userID int64, UUID strin
|
||||
}
|
||||
|
||||
if err := s.tokenStore.Store(ctx, accessToken, metadata, ttl); err != nil {
|
||||
s.logger.Warn("存储Token到Redis失败", zap.Error(err))
|
||||
// 不返回错误,因为JWT本身已经生成成功
|
||||
// Redis 存储失败必须返回错误:否则 Validate 拿不到元数据,Token 立即失效
|
||||
s.logger.Error("存储Token到Redis失败", zap.Error(err))
|
||||
return nil, nil, "", "", fmt.Errorf("存储Token失败: %w", err)
|
||||
}
|
||||
|
||||
return selectedProfileID, availableProfiles, accessToken, clientToken, nil
|
||||
@@ -473,7 +473,7 @@ func (s *tokenServiceRedis) validateProfileByUserID(ctx context.Context, userID
|
||||
|
||||
profile, err := s.profileRepo.FindByUUID(ctx, UUID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
if repository.IsNotFound(err) {
|
||||
return false, errors.New("配置文件不存在")
|
||||
}
|
||||
return false, fmt.Errorf("验证配置文件失败: %w", err)
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
// userService UserService的实现
|
||||
type userService struct {
|
||||
cfg *config.Config
|
||||
userRepo repository.UserRepository
|
||||
jwtService *auth.JWTService
|
||||
redis *redis.Client
|
||||
@@ -38,6 +39,7 @@ type userService struct {
|
||||
|
||||
// NewUserService 创建UserService实例
|
||||
func NewUserService(
|
||||
cfg *config.Config,
|
||||
userRepo repository.UserRepository,
|
||||
jwtService *auth.JWTService,
|
||||
redisClient *redis.Client,
|
||||
@@ -48,6 +50,7 @@ func NewUserService(
|
||||
// CacheKeyBuilder 使用空前缀,因为 CacheManager 已经处理了前缀
|
||||
// 这样缓存键的格式为: CacheManager前缀 + CacheKeyBuilder生成的键
|
||||
return &userService{
|
||||
cfg: cfg,
|
||||
userRepo: userRepo,
|
||||
jwtService: jwtService,
|
||||
redis: redisClient,
|
||||
@@ -81,7 +84,7 @@ func (s *userService) Register(ctx context.Context, username, password, email, a
|
||||
// 加密密码
|
||||
hashedPassword, err := auth.HashPassword(password)
|
||||
if err != nil {
|
||||
return nil, "", errors.New("密码加密失败")
|
||||
return nil, "", fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
|
||||
// 确定头像URL
|
||||
@@ -112,7 +115,7 @@ func (s *userService) Register(ctx context.Context, username, password, email, a
|
||||
// 生成JWT Token
|
||||
token, err := s.jwtService.GenerateToken(user.ID, user.Username, user.Role)
|
||||
if err != nil {
|
||||
return nil, "", errors.New("生成Token失败")
|
||||
return nil, "", fmt.Errorf("生成Token失败: %w", err)
|
||||
}
|
||||
|
||||
return user, token, nil
|
||||
@@ -167,15 +170,20 @@ func (s *userService) Login(ctx context.Context, usernameOrEmail, password, ipAd
|
||||
// 生成JWT Token
|
||||
token, err := s.jwtService.GenerateToken(user.ID, user.Username, user.Role)
|
||||
if err != nil {
|
||||
return nil, "", errors.New("生成Token失败")
|
||||
return nil, "", fmt.Errorf("生成Token失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新最后登录时间
|
||||
// 更新最后登录时间(非关键路径,错误降级为 Warn)
|
||||
now := time.Now()
|
||||
user.LastLoginAt = &now
|
||||
_ = s.userRepo.UpdateFields(ctx, user.ID, map[string]interface{}{
|
||||
if err := s.userRepo.UpdateFields(ctx, user.ID, map[string]interface{}{
|
||||
"last_login_at": now,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logger.Warn("更新最后登录时间失败",
|
||||
zap.Int64("user_id", user.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
|
||||
// 记录成功登录日志
|
||||
s.logSuccessLogin(ctx, user.ID, ipAddress, userAgent)
|
||||
@@ -239,7 +247,10 @@ func (s *userService) UpdateAvatar(ctx context.Context, userID int64, avatarURL
|
||||
|
||||
func (s *userService) ChangePassword(ctx context.Context, userID int64, oldPassword, newPassword string) error {
|
||||
user, err := s.userRepo.FindByID(ctx, userID)
|
||||
if err != nil || user == nil {
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询用户失败: %w", err)
|
||||
}
|
||||
if user == nil {
|
||||
return errors.New("用户不存在")
|
||||
}
|
||||
|
||||
@@ -249,7 +260,7 @@ func (s *userService) ChangePassword(ctx context.Context, userID int64, oldPassw
|
||||
|
||||
hashedPassword, err := auth.HashPassword(newPassword)
|
||||
if err != nil {
|
||||
return errors.New("密码加密失败")
|
||||
return fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
|
||||
err = s.userRepo.UpdateFields(ctx, userID, map[string]interface{}{
|
||||
@@ -267,13 +278,16 @@ func (s *userService) ChangePassword(ctx context.Context, userID int64, oldPassw
|
||||
|
||||
func (s *userService) ResetPassword(ctx context.Context, email, newPassword string) error {
|
||||
user, err := s.userRepo.FindByEmail(ctx, email)
|
||||
if err != nil || user == nil {
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询用户失败: %w", err)
|
||||
}
|
||||
if user == nil {
|
||||
return errors.New("用户不存在")
|
||||
}
|
||||
|
||||
hashedPassword, err := auth.HashPassword(newPassword)
|
||||
if err != nil {
|
||||
return errors.New("密码加密失败")
|
||||
return fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
|
||||
err = s.userRepo.UpdateFields(ctx, user.ID, map[string]interface{}{
|
||||
@@ -350,14 +364,13 @@ func (s *userService) ValidateAvatarURL(ctx context.Context, avatarURL string) e
|
||||
return errors.New("URL缺少主机名")
|
||||
}
|
||||
|
||||
// 从配置获取允许的域名列表
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
allowedDomains := []string{"localhost", "127.0.0.1"}
|
||||
return s.checkDomainAllowed(host, allowedDomains)
|
||||
// 从注入的配置获取允许的域名列表
|
||||
allowedDomains := s.cfg.Security.AllowedDomains
|
||||
if len(allowedDomains) == 0 {
|
||||
allowedDomains = []string{"localhost", "127.0.0.1"}
|
||||
}
|
||||
|
||||
return s.checkDomainAllowed(host, cfg.Security.AllowedDomains)
|
||||
return s.checkDomainAllowed(host, allowedDomains)
|
||||
}
|
||||
|
||||
func (s *userService) UploadAvatar(ctx context.Context, userID int64, fileData []byte, fileName string) (string, error) {
|
||||
@@ -422,29 +435,23 @@ func (s *userService) UploadAvatar(ctx context.Context, userID int64, fileData [
|
||||
}
|
||||
|
||||
func (s *userService) GetMaxProfilesPerUser() int {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil || cfg.Site.MaxProfilesPerUser <= 0 {
|
||||
if s.cfg.Site.MaxProfilesPerUser <= 0 {
|
||||
return 5
|
||||
}
|
||||
return cfg.Site.MaxProfilesPerUser
|
||||
return s.cfg.Site.MaxProfilesPerUser
|
||||
}
|
||||
|
||||
func (s *userService) GetMaxTexturesPerUser() int {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil || cfg.Site.MaxTexturesPerUser <= 0 {
|
||||
if s.cfg.Site.MaxTexturesPerUser <= 0 {
|
||||
return 50
|
||||
}
|
||||
return cfg.Site.MaxTexturesPerUser
|
||||
return s.cfg.Site.MaxTexturesPerUser
|
||||
}
|
||||
|
||||
// 私有辅助方法
|
||||
|
||||
func (s *userService) getDefaultAvatar() string {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return cfg.Site.DefaultAvatar
|
||||
return s.cfg.Site.DefaultAvatar
|
||||
}
|
||||
|
||||
func (s *userService) checkDomainAllowed(host string, allowedDomains []string) error {
|
||||
@@ -505,3 +512,21 @@ func (s *userService) logFailedLogin(ctx context.Context, userID int64, ipAddres
|
||||
}
|
||||
_ = s.userRepo.CreateLoginLog(ctx, log)
|
||||
}
|
||||
|
||||
// ==================== 管理员操作 ====================
|
||||
|
||||
// ListUsers 分页查询用户列表(管理员)
|
||||
func (s *userService) ListUsers(ctx context.Context, page, pageSize int) ([]*model.User, int64, error) {
|
||||
page, pageSize = NormalizePagination(page, pageSize)
|
||||
return s.userRepo.List(ctx, page, pageSize)
|
||||
}
|
||||
|
||||
// SetRole 设置用户角色(管理员)
|
||||
func (s *userService) SetRole(ctx context.Context, userID int64, role string) error {
|
||||
return s.userRepo.UpdateFields(ctx, userID, map[string]interface{}{"role": role})
|
||||
}
|
||||
|
||||
// SetStatus 设置用户状态(管理员)
|
||||
func (s *userService) SetStatus(ctx context.Context, userID int64, status int16) error {
|
||||
return s.userRepo.UpdateFields(ctx, userID, map[string]interface{}{"status": status})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"carrotskin/internal/model"
|
||||
"carrotskin/pkg/auth"
|
||||
"carrotskin/pkg/config"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
@@ -14,11 +15,12 @@ func TestUserServiceImpl_Register(t *testing.T) {
|
||||
userRepo := NewMockUserRepository()
|
||||
jwtService := auth.NewJWTService("secret", 1)
|
||||
logger := zap.NewNop()
|
||||
cfg := &config.Config{}
|
||||
|
||||
// 初始化Service
|
||||
// 注意:redisClient 和 storageClient 传入 nil,因为 Register 方法中没有使用它们
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(cfg, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -128,7 +130,8 @@ func TestUserServiceImpl_Login(t *testing.T) {
|
||||
_ = userRepo.Create(context.Background(), testUser)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
cfg := &config.Config{}
|
||||
userService := NewUserService(cfg, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -208,7 +211,7 @@ func TestUserServiceImpl_BasicGettersAndUpdates(t *testing.T) {
|
||||
_ = userRepo.Create(context.Background(), user)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(&config.Config{}, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -255,7 +258,7 @@ func TestUserServiceImpl_ChangePassword(t *testing.T) {
|
||||
_ = userRepo.Create(context.Background(), user)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(&config.Config{}, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -289,7 +292,7 @@ func TestUserServiceImpl_ResetPassword(t *testing.T) {
|
||||
_ = userRepo.Create(context.Background(), user)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(&config.Config{}, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -316,7 +319,7 @@ func TestUserServiceImpl_ChangeEmail(t *testing.T) {
|
||||
_ = userRepo.Create(context.Background(), user2)
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(&config.Config{}, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -338,7 +341,7 @@ func TestUserServiceImpl_ValidateAvatarURL(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(&config.Config{}, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -374,7 +377,7 @@ func TestUserServiceImpl_MaxLimits(t *testing.T) {
|
||||
|
||||
// 未配置时走默认值
|
||||
cacheManager := NewMockCacheManager()
|
||||
userService := NewUserService(userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
userService := NewUserService(&config.Config{}, userRepo, jwtService, nil, cacheManager, nil, logger)
|
||||
if got := userService.GetMaxProfilesPerUser(); got != 5 {
|
||||
t.Fatalf("GetMaxProfilesPerUser 默认值错误, got=%d", got)
|
||||
}
|
||||
|
||||
@@ -26,16 +26,19 @@ const (
|
||||
|
||||
// verificationService VerificationService的实现
|
||||
type verificationService struct {
|
||||
cfg *config.Config
|
||||
redis *redis.Client
|
||||
emailService *email.Service
|
||||
}
|
||||
|
||||
// NewVerificationService 创建VerificationService实例
|
||||
func NewVerificationService(
|
||||
cfg *config.Config,
|
||||
redisClient *redis.Client,
|
||||
emailService *email.Service,
|
||||
) VerificationService {
|
||||
return &verificationService{
|
||||
cfg: cfg,
|
||||
redis: redisClient,
|
||||
emailService: emailService,
|
||||
}
|
||||
@@ -44,8 +47,7 @@ func NewVerificationService(
|
||||
// SendCode 发送验证码
|
||||
func (s *verificationService) SendCode(ctx context.Context, email, codeType string) error {
|
||||
// 测试环境下直接跳过,不存储也不发送
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
if s.cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -89,8 +91,7 @@ func (s *verificationService) SendCode(ctx context.Context, email, codeType stri
|
||||
// VerifyCode 验证验证码
|
||||
func (s *verificationService) VerifyCode(ctx context.Context, email, code, codeType string) error {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
if s.cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -166,8 +167,5 @@ func (s *verificationService) sendEmail(to, code, codeType string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteVerificationCode 删除验证码(工具函数,保持向后兼容)
|
||||
func DeleteVerificationCode(ctx context.Context, redisClient *redis.Client, email, codeType string) error {
|
||||
codeKey := fmt.Sprintf("verification:code:%s:%s", codeType, email)
|
||||
return redisClient.Del(ctx, codeKey)
|
||||
}
|
||||
// DeleteVerificationCode 已移除:重构后无调用方。如需删除验证码,请使用
|
||||
// VerificationService 实现内的 redis.Del 路径。
|
||||
|
||||
@@ -9,14 +9,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// yggdrasilAuthService Yggdrasil认证服务实现
|
||||
// 负责认证和密码管理
|
||||
type yggdrasilAuthService struct {
|
||||
db *gorm.DB
|
||||
userRepo repository.UserRepository
|
||||
yggdrasilRepo repository.YggdrasilRepository
|
||||
logger *zap.Logger
|
||||
@@ -24,13 +21,11 @@ type yggdrasilAuthService struct {
|
||||
|
||||
// NewYggdrasilAuthService 创建Yggdrasil认证服务实例(内部使用)
|
||||
func NewYggdrasilAuthService(
|
||||
db *gorm.DB,
|
||||
userRepo repository.UserRepository,
|
||||
yggdrasilRepo repository.YggdrasilRepository,
|
||||
logger *zap.Logger,
|
||||
) *yggdrasilAuthService {
|
||||
return &yggdrasilAuthService{
|
||||
db: db,
|
||||
userRepo: userRepo,
|
||||
yggdrasilRepo: yggdrasilRepo,
|
||||
logger: logger,
|
||||
@@ -78,7 +73,7 @@ func (s *yggdrasilAuthService) ResetYggdrasilPassword(ctx context.Context, userI
|
||||
ID: userID,
|
||||
Password: hashedPassword,
|
||||
}
|
||||
if err := s.db.Create(&yggdrasil).Error; err != nil {
|
||||
if err := s.yggdrasilRepo.Create(ctx, &yggdrasil); err != nil {
|
||||
return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err)
|
||||
}
|
||||
return plainPassword, nil
|
||||
|
||||
@@ -64,7 +64,7 @@ func (s *yggdrasilCertificateService) GeneratePlayerCertificate(ctx context.Cont
|
||||
s.logger.Info("为用户创建新的密钥对",
|
||||
zap.String("uuid", uuid),
|
||||
)
|
||||
keyPair, err = s.signatureService.NewKeyPair()
|
||||
keyPair, err = s.signatureService.NewKeyPair(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("生成玩家证书密钥对失败",
|
||||
zap.Error(err),
|
||||
@@ -107,6 +107,6 @@ func (s *yggdrasilCertificateService) GeneratePlayerCertificate(ctx context.Cont
|
||||
|
||||
// GetPublicKey 获取公钥
|
||||
func (s *yggdrasilCertificateService) GetPublicKey(ctx context.Context) (string, error) {
|
||||
return s.signatureService.GetPublicKeyFromRedis()
|
||||
return s.signatureService.GetPublicKeyFromRedis(ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ func (s *yggdrasilSerializationService) SerializeProfileWithUnsigned(ctx context
|
||||
// 只有在 unsigned=false 时才签名
|
||||
var signature string
|
||||
if !unsigned {
|
||||
signature, err = s.signatureService.SignStringWithSHA1withRSA(textureData)
|
||||
signature, err = s.signatureService.SignStringWithSHA1withRSA(ctx, textureData)
|
||||
if err != nil {
|
||||
s.logger.Error("签名textures失败",
|
||||
zap.Error(err),
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// yggdrasilServiceComposite 组合服务,保持接口兼容性
|
||||
@@ -28,20 +26,20 @@ type yggdrasilServiceComposite struct {
|
||||
|
||||
// NewYggdrasilServiceComposite 创建组合服务实例
|
||||
func NewYggdrasilServiceComposite(
|
||||
db *gorm.DB,
|
||||
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(db, userRepo, yggdrasilRepo, logger)
|
||||
authService := NewYggdrasilAuthService(userRepo, yggdrasilRepo, logger)
|
||||
sessionService := NewSessionService(redisClient, logger)
|
||||
serializationService := NewSerializationService(
|
||||
repository.NewTextureRepository(db),
|
||||
textureRepo,
|
||||
signatureService,
|
||||
logger,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user