update
This commit is contained in:
120
internal/handler/article_handler.go
Normal file
120
internal/handler/article_handler.go
Normal file
@ -0,0 +1,120 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"fashionapi/internal/dto"
|
||||
"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/public/articles
|
||||
//
|
||||
// 查询参数:
|
||||
// - 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
|
||||
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()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Detail 文章详情(含完整图片集)。
|
||||
//
|
||||
// GET /api/public/articles/: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.Decode(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))
|
||||
if err != nil {
|
||||
fail(c, err)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user