74 lines
2.5 KiB
Go
74 lines
2.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"errors"
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"fashionapi/internal/dto"
|
||
"fashionapi/internal/service"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// IngestHandler 爬虫上报入口(:8092 的 /admin/internal/ingest)。
|
||
//
|
||
// 安全:不挂后台登录 cookie 中间件,改走 HMAC 验签中间件(middleware.IngestAuth);
|
||
// 因此多节点爬虫可直连上报,无需回环绑定。接口本身很薄:验签 → Submit 入队 → 立即 202。
|
||
type IngestHandler struct {
|
||
ingest *service.IngestService
|
||
brands service.BrandService
|
||
}
|
||
|
||
// NewIngestHandler 创建入库 handler。
|
||
func NewIngestHandler(ingest *service.IngestService, brands service.BrandService) *IngestHandler {
|
||
return &IngestHandler{ingest: ingest, brands: brands}
|
||
}
|
||
|
||
// Submit 接收单场走秀上报,入队后返回 202 Accepted(异步处理)。
|
||
func (h *IngestHandler) Submit(c *gin.Context) {
|
||
var p dto.RunwayIngest
|
||
if err := c.ShouldBindJSON(&p); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid json: " + err.Error()})
|
||
return
|
||
}
|
||
id, err := h.ingest.Submit(c.Request.Context(), p)
|
||
if err != nil {
|
||
// 业务错误(缺字段 / 未知品牌等)映射为 4xx,其余 5xx
|
||
if errors.Is(err, service.ErrBrandNotFound) || isClientErr(err) {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "enqueue failed"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusAccepted, gin.H{"job_id": id, "status": "queued"})
|
||
}
|
||
|
||
// CrawlBrands 爬虫取任务接口(GET /admin/internal/crawl/brands,复用 IngestAuth HMAC 验签)。
|
||
// 返回所有可抓取品牌的 brand_uid(hashid)+ 英文品牌名,供 spider 拼 vogue 抓取 URL 并上送 ingest。
|
||
func (h *IngestHandler) CrawlBrands(c *gin.Context) {
|
||
// 支持 ?brand=<numeric id> 单品牌调试:解析失败或非正数时回落全量。
|
||
var brandID uint32
|
||
if v := c.Query("brand"); v != "" {
|
||
if id, err := strconv.ParseUint(v, 10, 32); err == nil {
|
||
brandID = uint32(id)
|
||
}
|
||
}
|
||
brands, err := h.brands.CrawlTasks(c.Request.Context(), brandID)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "fetch crawl tasks failed"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"brands": brands})
|
||
}
|
||
|
||
func isClientErr(err error) bool {
|
||
// service 层 NewError 带 http 状态码;这里简单按消息前缀判断,避免暴露内部类型。
|
||
if err == nil {
|
||
return false
|
||
}
|
||
msg := err.Error()
|
||
return len(msg) > 0 && msg == "brand_uid required"
|
||
}
|