refactor: 重构后端架构为 go-zero 框架,优化项目结构
主要变更: - 采用 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 最佳实践
This commit is contained in:
516
backend/internal/repository/postgres/user_repository_impl.go
Normal file
516
backend/internal/repository/postgres/user_repository_impl.go
Normal file
@ -0,0 +1,516 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"photography-backend/internal/model/entity"
|
||||
"photography-backend/internal/repository/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// userRepositoryImpl 用户仓储实现
|
||||
type userRepositoryImpl struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserRepository 创建用户仓储实现
|
||||
func NewUserRepository(db *gorm.DB, logger *zap.Logger) interfaces.UserRepository {
|
||||
return &userRepositoryImpl{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
func (r *userRepositoryImpl) Create(ctx context.Context, user *entity.User) error {
|
||||
return r.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户
|
||||
func (r *userRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.WithContext(ctx).First(&user, id).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByEmail 根据邮箱获取用户
|
||||
func (r *userRepositoryImpl) GetByEmail(ctx context.Context, email string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.WithContext(ctx).Where("email = ?", email).First(&user).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByUsername 根据用户名获取用户
|
||||
func (r *userRepositoryImpl) GetByUsername(ctx context.Context, username string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.WithContext(ctx).Where("username = ?", username).First(&user).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByEmailOrUsername 根据邮箱或用户名获取用户
|
||||
func (r *userRepositoryImpl) GetByEmailOrUsername(ctx context.Context, emailOrUsername string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("email = ? OR username = ?", emailOrUsername, emailOrUsername).
|
||||
First(&user).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Update 更新用户
|
||||
func (r *userRepositoryImpl) Update(ctx context.Context, user *entity.User) error {
|
||||
return r.db.WithContext(ctx).Save(user).Error
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (r *userRepositoryImpl) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&entity.User{}, id).Error
|
||||
}
|
||||
|
||||
// List 获取用户列表
|
||||
func (r *userRepositoryImpl) List(ctx context.Context, params *entity.UserListParams) ([]*entity.User, int64, error) {
|
||||
var users []*entity.User
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entity.User{})
|
||||
|
||||
// 应用过滤条件
|
||||
if params.Role != nil {
|
||||
query = query.Where("role = ?", *params.Role)
|
||||
}
|
||||
|
||||
if params.Status != nil {
|
||||
query = query.Where("status = ?", *params.Status)
|
||||
}
|
||||
|
||||
if params.Search != "" {
|
||||
query = query.Where("username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
|
||||
"%"+params.Search+"%", "%"+params.Search+"%", "%"+params.Search+"%", "%"+params.Search+"%")
|
||||
}
|
||||
|
||||
if params.CreatedFrom != nil {
|
||||
query = query.Where("created_at >= ?", *params.CreatedFrom)
|
||||
}
|
||||
|
||||
if params.CreatedTo != nil {
|
||||
query = query.Where("created_at <= ?", *params.CreatedTo)
|
||||
}
|
||||
|
||||
if params.LastLoginFrom != nil {
|
||||
query = query.Where("last_login_at >= ?", *params.LastLoginFrom)
|
||||
}
|
||||
|
||||
if params.LastLoginTo != nil {
|
||||
query = query.Where("last_login_at <= ?", *params.LastLoginTo)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
orderBy := "created_at DESC"
|
||||
if params.Sort != "" {
|
||||
order := "ASC"
|
||||
if params.Order == "desc" {
|
||||
order = "DESC"
|
||||
}
|
||||
orderBy = fmt.Sprintf("%s %s", params.Sort, order)
|
||||
}
|
||||
query = query.Order(orderBy)
|
||||
|
||||
// 应用分页
|
||||
if params.Page > 0 && params.Limit > 0 {
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
query = query.Offset(offset).Limit(params.Limit)
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
if err := query.Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// ListByRole 根据角色获取用户列表
|
||||
func (r *userRepositoryImpl) ListByRole(ctx context.Context, role entity.UserRole, params *entity.UserListParams) ([]*entity.User, int64, error) {
|
||||
if params == nil {
|
||||
params = &entity.UserListParams{}
|
||||
}
|
||||
params.Role = &role
|
||||
return r.List(ctx, params)
|
||||
}
|
||||
|
||||
// ListByStatus 根据状态获取用户列表
|
||||
func (r *userRepositoryImpl) ListByStatus(ctx context.Context, status entity.UserStatus, params *entity.UserListParams) ([]*entity.User, int64, error) {
|
||||
if params == nil {
|
||||
params = &entity.UserListParams{}
|
||||
}
|
||||
params.Status = &status
|
||||
return r.List(ctx, params)
|
||||
}
|
||||
|
||||
// SearchUsers 搜索用户
|
||||
func (r *userRepositoryImpl) SearchUsers(ctx context.Context, keyword string, params *entity.UserListParams) ([]*entity.User, int64, error) {
|
||||
if params == nil {
|
||||
params = &entity.UserListParams{}
|
||||
}
|
||||
params.Search = keyword
|
||||
return r.List(ctx, params)
|
||||
}
|
||||
|
||||
// Count 统计用户总数
|
||||
func (r *userRepositoryImpl) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entity.User{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountByRole 根据角色统计用户数
|
||||
func (r *userRepositoryImpl) CountByRole(ctx context.Context, role entity.UserRole) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("role = ?", role).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountByStatus 根据状态统计用户数
|
||||
func (r *userRepositoryImpl) CountByStatus(ctx context.Context, status entity.UserStatus) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("status = ?", status).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountActiveUsers 统计活跃用户数
|
||||
func (r *userRepositoryImpl) CountActiveUsers(ctx context.Context) (int64, error) {
|
||||
return r.CountByStatus(ctx, entity.UserStatusActive)
|
||||
}
|
||||
|
||||
// UpdateStatus 更新用户状态
|
||||
func (r *userRepositoryImpl) UpdateStatus(ctx context.Context, id uint, status entity.UserStatus) error {
|
||||
return r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// UpdateLastLogin 更新最后登录时间
|
||||
func (r *userRepositoryImpl) UpdateLastLogin(ctx context.Context, id uint) error {
|
||||
now := time.Now()
|
||||
return r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("last_login_at", now).Error
|
||||
}
|
||||
|
||||
// UpdatePassword 更新密码
|
||||
func (r *userRepositoryImpl) UpdatePassword(ctx context.Context, id uint, hashedPassword string) error {
|
||||
return r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("password", hashedPassword).Error
|
||||
}
|
||||
|
||||
// SoftDelete 软删除用户
|
||||
func (r *userRepositoryImpl) SoftDelete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&entity.User{}, id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复软删除用户
|
||||
func (r *userRepositoryImpl) Restore(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entity.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新用户状态
|
||||
func (r *userRepositoryImpl) BatchUpdateStatus(ctx context.Context, ids []uint, status entity.UserStatus) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("id IN ?", ids).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除用户
|
||||
func (r *userRepositoryImpl) BatchDelete(ctx context.Context, ids []uint) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Delete(&entity.User{}, ids).Error
|
||||
}
|
||||
|
||||
// ValidateEmailUnique 验证邮箱唯一性
|
||||
func (r *userRepositoryImpl) ValidateEmailUnique(ctx context.Context, email string, excludeID uint) error {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entity.User{}).Where("email = ?", email)
|
||||
|
||||
if excludeID > 0 {
|
||||
query = query.Where("id != ?", excludeID)
|
||||
}
|
||||
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return errors.New("email already exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateUsernameUnique 验证用户名唯一性
|
||||
func (r *userRepositoryImpl) ValidateUsernameUnique(ctx context.Context, username string, excludeID uint) error {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entity.User{}).Where("username = ?", username)
|
||||
|
||||
if excludeID > 0 {
|
||||
query = query.Where("id != ?", excludeID)
|
||||
}
|
||||
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return errors.New("username already exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserPhotos 获取用户照片
|
||||
func (r *userRepositoryImpl) GetUserPhotos(ctx context.Context, userID uint, params *entity.PhotoListParams) ([]*entity.Photo, int64, error) {
|
||||
var photos []*entity.Photo
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entity.Photo{}).Where("user_id = ?", userID)
|
||||
|
||||
// 应用过滤条件
|
||||
if params != nil {
|
||||
if params.Status != nil {
|
||||
query = query.Where("status = ?", *params.Status)
|
||||
}
|
||||
|
||||
if params.CategoryID != nil {
|
||||
query = query.Joins("JOIN photo_categories ON photos.id = photo_categories.photo_id").
|
||||
Where("photo_categories.category_id = ?", *params.CategoryID)
|
||||
}
|
||||
|
||||
if params.TagID != nil {
|
||||
query = query.Joins("JOIN photo_tags ON photos.id = photo_tags.photo_id").
|
||||
Where("photo_tags.tag_id = ?", *params.TagID)
|
||||
}
|
||||
|
||||
if params.DateFrom != nil {
|
||||
query = query.Where("taken_at >= ?", *params.DateFrom)
|
||||
}
|
||||
|
||||
if params.DateTo != nil {
|
||||
query = query.Where("taken_at <= ?", *params.DateTo)
|
||||
}
|
||||
|
||||
if params.Search != "" {
|
||||
query = query.Where("title ILIKE ? OR description ILIKE ?",
|
||||
"%"+params.Search+"%", "%"+params.Search+"%")
|
||||
}
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用排序和分页
|
||||
if params != nil {
|
||||
orderBy := "created_at DESC"
|
||||
if params.Sort != "" {
|
||||
order := "ASC"
|
||||
if params.Order == "desc" {
|
||||
order = "DESC"
|
||||
}
|
||||
orderBy = fmt.Sprintf("%s %s", params.Sort, order)
|
||||
}
|
||||
query = query.Order(orderBy)
|
||||
|
||||
if params.Page > 0 && params.Limit > 0 {
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
query = query.Offset(offset).Limit(params.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
query = query.Preload("Categories").Preload("Tags")
|
||||
|
||||
// 查询数据
|
||||
if err := query.Find(&photos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return photos, total, nil
|
||||
}
|
||||
|
||||
// GetUserStats 获取用户统计信息
|
||||
func (r *userRepositoryImpl) GetUserStats(ctx context.Context, userID uint) (*entity.UserStats, error) {
|
||||
var stats entity.UserStats
|
||||
|
||||
// 照片统计
|
||||
var photoCount int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Where("user_id = ?", userID).Count(&photoCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PhotoCount = photoCount
|
||||
|
||||
// 按状态统计照片
|
||||
for _, status := range []entity.PhotoStatus{
|
||||
entity.PhotoStatusActive,
|
||||
entity.PhotoStatusDraft,
|
||||
entity.PhotoStatusArchived,
|
||||
} {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Where("user_id = ? AND status = ?", userID, status).
|
||||
Count(&count).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case entity.PhotoStatusActive:
|
||||
stats.PublishedPhotos = count
|
||||
case entity.PhotoStatusDraft:
|
||||
stats.DraftPhotos = count
|
||||
case entity.PhotoStatusArchived:
|
||||
stats.ArchivedPhotos = count
|
||||
}
|
||||
}
|
||||
|
||||
// 总浏览数
|
||||
var totalViews int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Where("user_id = ?", userID).
|
||||
Select("COALESCE(SUM(view_count), 0)").Row().Scan(&totalViews); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.TotalViews = totalViews
|
||||
|
||||
// 总下载数
|
||||
var totalDownloads int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Where("user_id = ?", userID).
|
||||
Select("COALESCE(SUM(download_count), 0)").Row().Scan(&totalDownloads); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.TotalDownloads = totalDownloads
|
||||
|
||||
// 存储空间使用
|
||||
var storageUsed int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Where("user_id = ?", userID).
|
||||
Select("COALESCE(SUM(file_size), 0)").Row().Scan(&storageUsed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.StorageUsed = storageUsed
|
||||
|
||||
// 本月新增照片
|
||||
now := time.Now()
|
||||
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
|
||||
var monthlyPhotos int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Where("user_id = ? AND created_at >= ? AND created_at <= ?", userID, startOfMonth, endOfMonth).
|
||||
Count(&monthlyPhotos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.MonthlyPhotos = monthlyPhotos
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetAllStats 获取全部用户统计信息
|
||||
func (r *userRepositoryImpl) GetAllStats(ctx context.Context) (*entity.UserGlobalStats, error) {
|
||||
var stats entity.UserGlobalStats
|
||||
|
||||
// 总用户数
|
||||
if total, err := r.Count(ctx); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
stats.Total = total
|
||||
}
|
||||
|
||||
// 活跃用户数
|
||||
if active, err := r.CountActiveUsers(ctx); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
stats.Active = active
|
||||
}
|
||||
|
||||
// 按角色统计
|
||||
for _, role := range []entity.UserRole{
|
||||
entity.UserRoleAdmin,
|
||||
entity.UserRoleEditor,
|
||||
entity.UserRoleUser,
|
||||
} {
|
||||
if count, err := r.CountByRole(ctx, role); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
switch role {
|
||||
case entity.UserRoleAdmin:
|
||||
stats.Admins = count
|
||||
case entity.UserRoleEditor:
|
||||
stats.Editors = count
|
||||
case entity.UserRoleUser:
|
||||
stats.Users = count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 本月新注册用户
|
||||
now := time.Now()
|
||||
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
|
||||
var monthlyUsers int64
|
||||
if err := r.db.WithContext(ctx).Model(&entity.User{}).
|
||||
Where("created_at >= ? AND created_at <= ?", startOfMonth, endOfMonth).
|
||||
Count(&monthlyUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.MonthlyRegistrations = monthlyUsers
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
Reference in New Issue
Block a user