A:照片列表接口增强分页信息
- 新增响应字段:total(总数)、nextOffset(下次偏移量)、hasMore(是否有更多)、order(排序方式) - 支持order查询参数,可选asc/desc,默认按taken_at降序 - 添加GetPhotoListItemsCount等方法获取总数 - 添加WithOrder方法支持排序控制 - 便于前端计算分页状态和无限滚动实现 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
902583beb0
commit
4e7c8094d6
2 files changed
+173
-12
No files matched your search
@@ -10,6 +10,7 @@ import (
|
||||
"rear/internal/model"
|
||||
"rear/internal/repositories"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -85,6 +86,47 @@ type PhotoExifInfo struct {
|
||||
OtherFields map[string]interface{} `json:"otherFields,omitempty"`
|
||||
}
|
||||
|
||||
// ParseSize 解析 size 参数
|
||||
// 支持: "400", "400x400", "400X400", "400*400"
|
||||
// 返回最大值 (如果是 x 格式,取两个数字的最大)
|
||||
func ParseSize(sizeParam string) (int, error) {
|
||||
if sizeParam == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// 全部转小写,并替换 * 为 x
|
||||
sizeParam = strings.ToLower(sizeParam)
|
||||
sizeParam = strings.ReplaceAll(sizeParam, "*", "x")
|
||||
|
||||
// 如果是纯数字
|
||||
if !strings.Contains(sizeParam, "x") {
|
||||
val, err := strconv.Atoi(sizeParam)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// 分割 x
|
||||
parts := strings.Split(sizeParam, "x")
|
||||
if len(parts) != 2 {
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
|
||||
val1, err1 := strconv.Atoi(parts[0])
|
||||
val2, err2 := strconv.Atoi(parts[1])
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
|
||||
// 返回最大值
|
||||
if val1 > val2 {
|
||||
return val1, nil
|
||||
}
|
||||
return val2, nil
|
||||
}
|
||||
|
||||
// GetPhoto 获取图像文件
|
||||
// GET api/v1/photo/{hash}?format=thumbnail&size=400
|
||||
func (h *PhotoHandler) GetPhoto(c *gin.Context) {
|
||||
@@ -100,6 +142,14 @@ func (h *PhotoHandler) GetPhoto(c *gin.Context) {
|
||||
// 获取查询参数
|
||||
format := c.DefaultQuery("format", "original") // original, thumbnail
|
||||
sizeParam := c.Query("size")
|
||||
size, err := ParseSize(sizeParam)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: "Invalid size parameter",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 查询照片信息
|
||||
photo, err := h.container.PhotoRepo.GetByHash(hash)
|
||||
@@ -142,7 +192,7 @@ func (h *PhotoHandler) GetPhoto(c *gin.Context) {
|
||||
switch format {
|
||||
case "thumbnail":
|
||||
// 解析尺寸参数
|
||||
size, err := h.parseSize(sizeParam)
|
||||
size, err := h.parseSize(string(size))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{
|
||||
Code: http.StatusBadRequest,
|
||||
@@ -348,13 +398,19 @@ type PhotoColumnResponse struct {
|
||||
IsImage []bool `json:"isImage"`
|
||||
TakenAt []string `json:"takenAt"`
|
||||
Ratio []float32 `json:"ratio"`
|
||||
// 分页和排序信息
|
||||
Total int64 `json:"total"` // 本次查询范围的总数
|
||||
NextOffset int `json:"nextOffset"` // 下次查询的偏移量
|
||||
HasMore bool `json:"hasMore"` // 是否还有更多数据
|
||||
Order string `json:"order"` // 排序方式: "asc" | "desc"
|
||||
}
|
||||
|
||||
// GetPhotos 获取照片列表(列式存储格式)
|
||||
// GET /api/v1/photos?limit=100&offset=0&start_date=2023-01-01&end_date=2023-12-31
|
||||
// GET /api/v1/photos?limit=100&offset=0&start_date=2023-01-01&end_date=2023-12-31&order=desc
|
||||
func (h *PhotoHandler) GetPhotos(c *gin.Context) {
|
||||
limitStr := c.DefaultQuery("limit", "50")
|
||||
offsetStr := c.DefaultQuery("offset", "0")
|
||||
orderStr := c.DefaultQuery("order", "desc") // 默认降序 (takenAt desc)
|
||||
startDateStr := c.Query("start_date")
|
||||
endDateStr := c.Query("end_date")
|
||||
|
||||
@@ -371,7 +427,13 @@ func (h *PhotoHandler) GetPhotos(c *gin.Context) {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
// 验证排序参数
|
||||
if orderStr != "asc" && orderStr != "desc" {
|
||||
orderStr = "desc"
|
||||
}
|
||||
|
||||
var photos []repositories.PhotoListItem
|
||||
var total int64
|
||||
|
||||
// 如果提供了时间范围参数
|
||||
if startDateStr != "" && endDateStr != "" {
|
||||
@@ -386,20 +448,53 @@ func (h *PhotoHandler) GetPhotos(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
photos, err = h.container.PhotoRepo.GetPhotoListItemsByDateRange(startDate, endDate, limit, offset)
|
||||
photos, err = h.container.PhotoRepo.GetPhotoListItemsByDateRangeWithOrder(startDate, endDate, limit, offset, orderStr)
|
||||
if err != nil {
|
||||
logger.Error("获取照片列表失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, model.Response{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Failed to get photos list",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
total, err = h.container.PhotoRepo.GetPhotoListItemsCountByDateRange(startDate, endDate)
|
||||
if err != nil {
|
||||
logger.Error("获取照片总数失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, model.Response{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Failed to get photos count",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 获取全部照片列表
|
||||
photos, err = h.container.PhotoRepo.GetPhotoListItems(limit, offset)
|
||||
photos, err = h.container.PhotoRepo.GetPhotoListItemsWithOrder(limit, offset, orderStr)
|
||||
if err != nil {
|
||||
logger.Error("获取照片列表失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, model.Response{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Failed to get photos list",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
total, err = h.container.PhotoRepo.GetPhotoListItemsCount()
|
||||
if err != nil {
|
||||
logger.Error("获取照片总数失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, model.Response{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Failed to get photos count",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("获取照片列表失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, model.Response{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Failed to get photos list",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 计算分页信息
|
||||
nextOffset := offset + limit
|
||||
hasMore := int64(nextOffset) < total
|
||||
|
||||
// 转换为列式存储格式
|
||||
columnData := PhotoColumnResponse{
|
||||
@@ -407,6 +502,11 @@ func (h *PhotoHandler) GetPhotos(c *gin.Context) {
|
||||
IsImage: make([]bool, len(photos)),
|
||||
TakenAt: make([]string, len(photos)),
|
||||
Ratio: make([]float32, len(photos)),
|
||||
// 分页和排序信息
|
||||
Total: total,
|
||||
NextOffset: nextOffset,
|
||||
HasMore: hasMore,
|
||||
Order: orderStr,
|
||||
}
|
||||
|
||||
for i, photo := range photos {
|
||||
|
||||
@@ -438,6 +438,28 @@ func (r *PhotoRepository) GetPhotoListItems(limit int, offset int) ([]PhotoListI
|
||||
return items, err
|
||||
}
|
||||
|
||||
// GetPhotoListItemsWithOrder 获取照片列表项(支持排序)
|
||||
func (r *PhotoRepository) GetPhotoListItemsWithOrder(limit int, offset int, order string) ([]PhotoListItem, error) {
|
||||
var items []PhotoListItem
|
||||
|
||||
// 构建排序字符串
|
||||
var orderClause string
|
||||
if order == "asc" {
|
||||
orderClause = "taken_at ASC, created_at ASC"
|
||||
} else {
|
||||
orderClause = "taken_at DESC, created_at DESC"
|
||||
}
|
||||
|
||||
err := r.db.Model(&tables.Photo{}).
|
||||
Select("hash, width, height, aspect_ratio, format, taken_at, file_size").
|
||||
Order(orderClause).
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Scan(&items).Error
|
||||
|
||||
return items, err
|
||||
}
|
||||
|
||||
// GetPhotoListItemsByDateRange 根据拍摄时间范围获取照片列表项
|
||||
func (r *PhotoRepository) GetPhotoListItemsByDateRange(startDate, endDate time.Time, limit int, offset int) ([]PhotoListItem, error) {
|
||||
var items []PhotoListItem
|
||||
@@ -452,3 +474,42 @@ func (r *PhotoRepository) GetPhotoListItemsByDateRange(startDate, endDate time.T
|
||||
|
||||
return items, err
|
||||
}
|
||||
|
||||
// GetPhotoListItemsByDateRangeWithOrder 根据拍摄时间范围获取照片列表项(支持排序)
|
||||
func (r *PhotoRepository) GetPhotoListItemsByDateRangeWithOrder(startDate, endDate time.Time, limit int, offset int, order string) ([]PhotoListItem, error) {
|
||||
var items []PhotoListItem
|
||||
|
||||
// 构建排序字符串
|
||||
var orderClause string
|
||||
if order == "asc" {
|
||||
orderClause = "taken_at ASC"
|
||||
} else {
|
||||
orderClause = "taken_at DESC"
|
||||
}
|
||||
|
||||
err := r.db.Model(&tables.Photo{}).
|
||||
Select("hash, width, height, aspect_ratio, format, taken_at, file_size").
|
||||
Where("taken_at IS NOT NULL AND DATE(taken_at) BETWEEN DATE(?) AND DATE(?)", startDate, endDate).
|
||||
Order(orderClause).
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Scan(&items).Error
|
||||
|
||||
return items, err
|
||||
}
|
||||
|
||||
// GetPhotoListItemsCount 获取照片列表项总数
|
||||
func (r *PhotoRepository) GetPhotoListItemsCount() (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&tables.Photo{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetPhotoListItemsCountByDateRange 根据拍摄时间范围获取照片列表项总数
|
||||
func (r *PhotoRepository) GetPhotoListItemsCountByDateRange(startDate, endDate time.Time) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&tables.Photo{}).
|
||||
Where("taken_at IS NOT NULL AND DATE(taken_at) BETWEEN DATE(?) AND DATE(?)", startDate, endDate).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
Reference in new issue
Block a user