update
This commit is contained in:
90
internal/handler/ssg_handler.go
Normal file
90
internal/handler/ssg_handler.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user