package service import ( "carrotskin/internal/model" "carrotskin/internal/repository" "context" "errors" "fmt" "strconv" "time" "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 ( tokenExtendedTimeout = 10 * time.Second tokensMaxCount = 10 ) 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() // 验证用户存在 if UUID != "" { _, err := s.profileRepo.FindByUUID(UUID) if err != nil { return selectedProfileID, availableProfiles, "", "", fmt.Errorf("获取用户信息失败: %w", err) } } // 生成令牌 if clientToken == "" { clientToken = uuid.New().String() } accessToken := uuid.New().String() token := model.Token{ AccessToken: accessToken, ClientToken: clientToken, UserID: userID, Usable: true, IssueDate: time.Now(), } // 获取用户配置文件 profiles, err := s.profileRepo.FindByUserID(userID) if err != nil { return selectedProfileID, availableProfiles, "", "", fmt.Errorf("获取用户配置文件失败: %w", err) } // 如果用户只有一个配置文件,自动选择 if len(profiles) == 1 { selectedProfileID = profiles[0] token.ProfileId = selectedProfileID.UUID } availableProfiles = profiles // 插入令牌 err = s.tokenRepo.Create(&token) if err != nil { return selectedProfileID, availableProfiles, "", "", fmt.Errorf("创建Token失败: %w", err) } // 清理多余的令牌 go s.checkAndCleanupExcessTokens(userID) return selectedProfileID, availableProfiles, accessToken, clientToken, nil } func (s *tokenServiceImpl) Validate(accessToken, clientToken string) bool { if accessToken == "" { return false } token, err := s.tokenRepo.FindByAccessToken(accessToken) if err != nil { return false } if !token.Usable { return false } if clientToken == "" { return true } return token.ClientToken == clientToken } func (s *tokenServiceImpl) Refresh(accessToken, clientToken, selectedProfileID string) (string, string, error) { if accessToken == "" { return "", "", errors.New("accessToken不能为空") } // 查找旧令牌 oldToken, err := s.tokenRepo.FindByAccessToken(accessToken) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return "", "", errors.New("accessToken无效") } s.logger.Error("查询Token失败", zap.Error(err), zap.String("accessToken", accessToken)) return "", "", fmt.Errorf("查询令牌失败: %w", err) } // 验证profile if selectedProfileID != "" { valid, validErr := s.validateProfileByUserID(oldToken.UserID, selectedProfileID) if validErr != nil { s.logger.Error("验证Profile失败", zap.Error(err), zap.Int64("userId", oldToken.UserID), zap.String("profileId", selectedProfileID), ) return "", "", fmt.Errorf("验证角色失败: %w", err) } if !valid { return "", "", errors.New("角色与用户不匹配") } } // 检查 clientToken 是否有效 if clientToken != "" && clientToken != oldToken.ClientToken { return "", "", errors.New("clientToken无效") } // 检查 selectedProfileID 的逻辑 if selectedProfileID != "" { if oldToken.ProfileId != "" && oldToken.ProfileId != selectedProfileID { return "", "", errors.New("原令牌已绑定角色,无法选择新角色") } } else { selectedProfileID = oldToken.ProfileId } // 生成新令牌 newAccessToken := uuid.New().String() newToken := model.Token{ AccessToken: newAccessToken, ClientToken: oldToken.ClientToken, UserID: oldToken.UserID, Usable: true, ProfileId: selectedProfileID, IssueDate: time.Now(), } // 先插入新令牌,再删除旧令牌 err = s.tokenRepo.Create(&newToken) if err != nil { s.logger.Error("创建新Token失败", zap.Error(err), zap.String("accessToken", accessToken)) return "", "", fmt.Errorf("创建新Token失败: %w", err) } err = s.tokenRepo.DeleteByAccessToken(accessToken) if err != nil { s.logger.Warn("删除旧Token失败,但新Token已创建", zap.Error(err), zap.String("oldToken", oldToken.AccessToken), zap.String("newToken", newAccessToken), ) } s.logger.Info("成功刷新Token", zap.Int64("userId", oldToken.UserID), zap.String("accessToken", newAccessToken)) return newAccessToken, oldToken.ClientToken, nil } func (s *tokenServiceImpl) Invalidate(accessToken string) { if accessToken == "" { return } err := s.tokenRepo.DeleteByAccessToken(accessToken) if err != nil { s.logger.Error("删除Token失败", zap.Error(err), zap.String("accessToken", accessToken)) return } s.logger.Info("成功删除Token", zap.String("token", accessToken)) } func (s *tokenServiceImpl) InvalidateUserTokens(userID int64) { if userID == 0 { return } err := s.tokenRepo.DeleteByUserID(userID) if err != nil { s.logger.Error("删除用户Token失败", zap.Error(err), zap.Int64("userId", userID)) return } 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 }