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