主要变更: - 采用 go-zero 框架替代 Gin,提升开发效率 - 重构项目结构,API 文件模块化组织 - 将 model 移至 api/internal/model 目录 - 移除 common 包,改为标准 pkg 目录结构 - 实现统一的仓储模式,支持配置驱动数据库切换 - 简化测试策略,专注 API 集成测试 - 更新 CLAUDE.md 文档,提供详细的开发指导 技术栈更新: - 框架: Gin → go-zero v1.6.0+ - 代码生成: 引入 goctl 工具 - 架构模式: 四层架构 → go-zero 三层架构 (Handler→Logic→Model) - 项目布局: 遵循 Go 社区标准和 go-zero 最佳实践
432 lines
12 KiB
Go
432 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"photography-backend/internal/model/entity"
|
|
|
|
"go.uber.org/zap"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type UserService struct {
|
|
db *gorm.DB
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewUserService(db *gorm.DB, logger *zap.Logger) *UserService {
|
|
return &UserService{
|
|
db: db,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// UserListParams 用户列表查询参数
|
|
type UserListParams struct {
|
|
Page int `json:"page" form:"page"`
|
|
Limit int `json:"limit" form:"limit"`
|
|
Search string `json:"search" form:"search"`
|
|
Role string `json:"role" form:"role"`
|
|
IsActive *bool `json:"is_active" form:"is_active"`
|
|
}
|
|
|
|
// UserListResponse 用户列表响应
|
|
type UserListResponse struct {
|
|
Users []entity.User `json:"users"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
Limit int `json:"limit"`
|
|
Pages int `json:"pages"`
|
|
}
|
|
|
|
// GetUsers 获取用户列表
|
|
func (s *UserService) GetUsers(ctx context.Context, params UserListParams) (*UserListResponse, error) {
|
|
// 设置默认值
|
|
if params.Page <= 0 {
|
|
params.Page = 1
|
|
}
|
|
if params.Limit <= 0 {
|
|
params.Limit = 20
|
|
}
|
|
if params.Limit > 100 {
|
|
params.Limit = 100
|
|
}
|
|
|
|
// 构建查询
|
|
query := s.db.WithContext(ctx)
|
|
|
|
// 搜索过滤
|
|
if params.Search != "" {
|
|
searchPattern := "%" + params.Search + "%"
|
|
query = query.Where("username ILIKE ? OR email ILIKE ?", searchPattern, searchPattern)
|
|
}
|
|
|
|
// 角色过滤
|
|
if params.Role != "" {
|
|
query = query.Where("role = ?", params.Role)
|
|
}
|
|
|
|
// 状态过滤
|
|
if params.IsActive != nil {
|
|
query = query.Where("is_active = ?", *params.IsActive)
|
|
}
|
|
|
|
// 计算总数
|
|
var total int64
|
|
countQuery := query
|
|
if err := countQuery.Model(&entity.User{}).Count(&total).Error; err != nil {
|
|
s.logger.Error("Failed to count users", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
// 分页查询
|
|
offset := (params.Page - 1) * params.Limit
|
|
var users []entity.User
|
|
if err := query.
|
|
Order("created_at DESC").
|
|
Offset(offset).
|
|
Limit(params.Limit).
|
|
Find(&users).Error; err != nil {
|
|
s.logger.Error("Failed to get users", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
// 计算总页数
|
|
pages := int((total + int64(params.Limit) - 1) / int64(params.Limit))
|
|
|
|
return &UserListResponse{
|
|
Users: users,
|
|
Total: total,
|
|
Page: params.Page,
|
|
Limit: params.Limit,
|
|
Pages: pages,
|
|
}, nil
|
|
}
|
|
|
|
// GetUserByID 根据ID获取用户
|
|
func (s *UserService) GetUserByID(ctx context.Context, id uint) (*entity.User, error) {
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
s.logger.Error("Failed to get user by ID", zap.Error(err), zap.Uint("id", id))
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// GetUserByUsername 根据用户名获取用户
|
|
func (s *UserService) GetUserByUsername(ctx context.Context, username string) (*entity.User, error) {
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).Where("username = ?", username).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
s.logger.Error("Failed to get user by username", zap.Error(err), zap.String("username", username))
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// GetUserByEmail 根据邮箱获取用户
|
|
func (s *UserService) GetUserByEmail(ctx context.Context, email string) (*entity.User, error) {
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
s.logger.Error("Failed to get user by email", zap.Error(err), zap.String("email", email))
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// CreateUser 创建用户
|
|
func (s *UserService) CreateUser(ctx context.Context, req *entity.CreateUserRequest) (*entity.User, error) {
|
|
// 验证用户名唯一性
|
|
var existingUser entity.User
|
|
if err := s.db.WithContext(ctx).Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("username already exists")
|
|
}
|
|
|
|
// 验证邮箱唯一性
|
|
if err := s.db.WithContext(ctx).Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("email already exists")
|
|
}
|
|
|
|
// 加密密码
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
s.logger.Error("Failed to hash password", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
user := &entity.User{
|
|
Username: req.Username,
|
|
Email: req.Email,
|
|
Password: string(hashedPassword),
|
|
Role: req.Role,
|
|
IsActive: true,
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).Create(user).Error; err != nil {
|
|
s.logger.Error("Failed to create user", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
s.logger.Info("User created successfully", zap.Uint("id", user.ID))
|
|
return user, nil
|
|
}
|
|
|
|
// UpdateUser 更新用户
|
|
func (s *UserService) UpdateUser(ctx context.Context, id uint, req *entity.UpdateUserRequest) (*entity.User, error) {
|
|
// 检查用户是否存在
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// 构建更新数据
|
|
updates := map[string]interface{}{}
|
|
|
|
if req.Username != nil {
|
|
// 验证用户名唯一性
|
|
var existingUser entity.User
|
|
if err := s.db.WithContext(ctx).Where("username = ? AND id != ?", *req.Username, id).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("username already exists")
|
|
}
|
|
updates["username"] = *req.Username
|
|
}
|
|
|
|
if req.Email != nil {
|
|
// 验证邮箱唯一性
|
|
var existingUser entity.User
|
|
if err := s.db.WithContext(ctx).Where("email = ? AND id != ?", *req.Email, id).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("email already exists")
|
|
}
|
|
updates["email"] = *req.Email
|
|
}
|
|
|
|
if req.Role != nil {
|
|
updates["role"] = *req.Role
|
|
}
|
|
|
|
if req.IsActive != nil {
|
|
updates["is_active"] = *req.IsActive
|
|
}
|
|
|
|
if len(updates) > 0 {
|
|
if err := s.db.WithContext(ctx).Model(&user).Updates(updates).Error; err != nil {
|
|
s.logger.Error("Failed to update user", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
s.logger.Info("User updated successfully", zap.Uint("id", id))
|
|
return &user, nil
|
|
}
|
|
|
|
// UpdateCurrentUser 更新当前用户信息
|
|
func (s *UserService) UpdateCurrentUser(ctx context.Context, id uint, req *entity.UpdateCurrentUserRequest) (*entity.User, error) {
|
|
// 检查用户是否存在
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// 构建更新数据
|
|
updates := map[string]interface{}{}
|
|
|
|
if req.Username != nil {
|
|
// 验证用户名唯一性
|
|
var existingUser entity.User
|
|
if err := s.db.WithContext(ctx).Where("username = ? AND id != ?", *req.Username, id).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("username already exists")
|
|
}
|
|
updates["username"] = *req.Username
|
|
}
|
|
|
|
if req.Email != nil {
|
|
// 验证邮箱唯一性
|
|
var existingUser entity.User
|
|
if err := s.db.WithContext(ctx).Where("email = ? AND id != ?", *req.Email, id).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("email already exists")
|
|
}
|
|
updates["email"] = *req.Email
|
|
}
|
|
|
|
if len(updates) > 0 {
|
|
if err := s.db.WithContext(ctx).Model(&user).Updates(updates).Error; err != nil {
|
|
s.logger.Error("Failed to update current user", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
s.logger.Info("Current user updated successfully", zap.Uint("id", id))
|
|
return &user, nil
|
|
}
|
|
|
|
// DeleteUser 删除用户
|
|
func (s *UserService) DeleteUser(ctx context.Context, id uint) error {
|
|
// 检查用户是否存在
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("user not found")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// 删除用户
|
|
if err := s.db.WithContext(ctx).Delete(&user).Error; err != nil {
|
|
s.logger.Error("Failed to delete user", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
s.logger.Info("User deleted successfully", zap.Uint("id", id))
|
|
return nil
|
|
}
|
|
|
|
// ChangePassword 修改密码
|
|
func (s *UserService) ChangePassword(ctx context.Context, id uint, req *entity.ChangePasswordRequest) error {
|
|
// 检查用户是否存在
|
|
var user entity.User
|
|
if err := s.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("user not found")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// 验证旧密码
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
|
|
return errors.New("old password is incorrect")
|
|
}
|
|
|
|
// 加密新密码
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
s.logger.Error("Failed to hash new password", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
// 更新密码
|
|
if err := s.db.WithContext(ctx).Model(&user).Update("password", string(hashedPassword)).Error; err != nil {
|
|
s.logger.Error("Failed to update password", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
s.logger.Info("Password changed successfully", zap.Uint("id", id))
|
|
return nil
|
|
}
|
|
|
|
// ValidateCredentials 验证用户凭据
|
|
func (s *UserService) ValidateCredentials(ctx context.Context, username, password string) (*entity.User, error) {
|
|
var user entity.User
|
|
|
|
// 根据用户名或邮箱查找用户
|
|
if err := s.db.WithContext(ctx).Where("username = ? OR email = ?", username, username).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("invalid credentials")
|
|
}
|
|
s.logger.Error("Failed to find user", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
// 检查用户是否激活
|
|
if !user.IsActive {
|
|
return nil, errors.New("user account is disabled")
|
|
}
|
|
|
|
// 验证密码
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
|
|
return nil, errors.New("invalid credentials")
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// GetUserStats 获取用户统计信息
|
|
func (s *UserService) GetUserStats(ctx context.Context) (*entity.UserStats, error) {
|
|
var stats entity.UserStats
|
|
|
|
// 总用户数
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).Count(&stats.Total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 活跃用户数
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).
|
|
Where("is_active = ?", true).Count(&stats.Active).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 按角色统计
|
|
var roleStats []struct {
|
|
Role string `json:"role"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).
|
|
Select("role, COUNT(*) as count").
|
|
Where("is_active = ?", true).
|
|
Group("role").
|
|
Find(&roleStats).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stats.RoleStats = make(map[string]int64)
|
|
for _, stat := range roleStats {
|
|
stats.RoleStats[stat.Role] = stat.Count
|
|
}
|
|
|
|
// 本月新增用户
|
|
startOfMonth := time.Now().AddDate(0, 0, -time.Now().Day()+1)
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).
|
|
Where("created_at >= ?", startOfMonth).
|
|
Count(&stats.ThisMonth).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 今日新增用户
|
|
startOfDay := time.Now().Truncate(24 * time.Hour)
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).
|
|
Where("created_at >= ?", startOfDay).
|
|
Count(&stats.Today).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &stats, nil
|
|
}
|
|
|
|
// IsUsernameAvailable 检查用户名是否可用
|
|
func (s *UserService) IsUsernameAvailable(ctx context.Context, username string) (bool, error) {
|
|
var count int64
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).
|
|
Where("username = ?", username).Count(&count).Error; err != nil {
|
|
return false, err
|
|
}
|
|
return count == 0, nil
|
|
}
|
|
|
|
// IsEmailAvailable 检查邮箱是否可用
|
|
func (s *UserService) IsEmailAvailable(ctx context.Context, email string) (bool, error) {
|
|
var count int64
|
|
if err := s.db.WithContext(ctx).Model(&entity.User{}).
|
|
Where("email = ?", email).Count(&count).Error; err != nil {
|
|
return false, err
|
|
}
|
|
return count == 0, nil
|
|
} |