diff --git a/db/20260914_slow_zombie_refund_tasks.sql b/db/20260914_slow_zombie_refund_tasks.sql new file mode 100644 index 00000000..acac224a --- /dev/null +++ b/db/20260914_slow_zombie_refund_tasks.sql @@ -0,0 +1,30 @@ +-- 2026-09-14 自动退款任务僵尸循环止血脚本 +-- +-- 背景:商户关闭自动退款开关的任务被置为 status='30'(开关关闭暂停)且 next_retry_time=NULL, +-- 扫描器对 next_retry_time IS NULL 的暂停任务每 30 秒无条件重扫,配合 wallet 配置服务不可达, +-- 形成 select for update + Feign 超时的死循环,拖垮订单接口(当晚 page-join 504)。 +-- 代码修复(暂停任务加 10 分钟退避)上线后新数据自动退避,本脚本只处理存量 NULL 行。 +-- +-- 执行方式:手工执行(无自动迁移),先于代码部署。 +-- 注意:不置终态——商户重新打开开关后任务仍可正常退款,仅把复查时间推迟一天降温。 + +-- 1. 执行前确认存量僵尸任务数量(预期为线上扫描风暴的规模) +SELECT COUNT(*) AS zombie_task_count +FROM hard_auto_refund_task +WHERE del_flag = '0' + AND status = '30' + AND next_retry_time IS NULL; + +-- 2. 将存量僵尸任务的复查时间推至一天后,立即脱离 30 秒扫描循环 +UPDATE hard_auto_refund_task +SET next_retry_time = DATE_ADD(NOW(), INTERVAL 1 DAY) +WHERE del_flag = '0' + AND status = '30' + AND next_retry_time IS NULL; + +-- 3. 执行后确认已无 NULL 复查时间的暂停任务(预期为 0) +SELECT COUNT(*) AS remaining_zombie_task_count +FROM hard_auto_refund_task +WHERE del_flag = '0' + AND status = '30' + AND next_retry_time IS NULL; diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java index 0135739a..f1746178 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java @@ -142,6 +142,25 @@ public interface HardOrderMapper extends PjcloudBaseMapper { */ Page scopePageList(Page page, @Param("dto") HardOrderDto dto, DataScope dataScope); + /** + * 管理端先按全部筛选条件和数据范围分页读取订单主键,展示表关联与最新售后/发票子查询不参与全量排序。 + * 查询列保留 merchant_dept_id 供数据权限拦截器包装部门过滤;总数由分页插件基于数据权限改写后的 SQL 自动统计。 + * + * @param page 主键分页参数,排序规则与 {@link #scopePageList} 完全一致 + * @param dto 订单、商户、状态、时间、设备类型及售后单号筛选条件 + * @param dataScope 后台商户部门数据范围 + * @return 当前页订单主键(附商户部门列)及符合条件的总数 + */ + Page scopePageIds(Page page, @Param("dto") HardOrderDto dto, DataScope dataScope); + + /** + * 按已分页的订单主键批量读取管理端展示详情,设备、最新售后单、付款人、发票和兑换券关联仅对当前页执行。 + * + * @param orderIds 当前页订单主键集合,调用方保证非空且已完成数据范围过滤 + * @return 当前租户内存在的订单详情;SQL 不承诺返回顺序,由服务层按主键页顺序重排 + */ + List listPageDetails(@Param("orderIds") List orderIds); + /** * 先按 App 可见范围和全部筛选条件分页读取订单主键,避免展示表关联参与全量排序。 * diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAutoRefundTaskServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAutoRefundTaskServiceImpl.java index 7db8fe85..11895dc8 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAutoRefundTaskServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAutoRefundTaskServiceImpl.java @@ -99,8 +99,16 @@ public class HardAutoRefundTaskServiceImpl extends ServiceImpl list = baseMapper.scopePageList(page, dto, dataScope); + // 管理端与 App 相同:先分页主键再回填详情,最新售后/发票相关子查询只对当前页执行,不参与全量排序与计数。 + Page list = idPageThenDetails(page, dto, dataScope); disposeList(list.getRecords(), dto); return list; } + /** + * 管理端先在订单主表上分页主键,再按主键回填当前页详情,避免售后、发票等展示关联参与全量排序与计数。 + * + * @param page 原始订单分页参数 + * @param dto 订单筛选条件 + * @param dataScope 后台商户部门数据范围 + * @return 记录顺序与主键分页查询完全一致的订单页 + */ + private Page idPageThenDetails(Page page, HardOrderDto dto, DataScope dataScope) { + Page idPage = new Page<>(page.getCurrent(), page.getSize(), page.searchCount()); + idPage.setMaxLimit(page.maxLimit()); + idPage.setOptimizeCountSql(page.optimizeCountSql()); + idPage.setOptimizeJoinOfCountSql(page.optimizeJoinOfCountSql()); + idPage.setOrders(page.orders()); + idPage = baseMapper.scopePageIds(idPage, dto, dataScope); + + List orderIds = idPage.getRecords().stream().map(HardOrderVo::getId).toList(); + List orderedRecords = List.of(); + if (ObjectUtil.isNotEmpty(orderIds)) { + Map detailMap = CollectionUtils.convertMap(baseMapper.listPageDetails(orderIds), HardOrderVo::getId); + orderedRecords = orderIds.stream().map(detailMap::get).filter(Objects::nonNull).toList(); + } + + Page result = new Page<>(idPage.getCurrent(), idPage.getSize(), idPage.getTotal(), idPage.searchCount()); + result.setRecords(orderedRecords); + result.setOrders(page.orders()); + result.setMaxLimit(idPage.maxLimit()); + return result; + } + /** * App 订单先分页主键、再关联当前页详情,避免售后和父购物车关联参与全部历史订单的排序与分页。 * @@ -390,7 +421,7 @@ public class HardOrderServiceImpl extends ServiceImpl list = baseMapper.scopePageList(page, dto, dataScope); + Page list = idPageThenDetails(page, dto, dataScope); disposeList(list.getRecords(), dto); return list; } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml index 5af00bd2..38581d45 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml @@ -360,6 +360,25 @@ + + + + + order by a.verification_time desc, a.id desc + + + order by a.appt_use_date desc, a.hour_type asc, a.id desc + + + order by a.update_time desc, a.id desc + + + + order by a.create_time desc, a.user_id desc, a.id desc + + + + m.name as hardName, @@ -416,27 +435,100 @@ and s.no = #{dto.afterSaleNo} - - - order by a.verification_time desc, a.id desc - - - order by a.appt_use_date desc, a.hour_type asc, a.id desc - - - order by a.update_time desc, a.id desc - - - - order by a.create_time desc, a.user_id desc, a.id desc - - + + + + + + +