fix
This commit is contained in:
677
backend-old/internal/service/photo_service.go
Normal file
677
backend-old/internal/service/photo_service.go
Normal file
@ -0,0 +1,677 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"photography-backend/internal/config"
|
||||
"photography-backend/internal/model/entity"
|
||||
"photography-backend/internal/repository/interfaces"
|
||||
"photography-backend/internal/service/storage"
|
||||
"photography-backend/internal/utils"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type PhotoService struct {
|
||||
photoRepo interfaces.PhotoRepository
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
storageService *storage.StorageService
|
||||
}
|
||||
|
||||
func NewPhotoService(photoRepo interfaces.PhotoRepository, config *config.Config, logger *zap.Logger, storageService *storage.StorageService) *PhotoService {
|
||||
return &PhotoService{
|
||||
photoRepo: photoRepo,
|
||||
config: config,
|
||||
logger: logger,
|
||||
storageService: storageService,
|
||||
}
|
||||
}
|
||||
|
||||
// PhotoListParams 照片列表查询参数
|
||||
type PhotoListParams struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Search string `json:"search" form:"search"`
|
||||
Status string `json:"status" form:"status"`
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
Tags []string `json:"tags" form:"tags"`
|
||||
StartDate string `json:"start_date" form:"start_date"`
|
||||
EndDate string `json:"end_date" form:"end_date"`
|
||||
SortBy string `json:"sort_by" form:"sort_by"`
|
||||
SortOrder string `json:"sort_order" form:"sort_order"`
|
||||
}
|
||||
|
||||
// PhotoListResponse 照片列表响应
|
||||
type PhotoListResponse struct {
|
||||
Photos []entity.Photo `json:"photos"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Pages int `json:"pages"`
|
||||
}
|
||||
|
||||
// GetPhotos 获取照片列表
|
||||
func (s *PhotoService) GetPhotos(ctx context.Context, params PhotoListParams) (*PhotoListResponse, 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).
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Formats")
|
||||
|
||||
// 搜索过滤
|
||||
if params.Search != "" {
|
||||
searchPattern := "%" + params.Search + "%"
|
||||
query = query.Where("title ILIKE ? OR description ILIKE ? OR original_filename ILIKE ?",
|
||||
searchPattern, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
// 状态过滤
|
||||
if params.Status != "" {
|
||||
query = query.Where("status = ?", params.Status)
|
||||
}
|
||||
|
||||
// 分类过滤
|
||||
if params.CategoryID > 0 {
|
||||
query = query.Joins("JOIN photo_categories ON photos.id = photo_categories.photo_id").
|
||||
Where("photo_categories.category_id = ?", params.CategoryID)
|
||||
}
|
||||
|
||||
// 标签过滤
|
||||
if len(params.Tags) > 0 {
|
||||
query = query.Joins("JOIN photo_tags ON photos.id = photo_tags.photo_id").
|
||||
Joins("JOIN tags ON photo_tags.tag_id = tags.id").
|
||||
Where("tags.slug IN ?", params.Tags)
|
||||
}
|
||||
|
||||
// 日期过滤
|
||||
if params.StartDate != "" {
|
||||
if startDate, err := time.Parse("2006-01-02", params.StartDate); err == nil {
|
||||
query = query.Where("taken_at >= ?", startDate)
|
||||
}
|
||||
}
|
||||
if params.EndDate != "" {
|
||||
if endDate, err := time.Parse("2006-01-02", params.EndDate); err == nil {
|
||||
query = query.Where("taken_at <= ?", endDate)
|
||||
}
|
||||
}
|
||||
|
||||
// 排序
|
||||
sortBy := "created_at"
|
||||
sortOrder := "desc"
|
||||
if params.SortBy != "" {
|
||||
allowedSortFields := []string{"created_at", "updated_at", "taken_at", "title", "file_size"}
|
||||
if utils.Contains(allowedSortFields, params.SortBy) {
|
||||
sortBy = params.SortBy
|
||||
}
|
||||
}
|
||||
if params.SortOrder == "asc" {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
var total int64
|
||||
countQuery := query
|
||||
if err := countQuery.Model(&entity.Photo{}).Count(&total).Error; err != nil {
|
||||
s.logger.Error("Failed to count photos", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
var photos []entity.Photo
|
||||
if err := query.
|
||||
Order(fmt.Sprintf("%s %s", sortBy, sortOrder)).
|
||||
Offset(offset).
|
||||
Limit(params.Limit).
|
||||
Find(&photos).Error; err != nil {
|
||||
s.logger.Error("Failed to get photos", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 计算总页数
|
||||
pages := int((total + int64(params.Limit) - 1) / int64(params.Limit))
|
||||
|
||||
return &PhotoListResponse{
|
||||
Photos: photos,
|
||||
Total: total,
|
||||
Page: params.Page,
|
||||
Limit: params.Limit,
|
||||
Pages: pages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPhotoByID 根据ID获取照片
|
||||
func (s *PhotoService) GetPhotoByID(ctx context.Context, id uint) (*entity.Photo, error) {
|
||||
var photo entity.Photo
|
||||
if err := s.db.WithContext(ctx).
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Formats").
|
||||
First(&photo, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("photo not found")
|
||||
}
|
||||
s.logger.Error("Failed to get photo by ID", zap.Error(err), zap.Uint("id", id))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &photo, nil
|
||||
}
|
||||
|
||||
// CreatePhoto 创建照片
|
||||
func (s *PhotoService) CreatePhoto(ctx context.Context, req *entity.CreatePhotoRequest) (*entity.Photo, error) {
|
||||
// 生成唯一的文件名
|
||||
uniqueFilename := utils.GenerateUniqueFilename(req.OriginalFilename)
|
||||
|
||||
photo := &entity.Photo{
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
OriginalFilename: req.OriginalFilename,
|
||||
UniqueFilename: uniqueFilename,
|
||||
FileSize: req.FileSize,
|
||||
Status: entity.PhotoStatus(req.Status),
|
||||
Camera: req.Camera,
|
||||
Lens: req.Lens,
|
||||
ISO: req.ISO,
|
||||
Aperture: req.Aperture,
|
||||
ShutterSpeed: req.ShutterSpeed,
|
||||
FocalLength: req.FocalLength,
|
||||
TakenAt: req.TakenAt,
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx := s.db.WithContext(ctx).Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 创建照片记录
|
||||
if err := tx.Create(photo).Error; err != nil {
|
||||
s.logger.Error("Failed to create photo", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 关联分类
|
||||
if len(req.CategoryIDs) > 0 {
|
||||
var categories []entity.Category
|
||||
if err := tx.Where("id IN ?", req.CategoryIDs).Find(&categories).Error; err != nil {
|
||||
s.logger.Error("Failed to find categories", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Model(photo).Association("Categories").Replace(categories); err != nil {
|
||||
s.logger.Error("Failed to associate categories", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 关联标签
|
||||
if len(req.TagIDs) > 0 {
|
||||
var tags []entity.Tag
|
||||
if err := tx.Where("id IN ?", req.TagIDs).Find(&tags).Error; err != nil {
|
||||
s.logger.Error("Failed to find tags", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Model(photo).Association("Tags").Replace(tags); err != nil {
|
||||
s.logger.Error("Failed to associate tags", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
s.logger.Error("Failed to commit transaction", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新加载关联数据
|
||||
if err := s.db.WithContext(ctx).
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Formats").
|
||||
First(photo, photo.ID).Error; err != nil {
|
||||
s.logger.Error("Failed to reload photo", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Photo created successfully", zap.Uint("id", photo.ID))
|
||||
return photo, nil
|
||||
}
|
||||
|
||||
// UpdatePhoto 更新照片
|
||||
func (s *PhotoService) UpdatePhoto(ctx context.Context, id uint, req *entity.UpdatePhotoRequest) (*entity.Photo, error) {
|
||||
// 检查照片是否存在
|
||||
var photo entity.Photo
|
||||
if err := s.db.WithContext(ctx).First(&photo, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("photo not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx := s.db.WithContext(ctx).Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 更新照片基本信息
|
||||
updates := map[string]interface{}{}
|
||||
if req.Title != nil {
|
||||
updates["title"] = *req.Title
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.Status != nil {
|
||||
updates["status"] = *req.Status
|
||||
}
|
||||
if req.Camera != nil {
|
||||
updates["camera"] = *req.Camera
|
||||
}
|
||||
if req.Lens != nil {
|
||||
updates["lens"] = *req.Lens
|
||||
}
|
||||
if req.ISO != nil {
|
||||
updates["iso"] = *req.ISO
|
||||
}
|
||||
if req.Aperture != nil {
|
||||
updates["aperture"] = *req.Aperture
|
||||
}
|
||||
if req.ShutterSpeed != nil {
|
||||
updates["shutter_speed"] = *req.ShutterSpeed
|
||||
}
|
||||
if req.FocalLength != nil {
|
||||
updates["focal_length"] = *req.FocalLength
|
||||
}
|
||||
if req.TakenAt != nil {
|
||||
updates["taken_at"] = *req.TakenAt
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
if err := tx.Model(&photo).Updates(updates).Error; err != nil {
|
||||
s.logger.Error("Failed to update photo", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 更新分类关联
|
||||
if req.CategoryIDs != nil {
|
||||
var categories []entity.Category
|
||||
if len(*req.CategoryIDs) > 0 {
|
||||
if err := tx.Where("id IN ?", *req.CategoryIDs).Find(&categories).Error; err != nil {
|
||||
s.logger.Error("Failed to find categories", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tx.Model(&photo).Association("Categories").Replace(categories); err != nil {
|
||||
s.logger.Error("Failed to update categories", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 更新标签关联
|
||||
if req.TagIDs != nil {
|
||||
var tags []entity.Tag
|
||||
if len(*req.TagIDs) > 0 {
|
||||
if err := tx.Where("id IN ?", *req.TagIDs).Find(&tags).Error; err != nil {
|
||||
s.logger.Error("Failed to find tags", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tx.Model(&photo).Association("Tags").Replace(tags); err != nil {
|
||||
s.logger.Error("Failed to update tags", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
s.logger.Error("Failed to commit transaction", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新加载照片数据
|
||||
if err := s.db.WithContext(ctx).
|
||||
Preload("Categories").
|
||||
Preload("Tags").
|
||||
Preload("Formats").
|
||||
First(&photo, id).Error; err != nil {
|
||||
s.logger.Error("Failed to reload photo", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Photo updated successfully", zap.Uint("id", id))
|
||||
return &photo, nil
|
||||
}
|
||||
|
||||
// DeletePhoto 删除照片
|
||||
func (s *PhotoService) DeletePhoto(ctx context.Context, id uint) error {
|
||||
// 检查照片是否存在
|
||||
var photo entity.Photo
|
||||
if err := s.db.WithContext(ctx).First(&photo, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("photo not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx := s.db.WithContext(ctx).Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 删除关联的格式文件
|
||||
if err := tx.Where("photo_id = ?", id).Delete(&entity.PhotoFormat{}).Error; err != nil {
|
||||
s.logger.Error("Failed to delete photo formats", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除关联关系
|
||||
if err := tx.Model(&photo).Association("Categories").Clear(); err != nil {
|
||||
s.logger.Error("Failed to clear categories", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&photo).Association("Tags").Clear(); err != nil {
|
||||
s.logger.Error("Failed to clear tags", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除照片记录
|
||||
if err := tx.Delete(&photo).Error; err != nil {
|
||||
s.logger.Error("Failed to delete photo", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
s.logger.Error("Failed to commit transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 异步删除文件
|
||||
go func() {
|
||||
if err := (*s.storageService).DeletePhoto(photo.UniqueFilename); err != nil {
|
||||
s.logger.Error("Failed to delete photo files", zap.Error(err), zap.String("filename", photo.UniqueFilename))
|
||||
}
|
||||
}()
|
||||
|
||||
s.logger.Info("Photo deleted successfully", zap.Uint("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadPhoto 上传照片
|
||||
func (s *PhotoService) UploadPhoto(ctx context.Context, file multipart.File, header *multipart.FileHeader, req *entity.CreatePhotoRequest) (*entity.Photo, error) {
|
||||
// 验证文件类型
|
||||
if !s.isValidImageFile(header.Filename) {
|
||||
return nil, errors.New("invalid file type")
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
if header.Size > s.config.Upload.MaxFileSize {
|
||||
return nil, errors.New("file size too large")
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
uniqueFilename := utils.GenerateUniqueFilename(header.Filename)
|
||||
|
||||
// 上传文件到存储服务
|
||||
uploadedFile, err := (*s.storageService).UploadPhoto(ctx, file, uniqueFilename)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to upload photo", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建照片记录
|
||||
req.OriginalFilename = header.Filename
|
||||
req.FileSize = header.Size
|
||||
|
||||
photo, err := s.CreatePhoto(ctx, req)
|
||||
if err != nil {
|
||||
// 如果创建记录失败,删除已上传的文件
|
||||
go func() {
|
||||
if err := (*s.storageService).DeletePhoto(uniqueFilename); err != nil {
|
||||
s.logger.Error("Failed to cleanup uploaded file", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 异步处理图片格式转换
|
||||
go func() {
|
||||
s.processPhotoFormats(context.Background(), photo, uploadedFile)
|
||||
}()
|
||||
|
||||
return photo, nil
|
||||
}
|
||||
|
||||
// BatchUpdatePhotos 批量更新照片
|
||||
func (s *PhotoService) BatchUpdatePhotos(ctx context.Context, ids []uint, req *entity.BatchUpdatePhotosRequest) error {
|
||||
if len(ids) == 0 {
|
||||
return errors.New("no photos to update")
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx := s.db.WithContext(ctx).Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 构建更新数据
|
||||
updates := map[string]interface{}{}
|
||||
if req.Status != nil {
|
||||
updates["status"] = *req.Status
|
||||
}
|
||||
|
||||
// 基础字段更新
|
||||
if len(updates) > 0 {
|
||||
if err := tx.Model(&entity.Photo{}).Where("id IN ?", ids).Updates(updates).Error; err != nil {
|
||||
s.logger.Error("Failed to batch update photos", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新分类
|
||||
if req.CategoryIDs != nil {
|
||||
// 先删除现有关联
|
||||
if err := tx.Exec("DELETE FROM photo_categories WHERE photo_id IN ?", ids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加新关联
|
||||
if len(*req.CategoryIDs) > 0 {
|
||||
for _, photoID := range ids {
|
||||
for _, categoryID := range *req.CategoryIDs {
|
||||
if err := tx.Exec("INSERT INTO photo_categories (photo_id, category_id) VALUES (?, ?)", photoID, categoryID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新标签
|
||||
if req.TagIDs != nil {
|
||||
// 先删除现有关联
|
||||
if err := tx.Exec("DELETE FROM photo_tags WHERE photo_id IN ?", ids).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加新关联
|
||||
if len(*req.TagIDs) > 0 {
|
||||
for _, photoID := range ids {
|
||||
for _, tagID := range *req.TagIDs {
|
||||
if err := tx.Exec("INSERT INTO photo_tags (photo_id, tag_id) VALUES (?, ?)", photoID, tagID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
s.logger.Error("Failed to commit batch update", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("Batch update completed", zap.Int("count", len(ids)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// BatchDeletePhotos 批量删除照片
|
||||
func (s *PhotoService) BatchDeletePhotos(ctx context.Context, ids []uint) error {
|
||||
if len(ids) == 0 {
|
||||
return errors.New("no photos to delete")
|
||||
}
|
||||
|
||||
// 获取要删除的照片信息
|
||||
var photos []entity.Photo
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&photos).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx := s.db.WithContext(ctx).Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 删除关联的格式文件
|
||||
if err := tx.Where("photo_id IN ?", ids).Delete(&entity.PhotoFormat{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除关联关系
|
||||
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
|
||||
}
|
||||
|
||||
// 删除照片记录
|
||||
if err := tx.Where("id IN ?", ids).Delete(&entity.Photo{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 异步删除文件
|
||||
go func() {
|
||||
for _, photo := range photos {
|
||||
if err := (*s.storageService).DeletePhoto(photo.UniqueFilename); err != nil {
|
||||
s.logger.Error("Failed to delete photo files", zap.Error(err), zap.String("filename", photo.UniqueFilename))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
s.logger.Info("Batch delete completed", zap.Int("count", len(ids)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPhotoStats 获取照片统计信息
|
||||
func (s *PhotoService) GetPhotoStats(ctx context.Context) (*entity.PhotoStats, error) {
|
||||
var stats entity.PhotoStats
|
||||
|
||||
// 总数统计
|
||||
if err := s.db.WithContext(ctx).Model(&entity.Photo{}).Count(&stats.Total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 按状态统计
|
||||
var statusStats []struct {
|
||||
Status string `json:"status"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Select("status, COUNT(*) as count").
|
||||
Group("status").
|
||||
Find(&statusStats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats.StatusStats = make(map[string]int64)
|
||||
for _, stat := range statusStats {
|
||||
stats.StatusStats[stat.Status] = stat.Count
|
||||
}
|
||||
|
||||
// 本月新增
|
||||
startOfMonth := time.Now().AddDate(0, 0, -time.Now().Day()+1)
|
||||
if err := s.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
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.Photo{}).
|
||||
Where("created_at >= ?", startOfDay).
|
||||
Count(&stats.Today).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 总存储大小
|
||||
var totalSize sql.NullInt64
|
||||
if err := s.db.WithContext(ctx).Model(&entity.Photo{}).
|
||||
Select("SUM(file_size)").
|
||||
Row().Scan(&totalSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if totalSize.Valid {
|
||||
stats.TotalSize = totalSize.Int64
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// isValidImageFile 验证图片文件类型
|
||||
func (s *PhotoService) isValidImageFile(filename string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||
return utils.Contains(allowedExts, ext)
|
||||
}
|
||||
|
||||
// processPhotoFormats 处理照片格式转换
|
||||
func (s *PhotoService) processPhotoFormats(ctx context.Context, photo *entity.Photo, uploadedFile *storage.UploadedFile) {
|
||||
// 这里将实现图片格式转换逻辑
|
||||
// 生成不同尺寸和格式的图片
|
||||
// 更新 photo_formats 表
|
||||
|
||||
s.logger.Info("Processing photo formats", zap.Uint("photo_id", photo.ID))
|
||||
|
||||
// TODO: 实现图片处理逻辑
|
||||
// 1. 生成缩略图
|
||||
// 2. 生成不同尺寸的图片
|
||||
// 3. 转换为不同格式 (WebP, AVIF)
|
||||
// 4. 更新数据库记录
|
||||
}
|
||||
Reference in New Issue
Block a user