店铺数据抓取累计文件改为店铺级共享,按国家覆盖更新

同店同日在不同账号下产生多份互不相干的累计文件(唯一键含 user_id),
后台管理页每店只显示最新一份,其他账号抓的国家看起来丢失。

- V95 迁移:加 shop_key/country_codes_json/compensation_done 列,按店铺
  合并存量 daily_file 与成员行,唯一键改为 (shop_key, business_date)
- DailyFileService:findForUpdate/findOlder/acquireLock 去 user 维度,
  店铺级锁跨账号串行
- TaskService:聚合按店铺定位;applyCountryCoverage 按国家覆盖(本次
  回传的国家替换旧行,未更新的国家保留);启动补偿组件按成员快照重建
  合并文件并回填国家列表
- 管理页:country_codes 改从 daily_file.country_codes_json 取,行数用
  累计文件实际值
This commit is contained in:
2026-08-30 16:30:09 +08:00
parent ae95b294ec
commit 97ae8a7c51
11 changed files with 495 additions and 160 deletions
@@ -0,0 +1,70 @@
-- V95: 店铺数据抓取累计文件改为店铺级共享(去 user 维度)
-- 问题:同店同日在不同账号(尾号5578江秀珍1~5)下产生多份互不相干的累计文件,
-- 管理页每店只显示最新一份,其他账号抓的国家(如英国)看起来"没了"。
-- 目标:每店每天只保留一份累计文件,(shop_key, business_date) 唯一;
-- 聚合层按国家覆盖(见 ShopDataCrawlTaskService),未更新的国家保留。
--
-- 存量合并策略:
-- 1) 回填 shop_key(店铺名,来自 daily_member.result_id -> biz_file_result.source_filename
-- 2) 同店同日多份 daily_file 合并为一个分组(保留各 result 的成员行),
-- 由启动补偿组件按成员 result 快照重建一份累计文件
-- 3) 唯一键改为 (shop_key, business_date)
-- 1. 新增列(country_codes_json 供后台管理页展示累计文件实际包含的国家)
ALTER TABLE biz_shop_data_crawl_daily_file
ADD COLUMN `shop_key` VARCHAR(1000) NULL DEFAULT NULL COMMENT '店铺名(去 user 后的店铺级归属键)' AFTER `shop_key_hash`,
ADD COLUMN `country_codes_json` VARCHAR(512) NULL DEFAULT NULL COMMENT '累计文件实际包含的国家代码 JSON(后台管理页展示用)' AFTER `result_content_type`,
ADD COLUMN `compensation_done` TINYINT NOT NULL DEFAULT 0 COMMENT '0=启动补偿组件待重建,1=已按成员快照重建' AFTER `version`;
-- 2. 回填 shop_keydaily_member 记录了每个结果行属于哪个 daily_file
UPDATE biz_shop_data_crawl_daily_file df
JOIN biz_shop_data_crawl_daily_member m ON m.daily_file_id = df.id
JOIN biz_file_result r ON r.id = m.result_id
SET df.shop_key = TRIM(r.source_filename)
WHERE df.shop_key IS NULL OR df.shop_key = '';
-- 3. 兜底:无成员的 daily_file 用 latest_result_id 回填
UPDATE biz_shop_data_crawl_daily_file df
LEFT JOIN biz_shop_data_crawl_daily_member m ON m.daily_file_id = df.id
JOIN biz_file_result r ON r.id = df.latest_result_id
SET df.shop_key = TRIM(r.source_filename)
WHERE m.id IS NULL AND (df.shop_key IS NULL OR df.shop_key = '');
-- 4. 清理仍为空的(无任何可反解依据的历史残留)
DELETE df FROM biz_shop_data_crawl_daily_file df
WHERE df.shop_key IS NULL OR df.shop_key = '';
-- 5. 交换唯一键:先删旧 (user_id, shop_key_hash, business_date),再加 (shop_key, business_date)
ALTER TABLE biz_shop_data_crawl_daily_file
DROP KEY uk_shop_data_crawl_daily_file;
-- 6. 同店同日合并成员行归属:保留最新一份 daily_file,其余 daily_file 的成员行迁到它名下
UPDATE biz_shop_data_crawl_daily_member m
JOIN biz_shop_data_crawl_daily_file cur ON cur.id = m.daily_file_id
JOIN (
SELECT shop_key, business_date, MAX(id) AS max_id
FROM biz_shop_data_crawl_daily_file
GROUP BY shop_key, business_date
HAVING COUNT(*) > 1
) g ON g.shop_key = cur.shop_key AND g.business_date = cur.business_date AND g.max_id != cur.id
SET m.daily_file_id = g.max_id;
-- 7. 删除同店同日合并后的多余 daily_file 行(对象存储文件由补偿组件按引用计数清理)
DELETE df FROM biz_shop_data_crawl_daily_file df
WHERE df.id NOT IN (
SELECT keep_id FROM (
SELECT MAX(id) AS keep_id
FROM biz_shop_data_crawl_daily_file
GROUP BY shop_key, business_date
) t
);
-- 8. 唯一键(shop_key 已非空;兜底统一改为未命名)
UPDATE biz_shop_data_crawl_daily_file SET shop_key = '未命名' WHERE TRIM(shop_key) = '';
-- 9. country_codes_json 由启动补偿组件加载成员 result 快照后回填
-- biz_task_result_item.payload_json 是 rustfs 指针,SQL 无法解析内容)
-- 10. 新唯一键
ALTER TABLE biz_shop_data_crawl_daily_file
ADD UNIQUE KEY uk_shop_data_crawl_daily_file (shop_key, business_date);