package services import ( "crypto/rand" "encoding/base64" "errors" "fmt" "strings" "time" "smart-customer-service/config" "smart-customer-service/internal/models" "smart-customer-service/pkg/database" "smart-customer-service/pkg/jwt" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) type AuthService struct { cfg *config.Config db *gorm.DB jwt *jwt.JWTManager } func NewAuthService(cfg *config.Config, db *gorm.DB) *AuthService { return &AuthService{ cfg: cfg, db: db, jwt: jwt.NewJWTManager(cfg.JWT.Secret), } } // LoginRequest 登录请求 type LoginRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` DeviceID string `json:"device_id"` DeviceName string `json:"device_name"` DeviceType string `json:"device_type"` IPAddress string `json:"ip_address"` UserAgent string `json:"user_agent"` } // LoginResponse 登录响应 type LoginResponse struct { User *models.User `json:"user"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresAt time.Time `json:"expires_at"` TokenType string `json:"token_type"` } // RegisterRequest 注册请求 type RegisterRequest struct { TenantID uint `json:"tenant_id"` Username string `json:"username" binding:"required,min=3,max=50"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` FullName string `json:"full_name"` Phone string `json:"phone"` } // RegisterResponse 注册响应 type RegisterResponse struct { User *models.User `json:"user"` Token string `json:"token"` } // Login 用户登录 func (s *AuthService) Login(req *LoginRequest) (*LoginResponse, error) { // 记录登录尝试 loginAttempt := &models.LoginAttempt{ Email: req.Email, IPAddress: req.IPAddress, UserAgent: req.UserAgent, Success: false, } defer s.recordLoginAttempt(loginAttempt) // 查找用户 var user models.User if err := s.db.Where("email = ?", req.Email).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { loginAttempt.FailureReason = "用户不存在" return nil, errors.New("用户名或密码错误") } loginAttempt.FailureReason = "数据库错误" return nil, err } // 检查用户状态 if user.Status != "active" { loginAttempt.FailureReason = "用户状态异常: " + user.Status return nil, errors.New("用户账户不可用") } // 验证密码 if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil { loginAttempt.UserID = &user.ID loginAttempt.FailureReason = "密码错误" return nil, errors.New("用户名或密码错误") } // 更新最后登录时间 now := time.Now() user.LastLoginAt = &now user.LastIP = req.IPAddress if err := s.db.Save(&user).Error; err != nil { loginAttempt.FailureReason = "更新登录信息失败" return nil, err } // 生成JWT令牌 accessToken, accessExpiresAt, err := s.jwt.GenerateAccessToken(user.ID, user.TenantID, user.Role) if err != nil { loginAttempt.FailureReason = "生成令牌失败" return nil, err } refreshToken, refreshExpiresAt, err := s.jwt.GenerateRefreshToken(user.ID, user.TenantID) if err != nil { loginAttempt.FailureReason = "生成刷新令牌失败" return nil, err } // 保存令牌到数据库 authToken := &models.AuthToken{ TenantID: user.TenantID, UserID: user.ID, Token: refreshToken, TokenType: "refresh", ExpiresAt: refreshExpiresAt, DeviceID: req.DeviceID, DeviceName: req.DeviceName, DeviceType: req.DeviceType, IPAddress: req.IPAddress, UserAgent: req.UserAgent, } if err := s.db.Create(authToken).Error; err != nil { loginAttempt.FailureReason = "保存令牌失败" return nil, err } // 创建会话 session := &models.Session{ TenantID: user.TenantID, UserID: user.ID, SessionID: generateSessionID(), ExpiresAt: refreshExpiresAt, DeviceID: req.DeviceID, DeviceName: req.DeviceName, DeviceType: req.DeviceType, IPAddress: req.IPAddress, UserAgent: req.UserAgent, LastActivity: now, } if err := s.db.Create(session).Error; err != nil { // 不影响主要登录流程,只记录日志 fmt.Printf("创建会话失败: %v\n", err) } // 标记登录尝试为成功 loginAttempt.UserID = &user.ID loginAttempt.Success = true loginAttempt.FailureReason = "" s.recordLoginAttempt(loginAttempt) return &LoginResponse{ User: &user, AccessToken: accessToken, RefreshToken: refreshToken, ExpiresAt: accessExpiresAt, TokenType: "Bearer", }, nil } // Register 用户注册 func (s *AuthService) Register(req *RegisterRequest) (*RegisterResponse, error) { // 检查邮箱是否已存在 var existingUser models.User if err := s.db.Where("email = ? AND tenant_id = ?", req.Email, req.TenantID).First(&existingUser).Error; err == nil { return nil, errors.New("邮箱已被注册") } // 检查用户名是否已存在 if err := s.db.Where("username = ? AND tenant_id = ?", req.Username, req.TenantID).First(&existingUser).Error; err == nil { return nil, errors.New("用户名已被使用") } // 加密密码 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { return nil, err } // 创建用户 user := &models.User{ TenantID: req.TenantID, Username: req.Username, Email: req.Email, Password: string(hashedPassword), FullName: req.FullName, Phone: req.Phone, Role: "user", Status: "active", IsVerified: false, } if err := s.db.Create(user).Error; err != nil { return nil, err } // 生成验证令牌(简化版,实际应发送验证邮件) verificationToken := generateRandomToken(32) return &RegisterResponse{ User: user, Token: verificationToken, }, nil } // RefreshToken 刷新访问令牌 func (s *AuthService) RefreshToken(refreshToken string) (*LoginResponse, error) { // 查找刷新令牌 var authToken models.AuthToken if err := s.db.Where("token = ? AND token_type = 'refresh' AND is_revoked = false", refreshToken).First(&authToken).Error; err != nil { return nil, errors.New("无效的刷新令牌") } // 检查令牌是否过期 if time.Now().After(authToken.ExpiresAt) { // 标记令牌为已撤销 now := time.Now() authToken.IsRevoked = true authToken.RevokedAt = &now authToken.RevokedReason = "令牌过期" s.db.Save(&authToken) return nil, errors.New("刷新令牌已过期") } // 查找用户 var user models.User if err := s.db.First(&user, authToken.UserID).Error; err != nil { return nil, errors.New("用户不存在") } // 检查用户状态 if user.Status != "active" { return nil, errors.New("用户账户不可用") } // 生成新的访问令牌 accessToken, expiresAt, err := s.jwt.GenerateAccessToken(user.ID, user.TenantID, user.Role) if err != nil { return nil, err } return &LoginResponse{ User: &user, AccessToken: accessToken, ExpiresAt: expiresAt, TokenType: "Bearer", }, nil } // Logout 用户登出 func (s *AuthService) Logout(token string, userID uint) error { // 撤销刷新令牌 var authToken models.AuthToken if err := s.db.Where("token = ? AND user_id = ?", token, userID).First(&authToken).Error; err != nil { return nil // 令牌不存在,无需处理 } now := time.Now() authToken.IsRevoked = true authToken.RevokedAt = &now authToken.RevokedReason = "用户主动登出" return s.db.Save(&authToken).Error } // ValidateToken 验证访问令牌 func (s *AuthService) ValidateToken(token string) (*jwt.Claims, error) { return s.jwt.ValidateToken(token) } // ChangePassword 修改密码 func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error { var user models.User if err := s.db.First(&user, userID).Error; err != nil { return errors.New("用户不存在") } // 验证旧密码 if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(oldPassword)); err != nil { return errors.New("旧密码错误") } // 加密新密码 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { return err } user.Password = string(hashedPassword) return s.db.Save(&user).Error } // RequestPasswordReset 请求密码重置 func (s *AuthService) RequestPasswordReset(email string, tenantID uint) (string, error) { var user models.User if err := s.db.Where("email = ? AND tenant_id = ?", email, tenantID).First(&user).Error; err != nil { // 出于安全考虑,即使用户不存在也返回成功 return "", nil } // 生成重置令牌 token := generateRandomToken(32) expiresAt := time.Now().Add(24 * time.Hour) // 24小时有效 resetRequest := &models.PasswordReset{ TenantID: tenantID, UserID: user.ID, Token: token, ExpiresAt: expiresAt, } if err := s.db.Create(resetRequest).Error; err != nil { return "", err } return token, nil } // ResetPassword 重置密码 func (s *AuthService) ResetPassword(token, newPassword string) error { var resetRequest models.PasswordReset if err := s.db.Where("token = ? AND is_used = false", token).First(&resetRequest).Error; err != nil { return errors.New("无效的重置令牌") } // 检查令牌是否过期 if time.Now().After(resetRequest.ExpiresAt) { return errors.New("重置令牌已过期") } // 查找用户 var user models.User if err := s.db.First(&user, resetRequest.UserID).Error; err != nil { return errors.New("用户不存在") } // 加密新密码 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { return err } // 更新密码 user.Password = string(hashedPassword) if err := s.db.Save(&user).Error; err != nil { return err } // 标记重置令牌为已使用 now := time.Now() resetRequest.IsUsed = true resetRequest.UsedAt = &now return s.db.Save(&resetRequest).Error } // 辅助函数 func (s *AuthService) recordLoginAttempt(attempt *models.LoginAttempt) { if err := s.db.Create(attempt).Error; err != nil { fmt.Printf("记录登录尝试失败: %v\n", err) } } func generateSessionID() string { b := make([]byte, 32) rand.Read(b) return base64.URLEncoding.EncodeToString(b) } func generateRandomToken(length int) string { b := make([]byte, length) rand.Read(b) return base64.URLEncoding.EncodeToString(b) } // GetUserByToken 通过令牌获取用户信息 func (s *AuthService) GetUserByToken(token string) (*models.User, error) { claims, err := s.ValidateToken(token) if err != nil { return nil, err } var user models.User if err := s.db.First(&user, claims.UserID).Error; err != nil { return nil, err } return &user, nil } // GetUserSessions 获取用户会话列表 func (s *AuthService) GetUserSessions(userID uint) ([]models.Session, error) { var sessions []models.Session err := s.db.Where("user_id = ? AND is_active = true", userID).Find(&sessions).Error return sessions, err } // RevokeSession 撤销会话 func (s *AuthService) RevokeSession(sessionID string, userID uint) error { var session models.Session if err := s.db.Where("session_id = ? AND user_id = ?", sessionID, userID).First(&session).Error; err != nil { return errors.New("会话不存在") } session.IsActive = false return s.db.Save(&session).Error }