xiaoyi
22 小时以前 8782476e9cb4480be5da588888d08022b7942d0c
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyRiskSourceService.java
@@ -3,24 +3,47 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.risk.SafetyRiskSourceSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.collect.MesCollectRealtimeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyRiskSourceDO;
import cn.iocoder.yudao.module.mes.dal.mysql.collect.MesCollectRealtimeMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyRiskSourceMapper;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.*;
import tools.jackson.core.type.TypeReference;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
/**
 * 安全生产 - 危险源监测。
 *
 * <p>支持两种数据来源:
 * <ol>
 *   <li>人工录入 currentValue:仅表示人工抄录的当前值;</li>
 *   <li>绑定设备(machineryId + metricKey):由数采实时表自动取值,复用现有设备采集,不引入新接口。</li>
 * </ol>
 */
@Slf4j
@Service public class SafetyRiskSourceService {
    @Resource private SafetyRiskSourceMapper mapper;
    @Resource private MesCollectRealtimeMapper realtimeMapper;
    public PageResult<SafetyRiskSourceDO> page(PageParam page, String name) {
        return mapper.selectPage(page, new LambdaQueryWrapperX<SafetyRiskSourceDO>().likeIfPresent(SafetyRiskSourceDO::getName,name).orderByDesc(SafetyRiskSourceDO::getId));
    }
    @Transactional(rollbackFor=Exception.class)
    public Long save(SafetyRiskSourceSaveReqVO req) {
        SafetyRiskSourceDO old = req.getId()==null ? null : mapper.selectById(req.getId());
        if (req.getId()!=null && old==null) throw invalidParamException("危险源不存在");
        if (req.getMachineryId()!=null && (req.getMetricKey()==null || req.getMetricKey().isBlank()))
            throw invalidParamException("绑定设备后必须填写数采指标键");
        if (req.getMachineryId()==null && req.getMetricKey()!=null && !req.getMetricKey().isBlank())
            throw invalidParamException("填写数采指标键前请先绑定设备");
        String status;
        try { status=SafetyRiskThreshold.evaluate(req.getCurrentValue(),req.getLowerLimit(),req.getUpperLimit()); }
        catch (IllegalArgumentException e) { throw invalidParamException(e.getMessage()); }
@@ -30,5 +53,78 @@
        if (old==null) mapper.insert(row); else mapper.updateById(row);
        return row.getId();
    }
    public void delete(Long id) { if (mapper.selectById(id)==null) throw invalidParamException("危险源不存在"); mapper.deleteById(id); }
    /**
     * 用设备采集数据刷新所有已绑定设备的危险源。
     *
     * <p>取值顺序:data_json 中 metricKey 对应字段;未取到时置 UNKNOWN 并保留上次观测时间不变。
     *
     * @return 本次刷新后处于 ALARM 状态的危险源
     */
    @Transactional(rollbackFor=Exception.class)
    public List<SafetyRiskSourceDO> refreshFromCollect() {
        List<SafetyRiskSourceDO> bound = mapper.selectList(new LambdaQueryWrapperX<SafetyRiskSourceDO>()
                .isNotNull(SafetyRiskSourceDO::getMachineryId));
        List<SafetyRiskSourceDO> alarms = new ArrayList<>();
        Map<Long, MesCollectRealtimeDO> cache = new HashMap<>();
        for (SafetyRiskSourceDO source : bound) {
            MesCollectRealtimeDO realtime = cache.computeIfAbsent(source.getMachineryId(), id ->
                    realtimeMapper.selectOne(new LambdaQueryWrapperX<MesCollectRealtimeDO>()
                            .eq(MesCollectRealtimeDO::getMachineryId, id)
                            .orderByDesc(MesCollectRealtimeDO::getCollectTime).last("LIMIT 1")));
            BigDecimal value = realtime == null ? null : extractMetric(realtime, source.getMetricKey());
            String status;
            try { status = SafetyRiskThreshold.evaluate(value, source.getLowerLimit(), source.getUpperLimit()); }
            catch (IllegalArgumentException e) { log.warn("危险源[{}]阈值配置非法:{}", source.getName(), e.getMessage()); continue; }
            if (value == null) {
                // 取不到采集值:不覆盖历史观测时间,避免伪造数据
                if (!"UNKNOWN".equals(source.getRiskStatus())) {
                    var upd = new SafetyRiskSourceDO();
                    upd.setId(source.getId()); upd.setRiskStatus(status);
                    mapper.updateById(upd);
                    source.setRiskStatus(status);
                }
                continue;
            }
            var upd = new SafetyRiskSourceDO();
            upd.setId(source.getId());
            upd.setCurrentValue(value);
            upd.setObservedAt(realtime.getCollectTime() == null ? LocalDateTime.now() : realtime.getCollectTime());
            upd.setRiskStatus(status);
            mapper.updateById(upd);
            source.setCurrentValue(value);
            source.setObservedAt(upd.getObservedAt());
            source.setRiskStatus(status);
            if ("ALARM".equals(status)) alarms.add(source);
        }
        return alarms;
    }
    /** 从实时采集记录中提取指标值;优先 data_json,其次复用实时表的标准字段。 */
    BigDecimal extractMetric(MesCollectRealtimeDO realtime, String metricKey) {
        if (metricKey == null || metricKey.isBlank() || realtime == null) return null;
        String json = realtime.getDataJson();
        if (json != null && !json.isBlank()) {
            try {
                Map<String, Object> map = JsonUtils.parseObjectQuietly(json, new TypeReference<Map<String, Object>>() {});
                Object raw = map == null ? null : map.get(metricKey);
                if (raw != null) {
                    if (raw instanceof Number n) return new BigDecimal(n.toString());
                    String text = raw.toString().trim();
                    if (!text.isEmpty()) return new BigDecimal(text);
                }
            } catch (Exception e) {
                log.debug("解析数采 data_json 失败,machineryId={}:{}", realtime.getMachineryId(), e.getMessage());
            }
        }
        // 兼容:metricKey 直接取实时表字段(ot 运行时长等)
        return switch (metricKey) {
            case "ot" -> realtime.getOt();
            case "sts" -> realtime.getSts() == null ? null : BigDecimal.valueOf(realtime.getSts());
            case "opm" -> realtime.getOpm() == null ? null : BigDecimal.valueOf(realtime.getOpm());
            default -> null;
        };
    }
}