2026-07-03 15:58:29 +08:00
|
|
|
// Package service 提供业务逻辑层
|
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
2026-07-18 10:52:32 +08:00
|
|
|
"time"
|
2026-07-03 15:58:29 +08:00
|
|
|
|
|
|
|
|
"seeyon-filesystem/config"
|
|
|
|
|
"seeyon-filesystem/dto"
|
|
|
|
|
"seeyon-filesystem/model"
|
|
|
|
|
"seeyon-filesystem/repository"
|
|
|
|
|
"seeyon-filesystem/utils"
|
|
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
const maxSessionsPerUser = 3 // 每用户最大同时登录数
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
// AuthService 认证服务
|
|
|
|
|
type AuthService struct {
|
|
|
|
|
userRepo *repository.UserRepository
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewAuthService 创建认证服务实例
|
|
|
|
|
func NewAuthService() *AuthService {
|
|
|
|
|
return &AuthService{
|
|
|
|
|
userRepo: repository.NewUserRepository(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// Login 用户登录(用户名+密码)
|
2026-07-03 15:58:29 +08:00
|
|
|
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("用户名或密码错误")
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
return s.generateLoginResponse(user, "", "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// generateLoginResponse 生成登录响应(统一处理Token生成和会话创建)
|
|
|
|
|
func (s *AuthService) generateLoginResponse(user *model.User, device, ip string) (*dto.LoginResponse, error) {
|
2026-07-03 15:58:29 +08:00
|
|
|
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失败")
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
// 创建会话(限制最大同时登录数)
|
|
|
|
|
s.createSession(user.ID, token, device, ip)
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
return &dto.LoginResponse{
|
|
|
|
|
Token: token,
|
|
|
|
|
Username: user.Username,
|
|
|
|
|
Nickname: user.Nickname,
|
|
|
|
|
Role: user.Role,
|
|
|
|
|
MustChangePwd: user.MustChangePwd == 1,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
// 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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
return s.generateLoginResponse(user, "", "")
|
2026-07-10 17:33:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
// 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{
|
2026-07-18 10:52:32 +08:00
|
|
|
ID: utils.GenID(),
|
2026-07-03 15:58:29 +08:00
|
|
|
Username: req.Username,
|
|
|
|
|
Password: hashedPassword,
|
|
|
|
|
Email: req.Email,
|
|
|
|
|
Nickname: req.Nickname,
|
|
|
|
|
Role: "user",
|
|
|
|
|
Status: 1,
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
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
|
2026-07-03 15:58:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|