().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); }
/**
* 用设备采集数据刷新所有已绑定设备的危险源。
*
* 取值顺序:data_json 中 metricKey 对应字段;未取到时置 UNKNOWN 并保留上次观测时间不变。
*
* @return 本次刷新后处于 ALARM 状态的危险源
*/
@Transactional(rollbackFor=Exception.class)
public List refreshFromCollect() {
List bound = mapper.selectList(new LambdaQueryWrapperX()
.isNotNull(SafetyRiskSourceDO::getMachineryId));
List alarms = new ArrayList<>();
Map cache = new HashMap<>();
for (SafetyRiskSourceDO source : bound) {
MesCollectRealtimeDO realtime = cache.computeIfAbsent(source.getMachineryId(), id ->
realtimeMapper.selectOne(new LambdaQueryWrapperX()
.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 map = JsonUtils.parseObjectQuietly(json, new TypeReference