xiaoyi
3 天以前 8782476e9cb4480be5da588888d08022b7942d0c
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/collect/MesCollectQueryService.java
@@ -17,6 +17,11 @@
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;
@@ -60,6 +65,100 @@
    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<MesCollectRealtimeHistoryDO>()
                .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<MesCollectRealtimeHistoryDO>()
                .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<LocalDateTime> 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);
    }
    /**
     * 设备停机明细报表:按采集间隔推算每台设备的停机区间与合计。
     *
     * <p>与单设备离线查询保持同一口径(在线超时阈值、保留期、七天上限),
     * 供 OEE 报表定位停机短板使用。
     */
    public cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO
            getDowntimeDetail(LocalDateTime start, LocalDateTime end) {
        LocalDateTime now = LocalDateTime.now();
        if (start == null || end == null || !start.isBefore(end) || end.isAfter(now)
                || Duration.between(start, end).compareTo(Duration.ofDays(31)) > 0) {
            throw invalidParamException("请选择有效的时间范围,单次最多查询三十一天,结束时间不能晚于当前时间");
        }
        if (start.isBefore(now.minusDays(collectProperties.getRetention().getRealtimeDays()))) {
            throw invalidParamException("查询时间超出数采历史保留期,无法可靠计算停机记录");
        }
        var result = new cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO();
        result.setStartTime(start);
        result.setEndTime(end);
        result.setTimeoutMinutes((int) ONLINE_TIMEOUT_MINUTES);
        long totalMinutes = Duration.between(start, end).toMinutes();
        result.setTotalMinutes(totalMinutes);
        var machineries = machineryMapper.selectListWithCollectDeviceCode();
        List<cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO.Item> items = new ArrayList<>();
        long sumDowntime = 0L;
        for (var machinery : machineries) {
            String deviceCode = machinery.getCollectDeviceCode();
            var prior = realtimeHistoryMapper.selectOne(new LambdaQueryWrapperX<MesCollectRealtimeHistoryDO>()
                    .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<MesCollectRealtimeHistoryDO>()
                    .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<LocalDateTime> samples = new ArrayList<>();
            int count = 0;
            if (prior != null) { samples.add(prior.getCollectTime()); count++; }
            history.forEach(item -> samples.add(item.getCollectTime()));
            count += history.size();
            var intervals = CollectOfflinePolicy.calculate(samples, start, end, Duration.ofMinutes(ONLINE_TIMEOUT_MINUTES)).stream()
                    .map(i -> new cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO.Interval(
                            i.start(), i.end(), Duration.between(i.start(), i.end()).getSeconds()))
                    .toList();
            long minutes = intervals.stream().mapToLong(i -> i.getSeconds() / 60).sum();
            sumDowntime += minutes;
            items.add(new cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO.Item(
                    machinery.getId(), machinery.getCode(), machinery.getName(), deviceCode,
                    count, intervals.size(), minutes, intervals));
        }
        items.sort((a, b) -> Long.compare(b.getDowntimeMinutes(), a.getDowntimeMinutes()));
        result.setItems(items);
        result.setDowntimeMinutes(sumDowntime);
        result.setDowntimeRate(totalMinutes > 0 && !items.isEmpty()
                ? Math.round(sumDowntime * 10000D / (totalMinutes * items.size())) / 100D : 0D);
        return result;
    }
    /**
     * 获取已配置数采编码的设备实时状态列表