Files
backend/internal/service/profile_service.go
Wu XiangYu 44f007936e Merge remote-tracking branch 'origin/feature/redis-auth-integration' into dev
# Conflicts:
#	go.mod
#	go.sum
#	internal/container/container.go
#	internal/repository/interfaces.go
#	internal/service/mocks_test.go
#	internal/service/texture_service_test.go
#	internal/service/token_service_test.go
#	pkg/redis/manager.go
2025-12-25 22:45:58 +08:00

256 lines
6.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"
"carrotskin/pkg/database"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/google/uuid"
"go.uber.org/zap"
"gorm.io/gorm"
)
// profileService ProfileService的实现
type profileService struct {
profileRepo repository.ProfileRepository
userRepo repository.UserRepository
cache *database.CacheManager
cacheKeys *database.CacheKeyBuilder
cacheInv *database.CacheInvalidator
logger *zap.Logger
}
// NewProfileService 创建ProfileService实例
func NewProfileService(
profileRepo repository.ProfileRepository,
userRepo repository.UserRepository,
cacheManager *database.CacheManager,
logger *zap.Logger,
) ProfileService {
return &profileService{
profileRepo: profileRepo,
userRepo: userRepo,
cache: cacheManager,
cacheKeys: database.NewCacheKeyBuilder(""),
cacheInv: database.NewCacheInvalidator(cacheManager),
logger: logger,
}
}
func (s *profileService) Create(ctx context.Context, userID int64, name string) (*model.Profile, error) {
// 验证用户存在
user, err := s.userRepo.FindByID(ctx, userID)
if err != nil || user == nil {
return nil, errors.New("用户不存在")
}
if user.Status != 1 {
return nil, errors.New("用户状态异常")
}
// 检查角色名是否已存在
existingName, err := s.profileRepo.FindByName(ctx, name)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("查询角色名失败: %w", err)
}
if existingName != nil {
return nil, errors.New("角色名已被使用")
}
// 生成UUID和RSA密钥
profileUUID := uuid.New().String()
privateKey, err := generateRSAPrivateKeyInternal()
if err != nil {
return nil, fmt.Errorf("生成RSA密钥失败: %w", err)
}
// 创建档案
profile := &model.Profile{
UUID: profileUUID,
UserID: userID,
Name: name,
RSAPrivateKey: privateKey,
}
if err := s.profileRepo.Create(ctx, profile); err != nil {
return nil, fmt.Errorf("创建档案失败: %w", err)
}
// 清除用户的 profile 列表缓存
s.cacheInv.OnCreate(ctx, s.cacheKeys.ProfileList(userID))
return profile, nil
}
func (s *profileService) GetByUUID(ctx context.Context, uuid string) (*model.Profile, error) {
// 尝试从缓存获取
cacheKey := s.cacheKeys.Profile(uuid)
var profile model.Profile
if ok, _ := s.cache.TryGet(ctx, cacheKey, &profile); ok {
return &profile, nil
}
// 缓存未命中,从数据库查询
profile2, err := s.profileRepo.FindByUUID(ctx, uuid)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrProfileNotFound
}
return nil, fmt.Errorf("查询档案失败: %w", err)
}
// 存入缓存(异步)
if profile2 != nil {
s.cache.SetAsync(context.Background(), cacheKey, profile2, s.cache.Policy.ProfileTTL)
}
return profile2, nil
}
func (s *profileService) GetByUserID(ctx context.Context, userID int64) ([]*model.Profile, error) {
// 尝试从缓存获取
cacheKey := s.cacheKeys.ProfileList(userID)
var profiles []*model.Profile
if ok, _ := s.cache.TryGet(ctx, cacheKey, &profiles); ok {
return profiles, nil
}
// 缓存未命中,从数据库查询
profiles, err := s.profileRepo.FindByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("查询档案列表失败: %w", err)
}
// 存入缓存(异步)
if profiles != nil {
s.cache.SetAsync(context.Background(), cacheKey, profiles, s.cache.Policy.ProfileListTTL)
}
return profiles, nil
}
func (s *profileService) Update(ctx context.Context, uuid string, userID int64, name *string, skinID, capeID *int64) (*model.Profile, error) {
// 获取档案并验证权限
profile, err := s.profileRepo.FindByUUID(ctx, uuid)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrProfileNotFound
}
return nil, fmt.Errorf("查询档案失败: %w", err)
}
if profile.UserID != userID {
return nil, ErrProfileNoPermission
}
// 检查角色名是否重复
if name != nil && *name != profile.Name {
existingName, err := s.profileRepo.FindByName(ctx, *name)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("查询角色名失败: %w", err)
}
if existingName != nil {
return nil, errors.New("角色名已被使用")
}
profile.Name = *name
}
// 更新皮肤和披风
if skinID != nil {
profile.SkinID = skinID
}
if capeID != nil {
profile.CapeID = capeID
}
if err := s.profileRepo.Update(ctx, profile); err != nil {
return nil, fmt.Errorf("更新档案失败: %w", err)
}
// 清除该 profile 和用户列表的缓存
s.cacheInv.OnUpdate(ctx,
s.cacheKeys.Profile(uuid),
s.cacheKeys.ProfileList(userID),
)
return s.profileRepo.FindByUUID(ctx, uuid)
}
func (s *profileService) Delete(ctx context.Context, uuid string, userID int64) error {
// 获取档案并验证权限
profile, err := s.profileRepo.FindByUUID(ctx, uuid)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrProfileNotFound
}
return fmt.Errorf("查询档案失败: %w", err)
}
if profile.UserID != userID {
return ErrProfileNoPermission
}
if err := s.profileRepo.Delete(ctx, uuid); err != nil {
return fmt.Errorf("删除档案失败: %w", err)
}
// 清除该 profile 和用户列表的缓存
s.cacheInv.OnDelete(ctx,
s.cacheKeys.Profile(uuid),
s.cacheKeys.ProfileList(userID),
)
return nil
}
func (s *profileService) CheckLimit(ctx context.Context, userID int64, maxProfiles int) error {
count, err := s.profileRepo.CountByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("查询档案数量失败: %w", err)
}
if int(count) >= maxProfiles {
return fmt.Errorf("已达到档案数量上限(%d个", maxProfiles)
}
return nil
}
func (s *profileService) GetByNames(ctx context.Context, names []string) ([]*model.Profile, error) {
profiles, err := s.profileRepo.GetByNames(ctx, names)
if err != nil {
return nil, fmt.Errorf("查找失败: %w", err)
}
return profiles, nil
}
func (s *profileService) GetByProfileName(ctx context.Context, name string) (*model.Profile, error) {
// Profile name 查询通常不会频繁缓存,但为了一致性也添加
profile, err := s.profileRepo.FindByName(ctx, name)
if err != nil {
return nil, errors.New("用户角色未创建")
}
return profile, nil
}
// generateRSAPrivateKeyInternal 生成RSA-2048私钥PEM格式
func generateRSAPrivateKeyInternal() (string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", err
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
})
return string(privateKeyPEM), nil
}