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) }