xiaoyi
2 天以前 8782476e9cb4480be5da588888d08022b7942d0c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package cn.iocoder.yudao.module.mes.service.safety;
import cn.iocoder.yudao.framework.common.pojo.*;
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.*;
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()); }
        var row=BeanUtils.toBean(req,SafetyRiskSourceDO.class);
        row.setRiskStatus(status);
        row.setObservedAt(req.getCurrentValue()==null ? null : old==null || !Objects.equals(old.getCurrentValue(),req.getCurrentValue()) ? LocalDateTime.now() : old.getObservedAt());
        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;
        };
    }
}