@ -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 < DmsPlanInspect > 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 ;
}
}
}