| | |
| | | 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()); } |
| | |
| | | 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; |
| | | }; |
| | | } |
| | | } |