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

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
}