package services import ( "context" "github.com/goravel/framework/contracts/database/orm" "github.com/goravel/framework/facades" apperrors "goravel/app/errors" "goravel/app/models" "goravel/app/utils" "goravel/app/utils/errorlog" ) type UserService interface { // GetByID 根据ID获取用户 GetByID(id uint) (*models.User, error) // GetList 获取用户列表 GetList(filters UserFilters, page, pageSize int) ([]models.User, int64, error) // Create 创建用户 Create(user *models.User) error // CreateWithValidation 创建用户(包含验证、密码加密、默认货币设置) CreateWithValidation(username, password, nickname, email, phone string, status uint8) (*models.User, error) // Update 更新用户 Update(id uint, user *models.User) error // Delete 删除用户(软删除) Delete(id uint) error // UpdateBalance 更新用户余额(同时创建余额变动记录) UpdateBalance(userID uint, amount float64, logType string, source string, sourceID *uint, description string, operatorID *uint, remark string) error // ResetPassword 重置用户密码 ResetPassword(userID uint, newPassword string) error // ValidateUserExists 验证用户是否存在(用户名、邮箱、手机号) ValidateUserExists(username, email, phone string, excludeID uint) error } type UserFilters struct { Username string Email string Phone string Nickname string Status string } // BuildUserQuery 构建用户查询(通用查询构建,供列表和导出复用) func BuildUserQuery(filters UserFilters) orm.Query { query := facades.Orm().Query().Model(&models.User{}) if filters.Username != "" { query = query.Where("username LIKE ?", "%"+filters.Username+"%") } if filters.Nickname != "" { query = query.Where("nickname LIKE ?", "%"+filters.Nickname+"%") } if filters.Email != "" { query = query.Where("email LIKE ?", "%"+filters.Email+"%") } if filters.Phone != "" { query = query.Where("phone LIKE ?", "%"+filters.Phone+"%") } if filters.Status != "" { query = query.Where("status", filters.Status) } return query } type UserServiceImpl struct { balanceLogService UserBalanceLogService } func NewUserService() UserService { return &UserServiceImpl{ balanceLogService: NewUserBalanceLogService(), } } // GetByID 根据ID获取用户 func (s *UserServiceImpl) GetByID(id uint) (*models.User, error) { var user models.User if err := facades.Orm().Query().Where("id", id).FirstOrFail(&user); err != nil { return nil, apperrors.ErrUserNotFound.WithError(err) } // 加载货币信息 if user.CurrencyID > 0 { var currency models.Currency if err := facades.Orm().Query().Where("id", user.CurrencyID).First(¤cy); err == nil { user.Currency = ¤cy } } // 格式化余额 user.Balance = utils.FormatBalance(user.Balance, user.Currency) return &user, nil } // GetList 获取用户列表 func (s *UserServiceImpl) GetList(filters UserFilters, page, pageSize int) ([]models.User, int64, error) { query := BuildUserQuery(filters) // 分页查询 var users []models.User var total int64 if err := query.Order("created_at desc").Paginate(page, pageSize, &users, &total); err != nil { return nil, 0, err } // 加载货币信息并格式化每个用户的余额 for i := range users { if users[i].CurrencyID > 0 { var currency models.Currency if err := facades.Orm().Query().Where("id", users[i].CurrencyID).First(¤cy); err == nil { users[i].Currency = ¤cy } } users[i].Balance = utils.FormatBalance(users[i].Balance, users[i].Currency) } return users, total, nil } // Create 创建用户 func (s *UserServiceImpl) Create(user *models.User) error { // 如果未设置货币ID,默认使用人民币 if user.CurrencyID == 0 { var cnyCurrency models.Currency if err := facades.Orm().Query().Where("code", "CNY").First(&cnyCurrency); err == nil { user.CurrencyID = cnyCurrency.ID } } // 使用 map 创建,确保 Status 为 0 时也能正确保存(与管理员创建方式一致) // GORM 在处理结构体时可能会忽略零值字段,使用 map 可以确保所有字段都被保存 userData := map[string]any{ "username": user.Username, "password": user.Password, "nickname": user.Nickname, "avatar": user.Avatar, "email": user.Email, "phone": user.Phone, "balance": user.Balance, "currency_id": user.CurrencyID, "status": user.Status, "last_login_at": user.LastLoginAt, } if err := facades.Orm().Query().Table("users").Create(userData); err != nil { return err } // 将创建后的 ID 赋值回 user 对象(GORM 会将生成的 ID 填充到 map 中) if id, ok := userData["id"].(uint); ok { user.ID = id } else if id, ok := userData["id"].(uint64); ok { user.ID = uint(id) } else { // 如果 map 中没有 ID,通过用户名查询获取(与管理员创建方式一致) var createdUser models.User if err := facades.Orm().Query().Where("username", user.Username).First(&createdUser); err == nil { user.ID = createdUser.ID } } return nil } // Update 更新用户 func (s *UserServiceImpl) Update(id uint, user *models.User) error { // 如果密码为空,则不更新密码字段 updateData := map[string]any{ "nickname": user.Nickname, "avatar": user.Avatar, "email": user.Email, "phone": user.Phone, "status": user.Status, "currency_id": user.CurrencyID, } // 只有用户名不为空时才更新用户名(通常用户名不允许修改,但保留此逻辑以防需要) if user.Username != "" { updateData["username"] = user.Username } // 只有密码不为空时才更新密码 if user.Password != "" { updateData["password"] = user.Password } _, err := facades.Orm().Query().Model(&models.User{}).Where("id", id).Update(updateData) return err } // Delete 删除用户(软删除) func (s *UserServiceImpl) Delete(id uint) error { _, err := facades.Orm().Query().Where("id", id).Delete(&models.User{}) return err } // UpdateBalance 更新用户余额(同时创建余额变动记录) func (s *UserServiceImpl) UpdateBalance(userID uint, amount float64, logType string, source string, sourceID *uint, description string, operatorID *uint, remark string) error { // 获取当前用户 user, err := s.GetByID(userID) if err != nil { return err } // 计算新余额 var newBalance float64 switch logType { case "income", "refund": newBalance = user.Balance + amount case "expense": newBalance = user.Balance - amount if newBalance < 0 { return apperrors.ErrInsufficientBalance.WithParams(map[string]any{ "balance": user.Balance, }) } default: return apperrors.ErrInvalidBalanceType.WithParams(map[string]any{ "type": logType, }) } // 1. 更新用户余额 _, err = facades.Orm().Query().Model(&models.User{}).Where("id", userID).Update(map[string]any{ "balance": newBalance, }) if err != nil { errorlog.Record(context.Background(), "user", "更新用户余额失败", map[string]any{ "user_id": userID, "amount": amount, "log_type": logType, "new_balance": newBalance, "error": err.Error(), }, "更新用户余额失败: %v", err) return apperrors.ErrUpdateFailed.WithError(err) } // 2. 创建余额变动记录(使用自定义分表逻辑) _, err = s.balanceLogService.CreateLog(userID, logType, amount, newBalance, source, sourceID, description, operatorID, "success", remark) if err != nil { // 如果创建记录失败,尝试回滚余额更新 // 注意:由于涉及分表,无法使用跨表事务,这里手动回滚 rollbackErr := s.rollbackBalance(userID, user.Balance) if rollbackErr != nil { errorlog.Record(context.Background(), "user", "回滚余额失败", map[string]any{ "user_id": userID, "old_balance": user.Balance, "new_balance": newBalance, "rollback_err": rollbackErr.Error(), }, "回滚余额失败: %v", rollbackErr) } errorlog.Record(context.Background(), "user", "创建余额变动记录失败", map[string]any{ "user_id": userID, "amount": amount, "log_type": logType, "new_balance": newBalance, "error": err.Error(), }, "创建余额变动记录失败: %v", err) return apperrors.ErrCreateFailed.WithError(err) } return nil } // rollbackBalance 回滚余额到指定值 func (s *UserServiceImpl) rollbackBalance(userID uint, balance float64) error { _, err := facades.Orm().Query().Model(&models.User{}).Where("id", userID).Update(map[string]any{ "balance": balance, }) return err } // ValidateUserExists 验证用户是否存在(用户名、邮箱、手机号) func (s *UserServiceImpl) ValidateUserExists(username, email, phone string, excludeID uint) error { // 检查用户名是否已存在 if username != "" { query := facades.Orm().Query().Model(&models.User{}).Where("username", username) if excludeID > 0 { query = query.Where("id != ?", excludeID) } exists, err := query.Exists() if err != nil { return apperrors.ErrCreateFailed.WithError(err) } if exists { return apperrors.ErrUsernameExists } } // 检查邮箱是否已存在(如果提供了邮箱) if email != "" { query := facades.Orm().Query().Model(&models.User{}).Where("email", email) if excludeID > 0 { query = query.Where("id != ?", excludeID) } exists, err := query.Exists() if err != nil { return apperrors.ErrCreateFailed.WithError(err) } if exists { return apperrors.NewBusinessError("email_already_exists", "邮箱已存在") } } // 检查手机号是否已存在(如果提供了手机号) if phone != "" { query := facades.Orm().Query().Model(&models.User{}).Where("phone", phone) if excludeID > 0 { query = query.Where("id != ?", excludeID) } exists, err := query.Exists() if err != nil { return apperrors.ErrCreateFailed.WithError(err) } if exists { return apperrors.NewBusinessError("phone_already_exists", "手机号已存在") } } return nil } // CreateWithValidation 创建用户(包含验证、密码加密、默认货币设置) func (s *UserServiceImpl) CreateWithValidation(username, password, nickname, email, phone string, status uint8) (*models.User, error) { // 验证用户是否存在 if err := s.ValidateUserExists(username, email, phone, 0); err != nil { return nil, err } // 密码加密 hashedPassword, err := facades.Hash().Make(password) if err != nil { return nil, apperrors.NewBusinessError("password_encrypt_failed", "密码加密失败").WithError(err) } // 如果未设置货币ID,默认使用人民币 var currencyID uint var cnyCurrency models.Currency if err := facades.Orm().Query().Where("code", "CNY").First(&cnyCurrency); err == nil { currencyID = cnyCurrency.ID } // 创建用户 user := &models.User{ Username: username, Password: hashedPassword, Nickname: nickname, Avatar: "", Email: email, Phone: phone, Balance: 0, CurrencyID: currencyID, Status: status, } if err := s.Create(user); err != nil { return nil, apperrors.ErrCreateFailed.WithError(err) } // 查询创建后的用户(确保获取完整信息) createdUser, err := s.GetByID(user.ID) if err != nil { return nil, err } return createdUser, nil } // ResetPassword 重置用户密码 func (s *UserServiceImpl) ResetPassword(userID uint, newPassword string) error { // 检查用户是否存在 _, err := s.GetByID(userID) if err != nil { return err } // 密码加密 hashedPassword, err := facades.Hash().Make(newPassword) if err != nil { return apperrors.ErrPasswordEncryptFailed.WithError(err) } // 更新密码 _, err = facades.Orm().Query().Model(&models.User{}).Where("id", userID).Update(map[string]any{ "password": hashedPassword, }) if err != nil { return apperrors.ErrUpdateFailed.WithError(err) } return nil }