This commit is contained in:
toom1996
2026-08-26 10:45:21 +08:00
parent 3cf1368e68
commit 30c9f21da4
48 changed files with 3442 additions and 0 deletions

View File

@ -0,0 +1,90 @@
package handler
import (
"net/http"
"strings"
"fashionapi/internal/dto"
"fashionapi/internal/pkg/response"
"fashionapi/internal/service"
"github.com/gin-gonic/gin"
)
// SSGHandler 承载「仅供构建期(astro build)使用的内部接口」。
//
// 这些接口返回全量/全 ID 数据,理论上不应暴露给公网,因此:
// - 它们只挂在独立的内部端口(默认 8091,见 router.NewSSG)上;
// - 该端口由 docker-compose 绑定 127.0.0.1(回环),nginx 也绝不反代;
// - 复用现有 service 层逻辑,与对外公开接口保持同一份数据源与映射规则,
// 不会出现「构建接口与线上接口返回结构不一致」的坑。
type SSGHandler struct {
brands service.BrandService
articles service.ArticleService
}
// NewSSGHandler 创建 SSG handler。
func NewSSGHandler(brands service.BrandService, articles service.ArticleService) *SSGHandler {
return &SSGHandler{brands: brands, articles: articles}
}
// Popular 首页「热门品牌」区块数据(SSG 内部版)。
//
// GET /api/ssg/brands/popular?limit=20
//
// 与公开版逻辑完全一致(复用 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
}
list, err := h.brands.Popular(c.Request.Context(), limit, parseLocale(c))
if err != nil {
fail(c, err)
return
}
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)
}