crawl(品牌抓取任务队列) - /admin/internal/crawl/tasks 领取 / :id/result 回报,spider 可常驻拉任务 - 后台品牌列表支持「加入抓取队列」,队列页可看状态与重试 - 租约超时的 running 自动回收为 queued,避免 worker 崩溃后任务卡死 media(媒体机直传) - ingest 载荷新增 main_key/detail_keys/main_size/detail_sizes: 有 key 就直接落库,跳过下载与上传;与 URL 形态可逐图混用 - 细节图按 Details/DetailKeys 两者较长者遍历(只给 key 时不再丢图) - 媒体机上传的 key 不进失败回滚清单(内容寻址、可能与他人共享) - /admin/internal/upload-url 签发 S4 PUT 预签名(spider 不持有 AK/SK) embeddings(向量回填) - GET /admin/internal/embeddings/pending 派发缺向量的图(含可直接下载的 url) - POST /admin/internal/embeddings 回写向量并重算近邻;失败项计入 image_embedding_failures,连续失败到上限后退出队列(坏图不占位) - 近邻重算改为异步串行:同步会让 100 张的批次 POST 超时(实测 >60s) - 新增 key 分支单测:混用 key/url、细节图只给 key、字节数、回滚清单、向量占位
81 lines
3.4 KiB
Go
81 lines
3.4 KiB
Go
package dto
|
||
|
||
// ---------- 请求 ----------
|
||
|
||
// BrandQuery 品牌列表查询条件。
|
||
type BrandQuery struct {
|
||
Letter string // A-Z 字母索引;OTHER 表示中文分桶;空表示全部拉丁字母
|
||
Keyword string
|
||
// All 仅后台列表使用:连「没有任何走秀档案」的品牌一起列出。
|
||
// 品牌目录有 2 万+ 条导入数据,默认仍按「有档案」收敛(否则 A-Z 索引被淹),
|
||
// 后台需要翻到任意品牌时开这个开关。公开侧忽略此字段。
|
||
All bool
|
||
Page int
|
||
Size int // 每页条数,已写死 brandDefaultSize(30),前端不传
|
||
|
||
// Featured 仅保留"代表品牌"精选集合(按指标排名的前 N 个)。
|
||
Featured bool
|
||
Metric string // images(默认)| shows
|
||
|
||
// Sort 后台列表排序:空 / "name"(默认,按英文名);"updated" 按「最近有动静」倒序
|
||
// (取该品牌下走秀图集的最大 updated_at 与抓取任务的最大 updated_at,两者取大)。
|
||
// 公开侧忽略此字段,避免改变前台 A-Z 索引的既定顺序。
|
||
Sort string
|
||
Limit int // 精选集合大小,默认 200
|
||
|
||
// Locale 语言:cn | en。空时由 service 回落默认 en。
|
||
// 决定 name / show_name 出参选取 name_en 还是 name_cn。
|
||
Locale string
|
||
}
|
||
|
||
// 品牌列表分页与精选集合的边界,与原项目保持一致。
|
||
const (
|
||
brandDefaultSize = 30
|
||
brandMaxSize = 500
|
||
featuredDefaultLimit = 200
|
||
featuredMaxLimit = 500
|
||
)
|
||
|
||
// Normalize 校正分页与精选集合参数。
|
||
func (q *BrandQuery) Normalize() {
|
||
if q.Page < 1 {
|
||
q.Page = 1
|
||
}
|
||
if q.Size < 1 || q.Size > brandMaxSize {
|
||
q.Size = brandDefaultSize
|
||
}
|
||
if q.Metric != "shows" {
|
||
q.Metric = "images"
|
||
}
|
||
if q.Limit < 1 || q.Limit > featuredMaxLimit {
|
||
q.Limit = featuredDefaultLimit
|
||
}
|
||
}
|
||
|
||
// Offset 返回 SQL 偏移量。
|
||
func (q BrandQuery) Offset() int { return (q.Page - 1) * q.Size }
|
||
|
||
// ---------- 响应 ----------
|
||
|
||
// PublicBrand 对外展示用的精简品牌结构,剔除 created_at / updated_at / is_deleted。
|
||
// 注意:对外只暴露编码后的 UID(非自增主键),防止顺序枚举。
|
||
// article_count(各品牌档案数)前端从不展示,已移除,故不再额外查一次分组计数。
|
||
type PublicBrand struct {
|
||
UID string `json:"id"` // 编码后的品牌 id(无序串),对外唯一标识
|
||
Name string `json:"name"` // 按 locale 选列(cn→name_cn,en→name_en)
|
||
NameEn string `json:"name_en"` // 英文规范名(始终返回,供前端组合双语展示)
|
||
NameCn string `json:"name_cn"` // 中文名(可能为空,供前端组合双语展示)
|
||
}
|
||
|
||
// PopularBrand 首页"热门品牌"区块所需结构。
|
||
//
|
||
// 与 PublicBrand 不同:这里要带上"代表走秀"的封面与标题,供 SSG 在构建期直接渲染卡片,
|
||
// 前端无需再逐篇请求详情。字段对齐需求:id(品牌 id 编码串)/ brand(品牌名)/ cover(封面图)/ title(代表走秀标题)/ article_id(代表走秀 id 编码串,用于跳转到详情页)。
|
||
type PopularBrand struct {
|
||
UID string `json:"id"` // 编码后的品牌 id(无序串)
|
||
ArticleUID string `json:"article_id"` // 编码后的代表走秀 id(无序串)
|
||
Brand string `json:"brand"` // 品牌名称
|
||
Cover string `json:"cover"` // 代表走秀封面图(相对路径,前端 toAbs 拼 base 后访问)
|
||
Title string `json:"title"` // 代表走秀标题
|
||
}
|