Files
photography/backend/internal/repository/postgres/photo_repository_impl.go
xujiang 39a42695d3 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 最佳实践
2025-07-10 15:05:52 +08:00

375 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package postgres
import (
"context"
"errors"
"fmt"
"time"
"photography-backend/internal/model/entity"
"photography-backend/internal/repository/interfaces"
"go.uber.org/zap"
"gorm.io/gorm"
)
// photoRepositoryImpl 照片仓储实现
type photoRepositoryImpl struct {
db *gorm.DB
logger *zap.Logger
}
// NewPhotoRepository 创建照片仓储实现
func NewPhotoRepository(db *gorm.DB, logger *zap.Logger) interfaces.PhotoRepository {
return &photoRepositoryImpl{
db: db,
logger: logger,
}
}
// Create 创建照片
func (r *photoRepositoryImpl) Create(ctx context.Context, photo *entity.Photo) error {
return r.db.WithContext(ctx).Create(photo).Error
}
// GetByID 根据ID获取照片
func (r *photoRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.Photo, error) {
var photo entity.Photo
err := r.db.WithContext(ctx).
Preload("User").
Preload("Categories").
Preload("Tags").
First(&photo, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("photo not found")
}
return nil, err
}
return &photo, nil
}
// GetByFilename 根据文件名获取照片
func (r *photoRepositoryImpl) GetByFilename(ctx context.Context, filename string) (*entity.Photo, error) {
var photo entity.Photo
err := r.db.WithContext(ctx).Where("filename = ?", filename).First(&photo).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("photo not found")
}
return nil, err
}
return &photo, nil
}
// Update 更新照片
func (r *photoRepositoryImpl) Update(ctx context.Context, photo *entity.Photo) error {
return r.db.WithContext(ctx).Save(photo).Error
}
// Delete 删除照片
func (r *photoRepositoryImpl) Delete(ctx context.Context, id uint) error {
return r.db.WithContext(ctx).Delete(&entity.Photo{}, id).Error
}
// List 获取照片列表
func (r *photoRepositoryImpl) List(ctx context.Context, params *entity.PhotoListParams) ([]*entity.Photo, int64, error) {
var photos []*entity.Photo
var total int64
query := r.db.WithContext(ctx).Model(&entity.Photo{})
// 应用过滤条件
if params.UserID != nil {
query = query.Where("user_id = ?", *params.UserID)
}
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
}
// 应用排序
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("User").Preload("Categories").Preload("Tags")
// 查询数据
if err := query.Find(&photos).Error; err != nil {
return nil, 0, err
}
return photos, total, nil
}
// ListByUserID 根据用户ID获取照片列表
func (r *photoRepositoryImpl) ListByUserID(ctx context.Context, userID uint, params *entity.PhotoListParams) ([]*entity.Photo, int64, error) {
if params == nil {
params = &entity.PhotoListParams{}
}
params.UserID = &userID
return r.List(ctx, params)
}
// ListByStatus 根据状态获取照片列表
func (r *photoRepositoryImpl) ListByStatus(ctx context.Context, status entity.PhotoStatus, params *entity.PhotoListParams) ([]*entity.Photo, int64, error) {
if params == nil {
params = &entity.PhotoListParams{}
}
params.Status = &status
return r.List(ctx, params)
}
// ListByCategory 根据分类获取照片列表
func (r *photoRepositoryImpl) ListByCategory(ctx context.Context, categoryID uint, params *entity.PhotoListParams) ([]*entity.Photo, int64, error) {
if params == nil {
params = &entity.PhotoListParams{}
}
params.CategoryID = &categoryID
return r.List(ctx, params)
}
// Search 搜索照片
func (r *photoRepositoryImpl) Search(ctx context.Context, query string, params *entity.PhotoListParams) ([]*entity.Photo, int64, error) {
if params == nil {
params = &entity.PhotoListParams{}
}
params.Search = query
return r.List(ctx, params)
}
// Count 统计照片总数
func (r *photoRepositoryImpl) Count(ctx context.Context) (int64, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entity.Photo{}).Count(&count).Error
return count, err
}
// CountByUser 统计用户照片数
func (r *photoRepositoryImpl) CountByUser(ctx context.Context, userID uint) (int64, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entity.Photo{}).
Where("user_id = ?", userID).Count(&count).Error
return count, err
}
// CountByStatus 统计指定状态照片数
func (r *photoRepositoryImpl) CountByStatus(ctx context.Context, status entity.PhotoStatus) (int64, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entity.Photo{}).
Where("status = ?", status).Count(&count).Error
return count, err
}
// CountByCategory 统计分类照片数
func (r *photoRepositoryImpl) CountByCategory(ctx context.Context, categoryID uint) (int64, error) {
var count int64
err := r.db.WithContext(ctx).
Table("photo_categories").
Where("category_id = ?", categoryID).
Count(&count).Error
return count, err
}
// CountByStatus 统计指定状态照片数
func (r *photoRepositoryImpl) CountByStatus(ctx context.Context, status string) (int64, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entity.Photo{}).
Where("status = ?", status).Count(&count).Error
return count, err
}
// BatchUpdate 批量更新
func (r *photoRepositoryImpl) BatchUpdate(ctx context.Context, ids []uint, updates map[string]interface{}) error {
if len(ids) == 0 || len(updates) == 0 {
return nil
}
return r.db.WithContext(ctx).Model(&entity.Photo{}).
Where("id IN ?", ids).
Updates(updates).Error
}
// BatchUpdateCategories 批量更新分类
func (r *photoRepositoryImpl) BatchUpdateCategories(ctx context.Context, photoIDs []uint, categoryIDs []uint) error {
if len(photoIDs) == 0 {
return nil
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 删除现有关联
if err := tx.Exec("DELETE FROM photo_categories WHERE photo_id IN ?", photoIDs).Error; err != nil {
return err
}
// 添加新关联
for _, photoID := range photoIDs {
for _, categoryID := range categoryIDs {
if err := tx.Exec("INSERT INTO photo_categories (photo_id, category_id) VALUES (?, ?)",
photoID, categoryID).Error; err != nil {
return err
}
}
}
return nil
})
}
// BatchUpdateTags 批量更新标签
func (r *photoRepositoryImpl) BatchUpdateTags(ctx context.Context, photoIDs []uint, tagIDs []uint) error {
if len(photoIDs) == 0 {
return nil
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 删除现有关联
if err := tx.Exec("DELETE FROM photo_tags WHERE photo_id IN ?", photoIDs).Error; err != nil {
return err
}
// 添加新关联
for _, photoID := range photoIDs {
for _, tagID := range tagIDs {
if err := tx.Exec("INSERT INTO photo_tags (photo_id, tag_id) VALUES (?, ?)",
photoID, tagID).Error; err != nil {
return err
}
}
}
return nil
})
}
// BatchDelete 批量删除
func (r *photoRepositoryImpl) BatchDelete(ctx context.Context, ids []uint) error {
if len(ids) == 0 {
return nil
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 删除关联关系
if err := tx.Exec("DELETE FROM photo_categories WHERE photo_id IN ?", ids).Error; err != nil {
return err
}
if err := tx.Exec("DELETE FROM photo_tags WHERE photo_id IN ?", ids).Error; err != nil {
return err
}
// 删除照片记录
return tx.Delete(&entity.Photo{}, ids).Error
})
}
// GetStats 获取照片统计信息
func (r *photoRepositoryImpl) GetStats(ctx context.Context) (*entity.PhotoStats, error) {
var stats entity.PhotoStats
// 总照片数
if total, err := r.Count(ctx); err != nil {
return nil, err
} else {
stats.Total = total
}
// 按状态统计
for _, status := range []entity.PhotoStatus{
entity.PhotoStatusActive,
entity.PhotoStatusDraft,
entity.PhotoStatusArchived,
} {
if count, err := r.CountByStatus(ctx, status); err != nil {
return nil, err
} else {
switch status {
case entity.PhotoStatusActive:
stats.Published = count
case entity.PhotoStatusDraft:
stats.Draft = count
case entity.PhotoStatusArchived:
stats.Archived = 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 monthlyCount int64
if err := r.db.WithContext(ctx).Model(&entity.Photo{}).
Where("created_at >= ? AND created_at <= ?", startOfMonth, endOfMonth).
Count(&monthlyCount).Error; err != nil {
return nil, err
}
stats.ThisMonth = monthlyCount
// 用户照片分布Top 10
var userPhotoStats []struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
PhotoCount int64 `json:"photo_count"`
}
if err := r.db.WithContext(ctx).
Table("photos").
Select("photos.user_id, users.username, COUNT(photos.id) as photo_count").
Joins("LEFT JOIN users ON photos.user_id = users.id").
Group("photos.user_id, users.username").
Order("photo_count DESC").
Limit(10).
Find(&userPhotoStats).Error; err != nil {
return nil, err
}
stats.UserPhotoCounts = make(map[string]int64)
for _, stat := range userPhotoStats {
stats.UserPhotoCounts[stat.Username] = stat.PhotoCount
}
return &stats, nil
}