13 changed files with 648 additions and 128 deletions
@ -0,0 +1,16 @@
|
||||
package com.mh.user.service; |
||||
|
||||
import java.text.ParseException; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project mh_esi |
||||
* @description pro_data_result修改java |
||||
* @date 2025-08-26 09:08:15 |
||||
*/ |
||||
public interface ProDataResultService { |
||||
|
||||
void processDailySummary(String curDate, String projectId) throws ParseException; |
||||
|
||||
} |
@ -0,0 +1,328 @@
|
||||
package com.mh.user.service.impl; |
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.mh.user.entity.DataResultChEntity; |
||||
import com.mh.user.entity.DataResultFifteenMiEntity; |
||||
import com.mh.user.entity.DataResultFiveMiEntity; |
||||
import com.mh.user.entity.DataResultOneMiEntity; |
||||
import com.mh.user.mapper.DataResultChMapper; |
||||
import com.mh.user.mapper.DataResultFifteenMiMapper; |
||||
import com.mh.user.mapper.DataResultFiveMiMapper; |
||||
import com.mh.user.mapper.DataResultOneMiMapper; |
||||
import com.mh.user.service.ProDataResultService; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import java.sql.Timestamp; |
||||
import java.text.ParseException; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.*; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project mh_esi |
||||
* @description pro_data_result改成java |
||||
* @date 2025-08-26 09:07:39 |
||||
*/ |
||||
@Slf4j |
||||
@Service |
||||
@Transactional(rollbackFor = Exception.class) |
||||
public class ProDataResultServiceImpl implements ProDataResultService { |
||||
|
||||
private static final List<String> DEVICE_CODES = Arrays.asList("0020", "0073", "0075", "0006", "0010", "0012", "0014"); |
||||
|
||||
@Autowired |
||||
private DataResultChMapper dataResultChMapper; |
||||
@Autowired |
||||
private DataResultOneMiMapper oneMiMapper; |
||||
@Autowired |
||||
private DataResultFiveMiMapper fiveMiMapper; |
||||
@Autowired |
||||
private DataResultFifteenMiMapper fifteenMiMapper; |
||||
|
||||
@Override |
||||
public void processDailySummary(String curDateStr, String projectId) throws ParseException { |
||||
// 时间格式标准化
|
||||
SimpleDateFormat inputSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
||||
Date curDate = inputSdf.parse(curDateStr); |
||||
SimpleDateFormat outputSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:00"); |
||||
String standardDate = outputSdf.format(curDate); |
||||
log.info("处理日期:{}", standardDate); |
||||
|
||||
// 计算时间窗口参数
|
||||
int minute = curDate.getMinutes(); |
||||
int intervalMinute = (minute / 5) * 5; |
||||
String minuteStr = String.format("%02d", intervalMinute); |
||||
log.info("分钟区间:{}", minuteStr); |
||||
|
||||
// 遍历设备编码
|
||||
for (String deviceCode : DEVICE_CODES) { |
||||
log.info("处理设备编码:{}", deviceCode); |
||||
|
||||
// 检查是否存在待处理数据
|
||||
Map<String, Object> countParams = new HashMap<>(); |
||||
// countParams.put("start", standardDate);
|
||||
// countParams.put("end", addMinutes(standardDate, 5));
|
||||
countParams.put("register_addr", deviceCode); |
||||
countParams.put("project_id", projectId); |
||||
int recordCount = Math.toIntExact(dataResultChMapper.selectCount(new QueryWrapper<DataResultChEntity>() |
||||
.allEq(countParams) |
||||
.ge("cur_date", standardDate) |
||||
.lt("cur_date", addMinutes(standardDate, 5)))); |
||||
|
||||
if (recordCount == 0) continue; |
||||
|
||||
// 获取当前设备编码下的所有设备地址
|
||||
List<String> deviceAddrs = dataResultChMapper.selectObjs(new QueryWrapper<DataResultChEntity>() |
||||
.select("DISTINCT device_addr") |
||||
.eq("register_addr", deviceCode) |
||||
.eq("project_id", projectId) |
||||
.between("cur_date", standardDate, addMinutes(standardDate, 5))) |
||||
.stream().map(Object::toString).collect(Collectors.toList()); |
||||
|
||||
// 处理每个设备地址
|
||||
for (String deviceAddr : deviceAddrs) { |
||||
// processIntervalData(standardDate, deviceCode, projectId, deviceAddr, 1);
|
||||
processIntervalData(standardDate, deviceCode, projectId, deviceAddr, 5, minuteStr); |
||||
processIntervalData(standardDate, deviceCode, projectId, deviceAddr, 15, minuteStr); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void processIntervalData(String baseDate, String registerAddr, String projectId, |
||||
String deviceAddr, int interval, String... extraParams) throws ParseException { |
||||
// 计算时间窗口
|
||||
String startTime = baseDate; |
||||
String endTime = addMinutes(baseDate, interval); |
||||
|
||||
// 获取最新数据
|
||||
Map<String, Object> params = new HashMap<>(); |
||||
params.put("start", startTime); |
||||
params.put("end", endTime); |
||||
params.put("registerAddr", registerAddr); |
||||
params.put("projectId", projectId); |
||||
params.put("deviceAddr", deviceAddr); |
||||
DataResultChEntity latest = dataResultChMapper.selectLatestByCondition(params); |
||||
|
||||
if (latest == null) return; |
||||
|
||||
// 数值有效性校验
|
||||
String curValue = isNumeric(latest.getCurValue()) ? latest.getCurValue() : "0.00"; |
||||
if (!isNumeric(latest.getCurValue())) { |
||||
log.info("无效数值,已替换为0.00:{}", latest.getCurValue()); |
||||
} |
||||
|
||||
// 计算汇总时间点
|
||||
String summaryTime = extraParams.length > 0 ? |
||||
baseDate.substring(0, 13) + ":" + extraParams[0] + ":00" : |
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(latest.getCurDate()); |
||||
|
||||
// 处理不同时间间隔的汇总
|
||||
switch (interval) { |
||||
case 1: |
||||
handleOneMinute(latest, deviceAddr, projectId, summaryTime, curValue); |
||||
break; |
||||
case 5: |
||||
handleFiveMinute(latest, deviceAddr, projectId, summaryTime, curValue); |
||||
break; |
||||
case 15: |
||||
handleFifteenMinute(latest, deviceAddr, projectId, summaryTime, curValue); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// 在类中添加静态锁对象
|
||||
private static final Map<String, Object> LOCK_MAP = new ConcurrentHashMap<>(); |
||||
|
||||
private void handleOneMinute(DataResultChEntity source, String deviceAddr, String projectId, |
||||
String summaryTime, String curValue) { |
||||
// 创建针对特定设备和项目的锁key
|
||||
String lockKey = deviceAddr + "_" + projectId; |
||||
Object lockObject = LOCK_MAP.computeIfAbsent(lockKey, k -> new Object()); |
||||
|
||||
try { |
||||
// 使用特定锁,减少锁竞争
|
||||
synchronized (lockObject) { |
||||
// 添加重试机制避免死锁
|
||||
int retryCount = 0; |
||||
final int maxRetries = 3; |
||||
|
||||
while (retryCount < maxRetries) { |
||||
try { |
||||
// 查询历史数据
|
||||
int count = oneMiMapper.selectCountByTimeDeviceProject(summaryTime, deviceAddr, projectId, source.getRegisterAddr(), source.getDeviceType()); |
||||
if (count == 0) { |
||||
// 插入新数据
|
||||
DataResultOneMiEntity record = new DataResultOneMiEntity(); |
||||
BeanUtils.copyProperties(source, record); |
||||
record.setCurDate(Timestamp.valueOf(summaryTime)); |
||||
record.setCurValue(curValue); |
||||
oneMiMapper.saveDataResultOneMi(record); |
||||
} |
||||
break; // 成功执行则跳出循环
|
||||
|
||||
} catch (Exception e) { |
||||
retryCount++; |
||||
if (retryCount >= maxRetries) { |
||||
throw e; // 达到最大重试次数后抛出异常
|
||||
} |
||||
|
||||
// 检查是否为死锁异常
|
||||
if (e.getMessage() != null && |
||||
(e.getMessage().contains("死锁") || e.getMessage().contains("deadlock"))) { |
||||
log.warn("检测到死锁,进行第{}次重试,设备地址:{},项目ID:{}", |
||||
retryCount, deviceAddr, projectId); |
||||
// 随机延迟后重试
|
||||
try { |
||||
Thread.sleep(new Random().nextInt(200) + 100); |
||||
} catch (InterruptedException ie) { |
||||
Thread.currentThread().interrupt(); |
||||
throw new RuntimeException(ie); |
||||
} |
||||
} else { |
||||
throw e; // 非死锁异常直接抛出
|
||||
} |
||||
} |
||||
} |
||||
} |
||||
} finally { |
||||
// 清理长时间不用的锁对象,避免内存泄漏
|
||||
cleanupLocks(); |
||||
} |
||||
} |
||||
|
||||
// 清理锁对象的方法
|
||||
private void cleanupLocks() { |
||||
// 简单的清理策略:当锁数量超过一定阈值时,清空所有锁
|
||||
// 实际项目中可以根据需要实现更复杂的清理逻辑
|
||||
if (LOCK_MAP.size() > 1000) { |
||||
synchronized (LOCK_MAP) { |
||||
if (LOCK_MAP.size() > 1000) { |
||||
LOCK_MAP.clear(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void handleFiveMinute(DataResultChEntity source, String deviceAddr, String projectId, |
||||
String summaryTime, String curValue) { |
||||
// 创建针对特定设备和项目的锁key
|
||||
String lockKey = "five_" + deviceAddr + "_" + projectId; |
||||
Object lockObject = LOCK_MAP.computeIfAbsent(lockKey, k -> new Object()); |
||||
|
||||
try { |
||||
// 使用特定锁,减少锁竞争
|
||||
synchronized (lockObject) { |
||||
// 添加重试机制避免死锁
|
||||
executeWithRetry(() -> { |
||||
Long existId = fiveMiMapper.selectExistId(summaryTime, deviceAddr, projectId); |
||||
|
||||
DataResultFiveMiEntity record = new DataResultFiveMiEntity(); |
||||
BeanUtils.copyProperties(source, record); |
||||
record.setCurDate(Timestamp.valueOf(summaryTime)); |
||||
record.setCurValue(curValue); |
||||
|
||||
if (existId != null) { |
||||
fiveMiMapper.updateByIdCustomize(existId, curValue, source.getGrade()); |
||||
} else { |
||||
fiveMiMapper.saveDataResultFiveMi(record); |
||||
} |
||||
}); |
||||
} |
||||
} finally { |
||||
// 清理长时间不用的锁对象,避免内存泄漏
|
||||
cleanupLocks(); |
||||
} |
||||
} |
||||
|
||||
private void handleFifteenMinute(DataResultChEntity source, String deviceAddr, String projectId, |
||||
String summaryTime, String curValue) { |
||||
// 创建针对特定设备和项目的锁key
|
||||
String lockKey = "fifteen_" + deviceAddr + "_" + projectId; |
||||
Object lockObject = LOCK_MAP.computeIfAbsent(lockKey, k -> new Object()); |
||||
|
||||
try { |
||||
// 使用特定锁,减少锁竞争
|
||||
synchronized (lockObject) { |
||||
// 添加重试机制避免死锁
|
||||
executeWithRetry(() -> { |
||||
Long existId = fifteenMiMapper.selectExistId(summaryTime, deviceAddr, projectId); |
||||
|
||||
DataResultFifteenMiEntity record = new DataResultFifteenMiEntity(); |
||||
BeanUtils.copyProperties(source, record); |
||||
record.setCurDate(Timestamp.valueOf(summaryTime)); |
||||
record.setCurValue(curValue); |
||||
|
||||
if (existId != null) { |
||||
fifteenMiMapper.updateByIdCustomize(existId, curValue, source.getGrade()); |
||||
} else { |
||||
fifteenMiMapper.saveDataResultFifteenMi(record); |
||||
} |
||||
}); |
||||
} |
||||
} finally { |
||||
// 清理长时间不用的锁对象,避免内存泄漏
|
||||
cleanupLocks(); |
||||
} |
||||
} |
||||
|
||||
// 通用重试方法
|
||||
private void executeWithRetry(Runnable operation) { |
||||
int retryCount = 0; |
||||
final int maxRetries = 3; |
||||
|
||||
while (retryCount < maxRetries) { |
||||
try { |
||||
operation.run(); |
||||
break; |
||||
} catch (Exception e) { |
||||
retryCount++; |
||||
if (retryCount >= maxRetries) { |
||||
throw new RuntimeException("操作失败,已重试" + maxRetries + "次", e); |
||||
} |
||||
|
||||
// 检查是否为死锁异常
|
||||
if (isDeadlockException(e)) { |
||||
log.warn("检测到死锁,进行第{}次重试", retryCount); |
||||
// 随机延迟后重试
|
||||
try { |
||||
Thread.sleep(new Random().nextInt(200) + 100); |
||||
} catch (InterruptedException ie) { |
||||
Thread.currentThread().interrupt(); |
||||
throw new RuntimeException(ie); |
||||
} |
||||
} else { |
||||
throw new RuntimeException("非死锁异常", e); // 非死锁异常直接抛出
|
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 判断是否为死锁异常
|
||||
private boolean isDeadlockException(Exception e) { |
||||
if (e.getMessage() != null) { |
||||
String message = e.getMessage().toLowerCase(); |
||||
return message.contains("死锁") || message.contains("deadlock"); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
|
||||
private String addMinutes(String baseDate, int minutes) throws ParseException { |
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
||||
Date date = sdf.parse(baseDate); |
||||
date.setTime(date.getTime() + minutes * 60 * 1000L); |
||||
return sdf.format(date); |
||||
} |
||||
|
||||
private boolean isNumeric(String str) { |
||||
return str != null && str.matches("-?\\d+(\\.\\d+)?"); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue