This commit is contained in:
toom1996
2026-08-26 19:40:16 +08:00
parent 30c9f21da4
commit a6724265ad
14 changed files with 400 additions and 101 deletions

View File

@ -80,10 +80,4 @@ func (h *AuthHandler) Me(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"user": user})
}
// Logout 登出。
//
// POST /api/auth/logout
// 无状态 JWT 服务端无需作废令牌,由客户端丢弃即可;保留接口以便将来扩展黑名单。
func (h *AuthHandler) Logout(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}

View File

@ -2,9 +2,7 @@ package handler
import (
"net/http"
"strings"
"fashionapi/internal/dto"
"fashionapi/internal/pkg/response"
"fashionapi/internal/service"
@ -19,27 +17,40 @@ import (
// - 复用现有 service 层逻辑,与对外公开接口保持同一份数据源与映射规则,
// 不会出现「构建接口与线上接口返回结构不一致」的坑。
type SSGHandler struct {
brands service.BrandService
articles service.ArticleService
brands service.BrandService
index service.IndexService
}
// NewSSGHandler 创建 SSG handler。
func NewSSGHandler(brands service.BrandService, articles service.ArticleService) *SSGHandler {
return &SSGHandler{brands: brands, articles: articles}
func NewSSGHandler(brands service.BrandService, index service.IndexService) *SSGHandler {
return &SSGHandler{brands: brands, index: index}
}
// Popular 首页「热门品牌」区块数据(SSG 内部版)。
// IndexRunway 首页(/index/)runway 区块数据。
//
// GET /api/ssg/brands/popular?limit=20
// GET /api/v1/ssg/index/runway
//
// 与公开版逻辑完全一致(复用 service.BrandService.Popular),只是挪到了内部端口,
// 不再挂在对外公开的 /api/public 下。limit 默认 20、上限 100。
func (h *SSGHandler) Popular(c *gin.Context) {
limit := queryInt(c, "limit", 20)
if limit < 1 || limit > 100 {
limit = 20
// 数据由各模块在服务端写死组合(不接收任何参数),当前仅有 hotBrand(热门品牌,固定 20 条)。
// 拼装逻辑由 service.IndexService.Runway 负责,handler 只做转发。
func (h *SSGHandler) IndexRunway(c *gin.Context) {
data, err := h.index.Runway(c.Request.Context(), parseLocale(c))
if err != nil {
fail(c, err)
return
}
list, err := h.brands.Popular(c.Request.Context(), limit, parseLocale(c))
response.Data(c, http.StatusOK, data)
}
// HotBrands 走秀页(RunwayLooks)品牌筛选的热门品牌数据。
//
// GET /api/v1/ssg/brands/hot
//
// 固定返回热度前 30 的品牌(按走秀图片总数排名,结果按热度顺序),不接收任何参数——
// 前端侧栏 filter 展示前 10 个,品牌弹窗 HOT 标签展示全部 30 个。
func (h *SSGHandler) HotBrands(c *gin.Context) {
const hotBrandLimit = 30
list, err := h.brands.Hot(c.Request.Context(), hotBrandLimit, parseLocale(c))
if err != nil {
fail(c, err)
return
@ -47,44 +58,4 @@ func (h *SSGHandler) Popular(c *gin.Context) {
response.Data(c, http.StatusOK, list)
}
// Brands 品牌索引页(HBX 风格 A-Z)的 SSG 全量数据。
//
// GET /api/ssg/brands?size=300&only_with_articles=1&featured=1
//
// 直接复用 BrandQuery + service.BrandService.List,支持字母/搜索/精选/分页等全部参数,
// 返回 {data:[...]} 与公开列表同构,前端 brands.astro 无需任何适配。
func (h *SSGHandler) Brands(c *gin.Context) {
q := dto.BrandQuery{
Letter: strings.ToUpper(strings.TrimSpace(c.Query("letter"))),
Keyword: strings.TrimSpace(c.Query("keyword")),
Page: queryInt(c, "page", 1),
Size: queryInt(c, "size", 200),
OnlyWithArticles: queryBool(c, "only_with_articles", true),
Featured: queryBool(c, "featured", false),
Metric: strings.ToLower(strings.TrimSpace(c.Query("metric"))),
Limit: queryInt(c, "limit", 200),
Locale: parseLocale(c),
}
q.Normalize()
list, total, err := h.brands.List(c.Request.Context(), q)
if err != nil {
fail(c, err)
return
}
response.Page(c, http.StatusOK, list, total, q.Page, q.Size)
}
// ArticleIDs 供 SSG 的 getStaticPaths 枚举详情页路径用。
//
// GET /api/ssg/articles/ids
//
// 只回 [id,...](升序),不带回封面/描述等大字段,比拉 size=500 再 .map(id) 省带宽。
func (h *SSGHandler) ArticleIDs(c *gin.Context) {
ids, err := h.articles.IDs(c.Request.Context())
if err != nil {
fail(c, err)
return
}
response.Data(c, http.StatusOK, ids)
}