407 lines
10 KiB
Go
407 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
"licserver/internal/model"
|
|
|
|
"licserver/internal/utils"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
|
|
"github.com/mojocn/base64Captcha"
|
|
)
|
|
|
|
type UserService struct {
|
|
db *gorm.DB
|
|
config *utils.Config
|
|
captchaService *CaptchaService
|
|
}
|
|
|
|
type UserProfile struct {
|
|
ID uint `json:"id"`
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Role string `json:"role"`
|
|
}
|
|
|
|
// LoginInput 登录输入
|
|
type LoginInput struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
CaptchaId string `json:"captchaId" binding:"required"`
|
|
Captcha string `json:"captcha" binding:"required"`
|
|
}
|
|
|
|
func NewUserService(db *gorm.DB, config *utils.Config) *UserService {
|
|
return &UserService{
|
|
db: db,
|
|
config: config,
|
|
captchaService: NewCaptchaService(db, &config.Email),
|
|
}
|
|
}
|
|
|
|
func (s *UserService) Register(username, password, email, captcha string) error {
|
|
// 验证验证码
|
|
if err := s.captchaService.VerifyCaptcha(email, "register", captcha); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 检查用户名是否已存在
|
|
var count int64
|
|
s.db.Model(&model.User{}).Where("username = ?", username).Count(&count)
|
|
if count > 0 {
|
|
return errors.New("用户名已存在")
|
|
}
|
|
|
|
// 检查邮箱是否已存在
|
|
s.db.Model(&model.User{}).Where("email = ?", email).Count(&count)
|
|
if count > 0 {
|
|
return errors.New("邮箱已被注册")
|
|
}
|
|
|
|
// 原有的注册逻辑
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user := model.User{
|
|
Username: username,
|
|
Password: string(hashedPassword),
|
|
Email: email,
|
|
Role: "user",
|
|
}
|
|
|
|
return s.db.Create(&user).Error
|
|
}
|
|
|
|
func (s *UserService) Login(username, password string) (string, error) {
|
|
var user model.User
|
|
if err := s.db.Where("username = ?", username).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return "", errors.New("用户不存在")
|
|
}
|
|
return "", err
|
|
|
|
}
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
|
|
return "", errors.New("密码错误")
|
|
}
|
|
|
|
// 生成 JWT token
|
|
token, err := utils.GenerateToken(user.ID, user.Username, user.Role, &s.config.JWT)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// 更新最后登录时间
|
|
s.db.Model(&user).Update("last_login", gorm.Expr("CURRENT_TIMESTAMP"))
|
|
return token, nil
|
|
|
|
}
|
|
|
|
func (s *UserService) GetUserByID(id uint) (*UserProfile, error) {
|
|
var user model.User
|
|
if err := s.db.First(&user, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &UserProfile{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (s *UserService) UpdateProfile(userID uint, email string) error {
|
|
// 检查邮箱是否被其他用户使用
|
|
var count int64
|
|
if err := s.db.Model(&model.User{}).Where("email = ? AND id != ?", email, userID).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return errors.New("邮箱已被其他用户使用")
|
|
}
|
|
|
|
// 更新用户信息
|
|
return s.db.Model(&model.User{}).Where("id = ?", userID).Update("email", email).Error
|
|
}
|
|
|
|
func (s *UserService) ChangePassword(userID uint, oldPassword, newPassword string) error {
|
|
var user model.User
|
|
if err := s.db.First(&user, userID).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// 验证旧密码
|
|
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
|
|
}
|
|
return s.db.Model(&user).Update("password", string(hashedPassword)).Error
|
|
}
|
|
|
|
func (s *UserService) ResetPassword(email, captcha string) error {
|
|
// 验证验证码
|
|
if err := s.captchaService.VerifyCaptcha(email, "reset", captcha); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 原有的重置密码逻辑
|
|
var user model.User
|
|
if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
|
|
return errors.New("邮箱不存在")
|
|
}
|
|
|
|
// 生成重置令牌
|
|
token, err := utils.GenerateResetToken()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 保存重置令牌
|
|
resetToken := model.PasswordResetToken{
|
|
UserID: user.ID,
|
|
Token: token,
|
|
ExpiresAt: time.Now().Add(24 * time.Hour),
|
|
Used: false,
|
|
}
|
|
if err := s.db.Create(&resetToken).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// 发送重置邮件
|
|
emailService := utils.NewEmailService(&s.config.Email)
|
|
resetLink := fmt.Sprintf("http://localhost:%s/reset-password?token=%s", s.config.Server.Port, token)
|
|
emailBody := fmt.Sprintf(`
|
|
<h3>密码重置</h3>
|
|
<p>您好,%s</p>
|
|
<p>请点击以下链接重置您的密码:</p>
|
|
<p><a href="%s">重置密码</a></p>
|
|
<p>此链接将在24小时后失效。</p>
|
|
<p>如果您没有请求重置密码,请忽略此邮件。</p>
|
|
`, user.Username, resetLink)
|
|
return emailService.SendEmail(user.Email, "密码重置", emailBody)
|
|
|
|
}
|
|
|
|
func (s *UserService) ValidateResetToken(token string) (*model.User, error) {
|
|
var resetToken model.PasswordResetToken
|
|
if err := s.db.Where("token = ? AND used = ? AND expires_at > ?",
|
|
token, false, time.Now()).First(&resetToken).Error; err != nil {
|
|
return nil, errors.New("无效或已过期的重置令牌")
|
|
}
|
|
|
|
var user model.User
|
|
if err := s.db.First(&user, resetToken.UserID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &user, nil
|
|
|
|
}
|
|
|
|
func (s *UserService) ResetPasswordWithToken(token, newPassword string) error {
|
|
user, err := s.ValidateResetToken(token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新密码
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 使用事务确保原子性
|
|
return s.db.Transaction(func(tx *gorm.DB) error {
|
|
|
|
if err := tx.Model(&user).Update("password", string(hashedPassword)).Error; err != nil {
|
|
return err
|
|
}
|
|
// 标记令牌为已使用
|
|
if err := tx.Model(&model.PasswordResetToken{}).
|
|
Where("token = ?", token).
|
|
Update("used", true).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (s *UserService) SendRegisterCaptcha(email string) error {
|
|
// 检查邮箱是否已被注册
|
|
var count int64
|
|
s.db.Model(&model.User{}).Where("email = ?", email).Count(&count)
|
|
if count > 0 {
|
|
return errors.New("邮箱已被注册")
|
|
}
|
|
|
|
return s.captchaService.SendEmailCaptcha(email, "register")
|
|
}
|
|
|
|
func (s *UserService) SendResetPasswordCaptcha(email string) error {
|
|
var user model.User
|
|
if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
|
|
return errors.New("邮箱不存在")
|
|
}
|
|
|
|
return s.captchaService.SendEmailCaptcha(email, "reset")
|
|
}
|
|
|
|
func (s *UserService) GetCaptchaService() *CaptchaService {
|
|
return s.captchaService
|
|
}
|
|
|
|
// GetUsers 获取用户列表
|
|
func (s *UserService) GetUsers(username, role string, page, pageSize int) ([]UserProfile, int64, error) {
|
|
var users []model.User
|
|
var total int64
|
|
var profiles []UserProfile
|
|
|
|
query := s.db.Model(&model.User{})
|
|
|
|
if username != "" {
|
|
query = query.Where("username LIKE ?", "%"+username+"%")
|
|
}
|
|
if role != "" {
|
|
query = query.Where("role = ?", role)
|
|
}
|
|
|
|
// 获取总数
|
|
query.Count(&total)
|
|
|
|
// 分页查询
|
|
if page > 0 && pageSize > 0 {
|
|
offset := (page - 1) * pageSize
|
|
query = query.Offset(offset).Limit(pageSize)
|
|
}
|
|
|
|
if err := query.Find(&users).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// 转换为 UserProfile
|
|
for _, user := range users {
|
|
profiles = append(profiles, UserProfile{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
})
|
|
}
|
|
|
|
return profiles, total, nil
|
|
}
|
|
|
|
// CreateUser 创建新用户
|
|
func (s *UserService) CreateUser(username, password, email, role string) error {
|
|
// 检查用户名是否已存在
|
|
var count int64
|
|
s.db.Model(&model.User{}).Where("username = ?", username).Count(&count)
|
|
if count > 0 {
|
|
return errors.New("用户名已存在")
|
|
}
|
|
|
|
// 检查邮箱是否已存在
|
|
s.db.Model(&model.User{}).Where("email = ?", email).Count(&count)
|
|
if count > 0 {
|
|
return errors.New("邮箱已被注册")
|
|
}
|
|
|
|
// 加密密码
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user := model.User{
|
|
Username: username,
|
|
Password: string(hashedPassword),
|
|
Email: email,
|
|
Role: role,
|
|
}
|
|
|
|
return s.db.Create(&user).Error
|
|
}
|
|
|
|
// UpdateUser 更新用户信息
|
|
func (s *UserService) UpdateUser(id uint, email, role string) error {
|
|
// 检查邮箱是否被其他用户使用
|
|
var count int64
|
|
s.db.Model(&model.User{}).Where("email = ? AND id != ?", email, id).Count(&count)
|
|
if count > 0 {
|
|
return errors.New("邮箱已被其他用户使用")
|
|
}
|
|
|
|
return s.db.Model(&model.User{}).Where("id = ?", id).Updates(map[string]interface{}{
|
|
"email": email,
|
|
"role": role,
|
|
}).Error
|
|
}
|
|
|
|
// DeleteUser 删除用户
|
|
func (s *UserService) DeleteUser(id uint) error {
|
|
// 检查是否为最后一个管理员
|
|
var adminCount int64
|
|
s.db.Model(&model.User{}).Where("role = ?", "admin").Count(&adminCount)
|
|
|
|
var user model.User
|
|
if err := s.db.First(&user, id).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if user.Role == "admin" && adminCount <= 1 {
|
|
return errors.New("不能删除最后一个管理员")
|
|
}
|
|
|
|
return s.db.Delete(&model.User{}, id).Error
|
|
}
|
|
|
|
// ValidateUser 验证用户名密码
|
|
func (s *UserService) ValidateUser(username, password string) (*model.User, error) {
|
|
var user model.User
|
|
if err := s.db.Where("username = ?", username).First(&user).Error; err != nil {
|
|
return nil, errors.New("用户不存在")
|
|
}
|
|
|
|
// 使用 bcrypt 比较密码
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
|
|
return nil, errors.New("密码错误")
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// GenerateToken 生成JWT token
|
|
func (s *UserService) GenerateToken(user *model.User) (string, error) {
|
|
claims := jwt.MapClaims{
|
|
"user_id": user.ID,
|
|
"username": user.Username,
|
|
"role": user.Role,
|
|
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return token.SignedString([]byte(s.config.JWT.Secret))
|
|
}
|
|
|
|
// VerifyCaptcha 验证验证码
|
|
func (s *UserService) VerifyCaptcha(captchaId, captcha string) bool {
|
|
return base64Captcha.DefaultMemStore.Verify(captchaId, captcha, true)
|
|
}
|