LicenseManger/internal/service/user.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)
}