feat: 品牌抓取历史(brand_crawl_logs)+ 品牌页展示
crawl_tasks 每品牌只有一行(uq_crawl_tasks_brand),入队/完成都是覆盖, 答不了「什么时候爬过、爬了几次、哪次没抓到」。故新增 append-only 历史表: - 在 Report 的同一事务里写一条:品牌 / 任务 / 状态 / 第几次尝试 / 场次上限 / 实际上报场次 / 错误 / 执行者 / 领取与回报时间 - 写入后只保留该品牌最近 30 条(CrawlLogKeepPerBrand), 历史用于排障而非归档,避免无限增长 - 品牌详情页新增「抓取历史」区块(结果 / 明细 / 耗时 / 执行者), 与「更新记录」上下相邻,构成「抓取一次 → 每场入库结果」的完整链路 量级:每品牌每次抓取 1 行、约 200B(含索引);全站跑一遍 23237 行 ≈ 4.6MB。
This commit is contained in:
@ -38,6 +38,10 @@ type CrawlRepository interface {
|
||||
// BrandCrawlStates 返回这些品牌在抓取队列里的「最近状态 + 最近时间」。
|
||||
// 品牌列表用它展示「抓取状态 / 上次抓取」;没有任务记录的品牌不会出现在 map 里。
|
||||
BrandCrawlStates(ctx context.Context, brandIDs []uint32) (map[uint32]BrandCrawlState, error)
|
||||
// ListCrawlLogs 取某品牌最近的抓取历史(id 倒序),品牌详情页展示用。
|
||||
//
|
||||
// crawl_tasks 每品牌只有一行且会被覆盖,历史只存在 brand_crawl_logs 里。
|
||||
ListCrawlLogs(ctx context.Context, brandID uint32, limit int) ([]model.CrawlLog, error)
|
||||
}
|
||||
|
||||
// BrandCrawlState 品牌最近一次抓取任务的状态快照。
|
||||
@ -244,7 +248,7 @@ func (r *crawlRepository) Report(ctx context.Context, id uint32, status string,
|
||||
if status == model.CrawlFailed && cur.Attempts < model.CrawlMaxAttempts {
|
||||
next = model.CrawlQueued
|
||||
}
|
||||
return tx.Model(&model.CrawlTask{}).Where("id = ?", id).
|
||||
if err := tx.Model(&model.CrawlTask{}).Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"status": next,
|
||||
"shows_submitted": shows,
|
||||
@ -252,7 +256,33 @@ func (r *crawlRepository) Report(ctx context.Context, id uint32, status string,
|
||||
"locked_at": 0,
|
||||
"worker": "",
|
||||
"updated_at": now,
|
||||
}).Error
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 同事务写一条抓取历史:crawl_tasks 每品牌只有一行会被覆盖,
|
||||
// 不在这里留痕就永远查不到「上次爬是什么时候、抓到几场」。
|
||||
log := model.CrawlLog{
|
||||
BrandID: cur.BrandID,
|
||||
TaskID: cur.ID,
|
||||
Status: status,
|
||||
Attempt: cur.Attempts + 1,
|
||||
ShowsRequested: cur.MaxShows,
|
||||
ShowsSubmitted: int32(shows),
|
||||
Message: errMsg,
|
||||
Worker: cur.Worker,
|
||||
StartedAt: cur.LockedAt,
|
||||
FinishedAt: now,
|
||||
}
|
||||
if err := tx.Create(&log).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 只保留该品牌最近 N 条:历史用于排障,不是归档,避免无限增长。
|
||||
return tx.Exec(
|
||||
"DELETE FROM brand_crawl_logs WHERE brand_id = ? AND id < "+
|
||||
"(SELECT id FROM brand_crawl_logs WHERE brand_id = ? ORDER BY id DESC OFFSET ? LIMIT 1)",
|
||||
cur.BrandID, cur.BrandID, model.CrawlLogKeepPerBrand).Error
|
||||
})
|
||||
}
|
||||
|
||||
@ -293,6 +323,22 @@ func (r *crawlRepository) Delete(ctx context.Context, id uint32) error {
|
||||
return r.db.WithContext(ctx).Exec(`DELETE FROM crawl_tasks WHERE id = ?`, id).Error
|
||||
}
|
||||
|
||||
// ListCrawlLogs 取某品牌最近的抓取历史。
|
||||
func (r *crawlRepository) ListCrawlLogs(ctx context.Context, brandID uint32, limit int) ([]model.CrawlLog, error) {
|
||||
if brandID == 0 || limit <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var rows []model.CrawlLog
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("brand_id = ?", brandID).
|
||||
Order("id DESC").
|
||||
Limit(limit).
|
||||
Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (r *crawlRepository) BrandCrawlStates(ctx context.Context, brandIDs []uint32) (map[uint32]BrandCrawlState, error) {
|
||||
out := make(map[uint32]BrandCrawlState, len(brandIDs))
|
||||
if len(brandIDs) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user