From 8cd51654b710d47c3de62b90f5fef1b75048191d Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Fri, 27 Feb 2026 14:09:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(inspect):=20=E4=BC=98=E5=8C=96=E5=B7=A1?= =?UTF-8?q?=E6=A3=80=E5=B7=A5=E5=8D=95=E7=94=9F=E6=88=90=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=BC=BA=E5=B9=B6=E5=8F=91=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增数据库当前时间获取方法,避免应用与数据库时钟差异 - 修复计划ID匹配逻辑,将模糊查询改为精确匹配 - 添加数据库表前缀别名,确保排序字段准确性 - 引入分布式锁机制,通过FOR UPDATE串行化并发任务 - 实现应用层幂等检查,避免重复生成工单 - 优化SQL查询性能,移除可能导致索引失效的TRUNC函数 - 添加Oracle唯一约束异常处理,提升系统容错能力 - 完善计划时间推进逻辑,基于计划当前时间准确计算下次执行时间 - 分离事务边界,确保工单生成与计划更新的独立性 - 重构序列名称,统一命名规范 - 优化分页查询语法,适配Oracle数据库特性 --- aucma-dms/DMS_SEQUENCES.sql | 54 ++++++++++-- .../mapper/DmsBillsInspectInstanceMapper.java | 6 ++ .../dms/mapper/DmsPlanInspectMapper.java | 8 ++ .../DmsBillsInspectInstanceServiceImpl.java | 83 +++++++++++++++++-- .../impl/DmsPlanInspectServiceImpl.java | 78 +++++++++++------ .../dms/DmsBillsInspectInstanceMapper.xml | 80 +++++++++--------- .../dms/DmsInspectInstanceDetailMapper.xml | 14 ++-- .../DmsInspectInstanceDetailProjectMapper.xml | 7 +- .../mapper/dms/DmsPlanInspectMapper.xml | 9 ++ 9 files changed, 251 insertions(+), 88 deletions(-) diff --git a/aucma-dms/DMS_SEQUENCES.sql b/aucma-dms/DMS_SEQUENCES.sql index 616b048..58a693e 100644 --- a/aucma-dms/DMS_SEQUENCES.sql +++ b/aucma-dms/DMS_SEQUENCES.sql @@ -73,16 +73,33 @@ CREATE SEQUENCE "HAIWEI"."SEQ_DMS_INSPECT_RTE_DTL" CACHE 20; -- ---------------------------- --- 4A. SEQ_DMS_INSPECT_INSTANCE_DETAIL (巡检工单明细) +-- 4A. SEQ_DMS_INSP_INST_DETAIL (巡检工单明细) -- 表: DMS_INSPECT_INSTANCE_DETAIL -- ---------------------------- BEGIN - EXECUTE IMMEDIATE 'DROP SEQUENCE "HAIWEI"."SEQ_DMS_INSPECT_INSTANCE_DETAIL"'; + EXECUTE IMMEDIATE 'DROP SEQUENCE "HAIWEI"."SEQ_DMS_INSP_INST_DETAIL"'; EXCEPTION WHEN OTHERS THEN NULL; END; / -CREATE SEQUENCE "HAIWEI"."SEQ_DMS_INSPECT_INSTANCE_DETAIL" +CREATE SEQUENCE "HAIWEI"."SEQ_DMS_INSP_INST_DETAIL" + MINVALUE 1 + MAXVALUE 9999999999999999999999999999 + START WITH 1 + INCREMENT BY 1 + CACHE 20; + +-- ---------------------------- +-- 4B. SEQ_DMS_INSP_DET_PROJ (巡检明细项目) +-- 表: DMS_INSPECT_INST_DET_PROJ +-- ---------------------------- +BEGIN + EXECUTE IMMEDIATE 'DROP SEQUENCE "HAIWEI"."SEQ_DMS_INSP_DET_PROJ"'; +EXCEPTION + WHEN OTHERS THEN NULL; +END; +/ +CREATE SEQUENCE "HAIWEI"."SEQ_DMS_INSP_DET_PROJ" MINVALUE 1 MAXVALUE 9999999999999999999999999999 START WITH 1 @@ -242,7 +259,6 @@ CREATE SEQUENCE "HAIWEI"."SEQ_FAULT_INSTANCE_ACTIVITY" INCREMENT BY 1 CACHE 20; --- ---------------------------- -- ---------------------------- -- 14. SEQ_DMS_BILLS_INSPECT_INSTANCE (巡检工单) -- 表: DMS_BILLS_INSPECT_INSTANCE @@ -276,6 +292,7 @@ CREATE SEQUENCE "HAIWEI"."SEQ_DMS_BILLS_INSPECT_ACTIVITY" START WITH 1 INCREMENT BY 1 CACHE 20; + -- 14. SEQ_DMS_MAINT_INST (保养工单) -- 表: DMS_BILLS_MAINT_INSTANCE -- ---------------------------- @@ -530,6 +547,23 @@ CREATE SEQUENCE "HAIWEI"."SEQ_DMS_REC_SHUT_DOWN" INCREMENT BY 1 CACHE 20; +-- ---------------------------- +-- 29. SEQ_MES_BASE_ATTACH_INFO (附件信息) +-- 表: DMS_BASE_ATTACH_INFO +-- ---------------------------- +BEGIN + EXECUTE IMMEDIATE 'DROP SEQUENCE "HAIWEI"."SEQ_MES_BASE_ATTACH_INFO"'; +EXCEPTION + WHEN OTHERS THEN NULL; +END; +/ +CREATE SEQUENCE "HAIWEI"."SEQ_MES_BASE_ATTACH_INFO" + MINVALUE 1 + MAXVALUE 9999999999999999999999999999 + START WITH 1 + INCREMENT BY 1 + CACHE 20; + -- ============================================ -- 序列对齐:按 MAX(主键列)+1 设置 NEXTVAL(Oracle 11g 兼容方案) -- ============================================ @@ -591,7 +625,9 @@ BEGIN -- 4. 点检路线明细 sync_seq('HAIWEI.SEQ_DMS_INSPECT_RTE_DTL', 'HAIWEI.DMS_INSPECT_ROUTE_DETAIL', 'ROUTE_DETAIL_ID'); -- 4A. 巡检工单明细 - sync_seq('HAIWEI.SEQ_DMS_INSPECT_INSTANCE_DETAIL','HAIWEI.DMS_INSPECT_INSTANCE_DETAIL', 'INSTANCE_DETAIL_ID'); + sync_seq('HAIWEI.SEQ_DMS_INSP_INST_DETAIL', 'HAIWEI.DMS_INSPECT_INSTANCE_DETAIL', 'INSTANCE_DETAIL_ID'); + -- 4B. 巡检明细项目 + sync_seq('HAIWEI.SEQ_DMS_INSP_DET_PROJ', 'HAIWEI.DMS_INSPECT_INST_DET_PROJ', 'INSTANCE_DETAIL_PROJECT_ID'); -- 5. 润滑标准 sync_seq('HAIWEI.SEQ_DMS_LUBE_STD', 'HAIWEI.DMS_BASE_LUBE_STANDARD', 'LUBE_STANDARD_ID'); -- 6. 润滑部位 @@ -644,13 +680,16 @@ BEGIN sync_seq('HAIWEI.SEQ_DMS_REPAIR_WORK_ORDER', 'HAIWEI.DMS_REPAIR_WORK_ORDER', 'WORK_ORDER_ID'); -- 28. 停机记录 sync_seq('HAIWEI.SEQ_DMS_REC_SHUT_DOWN', 'HAIWEI.DMS_RECORD_SHUT_DOWN', 'RECORD_SHUT_DOWN_ID'); + -- 29. 附件信息 + sync_seq('HAIWEI.SEQ_MES_BASE_ATTACH_INFO', 'HAIWEI.DMS_BASE_ATTACH_INFO', 'ATTACH_ID'); -- 为所有序列授权 + 创建公共同义词(保证跨 schema 应用可用) grant_and_synonym('SEQ_DMS_INSPECT_PROJ'); grant_and_synonym('SEQ_DMS_INSPECT_ROUTE'); grant_and_synonym('SEQ_DMS_INSPECT_STD'); grant_and_synonym('SEQ_DMS_INSPECT_RTE_DTL'); - grant_and_synonym('SEQ_DMS_INSPECT_INSTANCE_DETAIL'); + grant_and_synonym('SEQ_DMS_INSP_INST_DETAIL'); + grant_and_synonym('SEQ_DMS_INSP_DET_PROJ'); grant_and_synonym('SEQ_DMS_LUBE_STD'); grant_and_synonym('SEQ_DMS_LUBE_STATION'); grant_and_synonym('SEQ_DMS_MAINT_PROJ'); @@ -677,12 +716,13 @@ BEGIN grant_and_synonym('SEQ_DMS_REPAIR_RECORD'); grant_and_synonym('SEQ_DMS_REPAIR_WORK_ORDER'); grant_and_synonym('SEQ_DMS_REC_SHUT_DOWN'); + grant_and_synonym('SEQ_MES_BASE_ATTACH_INFO'); END; / -- ============================================ -- 执行完成提示 -- ============================================ --- 共创建 28 个序列,已按 MAX(主键)+1 对齐,已授权+同义词 +-- 共创建 33 个序列,已按 MAX(主键)+1 对齐,已授权+同义词 -- 验证: -- SELECT SEQUENCE_NAME, LAST_NUMBER FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER='HAIWEI' AND (SEQUENCE_NAME LIKE 'SEQ_DMS%' OR SEQUENCE_NAME LIKE 'SEQ_FAULT%'); diff --git a/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsBillsInspectInstanceMapper.java b/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsBillsInspectInstanceMapper.java index 05552eb..cb3d1d0 100644 --- a/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsBillsInspectInstanceMapper.java +++ b/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsBillsInspectInstanceMapper.java @@ -4,6 +4,7 @@ import com.aucma.dms.domain.DmsBillsInspectInstance; import com.aucma.dms.domain.DmsRecordInspect; import org.apache.ibatis.annotations.Param; +import java.util.Date; import java.util.List; /** @@ -91,4 +92,9 @@ public interface DmsBillsInspectInstanceMapper * @return 工单数量 */ int countByPlanIdAndToday(@Param("planInspectId") Long planInspectId); + + /** + * 获取数据库当前时间(SYSDATE) + */ + Date selectDbNow(); } diff --git a/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsPlanInspectMapper.java b/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsPlanInspectMapper.java index e2e2a55..3eb09ff 100644 --- a/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsPlanInspectMapper.java +++ b/aucma-dms/src/main/java/com/aucma/dms/mapper/DmsPlanInspectMapper.java @@ -80,4 +80,12 @@ public interface DmsPlanInspectMapper int updatePlanNextTime(@Param("planInspectId") Long planInspectId, @Param("planTime") Date planTime); + /** + * 锁定计划行(FOR UPDATE),用于并发场景下串行生成工单 + * + * @param planInspectId 计划ID + * @return 计划ID(不存在返回null) + */ + Long lockPlanById(@Param("planInspectId") Long planInspectId); + } diff --git a/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsBillsInspectInstanceServiceImpl.java b/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsBillsInspectInstanceServiceImpl.java index ca3b556..edd3067 100644 --- a/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsBillsInspectInstanceServiceImpl.java +++ b/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsBillsInspectInstanceServiceImpl.java @@ -13,6 +13,8 @@ import com.aucma.dms.service.IDmsBillsInspectInstanceService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -36,6 +38,8 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta @Autowired private DmsBillsInspectInstanceMapper dmsBillsInspectInstanceMapper; @Autowired + private DmsPlanInspectMapper dmsPlanInspectMapper; + @Autowired private DmsBillsInspectInstanceActivityMapper dmsBillsInspectInstanceActivityMapper; @Autowired private DmsRepairInstanceMapper dmsRepairInstanceMapper; @@ -105,7 +109,23 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta dmsBillsInspectInstance.setIsFlag("1"); dmsBillsInspectInstance.setInspectStatus(DmsConstants.DMS_BILLS_INSPECT_INSTANCE_INSPECT_STATUS_TO_INSPECT); dmsBillsInspectInstance.setCreateBy(SecurityUtils.getLoginUser()==null ? SecurityUtils.getUserId() :SecurityUtils.getLoginUser().getUser().getUserId()); - int i = dmsBillsInspectInstanceMapper.insertDmsBillsInspectInstance(dmsBillsInspectInstance); + int i; + try { + // 4) 插入主工单: + // 若并发场景下命中唯一约束,则视为“已生成”,返回 0。 + i = dmsBillsInspectInstanceMapper.insertDmsBillsInspectInstance(dmsBillsInspectInstance); + } catch (DuplicateKeyException e) { + log.debug("巡检工单触发唯一约束(重复生成),跳过 | planInspectId={}, billsInspectCode={}", + dmsBillsInspectInstance.getPlanInspectId(), dmsBillsInspectInstance.getBillsInspectCode()); + return 0; + } catch (DataIntegrityViolationException e) { + if (isOracleUniqueViolation(e)) { + log.debug("巡检工单触发 ORA-00001(重复生成),跳过 | planInspectId={}, billsInspectCode={}", + dmsBillsInspectInstance.getPlanInspectId(), dmsBillsInspectInstance.getBillsInspectCode()); + return 0; + } + throw e; + } log.info("新增巡检工单 | inspectInstanceId={}, billsInspectCode={}, inspectRouteId={}, inspectType={}", dmsBillsInspectInstance.getInspectInstanceId(), dmsBillsInspectInstance.getBillsInspectCode(), dmsBillsInspectInstance.getInspectRouteId(), dmsBillsInspectInstance.getInspectType()); @@ -153,6 +173,20 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta return i; } + private boolean isOracleUniqueViolation(Throwable e) { + Throwable cursor = e; + while (cursor != null) { + String msg = cursor.getMessage(); + // ORA-00001 = unique constraint violated + // 逐层检查 cause,兼容不同异常包装层(Spring/MyBatis/JDBC)。 + if (msg != null && msg.contains("ORA-00001")) { + return true; + } + cursor = cursor.getCause(); + } + return false; + } + /** * 修改巡检工单 * @@ -405,7 +439,30 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta // @Transactional(rollbackFor = Exception.class) @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public int insertCompletedInspectInstance(DmsBillsInspectInstance dmsBillsInspectInstance) { - Date now = new Date(); + Long planInspectId = dmsBillsInspectInstance.getPlanInspectId(); + if (planInspectId != null) { + // 1) 先锁计划行(FOR UPDATE): + // 将同一计划的并发任务串行化,避免同时进入插入阶段。 + Long lockedId = dmsPlanInspectMapper.lockPlanById(planInspectId); + if (lockedId == null) { + log.warn("巡检计划不存在,跳过工单生成 | planInspectId={}", planInspectId); + return 0; + } + // 2) 再做当天查重(应用层幂等): + // 这里返回已生成则直接跳过。 + int existCount = dmsBillsInspectInstanceMapper.countByPlanIdAndToday(planInspectId); + if (existCount > 0) { + log.debug("巡检计划今天已生成工单,跳过 | planInspectId={}", planInspectId); + return 0; + } + } + + // 3) 统一使用 DB 时间作为“当天”基准,减少应用机/DB 机时钟偏差带来的跨天误判。 + Date now = dmsBillsInspectInstanceMapper.selectDbNow(); + if (now == null) { + // 理论兜底:DB 时间查询异常时退回应用时间,避免任务中断。 + now = new Date(); + } log.info("开始创建已完成巡检工单 | billsInspectCode={}, planInspectId={}, inspectRouteId={}, inspectType={}", dmsBillsInspectInstance.getBillsInspectCode(), dmsBillsInspectInstance.getPlanInspectId(), dmsBillsInspectInstance.getInspectRouteId(), dmsBillsInspectInstance.getInspectType()); @@ -422,9 +479,23 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta dmsBillsInspectInstance.setCreateBy(-1L); } - int i = dmsBillsInspectInstanceMapper.insertDmsBillsInspectInstance(dmsBillsInspectInstance); + int i; + try { + i = dmsBillsInspectInstanceMapper.insertDmsBillsInspectInstance(dmsBillsInspectInstance); + } catch (DuplicateKeyException e) { + log.debug("巡检工单触发唯一约束(重复生成),跳过 | planInspectId={}, billsInspectCode={}", + dmsBillsInspectInstance.getPlanInspectId(), dmsBillsInspectInstance.getBillsInspectCode()); + return 0; + } catch (DataIntegrityViolationException e) { + if (isOracleUniqueViolation(e)) { + log.debug("巡检工单触发 ORA-00001(重复生成),跳过 | planInspectId={}, billsInspectCode={}", + dmsBillsInspectInstance.getPlanInspectId(), dmsBillsInspectInstance.getBillsInspectCode()); + return 0; + } + throw e; + } - // 2. 创建工单实例节点(第一步) + // 5) 创建工单活动节点(第一步) DmsBillsInspectInstanceActivity activity = new DmsBillsInspectInstanceActivity(); activity.setInspectInstanceId(dmsBillsInspectInstance.getInspectInstanceId()); activity.setInspectRouteId(dmsBillsInspectInstance.getInspectRouteId()); @@ -434,7 +505,7 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta activity.setProcessHandleStatus("3"); // 已完成 dmsBillsInspectInstanceActivityMapper.insertDmsBillsInspectInstanceActivity(activity); - // 3. 创建巡检工单明细:路线所有设备均为正常/已完成状态 + // 6) 创建巡检工单明细:路线上的设备默认“已完成 + 合格”。 if (dmsBillsInspectInstance.getInspectRouteId() != null) { DmsInspectRouteDetail queryRouteDetail = new DmsInspectRouteDetail(); queryRouteDetail.setInspectRouteId(dmsBillsInspectInstance.getInspectRouteId()); @@ -456,7 +527,7 @@ public class DmsBillsInspectInstanceServiceImpl implements IDmsBillsInspectInsta detail.setInspectStatus(DmsConstants.DMS_INSPECT_INSTANCE_DETAIL_INSPECT_STATUS_PASS); // 1=正常/合格 dmsInspectInstanceDetailMapper.insertDmsInspectInstanceDetail(detail); - // 创建明细项目记录 + // 7) 创建明细项目记录:仅在“巡检标准 -> 项目”映射存在时落库。 Long projectId = dmsInspectInstanceDetailMapper.selectProjectId(routeDetail.getInspectStandard()); if (projectId != null) { DmsInspectInstanceDetailProject detailProject = new DmsInspectInstanceDetailProject(); diff --git a/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsPlanInspectServiceImpl.java b/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsPlanInspectServiceImpl.java index 19febb6..a64625f 100644 --- a/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsPlanInspectServiceImpl.java +++ b/aucma-dms/src/main/java/com/aucma/dms/service/impl/DmsPlanInspectServiceImpl.java @@ -7,18 +7,18 @@ import com.aucma.common.utils.uuid.Seq; import com.aucma.dms.domain.DmsBillsInspectInstance; import com.aucma.dms.domain.DmsPlanInspect; import com.aucma.dms.mapper.DmsBaseInspectRouteMapper; -import com.aucma.dms.mapper.DmsBillsInspectInstanceMapper; import com.aucma.dms.mapper.DmsPlanInspectMapper; import com.aucma.dms.service.IDmsBillsInspectInstanceService; import com.aucma.dms.service.IDmsPlanInspectService; import com.aucma.quartz.util.CronUtils; +import org.quartz.CronExpression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import java.text.ParseException; import java.util.Date; import java.util.List; @@ -40,11 +40,10 @@ public class DmsPlanInspectServiceImpl implements IDmsPlanInspectService @Autowired private DmsBaseInspectRouteMapper dmsBaseInspectRouteMapper; - @Autowired - private DmsBillsInspectInstanceMapper dmsBillsInspectInstanceMapper; - @Autowired private IDmsBillsInspectInstanceService dmsBillsInspectInstanceService; + @Autowired + private DmsPlanInspectTxService dmsPlanInspectTxService; /** * 查询巡检计划信息 @@ -271,36 +270,34 @@ public class DmsPlanInspectServiceImpl implements IDmsPlanInspectService * 若有异常可在网页端维护工单明细中的设备状态 * cron表达式:秒 分 时 日 月 周 */ - @Scheduled(cron = "0 0 0 * * ?") + @Scheduled(cron = "0 0 0 * * ?") // 原生产策略:每天 0 点执行 +// @Scheduled(cron = "0 * * * * ?") // 测试策略:每分钟执行一次 // @Transactional(rollbackFor = Exception.class) public void generateDailyInspectWorkOrders() { log.info("========== 开始执行巡检工单生成任务 =========="); int count = 0; int skipCount = 0; try { - // 通过Mapper查询今天需要执行的巡检计划(plan_time <= 今天) + // 先把“到期的计划”全部拉出来,后续逐条处理。 + // 注意:这里不做事务包裹,避免某一条失败影响整批调度。 List plans = dmsPlanInspectMapper.selectPendingInspectPlans(); for (DmsPlanInspect plan : plans) { try { + // 先提取关键字段,便于后续日志统一打印。 Long planInspectId = plan.getPlanInspectId(); String planInspectCode = plan.getPlanInspectCode(); Long inspectRouteId = plan.getInspectRouteId(); + // 计划编码为空通常属于脏数据,直接跳过,避免生成不可追踪工单。 if (planInspectCode == null || planInspectCode.isEmpty()) { log.warn("巡检计划ID[{}]编号为空,跳过", planInspectId); skipCount++; continue; } - // 通过Mapper检查今天是否已生成工单(避免重复) - int existCount = dmsBillsInspectInstanceMapper.countByPlanIdAndToday(planInspectId); - if (existCount > 0) { - log.debug("巡检计划[{}]今天已生成工单,跳过", planInspectCode); - continue; - } - - // 构建巡检工单对象(工单中所有设备状态默认为正常,异常可在网页端维护) + // 预生成业务单号(仅用于业务展示)。 + // 真正“是否允许生成”在 TxA 内部通过加锁 + 查重 + 唯一约束做最终判定。 String billsCode = Seq.getId(Seq.dmsBillsFaultInstanceSeqType, "XJ"); DmsBillsInspectInstance instance = new DmsBillsInspectInstance(); instance.setBillsInspectCode(billsCode); @@ -309,18 +306,25 @@ public class DmsPlanInspectServiceImpl implements IDmsPlanInspectService instance.setInspectType(plan.getInspectType()); instance.setDeviceAmount(plan.getDeviceAmount()); instance.setPerformer(plan.getPerformer()); - // 创建人优先使用计划的创建人,若无则使用-1L表示系统自动创建 + // 创建人优先继承计划创建人;兜底 -1 表示系统自动生成。 instance.setCreateBy(plan.getCreateBy() != null ? plan.getCreateBy() : -1L); - // 通过Service创建已完成状态的巡检工单(含明细,路线所有设备正常) - dmsBillsInspectInstanceService.insertCompletedInspectInstance(instance); - count++; - log.info("已为巡检计划[{}]生成已完成工单[{}]", planInspectCode, billsCode); - - // 更新计划的下次执行时间(根据cron表达式或默认+1天) - updateInspectPlanNextTime(plan); + // TxA:生成工单(REQUIRES_NEW)。 + // 返回 0 代表被并发任务抢先生成或命中唯一约束,视为“正常跳过”。 + int insertRows = dmsBillsInspectInstanceService.insertCompletedInspectInstance(instance); + if (insertRows > 0) { + count++; + log.info("已为巡检计划[{}]生成已完成工单[{}]", planInspectCode, billsCode); + // TxB:推进下次执行时间(单独新事务)。 + // 即便 TxB 失败,也不回滚已成功提交的工单(避免“误回滚”)。 + updateInspectPlanNextTime(plan); + } else { + skipCount++; + log.debug("巡检计划[{}]已被其他任务生成或不满足生成条件,跳过", planInspectCode); + } } catch (Exception e) { + // 单条计划异常只记录,不中断整批调度任务。 log.error("为巡检计划生成工单失败 | planInspectId={}, planInspectCode={}, cronExpression={}, planTime={}, inspectType={}, inspectRouteId={}, 异常信息: {}", plan.getPlanInspectId(), plan.getPlanInspectCode(), plan.getCronExpression(), plan.getPlanTime(), plan.getInspectType(), plan.getInspectRouteId(), @@ -342,14 +346,17 @@ public class DmsPlanInspectServiceImpl implements IDmsPlanInspectService Date nextTime = null; String cronExpression = plan.getCronExpression(); if (cronExpression != null && !cronExpression.isEmpty()) { - nextTime = CronUtils.getNextExecution(cronExpression); + // 基于“计划当前时间”推进,而不是基于“当前系统时间”直接算一次, + // 可以避免周/月计划在某些场景下被推进到错误时间点。 + nextTime = calculateNextPlanTime(cronExpression, plan.getPlanTime()); } if (nextTime == null) { log.warn("巡检计划下次执行时间计算为null, 使用默认+1天 | planInspectId={}, planInspectCode={}, cronExpression={}", plan.getPlanInspectId(), plan.getPlanInspectCode(), cronExpression); nextTime = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000L); } - dmsPlanInspectMapper.updatePlanNextTime(plan.getPlanInspectId(), nextTime); + // 使用独立事务推进计划时间,避免影响已成功提交的工单数据。 + dmsPlanInspectTxService.updatePlanNextTimeInNewTx(plan.getPlanInspectId(), nextTime); log.debug("更新巡检计划下次执行时间 | planInspectCode={}, nextTime={}", plan.getPlanInspectCode(), nextTime); } catch (Exception e) { log.error("更新巡检计划下次执行时间失败 | planInspectId={}, planInspectCode={}, cronExpression={}, 异常信息: {}", @@ -357,4 +364,25 @@ public class DmsPlanInspectServiceImpl implements IDmsPlanInspectService e.getMessage(), e); } } + + /** + * 计算计划下次执行时间: + * 1) 基于计划当前 planTime 推进; + * 2) 若算出的时间仍不晚于当前时间,则继续推进,直到晚于当前时间。 + */ + private Date calculateNextPlanTime(String cronExpression, Date currentPlanTime) { + try { + CronExpression cron = new CronExpression(cronExpression); + Date now = new Date(); + Date cursor = currentPlanTime != null ? currentPlanTime : now; + Date next = cron.getNextValidTimeAfter(cursor); + while (next != null && !next.after(now)) { + next = cron.getNextValidTimeAfter(next); + } + return next; + } catch (ParseException e) { + log.error("cron表达式非法,无法计算下次执行时间 | cronExpression={}, 异常信息: {}", cronExpression, e.getMessage(), e); + return null; + } + } } diff --git a/aucma-dms/src/main/resources/mapper/dms/DmsBillsInspectInstanceMapper.xml b/aucma-dms/src/main/resources/mapper/dms/DmsBillsInspectInstanceMapper.xml index d376820..c279eae 100644 --- a/aucma-dms/src/main/resources/mapper/dms/DmsBillsInspectInstanceMapper.xml +++ b/aucma-dms/src/main/resources/mapper/dms/DmsBillsInspectInstanceMapper.xml @@ -54,7 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select - dbii.inspect_instance_id, - dbii.plan_inspect_id, - dbii.wf_process_id, - dbii.inspect_type, - dbii.bills_inspect_code, - dbii.plan_begin_time, - dbii.plan_end_time, - dbii.real_begin_time, - dbii.real_end_time, - dbii.inspect_status, - dbii.create_time, - dbir.route_name,a.plan_inspect_code, - a.remark inspectDesc - from dms_bills_inspect_instance dbii - left join dms_base_inspect_route dbir on - dbii.inspect_route_id = dbir.inspect_route_id left join dms_plan_inspect a on dbii.plan_inspect_id = a.plan_inspect_id + SELECT * FROM ( + select + dbii.inspect_instance_id, + dbii.plan_inspect_id, + dbii.inspect_type, + dbii.bills_inspect_code, + dbii.plan_begin_time, + dbii.plan_end_time, + dbii.real_begin_time, + dbii.real_end_time, + dbii.inspect_status, + dbii.create_time, + dbir.route_name,a.plan_inspect_code, + a.remark inspectDesc + from dms_bills_inspect_instance dbii + left join dms_base_inspect_route dbir on + dbii.inspect_route_id = dbir.inspect_route_id left join dms_plan_inspect a on dbii.plan_inspect_id = a.plan_inspect_id - - and dbii.inspect_route_id = #{inspectRouteId} - - - and dbii.inspect_type = #{inspectType} - and dbii.bills_inspect_code = #{billsInspectCode} - and dbii.inspect_status = #{inspectStatus} - and dbii.inspect_status in (${inspectStatusStr}) - and dbii.is_flag = #{isFlag} - - order by dbii.create_time desc limit 1 + + and dbii.inspect_route_id = #{inspectRouteId} + + + and dbii.inspect_type = #{inspectType} + and dbii.bills_inspect_code = #{billsInspectCode} + and dbii.inspect_status = #{inspectStatus} + and instr(',' || #{inspectStatusStr} || ',', ',' || dbii.inspect_status || ',') > 0 + and dbii.is_flag = #{isFlag} + + order by dbii.create_time desc + ) where rownum = 1 + + + diff --git a/aucma-dms/src/main/resources/mapper/dms/DmsInspectInstanceDetailMapper.xml b/aucma-dms/src/main/resources/mapper/dms/DmsInspectInstanceDetailMapper.xml index 2667663..82fedab 100644 --- a/aucma-dms/src/main/resources/mapper/dms/DmsInspectInstanceDetailMapper.xml +++ b/aucma-dms/src/main/resources/mapper/dms/DmsInspectInstanceDetailMapper.xml @@ -47,7 +47,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - SELECT HAIWEI.SEQ_DMS_INSPECT_INSTANCE_DETAIL.NEXTVAL FROM DUAL + SELECT HAIWEI.SEQ_DMS_INSP_INST_DETAIL.NEXTVAL FROM DUAL insert into dms_inspect_instance_detail @@ -113,11 +113,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - - select diid.instance_detail_id, @@ -153,9 +148,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + + + SELECT HAIWEI.SEQ_DMS_INSP_DET_PROJ.NEXTVAL FROM DUAL + insert into dms_inspect_inst_det_proj + instance_detail_project_id, instance_detail_id, inspect_project_id, inspect_project_status, @@ -68,6 +72,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update_time, + #{instanceDetailProjectId}, #{instanceDetailId}, #{inspectProjectId}, #{inspectProjectStatus}, diff --git a/aucma-dms/src/main/resources/mapper/dms/DmsPlanInspectMapper.xml b/aucma-dms/src/main/resources/mapper/dms/DmsPlanInspectMapper.xml index 823e3d1..38d7c29 100644 --- a/aucma-dms/src/main/resources/mapper/dms/DmsPlanInspectMapper.xml +++ b/aucma-dms/src/main/resources/mapper/dms/DmsPlanInspectMapper.xml @@ -169,4 +169,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" SET plan_time = #{planTime}, update_time = SYSDATE WHERE plan_inspect_id = #{planInspectId} + + +