package cn.iocoder.yudao.module.mes.service.collect; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.collect.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.collect.*; import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; import cn.iocoder.yudao.module.mes.dal.mysql.collect.*; import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; import cn.iocoder.yudao.module.mes.enums.collect.MesCollectAlarmStatusEnum; import cn.iocoder.yudao.module.mes.enums.collect.MesCollectOperateModeEnum; import cn.iocoder.yudao.module.mes.enums.collect.MesCollectRunStatusEnum; import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.framework.collect.config.MesCollectProperties; import java.time.Duration; import java.util.ArrayList; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.COLLECT_ALARM_ALREADY_CLEARED; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.COLLECT_ALARM_NOT_EXISTS; /** * MES 数采数据查询 Service 实现类 * * @author 超级管理员 */ @Service public class MesCollectQueryService { /** * 超过该时长未采集数据视为离线 */ private static final long ONLINE_TIMEOUT_MINUTES = 5L; @Resource private MesDvMachineryMapper machineryMapper; @Resource private MesCollectRealtimeMapper realtimeMapper; @Resource private MesCollectRealtimeHistoryMapper realtimeHistoryMapper; @Resource private MesCollectCycleMapper cycleMapper; @Resource private MesCollectTechMapper techMapper; @Resource private MesCollectOperateLogMapper collectOperateLogMapper; @Resource private MesCollectAlarmMapper alarmMapper; @Resource private MesProWorkOrderMapper workOrderMapper; @Resource private MesCollectTaskResolver taskResolver; @Resource private MesMdWorkstationService workstationService; @Resource private MesCollectProperties collectProperties; /** 基于保留的数采历史计算离线区间,首次采样之前不作推断。 */ public MesCollectOfflineResultRespVO getOfflineIntervals(String deviceCode, LocalDateTime start, LocalDateTime end) { LocalDateTime now = LocalDateTime.now(); if (deviceCode == null || deviceCode.isBlank() || start == null || end == null || !start.isBefore(end) || end.isAfter(now) || Duration.between(start, end).compareTo(Duration.ofDays(7)) > 0) { throw invalidParamException("请选择有效的设备和时间范围,单次最多查询七天,结束时间不能晚于当前时间"); } if (start.isBefore(now.minusDays(collectProperties.getRetention().getRealtimeDays()))) { throw invalidParamException("查询时间超出数采历史保留期,无法可靠计算停机记录"); } var prior = realtimeHistoryMapper.selectOne(new LambdaQueryWrapperX() .eq(MesCollectRealtimeHistoryDO::getDeviceCode, deviceCode) .ge(MesCollectRealtimeHistoryDO::getCollectTime, now.minusDays(collectProperties.getRetention().getRealtimeDays())) .le(MesCollectRealtimeHistoryDO::getCollectTime, start) .orderByDesc(MesCollectRealtimeHistoryDO::getCollectTime).last("LIMIT 1")); var history = realtimeHistoryMapper.selectList(new LambdaQueryWrapperX() .eq(MesCollectRealtimeHistoryDO::getDeviceCode, deviceCode) .gt(MesCollectRealtimeHistoryDO::getCollectTime, start) .le(MesCollectRealtimeHistoryDO::getCollectTime, end) .orderByAsc(MesCollectRealtimeHistoryDO::getCollectTime).last("LIMIT 100001")); if (history.size() > 100000) throw invalidParamException("采样数据过多,请缩短查询时间范围"); List samples = new ArrayList<>(); if (prior != null) samples.add(prior.getCollectTime()); history.forEach(item -> samples.add(item.getCollectTime())); var intervals = CollectOfflinePolicy.calculate(samples, start, end, Duration.ofMinutes(ONLINE_TIMEOUT_MINUTES)).stream() .map(item -> new MesCollectOfflineIntervalRespVO(item.start(), item.end(), Duration.between(item.start(), item.end()).getSeconds())) .toList(); return new MesCollectOfflineResultRespVO(samples.isEmpty() ? null : samples.getFirst(), intervals); } /** * 获取已配置数采编码的设备实时状态列表 */ public List getDeviceList() { List machineries = machineryMapper.selectListWithCollectDeviceCode(); Map workstationMap = workstationService.getWorkstationMap( machineries.stream().map(MesDvMachineryDO::getWorkstationId).filter(Objects::nonNull).toList()); return machineries.stream().map(machinery -> { MesCollectDeviceRespVO vo = new MesCollectDeviceRespVO(); vo.setMachineryId(machinery.getId()); vo.setMachineryCode(machinery.getCode()); vo.setMachineryName(machinery.getName()); vo.setCollectDeviceCode(machinery.getCollectDeviceCode()); vo.setCollectEnabled(Boolean.TRUE.equals(machinery.getCollectEnabled())); MesMdWorkstationDO workstation = workstationMap.get(machinery.getWorkstationId()); if (workstation != null) { vo.setWorkstationId(workstation.getId()); vo.setWorkstationName(workstation.getName()); } MesCollectRealtimeDO realtime = realtimeMapper.selectByDeviceCode(machinery.getCollectDeviceCode()); if (realtime != null) { vo.setLastCollectTime(realtime.getCollectTime()); vo.setOnline(realtime.getCollectTime() != null && realtime.getCollectTime().isAfter(LocalDateTime.now().minusMinutes(ONLINE_TIMEOUT_MINUTES))); vo.setSts(realtime.getSts()); vo.setStsName(MesCollectRunStatusEnum.getNameByStatus(realtime.getSts())); vo.setOpm(realtime.getOpm()); vo.setOpmName(MesCollectOperateModeEnum.getNameByMode(realtime.getOpm())); vo.setOt(realtime.getOt()); vo.setTemps(realtime.getTemps()); // 网关格式报文的结构化字段为空,前端「当前数据」列直接展示原始 JSON vo.setDataJson(realtime.getDataJson()); } else { vo.setOnline(false); } MesProTaskDO task = taskResolver.resolve(machinery.getWorkstationId()); if (task != null) { vo.setTaskId(task.getId()); vo.setTaskCode(task.getCode()); vo.setTaskName(task.getName()); vo.setWorkOrderId(task.getWorkOrderId()); MesProWorkOrderDO workOrder = task.getWorkOrderId() != null ? workOrderMapper.selectById(task.getWorkOrderId()) : null; if (workOrder != null) { vo.setWorkOrderCode(workOrder.getCode()); } } vo.setActiveAlarmCount(alarmMapper.selectActiveCountByDeviceCode(machinery.getCollectDeviceCode())); return vo; }).toList(); } /** * 获取指定数采设备的实时状态快照 */ public MesCollectRealtimeRespVO getRealtime(String deviceCode) { MesCollectRealtimeDO realtime = realtimeMapper.selectByDeviceCode(deviceCode); if (realtime == null) { return null; } MesCollectRealtimeRespVO vo = BeanUtils.toBean(realtime, MesCollectRealtimeRespVO.class); vo.setStsName(MesCollectRunStatusEnum.getNameByStatus(realtime.getSts())); vo.setOpmName(MesCollectOperateModeEnum.getNameByMode(realtime.getOpm())); return vo; } /** * 分页查询实时状态历史 */ public PageResult getRealtimeHistoryPage(MesCollectRealtimeHistoryPageReqVO reqVO) { PageResult pageResult = realtimeHistoryMapper.selectPage(reqVO); PageResult result = BeanUtils.toBean(pageResult, MesCollectRealtimeRespVO.class); result.getList().forEach(vo -> { vo.setStsName(MesCollectRunStatusEnum.getNameByStatus(vo.getSts())); vo.setOpmName(MesCollectOperateModeEnum.getNameByMode(vo.getOpm())); }); return result; } /** * 分页查询周期过程数据 */ public PageResult getCyclePage(MesCollectCyclePageReqVO reqVO) { return BeanUtils.toBean(cycleMapper.selectPage(reqVO), MesCollectCycleRespVO.class); } /** * 获取单条周期过程数据 */ public MesCollectCycleRespVO getCycle(Long id) { MesCollectCycleDO cycle = cycleMapper.selectById(id); return cycle == null ? null : BeanUtils.toBean(cycle, MesCollectCycleRespVO.class); } /** * 获取指定数采设备的最新工艺参数快照 */ public MesCollectTechRespVO getTech(String deviceCode) { MesCollectTechDO tech = techMapper.selectByDeviceCode(deviceCode); return tech == null ? null : BeanUtils.toBean(tech, MesCollectTechRespVO.class); } /** * 分页查询参数变更日志 */ public PageResult getOperateLogPage(MesCollectOperateLogPageReqVO reqVO) { return BeanUtils.toBean(collectOperateLogMapper.selectPage(reqVO), MesCollectOperateLogRespVO.class); } /** * 分页查询报警记录 */ public PageResult getAlarmPage(MesCollectAlarmPageReqVO reqVO) { PageResult pageResult = alarmMapper.selectPage(reqVO); PageResult result = BeanUtils.toBean(pageResult, MesCollectAlarmRespVO.class); result.getList().forEach(vo -> vo.setStatusName(MesCollectAlarmStatusEnum.ACTIVE.getStatus().equals(vo.getStatus()) ? MesCollectAlarmStatusEnum.ACTIVE.getName() : MesCollectAlarmStatusEnum.CLEARED.getName())); return result; } /** * 人工确认消除报警 */ public void ackAlarm(Long id) { MesCollectAlarmDO alarm = alarmMapper.selectById(id); if (alarm == null) { throw exception(COLLECT_ALARM_NOT_EXISTS); } if (MesCollectAlarmStatusEnum.CLEARED.getStatus().equals(alarm.getStatus())) { throw exception(COLLECT_ALARM_ALREADY_CLEARED); } MesCollectAlarmDO updateObj = new MesCollectAlarmDO(); updateObj.setId(id); updateObj.setStatus(MesCollectAlarmStatusEnum.CLEARED.getStatus()); updateObj.setClearTime(LocalDateTime.now()); alarmMapper.updateById(updateObj); } }