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