修复双实例 owner-scoped 结果文件 job 被非归属实例误杀
Build Backend JAR / build (push) Has been cancelled

线上现象:货源查询任务 26402/26400(归属 server-121)在归属机正常推进,
却被 server-110 判为「结果生成失败」,错误为「该任务已绑定到另一台服务实例处理」。

两个缺陷叠加:
1. 两台实例共用一个 RocketMQ consumer group,dispatch 消息被轮询投递;
   worker 对"PENDING + owner 不匹配"的 job 选择本地抢跑,模块层
   ensureTaskOwnedByCurrentInstance 直接抛 TaskOwnerMismatchException,
   异常落进通用 catch 被当成生成失败,5 次抢跑即耗尽 retryCount 并终态失败。
2. runPendingJobs 双重 claim:claimRunnableJobsForOwner 已把 job 翻成 RUNNING,
   随后又走 claimRunning(条件 PENDING/FAILED)必然失败 → job 被静默丢弃,
   归属机的定时兜底从未真正生效。

修复:
- worker:非归属实例一律跳过 owner-scoped job;抽出 processClaimed 供已持有
  claim 的调用方使用;processInternal 单独捕获 TaskOwnerMismatchException,
  只 deferRunning 退回 PENDING,不计入重试。
- TaskFileJobLocalDispatcher 新增 dispatchClaimed。
- dispatch 事件带上 owner,coordinator 按归属路由:本机 owner 直接本地派发不发 MQ,
  他机 owner 不发不跑交由归属机轮询接手,owner 为空维持原有 MQ 行为。
- 新增 owner 路由回归测试两组,回滚配合"本地抢跑"的旧断言。

同时提交此前工作区内已随 JAR 上线的 backend-java 改动:SimilarAsin 解析载荷
groups 内嵌 items(Python 旧链路兼容)与 chunk 类型不匹配跳过、
PermissionMenuSchemaInitializer 与 V103 两级菜单分组迁移、相关测试。
This commit is contained in:
2026-09-01 21:07:25 +08:00
parent 55517f996b
commit 5f4fcad2ef
16 changed files with 679 additions and 44 deletions
@@ -0,0 +1,63 @@
-- V103: 后台管理菜单改为两级结构(一级分组 + 子页面)
-- 新增 4 个后台一级分组(不映射真实页面,route_path 为分组虚拟值),
-- 并把这 16 个后台页面菜单挂到对应分组下。
-- 分组只用于权限树层级与「上级菜单」候选;column_key / route_path 校验逻辑不变。
-- 对应 Java 端清单见 PermissionMenuSchemaInitializer.DEFAULT_ADMIN_GROUPS / DEFAULT_ADMIN_MENUS。
-- ---------- 1. 新增一级分组 ----------
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`, `parent_id`, `sort_order`)
SELECT '账号与权限', 'admin_group_account', 'admin', 'group-account', NULL, 5
WHERE NOT EXISTS (SELECT 1 FROM `columns` WHERE `column_key` = 'admin_group_account');
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`, `parent_id`, `sort_order`)
SELECT '数据管理', 'admin_group_data', 'admin', 'group-data', NULL, 15
WHERE NOT EXISTS (SELECT 1 FROM `columns` WHERE `column_key` = 'admin_group_data');
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`, `parent_id`, `sort_order`)
SELECT '店铺管理', 'admin_group_shop', 'admin', 'group-shop', NULL, 45
WHERE NOT EXISTS (SELECT 1 FROM `columns` WHERE `column_key` = 'admin_group_shop');
INSERT INTO `columns` (`name`, `column_key`, `menu_type`, `route_path`, `parent_id`, `sort_order`)
SELECT '记录与版本', 'admin_group_record', 'admin', 'group-record', NULL, 55
WHERE NOT EXISTS (SELECT 1 FROM `columns` WHERE `column_key` = 'admin_group_record');
-- ---------- 2. 页面菜单挂到分组下,并同步显示名 ----------
-- 只更新当前没有父级的页面(保留管理员手工调整过的层级),
-- COALESCE 保证已挂接的叶子不重复指向。
UPDATE `columns` child
JOIN `columns` parent ON parent.`column_key` = 'admin_group_account'
SET child.`parent_id` = COALESCE(child.`parent_id`, parent.`id`)
WHERE child.`column_key` IN ('admin_users', 'admin_columns', 'admin_group_manage');
UPDATE `columns` child
JOIN `columns` parent ON parent.`column_key` = 'admin_group_data'
SET child.`parent_id` = COALESCE(child.`parent_id`, parent.`id`)
WHERE child.`column_key` IN ('admin_dedupe_total_data', 'admin_invalid_asin_data',
'admin_query_asin', 'admin_product_categories');
UPDATE `columns` child
JOIN `columns` parent ON parent.`column_key` = 'admin_group_shop'
SET child.`parent_id` = COALESCE(child.`parent_id`, parent.`id`)
WHERE child.`column_key` IN ('admin_shop_keys', 'admin_shop_manage', 'admin_skip_price_asin',
'admin_shop_data_crawl_tasks');
UPDATE `columns` child
JOIN `columns` parent ON parent.`column_key` = 'admin_group_record'
SET child.`parent_id` = COALESCE(child.`parent_id`, parent.`id`)
WHERE child.`column_key` IN ('admin_history', 'admin_version', 'digital_human_version',
'admin_image_video_tasks');
-- ---------- 3. 显示名对齐前端界面 ---
UPDATE `columns` SET `name` = '品牌数据库'
WHERE `menu_type` = 'admin' AND `column_key` = 'admin_invalid_asin_data';
UPDATE `columns` SET `name` = '最低价ASIN设置'
WHERE `menu_type` = 'admin' AND `column_key` = 'admin_skip_price_asin';
UPDATE `columns` SET `name` = '视频任务记录'
WHERE `menu_type` = 'admin' AND `column_key` = 'admin_image_video_tasks';
UPDATE `columns` SET `name` = '店铺数据记录'
WHERE `menu_type` = 'admin' AND `column_key` = 'admin_shop_data_crawl_tasks';