This commit is contained in:
toom1996
2026-08-30 10:45:34 +08:00
parent a6724265ad
commit 9c3403a903
23 changed files with 284 additions and 508 deletions

View File

@ -23,49 +23,59 @@ func NewArticleHandler(articles service.ArticleService) *ArticleHandler {
return &ArticleHandler{articles: articles}
}
// List 文章列表(分页 + 多条件筛选 + 排序)。
// List 走秀列表(分页 + 单值筛选 + 排序)。
//
// GET /api/public/articles
// GET /api/v1/public/runway-looks
//
// 查询参数:
// - page / size 分页,size 默认 12、上限 100
// - keyword 标题模糊搜索
// - brand_id 单个品牌 id
// - brand_ids 多个品牌 id,逗号分隔
// - collection_type rtw / menswear / couture / resort / pre_fall(可重复传实现多选)
// - season spring / fall(可重复传)
// - season_code SS26 / FW25,精确匹配
// - year 年份(可重复传)
// - sort newest(默认)/ year_desc / year_asc / image_count
// - with_images 附带每篇前 N 张图,上限 12
// 查询参数(均为单值,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 := dto.ArticleQuery{
Page: queryInt(c, "page", 1),
Size: queryInt(c, "size", 12),
Keyword: c.Query("keyword"),
BrandID: decodeBrandID(c.Query("brand_id")),
BrandIDs: decodeBrandIDs(c.Query("brand_ids")),
CollectionTypes: c.QueryArray("collection_type"),
Seasons: c.QueryArray("season"),
SeasonCode: c.Query("season_code"),
Years: parseIntSlice(c.QueryArray("year")),
Sort: c.DefaultQuery("sort", "newest"),
WithImages: queryInt(c, "with_images", 0),
Locale: parseLocale(c),
}
q.Normalize()
h.respondRunways(c, h.parseListQuery(c))
}
// 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
}
// respondRunways 统一执行列表查询并回包(分页)。
func (h *ArticleHandler) respondRunways(c *gin.Context, q dto.ArticleQuery) {
q.Normalize()
list, total, err := h.articles.List(c.Request.Context(), q)
if err != nil {
fail(c, err)
return
}
response.Page(c, http.StatusOK, list, total, q.Page, q.Size)
response.Page(c, http.StatusOK, list, total, q.Size)
}
// Detail 文章详情(含完整图片集)。
// Detail 走秀详情(含完整图片集)。
//
// GET /api/public/articles/:id
// GET /api/public/runways/:id
// :id 是对外编码串(无序),需先解码为数字主键再查库;非法串视为不存在。
func (h *ArticleHandler) Detail(c *gin.Context) {
raw := strings.TrimSpace(c.Param("id"))
@ -86,35 +96,3 @@ func (h *ArticleHandler) Detail(c *gin.Context) {
}
response.Data(c, http.StatusOK, detail)
}
// decodeBrandID 把对外编码串还原为数字主键;非法或为空返回 ""(表示不过滤)。
func decodeBrandID(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return ""
}
n, err := hashid.Decode(s)
if err != nil {
return ""
}
return strconv.FormatUint(uint64(n), 10)
}
// decodeBrandIDs 解析逗号分隔的编码串列表,逐个解码为数字主键,跳过非法项。
func decodeBrandIDs(s string) []uint32 {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
var out []uint32
for _, p := range strings.Split(s, ",") {
p = strings.TrimSpace(p)
if p == "" {
continue
}
if n, err := hashid.Decode(p); err == nil {
out = append(out, n)
}
}
return out
}