|
|
@@ -3,12 +3,13 @@ package com.usky.cdi.service.impl;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.io.FileUtil;
|
|
|
+import cn.hutool.core.io.IoUtil;
|
|
|
import cn.hutool.core.lang.Assert;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
-import cn.hutool.json.JSONArray;
|
|
|
import cn.hutool.json.JSONObject;
|
|
|
import cn.hutool.json.JSONUtil;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
@@ -17,6 +18,7 @@ import com.usky.cdi.service.enums.MqttTopics;
|
|
|
import com.usky.cdi.mapper.*;
|
|
|
import com.usky.cdi.service.CdiDeliveryLogService;
|
|
|
import com.usky.cdi.service.mqtt.MqttConnectionTool;
|
|
|
+import com.usky.cdi.service.util.SnowflakeIdGenerator;
|
|
|
import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
|
|
|
import com.usky.cdi.service.vo.info.FloorPlaneVO;
|
|
|
import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
|
|
|
@@ -29,15 +31,20 @@ import org.apache.commons.collections4.CollectionUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
import javax.imageio.ImageIO;
|
|
|
import java.awt.image.BufferedImage;
|
|
|
import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
import java.net.URL;
|
|
|
import java.net.URLConnection;
|
|
|
+import java.net.URLEncoder;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
|
@@ -84,108 +91,60 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
@Autowired
|
|
|
private MqttConnectionTool mqttConnectionTool;
|
|
|
|
|
|
+ // 从配置文件读取Snowflake参数,默认值为1
|
|
|
+ @Value("${snowflake.worker-id:1}")
|
|
|
+ private long workerId;
|
|
|
+
|
|
|
+ @Value("${snowflake.data-center-id:1}")
|
|
|
+ private long dataCenterId;
|
|
|
+
|
|
|
+ private SnowflakeIdGenerator idGenerator;
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成数据包ID
|
|
|
+ */
|
|
|
+ private Long generateDataPacketID() {
|
|
|
+ return idGenerator.nextPacketId();
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public List<SyncTaskStatisticsVO> selectById(Long id) {
|
|
|
- // 1. 租户ID校验(必须非空,无租户直接返回空列表)
|
|
|
Integer tenantId = SecurityUtils.getTenantId();
|
|
|
- if (tenantId == null) {
|
|
|
+ if (tenantId == null || tenantId <= 0) {
|
|
|
log.warn("未获取到当前租户ID,无法查询人防投递日志");
|
|
|
return Collections.emptyList();
|
|
|
}
|
|
|
|
|
|
- // 2. 动态构建查询条件:id为null时只查租户,id不为null时租户+id精准查
|
|
|
- // 【小优化】按ID倒序,后续取最新数据更直观(ID自增则大ID是最新)
|
|
|
List<CdiDeliveryLog> logList = lambdaQuery()
|
|
|
.eq(CdiDeliveryLog::getTenantId, tenantId)
|
|
|
.eq(id != null, CdiDeliveryLog::getId, id)
|
|
|
- .orderByDesc(CdiDeliveryLog::getId) // 改为倒序,优先最新数据
|
|
|
+ .orderByDesc(CdiDeliveryLog::getId)
|
|
|
.list();
|
|
|
|
|
|
LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
queryWrapper.eq(CdiDefenseProject::getTenantId, tenantId);
|
|
|
- // 【空指针防护】新增非空判断,避免selectOne返回null时报错
|
|
|
CdiDefenseProject defenseProject = cdiDefenseProjectMapper.selectOne(queryWrapper);
|
|
|
boolean isEnable = defenseProject != null && defenseProject.getIsEnable() == 0;
|
|
|
|
|
|
List<SyncTaskStatisticsVO> finalResult = new ArrayList<>();
|
|
|
|
|
|
- // 3. 日志集合判空:无数据查询设备表(原逻辑不变)
|
|
|
- if (CollectionUtils.isEmpty(logList)) {
|
|
|
-
|
|
|
- // 单元数据
|
|
|
- List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
|
|
|
- SyncTaskStatisticsVO vo1 = new SyncTaskStatisticsVO();
|
|
|
- vo1.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
|
|
|
- vo1.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
|
|
|
- vo1.setDataType(1);
|
|
|
- vo1.setTotal(buildUnitList.size());
|
|
|
- vo1.setSuccessNumber(0);
|
|
|
- vo1.setFailNumber(0);
|
|
|
- vo1.setNotSynced(0);
|
|
|
- vo1.setState(isEnable ? 1 : 0);
|
|
|
- finalResult.add(vo1);
|
|
|
-
|
|
|
- // 平面图
|
|
|
- List<Integer> buildIdList = getBuildList(tenantId).stream().map(BaseBuild::getId).collect(Collectors.toList());
|
|
|
- List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIdList);
|
|
|
- SyncTaskStatisticsVO vo4 = new SyncTaskStatisticsVO();
|
|
|
- vo4.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
|
|
|
- vo4.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
|
|
|
- vo4.setDataType(2);
|
|
|
- vo4.setTotal(buildPlaneList.size());
|
|
|
- vo4.setSuccessNumber(0);
|
|
|
- vo4.setFailNumber(0);
|
|
|
- vo4.setNotSynced(0);
|
|
|
- vo4.setState(isEnable ? 1 : 0);
|
|
|
- finalResult.add(vo4);
|
|
|
-
|
|
|
- // 设施数据
|
|
|
- List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
|
|
|
- SyncTaskStatisticsVO vo2 = new SyncTaskStatisticsVO();
|
|
|
- vo2.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
|
|
|
- vo2.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
|
|
|
- vo2.setDataType(3);
|
|
|
- vo2.setTotal(buildFacilityList.size());
|
|
|
- vo2.setSuccessNumber(0);
|
|
|
- vo2.setFailNumber(0);
|
|
|
- vo2.setNotSynced(0);
|
|
|
- vo2.setState(isEnable ? 1 : 0);
|
|
|
- finalResult.add(vo2);
|
|
|
-
|
|
|
- // 监测数据
|
|
|
- SyncTaskStatisticsVO vo3 = new SyncTaskStatisticsVO();
|
|
|
- vo3.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
|
|
|
- vo3.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
|
|
|
- vo3.setDataType(4);
|
|
|
- vo3.setTotal(buildFacilityList.size());
|
|
|
- vo3.setSuccessNumber(0);
|
|
|
- vo3.setFailNumber(0);
|
|
|
- vo3.setNotSynced(0);
|
|
|
- vo3.setState(isEnable ? 1 : 0);
|
|
|
- finalResult.add(vo3);
|
|
|
-
|
|
|
- SyncTaskStatisticsVO vo5 = new SyncTaskStatisticsVO();
|
|
|
- vo5.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
|
|
|
- vo5.setTopic(MqttTopics.Alarm.MESSAGE.getTopic());
|
|
|
- vo5.setDataType(5);
|
|
|
- vo5.setTotal(0);
|
|
|
- vo5.setSuccessNumber(0);
|
|
|
- vo5.setFailNumber(0);
|
|
|
- vo5.setNotSynced(0);
|
|
|
- vo5.setState(isEnable ? 1 : 0);
|
|
|
- finalResult.add(vo5);
|
|
|
+ // 预先查询各类型的基础数据量(无日志时使用)
|
|
|
+ List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
|
|
|
+ List<BaseBuild> buildList = getBuildList(tenantId);
|
|
|
+ List<Integer> buildIdList = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
|
|
|
+ List<BaseBuildPlane> buildPlaneList = buildIdList.isEmpty() ? Collections.emptyList() : getBuildPlaneList(buildIdList);
|
|
|
+ List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
|
|
|
|
|
|
+ if (CollectionUtils.isEmpty(logList)) {
|
|
|
+ fillEmptyStatistics(finalResult, tenantId, isEnable);
|
|
|
return finalResult;
|
|
|
}
|
|
|
|
|
|
- // 优先自动解析:JSON数组直接转VO列表,简洁高效
|
|
|
- // JSONArray jsonArray = JSONUtil.parseArray(jsonContent);
|
|
|
- // List<SyncTaskStatisticsVO> autoParseList = JSONUtil.toList(jsonArray, SyncTaskStatisticsVO.class);
|
|
|
- // finalResult.addAll(autoParseList);
|
|
|
- // log.info("租户ID:{} 日志ID:{} 自动解析成功,解析出{}条同步统计数据", tenantId, logId, autoParseList.size());
|
|
|
-
|
|
|
- // ########## 核心改造:按dataType分组,取每种类型最新的一条日志 ##########
|
|
|
- // 步骤1:先过滤出infoContent非空的有效日志(提前过滤,减少分组计算量)
|
|
|
List<CdiDeliveryLog> validLogList = logList.stream()
|
|
|
.filter(logEntity -> logEntity != null
|
|
|
&& StrUtil.isNotBlank(logEntity.getInfoContent())
|
|
|
@@ -193,130 +152,185 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
if (CollectionUtils.isEmpty(validLogList)) {
|
|
|
- log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空,查询ID:{}", tenantId, logList.size(), id);
|
|
|
- return Collections.emptyList();
|
|
|
+ log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空", tenantId, logList.size());
|
|
|
+ fillEmptyStatistics(finalResult, tenantId, isEnable);
|
|
|
+ return finalResult;
|
|
|
}
|
|
|
|
|
|
- // 步骤2:按dataType分组,每组按ID倒序(最新)取第一条,保证1-4类型各一条
|
|
|
- Map<Integer, CdiDeliveryLog> latestLogByType = validLogList.stream()
|
|
|
- .collect(Collectors.groupingBy(
|
|
|
- CdiDeliveryLog::getDataType, // 分组键:dataType(1-4)
|
|
|
- Collectors.collectingAndThen(
|
|
|
- Collectors.maxBy(Comparator.comparingLong(CdiDeliveryLog::getId)), // 取组内ID最大的(最新)
|
|
|
- opt -> opt.orElse(null) // 空值处理
|
|
|
- )
|
|
|
- ));
|
|
|
+ // ★ 改动1:按 dataType 分组,保留每种类型的全部日志
|
|
|
+ Map<Integer, List<CdiDeliveryLog>> allLogsByType = validLogList.stream()
|
|
|
+ .collect(Collectors.groupingBy(CdiDeliveryLog::getDataType));
|
|
|
|
|
|
- // 步骤3:转换为列表,仅保留1-4类型的最新日志(过滤非目标类型)
|
|
|
- List<CdiDeliveryLog> finalValidLogList = new ArrayList<>();
|
|
|
for (int type = 1; type <= 5; type++) {
|
|
|
- CdiDeliveryLog latestLog = latestLogByType.get(type);
|
|
|
- if (latestLog != null) {
|
|
|
- finalValidLogList.add(latestLog);
|
|
|
+ List<CdiDeliveryLog> logsForType = allLogsByType.get(type);
|
|
|
+
|
|
|
+ // 创建基础 VO,设置 dataType 和 topic
|
|
|
+ SyncTaskStatisticsVO vo = createEmptyVO(type, isEnable);
|
|
|
+
|
|
|
+ // ★ total 始终从数据库查询获取(单元表、平面图表等),与日志记录无关
|
|
|
+ switch (type) {
|
|
|
+ case 1:
|
|
|
+ vo.setTotal(buildUnitList.size());
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ vo.setTotal(buildPlaneList.size());
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ case 4:
|
|
|
+ vo.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ default:
|
|
|
+ break;
|
|
|
}
|
|
|
- // 若某类型无日志,无需处理:后续解析后若该类型无数据,是否补空VO可按需求调整,原逻辑是返回解析到的内容
|
|
|
- }
|
|
|
|
|
|
- if (CollectionUtils.isEmpty(finalValidLogList)) {
|
|
|
- log.warn("租户ID:{} 无1-4类型的有效日志,查询ID:{}", tenantId, id);
|
|
|
- return Collections.emptyList();
|
|
|
- }
|
|
|
- // ########## 核心改造结束 ##########
|
|
|
-
|
|
|
- // 5. 遍历【每种类型最新的日志】,解析JSON并合并结果(单日志解析失败不影响其他)
|
|
|
- for (CdiDeliveryLog deliveryLog : finalValidLogList) {
|
|
|
- Long logId = deliveryLog.getId();
|
|
|
- String jsonContent = deliveryLog.getInfoContent();
|
|
|
- try {
|
|
|
- JSONArray array = JSONUtil.parseArray(jsonContent);
|
|
|
- List<SyncTaskStatisticsVO> manualParseList = new ArrayList<>(array.size());
|
|
|
- for (int i = 0; i < array.size(); i++) {
|
|
|
- JSONObject obj = array.getJSONObject(i);
|
|
|
- SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
|
|
|
- // 手动映射字段:按实际入库的JSON字段名匹配
|
|
|
- vo.setId(obj.getLong("id"));
|
|
|
- vo.setTopic(obj.getStr("topic"));
|
|
|
- switch (obj.getInt("dataType")) {
|
|
|
- case 1:
|
|
|
- vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
|
|
|
- break;
|
|
|
- case 3:
|
|
|
- vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
|
|
|
- break;
|
|
|
- case 4:
|
|
|
- vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
|
|
|
- break;
|
|
|
- case 5:
|
|
|
- vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
|
|
|
- break;
|
|
|
+ if (logsForType != null && !logsForType.isEmpty()) {
|
|
|
+ // ★ 有日志时:按类型累加 success / fail / notSynced
|
|
|
+ int sumSuccess = 0, sumFail = 0, sumNotSynced = 0;
|
|
|
+
|
|
|
+ for (CdiDeliveryLog logEntry : logsForType) {
|
|
|
+ try {
|
|
|
+ // ★ infoContent 是 JSON 对象,用 parseObj 解析
|
|
|
+ JSONObject o = JSONUtil.parseObj(logEntry.getInfoContent());
|
|
|
+ sumSuccess += o.getInt("successNumber");
|
|
|
+ sumFail += o.getInt("failNumber");
|
|
|
+ sumNotSynced += o.getInt("notSynced");
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ // 单条解析失败不影响其他日志的累加
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setSuccessNumber(sumSuccess);
|
|
|
+ vo.setFailNumber(sumFail);
|
|
|
+ vo.setNotSynced(sumNotSynced);
|
|
|
+
|
|
|
+ // 取最新一条日志作为元数据来源(createTime / costTime 等)
|
|
|
+ CdiDeliveryLog latestLog = logsForType.stream()
|
|
|
+ .max(Comparator.comparingLong(CdiDeliveryLog::getId))
|
|
|
+ .orElse(null);
|
|
|
+
|
|
|
+ if (latestLog != null) {
|
|
|
+ try {
|
|
|
+ // ★ infoContent 是 JSON 对象,用 parseObj 解析
|
|
|
+ JSONObject obj = JSONUtil.parseObj(latestLog.getInfoContent());
|
|
|
+ vo.setId(obj.getLong("id"));
|
|
|
+ vo.setCreateTime(obj.getStr("createTime"));
|
|
|
+ vo.setCostTime(obj.getFloat("costTime"));
|
|
|
+ vo.setState(isEnable ? 1 : obj.getInt("state"));
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.error("租户ID:{} dataType:{} 解析最新日志元数据失败", tenantId, type, ex);
|
|
|
}
|
|
|
- vo.setDataType(obj.getInt("dataType"));
|
|
|
- vo.setTotal(obj.getInt("total"));
|
|
|
- vo.setSuccessNumber(obj.getInt("successNumber"));
|
|
|
- vo.setFailNumber(obj.getInt("failNumber"));
|
|
|
- vo.setNotSynced(obj.getInt("notSynced"));
|
|
|
- vo.setCreateTime(obj.getStr("createTime"));
|
|
|
- vo.setCostTime(obj.getFloat("costTime"));
|
|
|
- vo.setState(isEnable ? 1 : obj.getInt("state"));
|
|
|
- manualParseList.add(vo);
|
|
|
}
|
|
|
- finalResult.addAll(manualParseList);
|
|
|
- log.info("租户ID:{} 日志ID:{} 手动解析成功,解析出{}条同步统计数据", tenantId, logId, manualParseList.size());
|
|
|
- } catch (Exception ex) {
|
|
|
- // 单日志解析失败,仅打印日志,继续解析其他日志
|
|
|
- log.error("租户ID:{} 日志ID:{} 解析失败,跳过该日志", tenantId, logId, ex);
|
|
|
+
|
|
|
+ log.info("租户ID:{} dataType:{} 共{}条日志, 聚合结果: total={}, success={}, fail={}, notSynced={}",
|
|
|
+ tenantId, type, logsForType.size(), vo.getTotal(), sumSuccess, sumFail, sumNotSynced);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // ★ 无日志时:success/fail/notSynced 保持为0(createEmptyVO已设置)
|
|
|
}
|
|
|
+
|
|
|
+ finalResult.add(vo);
|
|
|
}
|
|
|
|
|
|
- // // 6. 【可选优化】若解析后部分类型缺失,补全空VO(和无日志时格式完全一致)
|
|
|
- // // 提取已解析的dataType
|
|
|
- // Set<Integer> parsedTypes = finalResult.stream()
|
|
|
- // .map(SyncTaskStatisticsVO::getDataType)
|
|
|
- // .filter(Objects::nonNull)
|
|
|
- // .collect(Collectors.toSet());
|
|
|
- // // 补全1-4中缺失的类型,设置默认值(和无日志时一致)
|
|
|
- // for (int type = 1; type <= 3; type++) {
|
|
|
- // if (!parsedTypes.contains(type)) {
|
|
|
- // SyncTaskStatisticsVO emptyVo = new SyncTaskStatisticsVO();
|
|
|
- // // 按类型设置名称、主题,和情况一保持一致
|
|
|
- // switch (type) {
|
|
|
- // case 1:
|
|
|
- // emptyVo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
|
|
|
- // emptyVo.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
|
|
|
- // break;
|
|
|
- // case 2:
|
|
|
- // emptyVo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
|
|
|
- // emptyVo.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
|
|
|
- // break;
|
|
|
- // case 3:
|
|
|
- // emptyVo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
|
|
|
- // emptyVo.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
|
|
|
- // break;
|
|
|
- // // case 4:
|
|
|
- // // emptyVo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
|
|
|
- // // emptyVo.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
|
|
|
- // // break;
|
|
|
- // }
|
|
|
- // emptyVo.setDataType(type);
|
|
|
- // emptyVo.setTotal(0);
|
|
|
- // emptyVo.setSuccessNumber(0);
|
|
|
- // emptyVo.setFailNumber(0);
|
|
|
- // emptyVo.setNotSynced(0);
|
|
|
- // emptyVo.setState(isEnable ? 1 : 0);
|
|
|
- // finalResult.add(emptyVo);
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // 对结果按dataType排序(1-4),和情况一返回顺序一致
|
|
|
finalResult.sort(Comparator.comparingInt(SyncTaskStatisticsVO::getDataType));
|
|
|
-
|
|
|
- // 6. 返回结果
|
|
|
return finalResult;
|
|
|
}
|
|
|
|
|
|
+ private void fillEmptyStatistics(List<SyncTaskStatisticsVO> result, Integer tenantId, boolean isEnable) {
|
|
|
+ List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
|
|
|
+ SyncTaskStatisticsVO vo1 = createEmptyVO(1, isEnable);
|
|
|
+ vo1.setTotal(buildUnitList.size());
|
|
|
+ result.add(vo1);
|
|
|
+
|
|
|
+ List<Integer> buildIdList = getBuildList(tenantId).stream().map(BaseBuild::getId).collect(Collectors.toList());
|
|
|
+ List<BaseBuildPlane> buildPlaneList = buildIdList.isEmpty() ? Collections.emptyList() : getBuildPlaneList(buildIdList);
|
|
|
+ SyncTaskStatisticsVO vo2 = createEmptyVO(2, isEnable);
|
|
|
+ vo2.setTotal(buildPlaneList.size());
|
|
|
+ result.add(vo2);
|
|
|
+
|
|
|
+ List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
|
|
|
+ SyncTaskStatisticsVO vo3 = createEmptyVO(3, isEnable);
|
|
|
+ vo3.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
|
|
|
+ result.add(vo3);
|
|
|
+
|
|
|
+ SyncTaskStatisticsVO vo4 = createEmptyVO(4, isEnable);
|
|
|
+ vo4.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
|
|
|
+ result.add(vo4);
|
|
|
+
|
|
|
+ SyncTaskStatisticsVO vo5 = createEmptyVO(5, isEnable);
|
|
|
+ result.add(vo5);
|
|
|
+ }
|
|
|
+
|
|
|
+ private SyncTaskStatisticsVO createEmptyVO(int dataType, boolean isEnable) {
|
|
|
+ SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
|
|
|
+ vo.setDataType(dataType);
|
|
|
+ vo.setSuccessNumber(0);
|
|
|
+ vo.setFailNumber(0);
|
|
|
+ vo.setNotSynced(0);
|
|
|
+ vo.setState(isEnable ? 1 : 0);
|
|
|
+
|
|
|
+ switch (dataType) {
|
|
|
+ case 1:
|
|
|
+ vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
|
|
|
+ vo.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
|
|
|
+ vo.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
|
|
|
+ vo.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
|
|
|
+ vo.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
|
|
|
+ vo.setTopic(MqttTopics.Alarm.MESSAGE.getTopic());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SyncTaskStatisticsVO parseSyncStatisticsVO(JSONObject obj, boolean isEnable) {
|
|
|
+ SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
|
|
|
+ vo.setId(obj.getLong("id"));
|
|
|
+ vo.setTopic(obj.getStr("topic"));
|
|
|
+
|
|
|
+ int dataType = obj.getInt("dataType");
|
|
|
+ vo.setDataType(dataType);
|
|
|
+
|
|
|
+ switch (dataType) {
|
|
|
+ case 1:
|
|
|
+ vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setTotal(obj.getInt("total"));
|
|
|
+ vo.setSuccessNumber(obj.getInt("successNumber"));
|
|
|
+ vo.setFailNumber(obj.getInt("failNumber"));
|
|
|
+ vo.setNotSynced(obj.getInt("notSynced"));
|
|
|
+ vo.setCreateTime(obj.getStr("createTime"));
|
|
|
+ vo.setCostTime(obj.getFloat("costTime"));
|
|
|
+ vo.setState(isEnable ? 1 : obj.getInt("state"));
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer
|
|
|
logType, String startTime, String endTime) {
|
|
|
@@ -432,6 +446,13 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
Integer tenantId = one.getTenantId();
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
|
+ String userName = "自动同步";
|
|
|
+ try {
|
|
|
+ userName = SecurityUtils.getUsername();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("无法获取用户名或姓名,使用默认‘自动同步’", e);
|
|
|
+ }
|
|
|
+
|
|
|
switch (vo.getDataType()) {
|
|
|
// 单元信息
|
|
|
case 1:
|
|
|
@@ -460,7 +481,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
endTime = System.currentTimeMillis();
|
|
|
|
|
|
notSynced = total - success - failure;
|
|
|
- saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1);
|
|
|
+ saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1, userName);
|
|
|
|
|
|
break;
|
|
|
// 平面图信息
|
|
|
@@ -472,10 +493,16 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
int total2 = 0, success2 = 0, failure2 = 0, notSynced2 = 0;
|
|
|
|
|
|
List<FloorPlaneVO> floorPlaneVOS = buildPlanes(tenantId, engineeringId);
|
|
|
+ if (CollUtil.isEmpty(floorPlaneVOS)) {
|
|
|
+ log.error("未找到楼层平面图信息!");
|
|
|
+ break;
|
|
|
+ }
|
|
|
total2 = floorPlaneVOS.size();
|
|
|
iotDataTransferService.createMqttConnection(username, password);
|
|
|
String topic1 = MqttTopics.Base.FLOOR_PLANE.getTopic();
|
|
|
String desc1 = MqttTopics.Base.FLOOR_PLANE.getDesc();
|
|
|
+
|
|
|
+
|
|
|
for (FloorPlaneVO floorPlaneVO : floorPlaneVOS) {
|
|
|
try {
|
|
|
iotDataTransferService.sendMqttMessage(topic1, floorPlaneVO, desc1, username);
|
|
|
@@ -488,7 +515,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
endTime2 = System.currentTimeMillis();
|
|
|
notSynced2 = total2 - success2 - failure2;
|
|
|
saveLog(topic1, desc1, 2, tenantId, engineeringId, now, startTime2, endTime2, total2, success2,
|
|
|
- failure2, notSynced2, failure2 > 0 ? 0 : 1);
|
|
|
+ failure2, notSynced2, failure2 > 0 ? 0 : 1, userName);
|
|
|
|
|
|
break;
|
|
|
// 推送设施信息
|
|
|
@@ -498,11 +525,11 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
long startTime3, endTime3;
|
|
|
startTime3 = System.currentTimeMillis();
|
|
|
|
|
|
- Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
|
|
|
+ Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId, engineeringId, username, password);
|
|
|
|
|
|
endTime3 = System.currentTimeMillis();
|
|
|
saveLog(MqttTopics.Base.SENSOR_INFO.getTopic(), MqttTopics.Base.SENSOR_INFO.getDesc(), 3, tenantId, engineeringId,
|
|
|
- now, startTime3, endTime3, map.get("total"), map.get("success"), map.get("failure"), map.get("notSynced"), map.get("failure") > 0 ? 0 : 1);
|
|
|
+ now, startTime3, endTime3, map.get("total"), map.get("success"), map.get("failure"), map.get("notSynced"), map.get("failure") > 0 ? 0 : 1, userName);
|
|
|
|
|
|
break;
|
|
|
// 推送监测数据
|
|
|
@@ -518,7 +545,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
@Override
|
|
|
@Async("asyncServiceExecutor")
|
|
|
public void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
|
|
|
- int total, int success, int failure, int notSynced, int pushFlag) {
|
|
|
+ int total, int success, int failure, int notSynced, int pushFlag, String userName) {
|
|
|
|
|
|
SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
|
|
|
vo.setDataType(dataType);
|
|
|
@@ -537,8 +564,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
log.setDataType(dataType);
|
|
|
log.setTopic(topic);
|
|
|
log.setDataTypeName(vo.getDataTypeName());
|
|
|
- log.setUserName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getUsername());
|
|
|
- log.setNickName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getLoginUser().getSysUser().getNickName());
|
|
|
+ log.setUserName(userName);
|
|
|
log.setCreateTime(now);
|
|
|
log.setTenantId(tenantId);
|
|
|
log.setPushFlag(pushFlag);
|
|
|
@@ -559,6 +585,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
List<ProtectiveUnitVO> result = new ArrayList<>(buildUnitList.size());
|
|
|
for (BaseBuildUnit buildUnit : buildUnitList) {
|
|
|
ProtectiveUnitVO vo = new ProtectiveUnitVO();
|
|
|
+ vo.setDataPacketID(generateDataPacketID());
|
|
|
vo.setEngineeringID(engineeringId);
|
|
|
vo.setUnitName(buildUnit.getUnitName());
|
|
|
vo.setFloor(buildUnit.getFloor());
|
|
|
@@ -586,21 +613,42 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
List<Integer> buildIds = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
|
|
|
List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIds);
|
|
|
|
|
|
- String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
|
|
|
+ if (CollUtil.isEmpty(buildPlaneList)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, BaseBuildPlane> latestPlaneByFloor = buildPlaneList.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ BaseBuildPlane::getFloor,
|
|
|
+ Collectors.collectingAndThen(
|
|
|
+ Collectors.maxBy(Comparator.comparingInt(BaseBuildPlane::getId)),
|
|
|
+ opt -> opt.orElse(null)
|
|
|
+ )
|
|
|
+ ));
|
|
|
|
|
|
- List<FloorPlaneVO> result = new ArrayList<>(buildPlaneList.size());
|
|
|
- for (BaseBuildPlane buildPlane : buildPlaneList) {
|
|
|
+ List<BaseBuildPlane> filteredPlaneList = latestPlaneByFloor.values().stream()
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .sorted(Comparator.comparing(BaseBuildPlane::getFloor))
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
|
- String planeViewUrl = buildPlane.getPlaneViewUrl();
|
|
|
+ log.info("楼层平面图数据过滤:原始{}条,按楼层去重后{}条", buildPlaneList.size(), filteredPlaneList.size());
|
|
|
|
|
|
+ String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
|
|
|
|
|
|
+ List<FloorPlaneVO> result = new ArrayList<>(filteredPlaneList.size());
|
|
|
+ for (BaseBuildPlane buildPlane : filteredPlaneList) {
|
|
|
+ String planeViewUrl = buildPlane.getPlaneViewUrl();
|
|
|
FloorPlaneVO vo = new FloorPlaneVO();
|
|
|
checkFileSize(vo, planeViewUrl);
|
|
|
+ fillImageInfo(vo, planeViewUrl);
|
|
|
+
|
|
|
+ vo.setDataPacketID(generateDataPacketID());
|
|
|
vo.setEngineeringID(engineeringId);
|
|
|
vo.setFloor(buildPlane.getFloor());
|
|
|
vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
|
|
|
- fillImageInfo(vo, planeViewUrl);
|
|
|
vo.setPublishTime(time);
|
|
|
+
|
|
|
+ result.add(vo);
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
@@ -613,42 +661,141 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
|
|
|
private void checkFileSize(FloorPlaneVO vo, String filePath) {
|
|
|
Assert.notBlank(filePath, "文件路径不能为空");
|
|
|
|
|
|
- long size = FileUtil.size(new File(filePath));
|
|
|
- if (size > MAX_FILE_SIZE_BYTES) {
|
|
|
- double sizeMB = size / 1024.0 / 1024.0;
|
|
|
- throw new BusinessException(
|
|
|
- StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
|
|
|
- );
|
|
|
+ byte[] fileBytes;
|
|
|
+
|
|
|
+ if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
|
|
|
+ fileBytes = downloadFileFromUrl(filePath);
|
|
|
+ } else {
|
|
|
+ File localFile = new File(filePath);
|
|
|
+ if (!localFile.exists()) {
|
|
|
+ log.warn("本地文件不存在: {}", filePath);
|
|
|
+ throw new BusinessException("楼层平面图文件不存在:" + filePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ long size = FileUtil.size(localFile);
|
|
|
+ if (size > MAX_FILE_SIZE_BYTES) {
|
|
|
+ double sizeMB = size / 1024.0 / 1024.0;
|
|
|
+ throw new BusinessException(
|
|
|
+ StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ fileBytes = FileUtil.readBytes(localFile);
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setFloorFile(fileBytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ private byte[] downloadFileFromUrl(String fileUrl) {
|
|
|
+ try {
|
|
|
+ String encodedUrl = encodeUrl(fileUrl);
|
|
|
+ URL url = new URL(encodedUrl);
|
|
|
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
|
+ conn.setRequestMethod("GET");
|
|
|
+ conn.setConnectTimeout(5000);
|
|
|
+ conn.setReadTimeout(10000);
|
|
|
+ conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
|
|
+
|
|
|
+ int responseCode = conn.getResponseCode();
|
|
|
+ if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
|
+ log.error("下载文件失败,HTTP状态码: {}, URL: {}", responseCode, fileUrl);
|
|
|
+ throw new BusinessException("下载楼层平面图失败,服务器返回状态码:" + responseCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ int contentLength = conn.getContentLength();
|
|
|
+ if (contentLength > MAX_FILE_SIZE_BYTES) {
|
|
|
+ double sizeMB = contentLength / 1024.0 / 1024.0;
|
|
|
+ throw new BusinessException(
|
|
|
+ StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ try (InputStream inputStream = conn.getInputStream()) {
|
|
|
+ return IoUtil.readBytes(inputStream);
|
|
|
+ }
|
|
|
+ } catch (BusinessException e) {
|
|
|
+ throw e;
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("从URL下载文件失败: {}", fileUrl, e);
|
|
|
+ throw new BusinessException("下载楼层平面图失败:" + e.getMessage());
|
|
|
}
|
|
|
- vo.setFloorFile(FileUtil.readBytes(filePath));
|
|
|
}
|
|
|
|
|
|
- // 获取图片信息
|
|
|
+ private String encodeUrl(String url) throws UnsupportedEncodingException {
|
|
|
+ if (StrUtil.isBlank(url)) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(url.startsWith("http://") || url.startsWith("https://"))) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ int protocolEnd = url.indexOf("://");
|
|
|
+ String protocol = url.substring(0, protocolEnd + 3);
|
|
|
+ String rest = url.substring(protocolEnd + 3);
|
|
|
+
|
|
|
+ int firstSlash = rest.indexOf("/");
|
|
|
+ if (firstSlash == -1) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ String hostAndPort = rest.substring(0, firstSlash);
|
|
|
+ String path = rest.substring(firstSlash);
|
|
|
+
|
|
|
+ String[] pathSegments = path.split("/");
|
|
|
+ StringBuilder encodedPath = new StringBuilder();
|
|
|
+ for (int i = 0; i < pathSegments.length; i++) {
|
|
|
+ if (i > 0) {
|
|
|
+ encodedPath.append("/");
|
|
|
+ }
|
|
|
+ encodedPath.append(URLEncoder.encode(pathSegments[i], "UTF-8"));
|
|
|
+ }
|
|
|
+
|
|
|
+ return protocol + hostAndPort + encodedPath.toString();
|
|
|
+ }
|
|
|
+
|
|
|
private void fillImageInfo(FloorPlaneVO vo, String imageUrl) {
|
|
|
if (StrUtil.isBlank(imageUrl)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 提取文件名信息
|
|
|
String fileName = FileUtil.getName(imageUrl);
|
|
|
vo.setFloorFileName(FileUtil.mainName(fileName));
|
|
|
vo.setFloorFileSuffix(FileUtil.extName(fileName));
|
|
|
|
|
|
- // 读取像素尺寸(带超时控制)
|
|
|
try {
|
|
|
- URLConnection conn = new URL(imageUrl).openConnection();
|
|
|
- conn.setConnectTimeout(3000);
|
|
|
- conn.setReadTimeout(5000);
|
|
|
-
|
|
|
- try (InputStream in = conn.getInputStream()) {
|
|
|
- BufferedImage image = ImageIO.read(in);
|
|
|
- if (image != null) {
|
|
|
- vo.setFilePixWidth(image.getWidth());
|
|
|
- vo.setFilePixHeight(image.getHeight());
|
|
|
+ BufferedImage image;
|
|
|
+ if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
|
|
|
+ String encodedUrl = encodeUrl(imageUrl);
|
|
|
+ URL url = new URL(encodedUrl);
|
|
|
+ URLConnection conn = url.openConnection();
|
|
|
+ conn.setConnectTimeout(3000);
|
|
|
+ conn.setReadTimeout(5000);
|
|
|
+
|
|
|
+ try (InputStream in = conn.getInputStream()) {
|
|
|
+ image = ImageIO.read(in);
|
|
|
}
|
|
|
+ } else {
|
|
|
+ File imageFile = new File(imageUrl);
|
|
|
+ if (imageFile.exists()) {
|
|
|
+ image = ImageIO.read(imageFile);
|
|
|
+ } else {
|
|
|
+ log.warn("图片文件不存在: {}", imageUrl);
|
|
|
+ vo.setFilePixWidth(7016);
|
|
|
+ vo.setFilePixHeight(9933);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (image != null) {
|
|
|
+ vo.setFilePixWidth(image.getWidth());
|
|
|
+ vo.setFilePixHeight(image.getHeight());
|
|
|
+ } else {
|
|
|
+ log.warn("无法读取图片尺寸: {}", imageUrl);
|
|
|
+ vo.setFilePixWidth(7016);
|
|
|
+ vo.setFilePixHeight(9933);
|
|
|
}
|
|
|
} catch (IOException e) {
|
|
|
- log.error("获取图片尺寸失败: {}", imageUrl);
|
|
|
+ log.error("获取图片尺寸失败: {}", imageUrl, e);
|
|
|
vo.setFilePixWidth(7016);
|
|
|
vo.setFilePixHeight(9933);
|
|
|
}
|