|
|
|
|
@ -1,7 +1,9 @@
|
|
|
|
|
package org.dromara.ems.record.service.impl;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.http.HttpStatus;
|
|
|
|
|
import jakarta.annotation.PreDestroy;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.dromara.common.core.exception.ServiceException;
|
|
|
|
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
|
|
|
import org.dromara.ems.base.domain.EmsBaseMonitorInfo;
|
|
|
|
|
@ -23,7 +25,9 @@ import java.time.format.DateTimeParseException;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
|
import java.util.concurrent.ExecutionException;
|
|
|
|
|
import java.util.concurrent.Future;
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
@ -34,6 +38,7 @@ import java.util.stream.Collectors;
|
|
|
|
|
* @date 2025-04-28
|
|
|
|
|
*/
|
|
|
|
|
@Service
|
|
|
|
|
@Slf4j
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class RecordIotenvInstantServiceImpl implements IRecordIotenvInstantService
|
|
|
|
|
{
|
|
|
|
|
@ -49,6 +54,32 @@ public class RecordIotenvInstantServiceImpl implements IRecordIotenvInstantServi
|
|
|
|
|
private static final String BEGIN_RECORD_TIME_KEY = "beginRecordTime";
|
|
|
|
|
private static final String END_RECORD_TIME_KEY = "endRecordTime";
|
|
|
|
|
private static final int LATEST_TABLE_LOOKBACK_DAYS = 7;
|
|
|
|
|
private static final int MULTI_MONITOR_QUERY_THREADS = 20;
|
|
|
|
|
private static final long EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS = 30L;
|
|
|
|
|
private static final BigDecimal SENSOR_POSITIVE_MIN_VALUE = BigDecimal.ZERO;
|
|
|
|
|
/**
|
|
|
|
|
* 温湿度/噪声导出沿用历史有效量程上限 79。
|
|
|
|
|
* 这里先集中为常量,避免分页查询、导出过滤和后续字典配置迁移时出现多处魔法值不一致。
|
|
|
|
|
*/
|
|
|
|
|
private static final BigDecimal ENV_SENSOR_VALID_MAX_VALUE = new BigDecimal("79");
|
|
|
|
|
/**
|
|
|
|
|
* 振动类字段沿用历史有效量程上限 80,当前业务语义为小于 80 才进入有效数据。
|
|
|
|
|
*/
|
|
|
|
|
private static final BigDecimal VIBRATION_SENSOR_VALID_MAX_VALUE = new BigDecimal("80");
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 多测点历史查询共用线程池。
|
|
|
|
|
* 这里不在每次查询时 new 线程池,避免高并发查询下反复创建/销毁线程造成资源泄露和数据库连接抖动。
|
|
|
|
|
*/
|
|
|
|
|
private final ExecutorService multiMonitorQueryExecutor = Executors.newFixedThreadPool(
|
|
|
|
|
MULTI_MONITOR_QUERY_THREADS,
|
|
|
|
|
runnable -> {
|
|
|
|
|
Thread thread = new Thread(runnable);
|
|
|
|
|
thread.setName("iotenv-multi-query-" + thread.getId());
|
|
|
|
|
thread.setDaemon(true);
|
|
|
|
|
return thread;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询物联网数据
|
|
|
|
|
@ -315,47 +346,63 @@ public class RecordIotenvInstantServiceImpl implements IRecordIotenvInstantServi
|
|
|
|
|
* @return 合并后的查询结果
|
|
|
|
|
*/
|
|
|
|
|
private List<RecordIotenvInstant> selectMultipleMonitorData(List<String> tableNames, RecordIotenvInstant recordIotenvInstant,String[] monitorIds) {
|
|
|
|
|
// 创建线程池
|
|
|
|
|
int threadCount = Math.min(monitorIds.length,20);
|
|
|
|
|
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
|
|
|
|
|
if (monitorIds == null || monitorIds.length == 0) {
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//创建任务列表
|
|
|
|
|
List<Future<List<RecordIotenvInstant>>> futures = new ArrayList<>();
|
|
|
|
|
// 每个测点一个独立查询对象,避免并发任务共享同一个 BO 导致 monitorId 被互相覆盖。
|
|
|
|
|
List<Future<List<RecordIotenvInstant>>> futures = new ArrayList<>();
|
|
|
|
|
for (String monitorId : monitorIds) {
|
|
|
|
|
Future<List<RecordIotenvInstant>> future = multiMonitorQueryExecutor.submit(() -> {
|
|
|
|
|
RecordIotenvInstant query = new RecordIotenvInstant();
|
|
|
|
|
BeanUtils.copyProperties(recordIotenvInstant, query);
|
|
|
|
|
query.setMonitorId(monitorId);
|
|
|
|
|
query.setMonitorIds(null);
|
|
|
|
|
return recordIotenvInstantMapper.selectRecordIotenvInstantListFromTables(tableNames, query);
|
|
|
|
|
});
|
|
|
|
|
futures.add(future);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 为每个设备创建查询任务
|
|
|
|
|
for(String monitorId : monitorIds){
|
|
|
|
|
Future<List<RecordIotenvInstant>> future
|
|
|
|
|
= executorService.submit(() -> {
|
|
|
|
|
// 创建新的查询对象,避免并发修改
|
|
|
|
|
RecordIotenvInstant query = new RecordIotenvInstant();
|
|
|
|
|
BeanUtils.copyProperties(recordIotenvInstant, query);
|
|
|
|
|
query.setMonitorId(monitorId);
|
|
|
|
|
query.setMonitorIds(null); // 清空数组,使用单个ID查询
|
|
|
|
|
List<RecordIotenvInstant> result = new ArrayList<>();
|
|
|
|
|
for (Future<List<RecordIotenvInstant>> future : futures) {
|
|
|
|
|
try {
|
|
|
|
|
List<RecordIotenvInstant> partialResult = future.get();
|
|
|
|
|
if (partialResult != null && !partialResult.isEmpty()) {
|
|
|
|
|
result.addAll(partialResult);
|
|
|
|
|
}
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
cancelUnfinishedTasks(futures);
|
|
|
|
|
throw new ServiceException("多测点数据查询被中断,请稍后重试");
|
|
|
|
|
} catch (ExecutionException e) {
|
|
|
|
|
cancelUnfinishedTasks(futures);
|
|
|
|
|
log.error("多测点数据查询失败", e);
|
|
|
|
|
throw new ServiceException("多测点数据查询失败,请稍后重试");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 执行查询
|
|
|
|
|
return recordIotenvInstantMapper
|
|
|
|
|
.selectRecordIotenvInstantListFromTables(tableNames, query);
|
|
|
|
|
});
|
|
|
|
|
futures.add(future);
|
|
|
|
|
}
|
|
|
|
|
private void cancelUnfinishedTasks(List<Future<List<RecordIotenvInstant>>> futures) {
|
|
|
|
|
for (Future<List<RecordIotenvInstant>> future : futures) {
|
|
|
|
|
if (!future.isDone()) {
|
|
|
|
|
future.cancel(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 收集所有查询结果
|
|
|
|
|
List<RecordIotenvInstant> result = new ArrayList<>();
|
|
|
|
|
for (Future<List<RecordIotenvInstant>> future : futures) {
|
|
|
|
|
try {
|
|
|
|
|
List<RecordIotenvInstant> partialResult = future.get();
|
|
|
|
|
if (partialResult != null && !partialResult.isEmpty()) {
|
|
|
|
|
result.addAll(partialResult);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 记录异常但继续处理其他结果
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 关闭线程池
|
|
|
|
|
executorService.shutdown();
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
@PreDestroy
|
|
|
|
|
public void shutdownMultiMonitorQueryExecutor() {
|
|
|
|
|
multiMonitorQueryExecutor.shutdown();
|
|
|
|
|
try {
|
|
|
|
|
if (!multiMonitorQueryExecutor.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
|
|
|
|
// 应用停机时不再等待长查询,避免进程无法及时退出。
|
|
|
|
|
multiMonitorQueryExecutor.shutdownNow();
|
|
|
|
|
}
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
multiMonitorQueryExecutor.shutdownNow();
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@ -597,7 +644,7 @@ public class RecordIotenvInstantServiceImpl implements IRecordIotenvInstantServi
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断指定振动参数是否有效:>0且<80 */
|
|
|
|
|
/** 判断指定振动参数是否有效:大于0且小于振动字段历史有效上限 */
|
|
|
|
|
private boolean isValidVibrationParam(RecordIotenvInstant record, String vibrationParam) {
|
|
|
|
|
BigDecimal value = null;
|
|
|
|
|
switch (vibrationParam) {
|
|
|
|
|
@ -616,7 +663,7 @@ public class RecordIotenvInstantServiceImpl implements IRecordIotenvInstantServi
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return value != null && value.compareTo(BigDecimal.ZERO) > 0 && value.compareTo(new BigDecimal("80")) < 0;
|
|
|
|
|
return isValidVibrationValue(value, VIBRATION_SENSOR_VALID_MAX_VALUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断记录对于指定类型是否有效 */
|
|
|
|
|
@ -635,33 +682,37 @@ public class RecordIotenvInstantServiceImpl implements IRecordIotenvInstantServi
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断温度是否有效:>0且<=79 */
|
|
|
|
|
/** 判断温度是否有效:大于0且不超过环境类字段历史有效上限 */
|
|
|
|
|
private boolean isValidTemperature(BigDecimal temperature) {
|
|
|
|
|
return temperature != null && temperature.compareTo(BigDecimal.ZERO) > 0 && temperature.compareTo(new BigDecimal("79")) <= 0;
|
|
|
|
|
return isValidEnvSensorValue(temperature);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断湿度是否有效:>0且<=79 */
|
|
|
|
|
/** 判断湿度是否有效:大于0且不超过环境类字段历史有效上限 */
|
|
|
|
|
private boolean isValidHumidity(BigDecimal humidity) {
|
|
|
|
|
return humidity != null && humidity.compareTo(BigDecimal.ZERO) > 0 && humidity.compareTo(new BigDecimal("79")) <= 0;
|
|
|
|
|
return isValidEnvSensorValue(humidity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断噪声是否有效:>0且<=79 */
|
|
|
|
|
/** 判断噪声是否有效:大于0且不超过环境类字段历史有效上限 */
|
|
|
|
|
private boolean isValidNoise(BigDecimal noise) {
|
|
|
|
|
return noise != null && noise.compareTo(BigDecimal.ZERO) > 0 && noise.compareTo(new BigDecimal("79")) <= 0;
|
|
|
|
|
return isValidEnvSensorValue(noise);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断是否有有效的振动数据:>0且<80 */
|
|
|
|
|
/** 判断是否有有效的振动数据:大于0且小于振动字段历史有效上限 */
|
|
|
|
|
private boolean hasValidVibrationData(RecordIotenvInstant record) {
|
|
|
|
|
BigDecimal upperLimit = new BigDecimal("80");
|
|
|
|
|
return isValidVibrationValue(record.getVibrationSpeed(), upperLimit) ||
|
|
|
|
|
isValidVibrationValue(record.getVibrationDisplacement(), upperLimit) ||
|
|
|
|
|
isValidVibrationValue(record.getVibrationAcceleration(), upperLimit) ||
|
|
|
|
|
isValidVibrationValue(record.getVibrationTemp(), upperLimit);
|
|
|
|
|
return isValidVibrationValue(record.getVibrationSpeed(), VIBRATION_SENSOR_VALID_MAX_VALUE) ||
|
|
|
|
|
isValidVibrationValue(record.getVibrationDisplacement(), VIBRATION_SENSOR_VALID_MAX_VALUE) ||
|
|
|
|
|
isValidVibrationValue(record.getVibrationAcceleration(), VIBRATION_SENSOR_VALID_MAX_VALUE) ||
|
|
|
|
|
isValidVibrationValue(record.getVibrationTemp(), VIBRATION_SENSOR_VALID_MAX_VALUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断振动值是否有效:>0且<80 */
|
|
|
|
|
/** 判断环境类传感器值是否有效,先保留历史上限,后续可迁移到配置表。 */
|
|
|
|
|
private boolean isValidEnvSensorValue(BigDecimal value) {
|
|
|
|
|
return value != null && value.compareTo(SENSOR_POSITIVE_MIN_VALUE) > 0 && value.compareTo(ENV_SENSOR_VALID_MAX_VALUE) <= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断振动值是否有效:大于0且小于指定上限 */
|
|
|
|
|
private boolean isValidVibrationValue(BigDecimal value, BigDecimal upperLimit) {
|
|
|
|
|
return value != null && value.compareTo(BigDecimal.ZERO) > 0 && value.compareTo(upperLimit) < 0;
|
|
|
|
|
return value != null && value.compareTo(SENSOR_POSITIVE_MIN_VALUE) > 0 && value.compareTo(upperLimit) < 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 判断记录是否有任何有效数据 */
|
|
|
|
|
|