主要变更: - 采用 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 最佳实践
244 lines
9.1 KiB
Go
244 lines
9.1 KiB
Go
package entity
|
||
|
||
import (
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// Photo 照片实体
|
||
type Photo struct {
|
||
ID uint `json:"id" gorm:"primarykey"`
|
||
Title string `json:"title" gorm:"not null;size:200"`
|
||
Description string `json:"description" gorm:"type:text"`
|
||
Filename string `json:"filename" gorm:"not null;size:255"`
|
||
OriginalFilename string `json:"original_filename" gorm:"not null;size:255"`
|
||
UniqueFilename string `json:"unique_filename" gorm:"not null;size:255"`
|
||
FilePath string `json:"file_path" gorm:"not null;size:500"`
|
||
OriginalURL string `json:"original_url" gorm:"not null;size:500"`
|
||
ThumbnailURL string `json:"thumbnail_url" gorm:"size:500"`
|
||
MediumURL string `json:"medium_url" gorm:"size:500"`
|
||
LargeURL string `json:"large_url" gorm:"size:500"`
|
||
FileSize int64 `json:"file_size"`
|
||
MimeType string `json:"mime_type" gorm:"size:100"`
|
||
Width int `json:"width"`
|
||
Height int `json:"height"`
|
||
Status PhotoStatus `json:"status" gorm:"default:active;size:20"`
|
||
|
||
// EXIF 信息
|
||
Camera string `json:"camera" gorm:"size:100"`
|
||
Lens string `json:"lens" gorm:"size:100"`
|
||
CameraMake string `json:"camera_make" gorm:"size:100"`
|
||
CameraModel string `json:"camera_model" gorm:"size:100"`
|
||
LensModel string `json:"lens_model" gorm:"size:100"`
|
||
FocalLength *float64 `json:"focal_length" gorm:"type:decimal(5,2)"`
|
||
Aperture *float64 `json:"aperture" gorm:"type:decimal(3,1)"`
|
||
ShutterSpeed string `json:"shutter_speed" gorm:"size:20"`
|
||
ISO *int `json:"iso"`
|
||
TakenAt *time.Time `json:"taken_at"`
|
||
|
||
// 地理位置信息
|
||
LocationName string `json:"location_name" gorm:"size:200"`
|
||
Latitude *float64 `json:"latitude" gorm:"type:decimal(10,8)"`
|
||
Longitude *float64 `json:"longitude" gorm:"type:decimal(11,8)"`
|
||
|
||
// 关联
|
||
UserID uint `json:"user_id" gorm:"not null;index"`
|
||
AlbumID *uint `json:"album_id" gorm:"index"`
|
||
CategoryID *uint `json:"category_id" gorm:"index"`
|
||
|
||
// 状态和统计
|
||
IsPublic bool `json:"is_public" gorm:"default:true;index"`
|
||
IsFeatured bool `json:"is_featured" gorm:"default:false;index"`
|
||
ViewCount int `json:"view_count" gorm:"default:0;index"`
|
||
LikeCount int `json:"like_count" gorm:"default:0;index"`
|
||
DownloadCount int `json:"download_count" gorm:"default:0"`
|
||
SortOrder int `json:"sort_order" gorm:"default:0;index"`
|
||
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||
|
||
// 关联对象
|
||
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
||
Album *Album `json:"album,omitempty" gorm:"foreignKey:AlbumID"`
|
||
Category *Category `json:"category,omitempty" gorm:"foreignKey:CategoryID"`
|
||
Tags []Tag `json:"tags,omitempty" gorm:"many2many:photo_tags;"`
|
||
}
|
||
|
||
// PhotoStatus 照片状态枚举
|
||
type PhotoStatus string
|
||
|
||
const (
|
||
PhotoStatusActive PhotoStatus = "active"
|
||
PhotoStatusInactive PhotoStatus = "inactive"
|
||
PhotoStatusDeleted PhotoStatus = "deleted"
|
||
PhotoStatusDraft PhotoStatus = "draft"
|
||
PhotoStatusPrivate PhotoStatus = "private"
|
||
)
|
||
|
||
// Status constants for compatibility
|
||
const (
|
||
StatusPublished PhotoStatus = "active"
|
||
StatusDraft PhotoStatus = "draft"
|
||
StatusArchived PhotoStatus = "inactive"
|
||
)
|
||
|
||
// PhotoTag 照片标签关联表
|
||
type PhotoTag struct {
|
||
PhotoID uint `json:"photo_id" gorm:"primaryKey"`
|
||
TagID uint `json:"tag_id" gorm:"primaryKey"`
|
||
}
|
||
|
||
// PhotoStats 照片统计信息
|
||
type PhotoStats struct {
|
||
Total int64 `json:"total"` // 总照片数
|
||
Published int64 `json:"published"` // 已发布照片数
|
||
Private int64 `json:"private"` // 私有照片数
|
||
Featured int64 `json:"featured"` // 精选照片数
|
||
TotalViews int64 `json:"total_views"` // 总浏览量
|
||
TotalLikes int64 `json:"total_likes"` // 总点赞数
|
||
TotalDownloads int64 `json:"total_downloads"` // 总下载数
|
||
FileSize int64 `json:"file_size"` // 总文件大小
|
||
TotalSize int64 `json:"total_size"` // 总大小(别名)
|
||
ThisMonth int64 `json:"this_month"` // 本月新增
|
||
Today int64 `json:"today"` // 今日新增
|
||
StatusStats map[string]int64 `json:"status_stats"` // 状态统计
|
||
CategoryCounts map[string]int64 `json:"category_counts"` // 各分类照片数量
|
||
TagCounts map[string]int64 `json:"tag_counts"` // 各标签照片数量
|
||
}
|
||
|
||
// PhotoListParams 照片列表查询参数
|
||
type PhotoListParams struct {
|
||
Page int `json:"page" form:"page"`
|
||
Limit int `json:"limit" form:"limit"`
|
||
Sort string `json:"sort" form:"sort"`
|
||
Order string `json:"order" form:"order"`
|
||
Search string `json:"search" form:"search"`
|
||
UserID *uint `json:"user_id" form:"user_id"`
|
||
Status *PhotoStatus `json:"status" form:"status"`
|
||
CategoryID *uint `json:"category_id" form:"category_id"`
|
||
TagID *uint `json:"tag_id" form:"tag_id"`
|
||
DateFrom *time.Time `json:"date_from" form:"date_from"`
|
||
DateTo *time.Time `json:"date_to" form:"date_to"`
|
||
}
|
||
|
||
// CreatePhotoRequest 创建照片请求
|
||
type CreatePhotoRequest struct {
|
||
Title string `json:"title" binding:"required,max=200"`
|
||
Description string `json:"description" binding:"max=1000"`
|
||
OriginalFilename string `json:"original_filename"`
|
||
FileSize int64 `json:"file_size"`
|
||
Status string `json:"status" binding:"oneof=active inactive"`
|
||
Camera string `json:"camera" binding:"max=100"`
|
||
Lens string `json:"lens" binding:"max=100"`
|
||
ISO *int `json:"iso"`
|
||
Aperture *float64 `json:"aperture"`
|
||
ShutterSpeed string `json:"shutter_speed" binding:"max=20"`
|
||
FocalLength *float64 `json:"focal_length"`
|
||
TakenAt *time.Time `json:"taken_at"`
|
||
CategoryIDs []uint `json:"category_ids"`
|
||
TagIDs []uint `json:"tag_ids"`
|
||
}
|
||
|
||
// UpdatePhotoRequest 更新照片请求
|
||
type UpdatePhotoRequest struct {
|
||
Title *string `json:"title" binding:"omitempty,max=200"`
|
||
Description *string `json:"description" binding:"max=1000"`
|
||
Status *string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||
Camera *string `json:"camera" binding:"omitempty,max=100"`
|
||
Lens *string `json:"lens" binding:"omitempty,max=100"`
|
||
ISO *int `json:"iso"`
|
||
Aperture *float64 `json:"aperture"`
|
||
ShutterSpeed *string `json:"shutter_speed" binding:"omitempty,max=20"`
|
||
FocalLength *float64 `json:"focal_length"`
|
||
TakenAt *time.Time `json:"taken_at"`
|
||
CategoryIDs *[]uint `json:"category_ids"`
|
||
TagIDs *[]uint `json:"tag_ids"`
|
||
}
|
||
|
||
// BatchUpdatePhotosRequest 批量更新照片请求
|
||
type BatchUpdatePhotosRequest struct {
|
||
Status *string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||
CategoryIDs *[]uint `json:"category_ids"`
|
||
TagIDs *[]uint `json:"tag_ids"`
|
||
}
|
||
|
||
// PhotoFormat 照片格式
|
||
type PhotoFormat struct {
|
||
ID uint `json:"id" gorm:"primarykey"`
|
||
PhotoID uint `json:"photo_id" gorm:"not null;index"`
|
||
Format string `json:"format" gorm:"not null;size:20"` // jpg, png, webp
|
||
Quality int `json:"quality" gorm:"not null"` // 1-100
|
||
Width int `json:"width" gorm:"not null"`
|
||
Height int `json:"height" gorm:"not null"`
|
||
FileSize int64 `json:"file_size" gorm:"not null"`
|
||
URL string `json:"url" gorm:"not null;size:500"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
func (PhotoFormat) TableName() string {
|
||
return "photo_formats"
|
||
}
|
||
|
||
// TableName 指定表名
|
||
func (Photo) TableName() string {
|
||
return "photos"
|
||
}
|
||
|
||
// TableName 指定关联表名
|
||
func (PhotoTag) TableName() string {
|
||
return "photo_tags"
|
||
}
|
||
|
||
// GetAspectRatio 获取宽高比
|
||
func (p *Photo) GetAspectRatio() float64 {
|
||
if p.Height == 0 {
|
||
return 0
|
||
}
|
||
return float64(p.Width) / float64(p.Height)
|
||
}
|
||
|
||
// IsLandscape 是否为横向
|
||
func (p *Photo) IsLandscape() bool {
|
||
return p.Width > p.Height
|
||
}
|
||
|
||
// IsPortrait 是否为纵向
|
||
func (p *Photo) IsPortrait() bool {
|
||
return p.Width < p.Height
|
||
}
|
||
|
||
// IsSquare 是否为正方形
|
||
func (p *Photo) IsSquare() bool {
|
||
return p.Width == p.Height
|
||
}
|
||
|
||
// HasLocation 是否有地理位置信息
|
||
func (p *Photo) HasLocation() bool {
|
||
return p.Latitude != nil && p.Longitude != nil
|
||
}
|
||
|
||
// HasEXIF 是否有EXIF信息
|
||
func (p *Photo) HasEXIF() bool {
|
||
return p.CameraMake != "" || p.CameraModel != "" || p.TakenAt != nil
|
||
}
|
||
|
||
// GetDisplayURL 获取显示URL(根据尺寸)
|
||
func (p *Photo) GetDisplayURL(size string) string {
|
||
switch size {
|
||
case "thumbnail":
|
||
if p.ThumbnailURL != "" {
|
||
return p.ThumbnailURL
|
||
}
|
||
case "medium":
|
||
if p.MediumURL != "" {
|
||
return p.MediumURL
|
||
}
|
||
case "large":
|
||
if p.LargeURL != "" {
|
||
return p.LargeURL
|
||
}
|
||
}
|
||
return p.OriginalURL
|
||
} |