refactor: Implement dependency injection for handlers and services

- Refactored AuthHandler, UserHandler, TextureHandler, ProfileHandler, CaptchaHandler, and YggdrasilHandler to use dependency injection.
- Removed direct instantiation of services and repositories within handlers, replacing them with constructor injection.
- Updated the container to initialize service instances and provide them to handlers.
- Enhanced code structure for better testability and adherence to Go best practices.
This commit is contained in:
lafay
2025-12-02 19:43:39 +08:00
parent 188a05caa7
commit 801f1b1397
33 changed files with 3628 additions and 4129 deletions

View File

@@ -6,35 +6,55 @@ import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
"strconv"
"time"
"gorm.io/gorm"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
)
// 常量定义
// tokenServiceImpl TokenService的实现
type tokenServiceImpl struct {
tokenRepo repository.TokenRepository
profileRepo repository.ProfileRepository
logger *zap.Logger
}
// NewTokenService 创建TokenService实例
func NewTokenService(
tokenRepo repository.TokenRepository,
profileRepo repository.ProfileRepository,
logger *zap.Logger,
) TokenService {
return &tokenServiceImpl{
tokenRepo: tokenRepo,
profileRepo: profileRepo,
logger: logger,
}
}
const (
ExtendedTimeout = 10 * time.Second
TokensMaxCount = 10 // 用户最多保留的token数量
tokenExtendedTimeout = 10 * time.Second
tokensMaxCount = 10
)
// NewToken 创建新令牌
func NewToken(db *gorm.DB, logger *zap.Logger, userId int64, UUID string, clientToken string) (*model.Profile, []*model.Profile, string, string, error) {
func (s *tokenServiceImpl) Create(userID int64, UUID string, clientToken string) (*model.Profile, []*model.Profile, string, string, error) {
var (
selectedProfileID *model.Profile
availableProfiles []*model.Profile
)
// 设置超时上下文
_, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
// 验证用户存在
_, err := repository.FindProfileByUUID(UUID)
if err != nil {
return selectedProfileID, availableProfiles, "", "", fmt.Errorf("获取用户信息失败: %w", err)
if UUID != "" {
_, err := s.profileRepo.FindByUUID(UUID)
if err != nil {
return selectedProfileID, availableProfiles, "", "", fmt.Errorf("获取用户信息失败: %w", err)
}
}
// 生成令牌
@@ -46,13 +66,13 @@ func NewToken(db *gorm.DB, logger *zap.Logger, userId int64, UUID string, client
token := model.Token{
AccessToken: accessToken,
ClientToken: clientToken,
UserID: userId,
UserID: userID,
Usable: true,
IssueDate: time.Now(),
}
// 获取用户配置文件
profiles, err := repository.FindProfilesByUserID(userId)
profiles, err := s.profileRepo.FindByUserID(userID)
if err != nil {
return selectedProfileID, availableProfiles, "", "", fmt.Errorf("获取用户配置文件失败: %w", err)
}
@@ -64,65 +84,24 @@ func NewToken(db *gorm.DB, logger *zap.Logger, userId int64, UUID string, client
}
availableProfiles = profiles
// 插入令牌到tokens集合
_, insertCancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer insertCancel()
err = repository.CreateToken(&token)
// 插入令牌
err = s.tokenRepo.Create(&token)
if err != nil {
return selectedProfileID, availableProfiles, "", "", fmt.Errorf("创建Token失败: %w", err)
}
// 清理多余的令牌
go CheckAndCleanupExcessTokens(db, logger, userId)
go s.checkAndCleanupExcessTokens(userID)
return selectedProfileID, availableProfiles, accessToken, clientToken, nil
}
// CheckAndCleanupExcessTokens 检查并清理用户多余的令牌只保留最新的10个
func CheckAndCleanupExcessTokens(db *gorm.DB, logger *zap.Logger, userId int64) {
if userId == 0 {
return
}
// 获取用户所有令牌,按发行日期降序排序
tokens, err := repository.GetTokensByUserId(userId)
if err != nil {
logger.Error("[ERROR] 获取用户Token失败: ", zap.Error(err), zap.String("userId", strconv.FormatInt(userId, 10)))
return
}
// 如果令牌数量不超过上限,无需清理
if len(tokens) <= TokensMaxCount {
return
}
// 获取需要删除的令牌ID列表
tokensToDelete := make([]string, 0, len(tokens)-TokensMaxCount)
for i := TokensMaxCount; i < len(tokens); i++ {
tokensToDelete = append(tokensToDelete, tokens[i].AccessToken)
}
// 执行批量删除,传入上下文和待删除的令牌列表(作为切片参数)
DeletedCount, err := repository.BatchDeleteTokens(tokensToDelete)
if err != nil {
logger.Error("[ERROR] 清理用户多余Token失败: ", zap.Error(err), zap.String("userId", strconv.FormatInt(userId, 10)))
return
}
if DeletedCount > 0 {
logger.Info("[INFO] 成功清理用户多余Token", zap.Any("userId:", userId), zap.Any("count:", DeletedCount))
}
}
// ValidToken 验证令牌有效性
func ValidToken(db *gorm.DB, accessToken string, clientToken string) bool {
func (s *tokenServiceImpl) Validate(accessToken, clientToken string) bool {
if accessToken == "" {
return false
}
// 使用投影只获取需要的字段
var token *model.Token
token, err := repository.FindTokenByID(accessToken)
token, err := s.tokenRepo.FindByAccessToken(accessToken)
if err != nil {
return false
}
@@ -131,47 +110,35 @@ func ValidToken(db *gorm.DB, accessToken string, clientToken string) bool {
return false
}
// 如果客户端令牌为空,只验证访问令牌
if clientToken == "" {
return true
}
// 否则验证客户端令牌是否匹配
return token.ClientToken == clientToken
}
func GetUUIDByAccessToken(db *gorm.DB, accessToken string) (string, error) {
return repository.GetUUIDByAccessToken(accessToken)
}
func GetUserIDByAccessToken(db *gorm.DB, accessToken string) (int64, error) {
return repository.GetUserIDByAccessToken(accessToken)
}
// RefreshToken 刷新令牌
func RefreshToken(db *gorm.DB, logger *zap.Logger, accessToken, clientToken string, selectedProfileID string) (string, string, error) {
func (s *tokenServiceImpl) Refresh(accessToken, clientToken, selectedProfileID string) (string, string, error) {
if accessToken == "" {
return "", "", errors.New("accessToken不能为空")
}
// 查找旧令牌
oldToken, err := repository.GetTokenByAccessToken(accessToken)
oldToken, err := s.tokenRepo.FindByAccessToken(accessToken)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return "", "", errors.New("accessToken无效")
}
logger.Error("[ERROR] 查询Token失败: ", zap.Error(err), zap.Any("accessToken:", accessToken))
s.logger.Error("查询Token失败", zap.Error(err), zap.String("accessToken", accessToken))
return "", "", fmt.Errorf("查询令牌失败: %w", err)
}
// 验证profile
if selectedProfileID != "" {
valid, validErr := ValidateProfileByUserID(db, oldToken.UserID, selectedProfileID)
valid, validErr := s.validateProfileByUserID(oldToken.UserID, selectedProfileID)
if validErr != nil {
logger.Error(
"验证Profile失败",
s.logger.Error("验证Profile失败",
zap.Error(err),
zap.Any("userId", oldToken.UserID),
zap.Int64("userId", oldToken.UserID),
zap.String("profileId", selectedProfileID),
)
return "", "", fmt.Errorf("验证角色失败: %w", err)
@@ -192,86 +159,119 @@ func RefreshToken(db *gorm.DB, logger *zap.Logger, accessToken, clientToken stri
return "", "", errors.New("原令牌已绑定角色,无法选择新角色")
}
} else {
selectedProfileID = oldToken.ProfileId // 如果未指定,则保持原角色
selectedProfileID = oldToken.ProfileId
}
// 生成新令牌
newAccessToken := uuid.New().String()
newToken := model.Token{
AccessToken: newAccessToken,
ClientToken: oldToken.ClientToken, // 新令牌的 clientToken 与原令牌相同
ClientToken: oldToken.ClientToken,
UserID: oldToken.UserID,
Usable: true,
ProfileId: selectedProfileID, // 绑定到指定角色或保持原角色
ProfileId: selectedProfileID,
IssueDate: time.Now(),
}
// 使用双重写入模式替代事务,先插入新令牌,再删除旧令牌
err = repository.CreateToken(&newToken)
// 先插入新令牌,再删除旧令牌
err = s.tokenRepo.Create(&newToken)
if err != nil {
logger.Error(
"创建新Token失败",
zap.Error(err),
zap.String("accessToken", accessToken),
)
s.logger.Error("创建新Token失败", zap.Error(err), zap.String("accessToken", accessToken))
return "", "", fmt.Errorf("创建新Token失败: %w", err)
}
err = repository.DeleteTokenByAccessToken(accessToken)
err = s.tokenRepo.DeleteByAccessToken(accessToken)
if err != nil {
// 删除旧令牌失败,记录日志但不阻止操作,因为新令牌已成功创建
logger.Warn(
"删除旧Token失败但新Token已创建",
s.logger.Warn("删除旧Token失败但新Token已创建",
zap.Error(err),
zap.String("oldToken", oldToken.AccessToken),
zap.String("newToken", newAccessToken),
)
}
logger.Info(
"成功刷新Token",
zap.Any("userId", oldToken.UserID),
zap.String("accessToken", newAccessToken),
)
s.logger.Info("成功刷新Token", zap.Int64("userId", oldToken.UserID), zap.String("accessToken", newAccessToken))
return newAccessToken, oldToken.ClientToken, nil
}
// InvalidToken 使令牌失效
func InvalidToken(db *gorm.DB, logger *zap.Logger, accessToken string) {
func (s *tokenServiceImpl) Invalidate(accessToken string) {
if accessToken == "" {
return
}
err := repository.DeleteTokenByAccessToken(accessToken)
err := s.tokenRepo.DeleteByAccessToken(accessToken)
if err != nil {
logger.Error(
"删除Token失败",
zap.Error(err),
zap.String("accessToken", accessToken),
)
s.logger.Error("删除Token失败", zap.Error(err), zap.String("accessToken", accessToken))
return
}
logger.Info("[INFO] 成功删除", zap.Any("Token:", accessToken))
s.logger.Info("成功删除Token", zap.String("token", accessToken))
}
// InvalidUserTokens 使用户所有令牌失效
func InvalidUserTokens(db *gorm.DB, logger *zap.Logger, userId int64) {
if userId == 0 {
func (s *tokenServiceImpl) InvalidateUserTokens(userID int64) {
if userID == 0 {
return
}
err := repository.DeleteTokenByUserId(userId)
err := s.tokenRepo.DeleteByUserID(userID)
if err != nil {
logger.Error(
"[ERROR]删除用户Token失败",
zap.Error(err),
zap.Any("userId", userId),
)
s.logger.Error("删除用户Token失败", zap.Error(err), zap.Int64("userId", userID))
return
}
logger.Info("[INFO] 成功删除用户Token", zap.Any("userId:", userId))
s.logger.Info("成功删除用户Token", zap.Int64("userId", userID))
}
func (s *tokenServiceImpl) GetUUIDByAccessToken(accessToken string) (string, error) {
return s.tokenRepo.GetUUIDByAccessToken(accessToken)
}
func (s *tokenServiceImpl) GetUserIDByAccessToken(accessToken string) (int64, error) {
return s.tokenRepo.GetUserIDByAccessToken(accessToken)
}
// 私有辅助方法
func (s *tokenServiceImpl) checkAndCleanupExcessTokens(userID int64) {
if userID == 0 {
return
}
tokens, err := s.tokenRepo.GetByUserID(userID)
if err != nil {
s.logger.Error("获取用户Token失败", zap.Error(err), zap.String("userId", strconv.FormatInt(userID, 10)))
return
}
if len(tokens) <= tokensMaxCount {
return
}
tokensToDelete := make([]string, 0, len(tokens)-tokensMaxCount)
for i := tokensMaxCount; i < len(tokens); i++ {
tokensToDelete = append(tokensToDelete, tokens[i].AccessToken)
}
deletedCount, err := s.tokenRepo.BatchDelete(tokensToDelete)
if err != nil {
s.logger.Error("清理用户多余Token失败", zap.Error(err), zap.String("userId", strconv.FormatInt(userID, 10)))
return
}
if deletedCount > 0 {
s.logger.Info("成功清理用户多余Token", zap.Int64("userId", userID), zap.Int64("count", deletedCount))
}
}
func (s *tokenServiceImpl) validateProfileByUserID(userID int64, UUID string) (bool, error) {
if userID == 0 || UUID == "" {
return false, errors.New("用户ID或配置文件ID不能为空")
}
profile, err := s.profileRepo.FindByUUID(UUID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return false, errors.New("配置文件不存在")
}
return false, fmt.Errorf("验证配置文件失败: %w", err)
}
return profile.UserID == userID, nil
}