update
This commit is contained in:
57
internal/model/ingest_job.go
Normal file
57
internal/model/ingest_job.go
Normal file
@ -0,0 +1,57 @@
|
||||
package model
|
||||
|
||||
// IngestJob 爬虫上报的走秀入库任务队列。
|
||||
//
|
||||
// 设计:爬虫 POST 到 :8092 的 ingest 接口(HMAC 验签后)只做「入队 + 立即 202」,
|
||||
// 真正的去重 / 补分类 / 下载图 / 写正式表 由后台 worker 异步处理,
|
||||
// 因此接口本身很薄、不会被慢任务(尤其是 OSS 上传)拖垮。
|
||||
//
|
||||
// 并发安全靠 MySQL 的 `SELECT ... FOR UPDATE SKIP LOCKED`:多个 worker 同时
|
||||
// Claim 不会抢到同一条,未提交的行对其它 worker 不可见。
|
||||
type IngestJob struct {
|
||||
ID uint32 `gorm:"primaryKey;column:id" json:"id"`
|
||||
CreatedAt uint32 `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt uint32 `gorm:"column:updated_at" json:"updated_at"`
|
||||
LockedAt uint32 `gorm:"column:locked_at" json:"locked_at"` // worker 领取时间;用于超时兜底回收
|
||||
SourceURL string `gorm:"column:source_url" json:"source_url"`
|
||||
Status string `gorm:"column:status;size:16;not null;default:'pending'" json:"status"` // pending|processing|done|failed
|
||||
Attempts uint8 `gorm:"column:attempts;not null;default:0" json:"attempts"`
|
||||
Payload string `gorm:"column:payload;type:text" json:"payload"` // 原始 JSON(RunwayIngest 或 MediaCleanupPayload)
|
||||
LastError string `gorm:"column:last_error;type:text" json:"last_error"`
|
||||
Kind string `gorm:"column:kind;size:16;not null;default:'crawl'" json:"kind"` // crawl|media_cleanup
|
||||
NextAttemptAt uint32 `gorm:"column:next_attempt_at;not null;default:0" json:"next_attempt_at"` // 重试退避:到达该 unix 时间才可被领取
|
||||
}
|
||||
|
||||
// TableName 指定队列表名。
|
||||
func (IngestJob) TableName() string { return "ingest_jobs" }
|
||||
|
||||
// IngestJob 状态常量。
|
||||
const (
|
||||
IngestStatusPending = "pending"
|
||||
IngestStatusProcessing = "processing"
|
||||
IngestStatusDone = "done"
|
||||
IngestStatusFailed = "failed"
|
||||
)
|
||||
|
||||
// IngestJob 任务类型(kind 列):区分「爬虫入库」与「清理七牛孤儿图」两条管线。
|
||||
const (
|
||||
IngestKindCrawl = "crawl" // 爬虫上报的走秀/街拍入库
|
||||
IngestKindMediaCleanup = "media_cleanup" // 删除图集时异步清理七牛孤儿文件
|
||||
)
|
||||
|
||||
// IngestMaxAttempts 单条任务最大处理次数(含首次)。达上限仍未成功则置 failed,需后台手动重试。
|
||||
const IngestMaxAttempts = 3
|
||||
|
||||
// IngestStuckTimeoutSec 任务「卡死」判定阈值:worker 领取后 processing 状态持续超过该秒数,
|
||||
// 视为 worker 崩溃/被杀遗留,由 Claim 兜底回收为 pending 重领。下载图/传图正常远低于此值。
|
||||
const IngestStuckTimeoutSec = 300
|
||||
|
||||
// IngestRetryBackoff 返回第 attempts 次失败后的退避秒数(指数退避,封顶 600s)。
|
||||
// attempts 从 1 起算:30s / 60s / 120s。用于失败任务的自动重试调度。
|
||||
func IngestRetryBackoff(attempts int) int {
|
||||
d := 30 * (1 << uint(attempts-1))
|
||||
if d > 600 {
|
||||
d = 600
|
||||
}
|
||||
return d
|
||||
}
|
||||
14
internal/model/ingest_nonce.go
Normal file
14
internal/model/ingest_nonce.go
Normal file
@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
// IngestNonce 爬虫上报的一次性随机串,用于防重放。
|
||||
//
|
||||
// 每次合法请求携带唯一 X-Nonce;后台先查库,若存在则拒绝(409 重放),
|
||||
// 否则放行并写入。nonce 本身随机且一次性,空间可忽略;
|
||||
// created_at 用于定期清理(如保留 7 天)以防表无限增长。
|
||||
type IngestNonce struct {
|
||||
Nonce string `gorm:"primaryKey;column:nonce;size:64" json:"nonce"`
|
||||
CreatedAt uint32 `gorm:"column:created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定 nonce 表名。
|
||||
func (IngestNonce) TableName() string { return "ingest_nonces" }
|
||||
54
internal/model/runway_draft.go
Normal file
54
internal/model/runway_draft.go
Normal file
@ -0,0 +1,54 @@
|
||||
package model
|
||||
|
||||
// BrandRunwayDraft 走秀草稿(爬虫入库待审核)。
|
||||
//
|
||||
// 爬虫 worker 处理完 ingest_jobs 后不直写正式表,而是写入本表并置为 pending;
|
||||
// 后台审核通过(Approve)后再晋升(SaveRunwayFromDraft)到 brand_runway 正式表。
|
||||
// 字段与 brand_runway 对齐(含 review 专用字段),便于审核页直接预览/微调。
|
||||
type BrandRunwayDraft struct {
|
||||
ID uint32 `gorm:"primaryKey;column:id" json:"id"`
|
||||
JobID uint32 `gorm:"column:job_id" json:"job_id"` // 关联 ingest_jobs.id(重跑溯源)
|
||||
BrandID uint32 `gorm:"column:brand_id" json:"brand_id"` // 品牌数字主键
|
||||
TitleEn string `gorm:"column:title_en" json:"title_en"`
|
||||
TitleCn string `gorm:"column:title_cn" json:"title_cn"`
|
||||
DescriptionEn string `gorm:"column:description_en" json:"description_en"`
|
||||
DescriptionCn string `gorm:"column:description_cn" json:"description_cn"`
|
||||
Year uint16 `gorm:"column:year" json:"year"`
|
||||
Season string `gorm:"column:season" json:"season"`
|
||||
CollectionType string `gorm:"column:collection_type" json:"collection_type"` // rtw/menswear/couture/resort/pre_fall
|
||||
SeasonCode string `gorm:"column:season_code" json:"season_code"` // SS26/FW25/RES26/PF25
|
||||
Cover string `gorm:"column:cover" json:"cover"`
|
||||
SourceURL string `gorm:"column:source_url" json:"source_url"`
|
||||
ImageCount uint16 `gorm:"column:image_count" json:"image_count"`
|
||||
Status string `gorm:"column:status" json:"status"` // pending | approved | rejected
|
||||
Reviewer string `gorm:"column:reviewer" json:"reviewer"` // 审核人(admin 账号)
|
||||
RejectReason string `gorm:"column:reject_reason" json:"reject_reason"`
|
||||
IsDeleted uint8 `gorm:"column:is_deleted" json:"is_deleted"`
|
||||
CreatedAt uint32 `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt uint32 `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定草稿主表名。
|
||||
func (BrandRunwayDraft) TableName() string { return "brand_runway_draft" }
|
||||
|
||||
// BrandRunwayDraftImage 草稿图片(对应 brand_runway_draft_images)。
|
||||
type BrandRunwayDraftImage struct {
|
||||
ID uint32 `gorm:"primaryKey;column:id" json:"id"`
|
||||
DraftID uint32 `gorm:"column:draft_id" json:"draft_id"`
|
||||
Image string `gorm:"column:image" json:"image"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
SortOrder uint32 `gorm:"column:sort_order" json:"sort_order"`
|
||||
IsDeleted uint8 `gorm:"column:is_deleted" json:"is_deleted"`
|
||||
CreatedAt uint32 `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt uint32 `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定草稿图片表名。
|
||||
func (BrandRunwayDraftImage) TableName() string { return "brand_runway_draft_images" }
|
||||
|
||||
// 草稿状态常量。
|
||||
const (
|
||||
DraftStatusPending = "pending"
|
||||
DraftStatusApproved = "approved"
|
||||
DraftStatusRejected = "rejected"
|
||||
)
|
||||
43
internal/model/street_snap_draft.go
Normal file
43
internal/model/street_snap_draft.go
Normal file
@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
// StreetSnapDraft 街拍草稿(爬虫入库待审核)。
|
||||
//
|
||||
// 与 brand_runway_draft 同构的审核流程:爬虫 worker 处理完 ingest_jobs 后写入本表并置
|
||||
// pending;后台审核通过(Approve)后再晋升(SaveStreetSnapFromDraft)到 street_snap 正式表。
|
||||
//
|
||||
// 字段对齐 street_snap 正式表(单 Title、有 City、无 brand_id、无 season 系列列),
|
||||
// 外加审核专用字段,便于审核页直接预览/微调。
|
||||
type StreetSnapDraft struct {
|
||||
ID uint32 `gorm:"primaryKey;column:id" json:"id"`
|
||||
JobID uint32 `gorm:"column:job_id" json:"job_id"` // 关联 ingest_jobs.id(重跑溯源)
|
||||
Title string `gorm:"column:title" json:"title"` // 单标题(街拍无双语)
|
||||
Year uint16 `gorm:"column:year" json:"year"`
|
||||
City string `gorm:"column:city" json:"city"` // 地区/城市,'' 表示未归类
|
||||
Cover string `gorm:"column:cover" json:"cover"`
|
||||
SourceURL string `gorm:"column:source_url" json:"source_url"`
|
||||
ImageCount uint16 `gorm:"column:image_count" json:"image_count"`
|
||||
Status string `gorm:"column:status" json:"status"` // pending | approved | rejected
|
||||
Reviewer string `gorm:"column:reviewer" json:"reviewer"` // 审核人(admin 账号)
|
||||
RejectReason string `gorm:"column:reject_reason" json:"reject_reason"`
|
||||
IsDeleted uint8 `gorm:"column:is_deleted" json:"is_deleted"`
|
||||
CreatedAt uint32 `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt uint32 `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定草稿主表名。
|
||||
func (StreetSnapDraft) TableName() string { return "street_snap_draft" }
|
||||
|
||||
// StreetSnapDraftImage 街拍草稿图片(对应 street_snap_draft_images)。
|
||||
type StreetSnapDraftImage struct {
|
||||
ID uint32 `gorm:"primaryKey;column:id" json:"id"`
|
||||
DraftID uint32 `gorm:"column:draft_id" json:"draft_id"`
|
||||
Image string `gorm:"column:image" json:"image"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
SortOrder uint32 `gorm:"column:sort_order" json:"sort_order"`
|
||||
IsDeleted uint8 `gorm:"column:is_deleted" json:"is_deleted"`
|
||||
CreatedAt uint32 `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt uint32 `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定草稿图片表名。
|
||||
func (StreetSnapDraftImage) TableName() string { return "street_snap_draft_images" }
|
||||
Reference in New Issue
Block a user