fix(ops): 修复巡检异步处理的 @Async 自调用和事务可见性问题

Code review 发现两个关键缺陷:
1. @Async 自调用:triggerAttributionAsync() 在同一类内调用,
   Spring AOP 代理不生效,实际同步执行
2. 事务可见性:异步任务可能在事务提交前读取未持久化数据

修复方案:
- 提取 InspectionAsyncHandler(独立 @Component),@Async 通过代理生效
- 使用 TransactionSynchronizationManager.afterCommit() 确保事务提交后触发
- 修复 InspectionRectificationServiceImpl 的 null 安全问题
- 修复 InspectionTemplateServiceImpl 更新路径缺少 id 空校验

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-05 19:30:01 +08:00
parent 162bf1d20d
commit 23cf3b62b2
4 changed files with 64 additions and 30 deletions

View File

@@ -0,0 +1,45 @@
package com.viewsh.module.ops.environment.service.inspection;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 巡检异步处理器(独立 Bean解决 @Async 自调用问题)
*
* 负责在事务提交后异步执行:
* 1. 归属判定
* 2. 整改工单创建
*/
@Component
@Slf4j
public class InspectionAsyncHandler {
@Resource
private InspectionAttributionService inspectionAttributionService;
@Resource
private InspectionRectificationService inspectionRectificationService;
/**
* 异步处理不合格巡检的后续逻辑
*/
@Async
public void handleFailedInspection(Long recordId, Long areaId) {
// 1. 归属判定
try {
inspectionAttributionService.determineAttribution(recordId, areaId);
} catch (Exception e) {
log.error("[handleFailedInspection] 归属判定异常: recordId={}, areaId={}", recordId, areaId, e);
}
// 2. 创建整改工单(不论归属判定结果,不合格即需整改)
try {
inspectionRectificationService.createRectificationOrder(recordId, areaId);
} catch (Exception e) {
log.error("[handleFailedInspection] 整改工单创建异常: recordId={}, areaId={}", recordId, areaId, e);
}
}
}

View File

@@ -8,9 +8,10 @@ import com.viewsh.module.ops.environment.dal.mysql.inspection.OpsInspectionRecor
import com.viewsh.module.ops.environment.dal.mysql.inspection.OpsInspectionRecordMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;
import java.util.List;
@@ -35,10 +36,7 @@ public class InspectionRecordServiceImpl implements InspectionRecordService {
private OpsInspectionRecordItemMapper inspectionRecordItemMapper;
@Resource
private InspectionAttributionService inspectionAttributionService;
@Resource
private InspectionRectificationService inspectionRectificationService;
private InspectionAsyncHandler inspectionAsyncHandler;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -69,32 +67,19 @@ public class InspectionRecordServiceImpl implements InspectionRecordService {
.toList();
inspectionRecordItemMapper.insertBatch(items);
// 4. 不合格时异步触发归属判定Task 6 实现)
// 4. 不合格时,在事务提交后异步触发归属判定 + 整改工单
if (resultStatus == RESULT_STATUS_FAILED) {
triggerAttributionAsync(record.getId(), submitReqVO.getAreaId());
Long recordId = record.getId();
Long areaId = submitReqVO.getAreaId();
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
inspectionAsyncHandler.handleFailedInspection(recordId, areaId);
}
});
}
return record.getId();
}
/**
* 异步触发归属判定 + 整改工单创建
*/
@Async
public void triggerAttributionAsync(Long recordId, Long areaId) {
// 1. 归属判定
try {
inspectionAttributionService.determineAttribution(recordId, areaId);
} catch (Exception e) {
log.error("[triggerAttributionAsync] 归属判定异常: recordId={}, areaId={}", recordId, areaId, e);
}
// 2. 创建整改工单(不论归属判定结果,不合格即需整改)
try {
inspectionRectificationService.createRectificationOrder(recordId, areaId);
} catch (Exception e) {
log.error("[triggerAttributionAsync] 整改工单创建异常: recordId={}, areaId={}", recordId, areaId, e);
}
}
}

View File

@@ -36,8 +36,9 @@ public class InspectionRectificationServiceImpl implements InspectionRectificati
public Long createRectificationOrder(Long recordId, Long areaId) {
// 1. 获取区域信息
OpsBusAreaDO area = opsBusAreaMapper.selectById(areaId);
String areaName = area != null ? area.getAreaName() : "未知区域";
Integer standardDuration = area != null ? area.getStandardDuration() : 30;
String areaName = (area != null) ? area.getAreaName() : "未知区域";
int expectedDuration = (area != null && area.getStandardDuration() != null)
? area.getStandardDuration() : 30;
// 2. 构建整改工单请求
CleanOrderAutoCreateReqDTO createReq = new CleanOrderAutoCreateReqDTO();
@@ -47,7 +48,7 @@ public class InspectionRectificationServiceImpl implements InspectionRectificati
createReq.setDescription("巡检发现不合格需重新清洁。巡检记录ID" + recordId);
createReq.setPriority(PriorityEnum.P1.getPriority());
createReq.setAreaId(areaId);
createReq.setExpectedDuration(standardDuration != null ? standardDuration : 30);
createReq.setExpectedDuration(expectedDuration);
createReq.setCleaningType("SPOT");
createReq.setDifficultyLevel(3);

View File

@@ -48,6 +48,9 @@ public class InspectionTemplateServiceImpl implements InspectionTemplateService
@Override
@Transactional(rollbackFor = Exception.class)
public void updateTemplate(InspectionTemplateSaveReqVO updateReqVO) {
if (updateReqVO.getId() == null) {
throw exception(INSPECTION_TEMPLATE_NOT_FOUND);
}
validateTemplateExists(updateReqVO.getId());
OpsInspectionTemplateDO updateObj = BeanUtils.toBean(updateReqVO, OpsInspectionTemplateDO.class);
inspectionTemplateMapper.updateById(updateObj);