You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.3 KiB
210 lines
5.3 KiB
package service |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"fmt" |
|
"strconv" |
|
"time" |
|
|
|
"gofaster/internal/auth/model" |
|
"gofaster/internal/auth/repository" |
|
|
|
"github.com/golang-jwt/jwt/v5" |
|
"golang.org/x/crypto/bcrypt" |
|
) |
|
|
|
type AuthService struct { |
|
userRepo repository.UserRepository |
|
captchaRepo repository.CaptchaRepository |
|
} |
|
|
|
func NewAuthService(userRepo repository.UserRepository, captchaRepo repository.CaptchaRepository) *AuthService { |
|
return &AuthService{ |
|
userRepo: userRepo, |
|
captchaRepo: captchaRepo, |
|
} |
|
} |
|
|
|
// Login 用户登录 |
|
func (s *AuthService) Login(ctx context.Context, username, password, captcha, captchaID string) (*model.LoginResponse, error) { |
|
// 验证验证码 |
|
if captcha != "" && captchaID != "" { |
|
expectedCaptcha, err := s.captchaRepo.Get(ctx, captchaID) |
|
if err != nil { |
|
return nil, errors.New("验证码错误或已过期") |
|
} |
|
if captcha != expectedCaptcha { |
|
return nil, errors.New("验证码错误") |
|
} |
|
} |
|
|
|
// 根据用户名查找用户 |
|
user, err := s.userRepo.GetByUsername(ctx, username) |
|
if err != nil { |
|
return nil, errors.New("用户名或密码错误") |
|
} |
|
|
|
// 验证密码 |
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { |
|
return nil, errors.New("用户名或密码错误") |
|
} |
|
|
|
// 检查用户状态 |
|
if !user.CanLogin() { |
|
return nil, errors.New("账户已被禁用或锁定") |
|
} |
|
|
|
// 更新最后登录信息 |
|
now := time.Now() |
|
user.LastLoginAt = &now |
|
user.LastLoginIP = "127.0.0.1" // 这里应该从请求中获取真实IP |
|
user.ResetPasswordError() // 重置密码错误次数 |
|
|
|
if err := s.userRepo.Update(ctx, user); err != nil { |
|
// 登录失败,但不影响登录流程 |
|
} |
|
|
|
// 生成JWT token |
|
token, err := s.generateJWTToken(user) |
|
if err != nil { |
|
return nil, errors.New("生成认证令牌失败") |
|
} |
|
|
|
// 检查是否需要强制修改密码 |
|
forceChangePassword := s.checkForceChangePassword(user) |
|
|
|
// 转换为UserInfo |
|
userInfo := model.UserInfo{ |
|
ID: user.ID, |
|
Username: user.Username, |
|
Email: user.Email, |
|
Phone: user.Phone, |
|
Status: user.Status, |
|
CreatedAt: user.CreatedAt, |
|
LastLoginAt: user.LastLoginAt, |
|
LastLoginIP: user.LastLoginIP, |
|
Roles: []model.RoleInfo{}, // 暂时为空,后续可以加载角色信息 |
|
} |
|
|
|
return &model.LoginResponse{ |
|
Token: token, |
|
TokenType: "Bearer", |
|
ExpiresIn: 86400, // 24小时 |
|
RefreshToken: "", // 暂时为空 |
|
User: userInfo, |
|
ForceChangePassword: forceChangePassword, |
|
}, nil |
|
} |
|
|
|
// GetCaptchaRepo 获取验证码仓库 |
|
func (s *AuthService) GetCaptchaRepo() repository.CaptchaRepository { |
|
return s.captchaRepo |
|
} |
|
|
|
// RefreshToken 刷新JWT token |
|
func (s *AuthService) RefreshToken(ctx context.Context, userID interface{}) (string, error) { |
|
// 安全地转换userID |
|
var uid uint |
|
switch v := userID.(type) { |
|
case uint: |
|
uid = v |
|
case int: |
|
if v < 0 { |
|
return "", errors.New("无效的用户ID") |
|
} |
|
uid = uint(v) |
|
case int64: |
|
if v < 0 { |
|
return "", errors.New("无效的用户ID") |
|
} |
|
uid = uint(v) |
|
case float64: |
|
if v < 0 || v > float64(^uint(0)) { |
|
return "", errors.New("无效的用户ID") |
|
} |
|
uid = uint(v) |
|
case string: |
|
if parsed, err := strconv.ParseUint(v, 10, 64); err == nil { |
|
uid = uint(parsed) |
|
} else { |
|
return "", errors.New("无效的用户ID") |
|
} |
|
default: |
|
return "", errors.New("无效的用户ID类型") |
|
} |
|
|
|
// 获取用户信息 |
|
user, err := s.userRepo.GetByID(ctx, uid) |
|
if err != nil { |
|
return "", errors.New("用户不存在") |
|
} |
|
|
|
// 生成新的JWT token |
|
return s.generateJWTToken(user) |
|
} |
|
|
|
// generateJWTToken 生成JWT token |
|
func (s *AuthService) generateJWTToken(user *model.User) (string, error) { |
|
// 创建claims |
|
claims := jwt.MapClaims{ |
|
"user_id": user.ID, |
|
"username": user.Username, |
|
"email": user.Email, |
|
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期 |
|
"iat": time.Now().Unix(), |
|
"iss": "gofaster", |
|
} |
|
|
|
// 创建token |
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
|
|
|
// 签名token |
|
tokenString, err := token.SignedString([]byte("your-secret-key")) |
|
if err != nil { |
|
return "", fmt.Errorf("签名token失败: %w", err) |
|
} |
|
|
|
return tokenString, nil |
|
} |
|
|
|
// checkForceChangePassword 检查是否需要强制修改密码 |
|
func (s *AuthService) checkForceChangePassword(user *model.User) bool { |
|
return user.ForceChangePassword |
|
} |
|
|
|
// GetUserInfo 获取用户信息 |
|
func (s *AuthService) GetUserInfo(ctx context.Context, userID uint) (*model.UserInfo, error) { |
|
// 根据用户ID获取用户信息 |
|
user, err := s.userRepo.GetByID(ctx, userID) |
|
if err != nil { |
|
return nil, errors.New("用户不存在") |
|
} |
|
|
|
// 检查用户状态 |
|
if !user.CanLogin() { |
|
return nil, errors.New("账户已被禁用或锁定") |
|
} |
|
|
|
// 转换为UserInfo结构 |
|
userInfo := &model.UserInfo{ |
|
ID: user.ID, |
|
Username: user.Username, |
|
Email: user.Email, |
|
Phone: user.Phone, |
|
Status: user.Status, |
|
CreatedAt: user.CreatedAt, |
|
LastLoginAt: user.LastLoginAt, |
|
LastLoginIP: user.LastLoginIP, |
|
Roles: []model.RoleInfo{}, // 暂时为空,后续可以加载角色信息 |
|
} |
|
|
|
return userInfo, nil |
|
} |
|
|
|
// Logout 用户登出 |
|
func (s *AuthService) Logout(ctx context.Context, token string) error { |
|
// 这里可以实现令牌黑名单逻辑 |
|
// 暂时简单返回成功 |
|
return nil |
|
}
|
|
|