Files
backend_v2/internal/handler/article_handler.go
toom1996 74ba700598 update
2026-09-07 00:01:48 +08:00

114 lines
3.9 KiB
Go
Raw Permalink 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 handler
import (
"net/http"
"strconv"
"strings"
"fashionapi/internal/dto"
"fashionapi/internal/middleware"
"fashionapi/internal/pkg/hashid"
"fashionapi/internal/pkg/response"
"fashionapi/internal/service"
"github.com/gin-gonic/gin"
)
// ArticleHandler 对外公开的文章接口。
type ArticleHandler struct {
articles service.ArticleService
}
// NewArticleHandler 创建文章 handler。
func NewArticleHandler(articles service.ArticleService) *ArticleHandler {
return &ArticleHandler{articles: articles}
}
// List 走秀列表(分页 + 单值筛选 + 排序)。
//
// GET /api/v1/public/runway-looks
//
// 查询参数(均为单值,size / images 由服务端固定,前端不传):
// - page 页码,默认 1(服务端固定每页 ArticleFixedSize=24 条)
// - collection rtw / menswear / couture / resort / pre_fall
// - season spring / fall
// - year 年份(整数),0 表示不过滤
// - sort newest(默认)/ year_desc / year_asc / image_count
// - brand_id hashid 编码串;传入则只返回该品牌走秀(查询参数,取代原嵌套路由 /brands/:brandId/runway-looks)
// - locale cn / en
//
// 每篇附带前 ArticleFixedImages=6 张缩略图(服务端固定,不进参数)。
func (h *ArticleHandler) List(c *gin.Context) {
q := h.parseListQuery(c)
q.Normalize()
list, total, err := h.articles.List(c.Request.Context(), q)
if err != nil {
fail(c, err)
return
}
// 列表首屏对游客完全公开(翻页门槛由 PublicFirstPageAuth 兜底),不做预览截断——
// 「未登录限制」已转移到详情页:每篇图集最多看 PreviewLimit 张图片,列表照常展示全部专辑。
response.Page(c, http.StatusOK, list, total, q.Size)
}
// isAnon 判断请求是否未携带有效登录态(供列表接口做未登录预览截断)。
func isAnon(c *gin.Context) bool {
_, ok := middleware.UserIDFrom(c)
return !ok
}
// parseListQuery 从 query string 解析走秀列表的筛选条件。
// brand_id 为 hashid 编码串;非法串忽略(当作不过滤,返回空结果集而非 404)。
func (h *ArticleHandler) parseListQuery(c *gin.Context) dto.ArticleQuery {
q := dto.ArticleQuery{
Page: queryInt(c, "page", 1),
Size: dto.ArticleFixedSize, // 服务端固定,前端不传
Collection: c.Query("collection"),
Season: c.Query("season"),
Year: queryInt(c, "year", 0),
Sort: c.DefaultQuery("sort", "newest"),
Images: dto.ArticleFixedImages, // 服务端固定,前端不传
Locale: parseLocale(c),
}
if raw := strings.TrimSpace(c.Query("brand_id")); raw != "" {
if n, err := hashid.Decode(raw); err == nil {
q.BrandIDs = []uint32{n}
}
}
return q
}
// Detail 走秀详情(含完整图片集)。
//
// GET /api/public/runways/:id
// :id 是对外编码串(无序),需先解码为数字主键再查库;非法串视为不存在。
func (h *ArticleHandler) Detail(c *gin.Context) {
raw := strings.TrimSpace(c.Param("id"))
if raw == "" {
response.Error(c, http.StatusBadRequest, "缺少文章 id")
return
}
_, numeric, err := hashid.DecodeTyped(raw)
if err != nil {
response.Error(c, http.StatusNotFound, "文章不存在")
return
}
detail, err := h.articles.Detail(c.Request.Context(), strconv.FormatUint(uint64(numeric), 10), parseLocale(c), middleware.IsVIP(middleware.TierFrom(c)))
if err != nil {
fail(c, err)
return
}
// 未登录:图片集仅返回预览上限(PreviewLimit)张,并打 preview=true,
// 前端据此在详情页展示「登录查看全部图片」门禁;已登录(带有效 Bearer)返回完整图片集。
anon := isAnon(c)
preview := false
imageTotal := len(detail.Images)
if anon && imageTotal > dto.PreviewLimit {
detail.Images = detail.Images[:dto.PreviewLimit]
preview = true
}
response.DataPreview(c, http.StatusOK, detail, preview, imageTotal)
}