688 lines
19 KiB
Go
688 lines
19 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
appErrors "github.com/topfans/backend/pkg/errors"
|
||
"github.com/topfans/backend/pkg/jwt"
|
||
"github.com/topfans/backend/pkg/logger"
|
||
"github.com/topfans/backend/pkg/models"
|
||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||
pb "github.com/topfans/backend/pkg/proto/user"
|
||
"github.com/topfans/backend/pkg/validator"
|
||
"github.com/topfans/backend/services/userService/repository"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// AuthService 认证Service接口
|
||
type AuthService interface {
|
||
// Register 注册
|
||
Register(req *pb.RegisterRequest) (*pb.RegisterResponse, error)
|
||
|
||
// Login 登录
|
||
Login(req *pb.LoginRequest) (*pb.LoginResponse, error)
|
||
|
||
// Logout 登出(userID 从网关的 attachments 中获取)
|
||
Logout(userID int64) (*pb.LogoutResponse, error)
|
||
|
||
// RefreshToken 刷新Token(userID 和 starID 从网关的 attachments 中获取)
|
||
RefreshToken(userID, starID int64) (*pb.RefreshTokenResponse, error)
|
||
|
||
// ValidateToken 验证Token(用于中间件)
|
||
ValidateToken(req *pb.ValidateTokenRequest) (*pb.ValidateTokenResponse, error)
|
||
}
|
||
|
||
// authService 认证Service实现
|
||
type authService struct {
|
||
userRepo repository.UserRepository
|
||
fanProfileRepo repository.FanProfileRepository
|
||
starRepo repository.StarRepository
|
||
db *gorm.DB
|
||
}
|
||
|
||
// NewAuthService 创建认证Service实例
|
||
func NewAuthService(
|
||
userRepo repository.UserRepository,
|
||
fanProfileRepo repository.FanProfileRepository,
|
||
starRepo repository.StarRepository,
|
||
db *gorm.DB,
|
||
) AuthService {
|
||
return &authService{
|
||
userRepo: userRepo,
|
||
fanProfileRepo: fanProfileRepo,
|
||
starRepo: starRepo,
|
||
db: db,
|
||
}
|
||
}
|
||
|
||
// Register 用户注册
|
||
func (s *authService) Register(req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateMobile(req.Mobile) {
|
||
logger.Logger.Warn("Invalid mobile format",
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return nil, appErrors.ErrInvalidMobile
|
||
}
|
||
|
||
if valid, msg := validator.ValidatePassword(req.Password); !valid {
|
||
logger.Logger.Warn("Invalid password",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.String("error", msg),
|
||
)
|
||
if msg == "password too short" {
|
||
return nil, appErrors.ErrPasswordTooShort
|
||
}
|
||
return nil, fmt.Errorf("invalid password: %s", msg)
|
||
}
|
||
|
||
if valid, msg := validator.ValidateNickname(req.Nickname); !valid {
|
||
logger.Logger.Warn("Invalid nickname",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.String("error", msg),
|
||
)
|
||
return nil, fmt.Errorf("invalid nickname: %s", msg)
|
||
}
|
||
|
||
if !validator.ValidateStarID(req.StarId) {
|
||
logger.Logger.Warn("Invalid star_id",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
return nil, appErrors.ErrInvalidStarID
|
||
}
|
||
|
||
// 2. 验证手机号是否已存在
|
||
existingUser, err := s.userRepo.GetByMobile(req.Mobile)
|
||
if err != nil && !errors.Is(err, appErrors.ErrUserNotFound) {
|
||
logger.Logger.Error("Failed to check mobile existence",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to check mobile: %w", err)
|
||
}
|
||
if existingUser != nil {
|
||
logger.Logger.Warn("Mobile already exists",
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return nil, appErrors.ErrUserAlreadyExists
|
||
}
|
||
|
||
// 3. 验证明星是否存在
|
||
_, err = s.starRepo.GetByID(req.StarId)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get star",
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Error(err),
|
||
)
|
||
if errors.Is(err, appErrors.ErrStarNotFound) {
|
||
return nil, appErrors.ErrStarNotFound
|
||
}
|
||
return nil, fmt.Errorf("failed to get star: %w", err)
|
||
}
|
||
|
||
// 4. 使用事务创建用户和粉丝档案
|
||
var user *models.User
|
||
var fanProfile *models.FanProfile
|
||
|
||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||
// 4.1 创建用户
|
||
now := time.Now().UnixMilli()
|
||
user = &models.User{
|
||
Mobile: req.Mobile,
|
||
PasswordHash: "", // 将在Repository中加密
|
||
IsActive: true,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
|
||
// 手动加密密码(因为需要在事务中使用)
|
||
hashedPassword, err := repository.HashPassword(req.Password)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to hash password: %w", err)
|
||
}
|
||
user.PasswordHash = hashedPassword
|
||
|
||
// 在事务中创建用户
|
||
if err := tx.Create(user).Error; err != nil {
|
||
logger.Logger.Error("Failed to create user in transaction",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Error(err),
|
||
)
|
||
return fmt.Errorf("failed to create user: %w", err)
|
||
}
|
||
|
||
// 4.2 创建第一个粉丝档案
|
||
fanProfile = &models.FanProfile{
|
||
UserID: user.ID,
|
||
StarID: req.StarId,
|
||
Nickname: req.Nickname,
|
||
Level: 1,
|
||
Times: 1,
|
||
Social: 0,
|
||
CoinBalance: 0,
|
||
CrystalBalance: 0,
|
||
Tags: models.StringArray{},
|
||
IsActive: true,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
|
||
if err := tx.Create(fanProfile).Error; err != nil {
|
||
logger.Logger.Error("Failed to create fan profile in transaction",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Int64("star_id", req.StarId),
|
||
zap.Error(err),
|
||
)
|
||
return fmt.Errorf("failed to create fan profile: %w", err)
|
||
}
|
||
|
||
// 4.3 生成JWT Token
|
||
token, err := jwt.GenerateToken(user.ID, req.StarId, user.UpdatedAt)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to generate token",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return fmt.Errorf("failed to generate token: %w", err)
|
||
}
|
||
|
||
// 4.4 更新用户Token
|
||
// 注意:使用 UpdateColumns 而不是 Updates,避免触发 BeforeUpdate 钩子
|
||
// 因为更新 Token 不应该改变 updated_at(updated_at 用于验证 Token 有效性)
|
||
tokenExpiresAt := jwt.GetExpiresAt()
|
||
if err := tx.Model(user).UpdateColumns(map[string]interface{}{
|
||
"access_token": token,
|
||
"token_expires_at": tokenExpiresAt,
|
||
}).Error; err != nil {
|
||
logger.Logger.Error("Failed to update token in transaction",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return fmt.Errorf("failed to update token: %w", err)
|
||
}
|
||
|
||
// 更新user对象以便返回
|
||
user.AccessToken = &token
|
||
expiresAt := tokenExpiresAt
|
||
user.TokenExpiresAt = &expiresAt
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
// 检查是否是唯一约束错误
|
||
errStr := err.Error()
|
||
if contains(errStr, "uk_fan_profiles_star_nickname") || contains(errStr, "该昵称已被注册") {
|
||
return nil, appErrors.ErrNicknameAlreadyExists
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// 5. 构建响应
|
||
response := &pb.RegisterResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
AccessToken: *user.AccessToken,
|
||
ExpiresIn: jwt.GetExpiresIn(),
|
||
User: ModelToProtoUser(user),
|
||
FanProfile: ModelToProtoFanProfile(fanProfile),
|
||
}
|
||
|
||
logger.Logger.Info("User registered successfully",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.String("mobile", user.Mobile),
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// Login 用户登录
|
||
func (s *authService) Login(req *pb.LoginRequest) (*pb.LoginResponse, error) {
|
||
// 1. 参数验证
|
||
if !validator.ValidateMobile(req.Mobile) {
|
||
logger.Logger.Warn("Invalid mobile format",
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return nil, appErrors.ErrInvalidMobile
|
||
}
|
||
|
||
if req.Password == "" {
|
||
logger.Logger.Warn("Password is empty",
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return nil, appErrors.ErrInvalidPassword
|
||
}
|
||
|
||
// 2. 根据手机号查询用户
|
||
user, err := s.userRepo.GetByMobile(req.Mobile)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrUserNotFound) {
|
||
logger.Logger.Warn("User not found",
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return nil, appErrors.ErrUserNotFound
|
||
}
|
||
logger.Logger.Error("Failed to get user by mobile",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||
}
|
||
|
||
// 3. 验证密码
|
||
if !s.userRepo.VerifyPassword(user, req.Password) {
|
||
logger.Logger.Warn("Invalid password",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
return nil, appErrors.ErrInvalidPassword
|
||
}
|
||
|
||
// 4. 验证用户是否激活
|
||
if !user.IsActive {
|
||
logger.Logger.Warn("User is inactive",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.String("mobile", req.Mobile),
|
||
)
|
||
return nil, appErrors.ErrUserInactive
|
||
}
|
||
|
||
// 4.1 检查账号状态(冻结/封号)
|
||
accountStatus, err := s.userRepo.GetAccountStatus(user.ID)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get account status",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get account status: %w", err)
|
||
}
|
||
|
||
// 如果有账号状态记录,需要检查具体状态
|
||
if accountStatus != nil {
|
||
if accountStatus.IsBanned() {
|
||
// 封号状态
|
||
reason := ""
|
||
if accountStatus.Reason != nil {
|
||
reason = *accountStatus.Reason
|
||
}
|
||
logger.Logger.Warn("User account is banned",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.String("reason", reason),
|
||
)
|
||
return nil, fmt.Errorf("账号已被封禁%s%s", map[bool]func() string{
|
||
true: func() string {
|
||
if reason != "" {
|
||
return ",原因:" + reason
|
||
}
|
||
return ""
|
||
},
|
||
}[true](), "")
|
||
}
|
||
|
||
if accountStatus.IsFrozen() {
|
||
// 冻结状态,检查是否已过解冻时间
|
||
if accountStatus.FrozenUntil != nil && time.Now().UnixMilli() > *accountStatus.FrozenUntil {
|
||
// 冻结已过期,理论上应该更新状态,但这里先放行让用户登录
|
||
logger.Logger.Info("User account frozen but expired",
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
} else {
|
||
reason := ""
|
||
if accountStatus.Reason != nil {
|
||
reason = *accountStatus.Reason
|
||
}
|
||
frozenUntilStr := ""
|
||
if accountStatus.FrozenUntil != nil {
|
||
frozenTime := time.UnixMilli(*accountStatus.FrozenUntil)
|
||
frozenUntilStr = frozenTime.Format("2006-01-02 15:04:05")
|
||
}
|
||
logger.Logger.Warn("User account is frozen",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.String("reason", reason),
|
||
zap.Int64("frozen_until", *accountStatus.FrozenUntil),
|
||
)
|
||
errMsg := "账号已被冻结"
|
||
if reason != "" {
|
||
errMsg += ",原因:" + reason
|
||
}
|
||
if frozenUntilStr != "" {
|
||
errMsg += ",解封时间:" + frozenUntilStr
|
||
}
|
||
return nil, errors.New(errMsg)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 5. 获取用户的粉丝档案列表
|
||
fanProfiles, err := s.fanProfileRepo.GetByUserID(user.ID)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to get fan profiles",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get fan profiles: %w", err)
|
||
}
|
||
|
||
if len(fanProfiles) == 0 {
|
||
logger.Logger.Error("User has no fan profiles",
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
return nil, fmt.Errorf("user has no fan profiles")
|
||
}
|
||
|
||
// 选择当前身份
|
||
var currentProfile *models.FanProfile
|
||
if req.StarId > 0 {
|
||
// 如果指定了 star_id,查找对应的粉丝档案
|
||
specifiedStarID := req.StarId
|
||
found := false
|
||
for _, profile := range fanProfiles {
|
||
if profile.StarID == specifiedStarID {
|
||
currentProfile = profile
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if !found {
|
||
logger.Logger.Warn("Specified star_id not found in user's fan profiles",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Int64("specified_star_id", specifiedStarID),
|
||
)
|
||
return nil, fmt.Errorf("你还不是该明星的粉丝,无法切换到该身份")
|
||
}
|
||
|
||
logger.Logger.Info("Using specified star_id for login",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Int64("star_id", specifiedStarID),
|
||
)
|
||
} else {
|
||
// 没有指定 star_id,使用第一个(最早创建的)粉丝档案
|
||
currentProfile = fanProfiles[0]
|
||
logger.Logger.Info("Using first fan profile for login",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Int64("star_id", currentProfile.StarID),
|
||
)
|
||
}
|
||
|
||
// 6. 生成JWT Token(包含user_id和当前star_id)
|
||
token, err := jwt.GenerateToken(user.ID, currentProfile.StarID, user.UpdatedAt)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to generate token",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to generate token: %w", err)
|
||
}
|
||
|
||
// 7. 更新用户Token
|
||
tokenExpiresAt := jwt.GetExpiresAt()
|
||
if err := s.userRepo.UpdateToken(user.ID, token, tokenExpiresAt); err != nil {
|
||
logger.Logger.Error("Failed to update token",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to update token: %w", err)
|
||
}
|
||
|
||
// 8. 构建响应
|
||
pbFanProfiles := make([]*pb.FanProfile, 0, len(fanProfiles))
|
||
for _, profile := range fanProfiles {
|
||
pbFanProfiles = append(pbFanProfiles, &pb.FanProfile{
|
||
Id: profile.ID,
|
||
UserId: profile.UserID,
|
||
StarId: profile.StarID,
|
||
Nickname: profile.Nickname,
|
||
Level: int32(profile.Level),
|
||
Times: int32(profile.Times),
|
||
Social: int32(profile.Social),
|
||
CoinBalance: profile.CoinBalance,
|
||
CrystalBalance: profile.CrystalBalance,
|
||
Tags: []string(profile.Tags),
|
||
CreatedAt: profile.CreatedAt,
|
||
})
|
||
}
|
||
|
||
response := &pb.LoginResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
AccessToken: token,
|
||
ExpiresIn: jwt.GetExpiresIn(),
|
||
User: ModelToProtoUser(user),
|
||
FanProfile: ModelToProtoFanProfile(currentProfile),
|
||
FanProfiles: pbFanProfiles,
|
||
}
|
||
|
||
logger.Logger.Info("User login successful",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.String("mobile", user.Mobile),
|
||
zap.Int64("star_id", currentProfile.StarID),
|
||
)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// Logout 用户登出
|
||
func (s *authService) Logout(userID int64) (*pb.LogoutResponse, error) {
|
||
// 清除用户Token
|
||
if err := s.userRepo.ClearToken(userID); err != nil {
|
||
logger.Logger.Error("Failed to clear token",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to clear token: %w", err)
|
||
}
|
||
|
||
logger.Logger.Info("User logout successful",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
|
||
return &pb.LogoutResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// RefreshToken 刷新Token
|
||
func (s *authService) RefreshToken(userID, starID int64) (*pb.RefreshTokenResponse, error) {
|
||
// 1. 查询用户
|
||
user, err := s.userRepo.GetByID(userID)
|
||
if err != nil {
|
||
if errors.Is(err, appErrors.ErrUserNotFound) {
|
||
logger.Logger.Warn("User not found during token refresh",
|
||
zap.Int64("user_id", userID),
|
||
)
|
||
return nil, appErrors.ErrUserNotFound
|
||
}
|
||
logger.Logger.Error("Failed to get user during token refresh",
|
||
zap.Int64("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||
}
|
||
|
||
// 2. 验证用户是否激活
|
||
if !user.IsActive {
|
||
logger.Logger.Warn("User is inactive during token refresh",
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
return nil, appErrors.ErrUserInactive
|
||
}
|
||
|
||
// 3. 生成新Token
|
||
newToken, err := jwt.GenerateToken(user.ID, starID, user.UpdatedAt)
|
||
if err != nil {
|
||
logger.Logger.Error("Failed to generate new token",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to generate token: %w", err)
|
||
}
|
||
|
||
// 4. 更新数据库中的Token
|
||
tokenExpiresAt := jwt.GetExpiresAt()
|
||
if err := s.userRepo.UpdateToken(user.ID, newToken, tokenExpiresAt); err != nil {
|
||
logger.Logger.Error("Failed to update token",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Error(err),
|
||
)
|
||
return nil, fmt.Errorf("failed to update token: %w", err)
|
||
}
|
||
|
||
logger.Logger.Info("Token refreshed successfully",
|
||
zap.Int64("user_id", user.ID),
|
||
zap.Int64("star_id", starID),
|
||
)
|
||
|
||
return &pb.RefreshTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
AccessToken: newToken,
|
||
ExpiresIn: jwt.GetExpiresIn(),
|
||
}, nil
|
||
}
|
||
|
||
// ValidateToken 验证Token(用于中间件)
|
||
func (s *authService) ValidateToken(req *pb.ValidateTokenRequest) (*pb.ValidateTokenResponse, error) {
|
||
// 1. 解析和验证Token(检查签名和过期时间)
|
||
claims, err := jwt.ValidateToken(req.AccessToken)
|
||
if err != nil {
|
||
logger.Logger.Warn("Token validation failed",
|
||
zap.Error(err),
|
||
)
|
||
return &pb.ValidateTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
|
||
Message: err.Error(),
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
UserId: 0,
|
||
StarId: 0,
|
||
IsValid: false,
|
||
ExpiresAt: 0,
|
||
}, nil
|
||
}
|
||
|
||
// 2. 查询用户验证Token是否匹配
|
||
user, err := s.userRepo.GetByID(claims.UserID)
|
||
if err != nil {
|
||
logger.Logger.Warn("User not found during token validation",
|
||
zap.Int64("user_id", claims.UserID),
|
||
zap.Error(err),
|
||
)
|
||
return &pb.ValidateTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
|
||
Message: "user not found",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
UserId: 0,
|
||
StarId: 0,
|
||
IsValid: false,
|
||
ExpiresAt: 0,
|
||
}, nil
|
||
}
|
||
|
||
// 3. 验证用户是否激活
|
||
if !user.IsActive {
|
||
logger.Logger.Warn("User is inactive during token validation",
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
return &pb.ValidateTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_FORBIDDEN,
|
||
Message: "user is inactive",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
UserId: claims.UserID,
|
||
StarId: claims.StarID,
|
||
IsValid: false,
|
||
ExpiresAt: 0,
|
||
}, nil
|
||
}
|
||
|
||
// 4. 验证Token是否匹配数据库中的Token
|
||
if user.AccessToken == nil || *user.AccessToken != req.AccessToken {
|
||
logger.Logger.Warn("Token mismatch during validation",
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
return &pb.ValidateTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
|
||
Message: "token mismatch",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
UserId: claims.UserID,
|
||
StarId: claims.StarID,
|
||
IsValid: false,
|
||
ExpiresAt: 0,
|
||
}, nil
|
||
}
|
||
|
||
// 5. 验证updated_at是否匹配
|
||
if user.UpdatedAt != claims.UpdatedAt {
|
||
logger.Logger.Warn("Token invalidated due to user info update",
|
||
zap.Int64("user_id", user.ID),
|
||
)
|
||
return &pb.ValidateTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
|
||
Message: "token expired due to user info update",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
UserId: claims.UserID,
|
||
StarId: claims.StarID,
|
||
IsValid: false,
|
||
ExpiresAt: 0,
|
||
}, nil
|
||
}
|
||
|
||
// 6. 获取过期时间
|
||
var expiresAt int64
|
||
if claims.RegisteredClaims.ExpiresAt != nil {
|
||
expiresAt = claims.RegisteredClaims.ExpiresAt.Time.UnixMilli()
|
||
}
|
||
|
||
logger.Logger.Debug("Token validated successfully",
|
||
zap.Int64("user_id", claims.UserID),
|
||
zap.Int64("star_id", claims.StarID),
|
||
)
|
||
|
||
return &pb.ValidateTokenResponse{
|
||
Base: &pbCommon.BaseResponse{
|
||
Code: pbCommon.StatusCode_STATUS_OK,
|
||
Message: "",
|
||
Timestamp: time.Now().UnixMilli(),
|
||
},
|
||
UserId: claims.UserID,
|
||
StarId: claims.StarID,
|
||
IsValid: true,
|
||
ExpiresAt: expiresAt,
|
||
}, nil
|
||
}
|
||
|
||
// min 返回两个整数的最小值
|
||
func min(a, b int) int {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|