// Package service 提供业务逻辑层 package service import ( "errors" "time" "seeyon-filesystem/config" "seeyon-filesystem/dto" "seeyon-filesystem/model" "seeyon-filesystem/repository" "seeyon-filesystem/utils" "gorm.io/gorm" ) const maxSessionsPerUser = 3 // 每用户最大同时登录数 // AuthService 认证服务 type AuthService struct { userRepo *repository.UserRepository } // NewAuthService 创建认证服务实例 func NewAuthService() *AuthService { return &AuthService{ userRepo: repository.NewUserRepository(), } } // Login 用户登录(用户名+密码) func (s *AuthService) Login(req *dto.LoginRequest) (*dto.LoginResponse, error) { // 查找用户 user, err := s.userRepo.FindByUsername(req.Username) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户名或密码错误") } return nil, err } // 检查用户状态 if user.Status != 1 { return nil, errors.New("账号已被禁用") } // 验证密码 if !utils.CheckPassword(req.Password, user.Password) { return nil, errors.New("用户名或密码错误") } return s.generateLoginResponse(user, "", "") } // generateLoginResponse 生成登录响应(统一处理Token生成和会话创建) func (s *AuthService) generateLoginResponse(user *model.User, device, ip string) (*dto.LoginResponse, error) { token, err := utils.GenerateToken( user.ID, user.Username, user.Role, config.AppConfig.JWT.Secret, config.AppConfig.JWT.Expiration, ) if err != nil { return nil, errors.New("生成Token失败") } // 创建会话(限制最大同时登录数) s.createSession(user.ID, token, device, ip) return &dto.LoginResponse{ Token: token, Username: user.Username, Nickname: user.Nickname, Role: user.Role, MustChangePwd: user.MustChangePwd == 1, }, nil } // Logout 退出登录, 删除当前会话 func (s *AuthService) Logout(userID uint64, token string) { config.DB.Where("user_id = ? AND token = ?", userID, token).Delete(&model.UserSession{}) } // GetSessions 获取用户的活跃会话列表 func (s *AuthService) GetSessions(userID uint64) []map[string]interface{} { var sessions []model.UserSession config.DB.Where("user_id = ? AND expires_at > ?", userID, time.Now()). Order("created_at DESC").Find(&sessions) result := make([]map[string]interface{}, 0, len(sessions)) for _, sess := range sessions { result = append(result, map[string]interface{}{ "id": sess.ID, "device": sess.Device, "ip": sess.IP, "created_at": sess.CreatedAt.Format("2006-01-02 15:04:05"), "expires_at": sess.ExpiresAt.Format("2006-01-02 15:04:05"), }) } return result } // createSession 创建登录会话, 超过上限时踢掉最旧的 func (s *AuthService) createSession(userID uint64, token, device, ip string) { // 清理该用户过期的会话 config.DB.Where("user_id = ? AND expires_at < ?", userID, time.Now()).Delete(&model.UserSession{}) // 统计当前活跃会话数 var count int64 config.DB.Model(&model.UserSession{}).Where("user_id = ?", userID).Count(&count) // 超过上限, 删除最旧的会话 if count >= maxSessionsPerUser { var oldest model.UserSession config.DB.Where("user_id = ?", userID).Order("created_at ASC").First(&oldest) if oldest.ID > 0 { config.DB.Delete(&oldest) } } // 创建新会话 expiresAt := time.Now().Add(time.Duration(config.AppConfig.JWT.Expiration) * time.Hour) config.DB.Create(&model.UserSession{ UserID: userID, Token: token, Device: device, IP: ip, ExpiresAt: expiresAt, }) } // validateAppLogin 校验 appId 登录 func (s *AuthService) validateAppLogin(appID string, userID uint64) error { // 1. appId 是否存在且启用 var app model.App if err := config.DB.Where("app_id = ? AND status = 1", appID).First(&app).Error; err != nil { return errors.New("应用ID无效或已禁用") } // 2. 用户是否已绑定该应用 var count int64 config.DB.Model(&model.AppUser{}).Where("app_id = ? AND user_id = ?", app.ID, userID).Count(&count) if count == 0 { return errors.New("用户未授权此应用") } return nil } // AppLogin 应用登录(用户名+appId) func (s *AuthService) AppLogin(req *dto.AppLoginRequest) (*dto.LoginResponse, error) { // 查找用户 user, err := s.userRepo.FindByUsername(req.Username) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户名或密码错误") } return nil, err } // 检查用户状态 if user.Status != 1 { return nil, errors.New("账号已被禁用") } // 校验 appId if err := s.validateAppLogin(req.AppID, user.ID); err != nil { return nil, err } return s.generateLoginResponse(user, "", "") } // Register 用户注册 func (s *AuthService) Register(req *dto.RegisterRequest) error { existing, _ := s.userRepo.FindByUsername(req.Username) if existing != nil { return errors.New("用户名已存在") } hashedPassword, err := utils.HashPassword(req.Password) if err != nil { return errors.New("密码加密失败") } user := &model.User{ ID: utils.GenID(), Username: req.Username, Password: hashedPassword, Email: req.Email, Nickname: req.Nickname, Role: "user", Status: 1, } if err := s.userRepo.Create(user); err != nil { return err } // 分配默认角色(user), 使存储策略分配生效 var role model.Role if config.DB.Where("name = ?", "user").First(&role).Error == nil { config.DB.Create(&model.UserRole{UserID: user.ID, RoleID: role.ID}) } return nil } // ChangePassword 修改密码 func (s *AuthService) ChangePassword(userID uint64, req *dto.ChangePasswordRequest) error { user, err := s.userRepo.FindByID(userID) if err != nil { return errors.New("用户不存在") } // 验证旧密码 if !utils.CheckPassword(req.OldPassword, user.Password) { return errors.New("当前密码错误") } // 新密码不能和旧密码相同 if req.OldPassword == req.NewPassword { return errors.New("新密码不能与当前密码相同") } // 加密新密码 hashedPassword, err := utils.HashPassword(req.NewPassword) if err != nil { return errors.New("密码加密失败") } user.Password = hashedPassword user.MustChangePwd = 0 // 清除强制改密码标记 return s.userRepo.Update(user) }