Compare commits

...

37 Commits

Author SHA1 Message Date
  wangguangyuan 952d3b4290 fix: 推送合并任务报告推送多个任务id用逗号拼接 2 days ago
  yinhuaiwei 3db44ddabc feat: 表计meter算法支持对读数“正常”的解析 2 days ago
  wangguangyuan c25a8d0354 fix: 修复报告上传消息无限重投递,超过 3 次直接丢弃 2 days ago
  wangguangyuan d5b7c89d98 Merge branch 'yanyuan' of http://git.ht-atia.cn/qinyanlei/inspect into yanyuan 3 days ago
  wangguangyuan 8b7b8c340f feat: 缺陷记录添加算法筛选,按算法名精确匹配 3 days ago
  yinhuaiwei 5667eaea71 feat: 新增分析流程完结字段completed用于数据上送 4 days ago
  wangguangyuan ff5dbf20b0 feat: 缺陷记录添加下拉筛选,按缺陷描述精确匹配 4 days ago
  yinhuaiwei 2fde923bf6 temp: 临时屏蔽 RequiresPermissions 注解 4 days ago
  yinhuaiwei 86055b2281 feat: 新增通道查询返回台账名称 6 days ago
  yinhuaiwei c7e0677b81 feat: 新增接口用于明文用户密码获取token 1 week ago
  wangguangyuan f7d4b63c2e Merge branch 'yanyuan' of http://git.ht-atia.cn/qinyanlei/inspect into yanyuan 1 week ago
  yinhuaiwei 40854cc240 fix: 修复修改用户信息时会将用户密码同步修改 1 week ago
  wangguangyuan 166ccb8b2b feat: 缺陷记录添加筛选条件 1 week ago
  yinhuaiwei b5ec64abe0 feat: 点位数据分析完成时通过WS上送 1 week ago
  wangguangyuan 6313f95ded fix(redis): 按分钟执行的间隔任务不再生成窗口外的策略 key 2 weeks ago
  wangguangyuan de77ada07a fix(redis): 生产路径禁止使用 KEYS,改用 SCAN,修复 Redis 阻塞导致巡检服务不可用 2 weeks ago
  yinhuaiwei 0aef3b2e15 feat: 取消数据查询权限限制 2 weeks ago
  wangguangyuan f37f7bbecc fix: 动环巡检报告目录调整 2 weeks ago
  wangguangyuan fef4796256 fix: 动环巡检报告排版对齐机器人巡检报告模板 2 weeks ago
  yinhuaiwei 8a19df5709 refactor: 优化盐源ws推送,支持任意消息格式 2 weeks ago
  yinhuaiwei aa54054e76 feat: 临时屏蔽接口所需权限 2 weeks ago
  WangGuangYuan c81de07f7a fix:巡视报告格式,字体修改 2 weeks ago
  wangguangyuan 443d30bf02 fix:动环报告合并及归档推送逻辑修改,新增了inspection_report_task表 3 weeks ago
  wangguangyuan a2649203c3 fix:报告推送任务id修改,新增自动归档开关与推送开关 3 weeks ago
  wangguangyuan b88c984f1c feat:雨刮和框选3D定位 3 weeks ago
  yinhuaiwei c10550aaef Merge branch 'yanyuan' of https://git.ht-atia.cn/qinyanlei/inspect into yanyuan 3 weeks ago
  yinhuaiwei 5ac0de509c fix: 修复因计划任务过多判断,导致任务错过下发时间未执行 3 weeks ago
  wangguangyuan e8a501d488 feat:巡视报告标题字体黑体 4号,标题段落首行缩进1字符 3 weeks ago
  wangguangyuan d07dda28b5 feat:缺陷汇总接口,相机通道接口 3 weeks ago
  wangguangyuan c87a45671c feat:盐源新版巡视报告字体嵌入,及http推送外部接口 4 weeks ago
  wangguangyuan 57e78d5c3f feat:盐源新版巡视报告,及自动归档,websocket推送 1 month ago
  yinhuaiwei 7bc91a250c feat: 新增getToken接口,用于第三方调用接口鉴权 1 month ago
  yinhuaiwei 6aaae83f4f feat: 新增盐源ws接口(通过AlarmPublisher发送ws) 1 month ago
  wangguangyuan 91706e3913 fix:巡视报告异常捕获 1 month ago
  wangguangyuan 57c6724f4d feat:视频预览,预置位操作,云台控制,抓拍,录像下载 1 month ago
  wangguangyuan c9a56d778a feat:1.历史回放接口,2.台账接口修改 1 month ago
  yinhuaiwei 7403bcd61f 第一次提交:1.新增inspect-main-api模块,用于开发盐源新特性接口;2.启动全局异常捕获器,对服务异常统一封装 2 months ago
125 changed files with 5817 additions and 644 deletions
Split View
  1. +14
    -0
      inspect-admin/src/main/java/com/inspect/admin/controller/TokenController.java
  2. +2
    -0
      inspect-admin/src/main/java/com/inspect/admin/service/SysLoginService.java
  3. +1
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/RedisConst.java
  4. +1
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/TokenConst.java
  5. +2
    -2
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AnalysisType.java
  6. +20
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java
  7. +1
    -1
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/sftp/SftpClient.java
  8. +110
    -3
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/utils/HttpClientUtils.java
  9. +4
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/web/domain/AjaxResult.java
  10. +51
    -0
      inspect-base/inspect-base-redis/src/main/java/com/inspect/base/redis/service/RedisService.java
  11. +4
    -3
      inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/aspect/PreAuthorizeAspect.java
  12. +2
    -3
      inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/handler/GlobalExceptionHandler.java
  13. +1
    -0
      inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/service/TokenService.java
  14. +15
    -0
      inspect-ivs/src/main/java/com/inspect/ivs/constant/IvsConst.java
  15. +33
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsControlController.java
  16. +22
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsDeviceController.java
  17. +45
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsPlaybackController.java
  18. +14
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsRecordController.java
  19. +28
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java
  20. +102
    -0
      inspect-ivs/src/main/java/com/inspect/ivs/service/HlsPlaybackComponent.java
  21. +3
    -0
      inspect-ivs/src/main/resources/bootstrap.yml
  22. +26
    -0
      inspect-job/src/main/java/com/inspect/job/client/ReportArchiveClient.java
  23. +15
    -15
      inspect-job/src/main/java/com/inspect/job/task/JobMainTask.java
  24. +81
    -0
      inspect-job/src/main/java/com/inspect/job/task/ReportArchiveScheduleTask.java
  25. +64
    -0
      inspect-main/inspect-main-api/pom.xml
  26. +98
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/config/RabbitMQConfig.java
  27. +25
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/constant/YanyuanConstants.java
  28. +63
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/controller/AlarmController.java
  29. +19
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/controller/PatrolController.java
  30. +19
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/AlarmMessage.java
  31. +12
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/ApiResult.java
  32. +12
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/AuthTokenData.java
  33. +23
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/ReportPushMessage.java
  34. +17
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/enums/AlarmCategory.java
  35. +14
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/enums/MessageType.java
  36. +15
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/enums/SystemCode.java
  37. +15
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/props/AppProperties.java
  38. +78
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/AlarmConsumer.java
  39. +25
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/AlarmPublisher.java
  40. +41
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ExtAuthService.java
  41. +34
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportPushPublisher.java
  42. +150
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportUploadConsumer.java
  43. +184
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportUploadService.java
  44. +133
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/websocket/AlarmWsConnection.java
  45. +4
    -0
      inspect-main/inspect-main-start/pom.xml
  46. +1
    -1
      inspect-main/inspect-main-start/src/main/java/com/inspect/InspectStartApplication.java
  47. +40
    -1
      inspect-main/inspect-main-start/src/main/resources/application.yml
  48. +5
    -5
      inspect-main/inspect-main-start/src/main/resources/bootstrap.yaml
  49. +39
    -19
      inspect-main/inspect-main-task-exec/src/main/java/com/inspect/exec/controller/PatrolTaskExecController.java
  50. +15
    -1
      inspect-main/inspect-main-task/pom.xml
  51. +152
    -5
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/controller/ResultAnalysisController.java
  52. +9
    -1
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/AnalyseResPoint.java
  53. +1
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/AnalyseResult.java
  54. +54
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/DefectListQuery.java
  55. +5
    -55
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/ResultAnalysis.java
  56. +206
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/service/DefectListAggregator.java
  57. +63
    -98
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/service/impl/AnalyseResponseServiceImpl.java
  58. +3
    -3
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java
  59. +7
    -2
      inspect-main/inspect-main-task/src/main/java/com/inspect/aspect/TaskStatusChangeAspect.java
  60. +3
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/img/domain/InspectionReportImg.java
  61. +27
    -36
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreport/controller/InspectionReportController.java
  62. +5
    -2
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreport/domain/InspectionReport.java
  63. +5
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreportdata/domain/InspectionReportData.java
  64. +32
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/domain/InspectionReportTask.java
  65. +36
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/mapper/InspectionReportTaskMapper.java
  66. +24
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/service/IInspectionReportTaskService.java
  67. +32
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/service/impl/InspectionReportTaskServiceImpl.java
  68. +2
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/message/MessageUtils.java
  69. +5
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/mapper/PatrolResultMapper.java
  70. +5
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/service/IPatrolResultService.java
  71. +122
    -63
      inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/service/impl/PatrolResultServiceImpl.java
  72. +283
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportfont/ReportFontEmbedder.java
  73. +46
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/config/ReportPushProperties.java
  74. +64
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/controller/ReportPushController.java
  75. +191
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/service/ReportAutoArchiveService.java
  76. +148
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/service/ReportPushService.java
  77. +1835
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/controller/DonghuanReportExportController.java
  78. +10
    -5
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/controller/PatrolTaskResultMainController.java
  79. +14
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/mapper/PatrolTaskResultMainMapper.java
  80. +9
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/service/IPatrolTaskResultMainService.java
  81. +6
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/service/impl/PatrolTaskResultMainServiceImpl.java
  82. +5
    -3
      inspect-main/inspect-main-task/src/main/java/com/inspect/task/controller/PatrolTaskController.java
  83. +25
    -250
      inspect-main/inspect-main-task/src/main/java/com/inspect/task/domain/PatrolData.java
  84. +7
    -4
      inspect-main/inspect-main-task/src/main/java/com/inspect/taskstatus/service/impl/PatrolTaskStatusServiceImpl.java
  85. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/simfang.ttf
  86. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/simhei.ttf
  87. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/仿宋_GB2312.ttf
  88. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/方正小标宋简体.ttf
  89. +4
    -2
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportDataMapper.xml
  90. +4
    -3
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportImgMapper.xml
  91. +5
    -0
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportMapper.xml
  92. +69
    -0
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportTaskMapper.xml
  93. +161
    -0
      inspect-main/inspect-main-task/src/main/resources/mapper/task/PatrolResultMapper.xml
  94. +12
    -0
      inspect-main/inspect-main-task/src/main/resources/mapper/task/PatrolTaskResultMainMapper.xml
  95. BIN
      inspect-main/inspect-main-task/src/main/resources/report/logo.png
  96. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/fontTable.xml
  97. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/styles.xml
  98. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/theme1.xml
  99. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/webSettings.xml
  100. +17
    -0
      inspect-main/inspect-main-video/src/main/java/com/inspect/patrol/contants/AccessApiConstant.java

+ 14
- 0
inspect-admin/src/main/java/com/inspect/admin/controller/TokenController.java View File

@ -14,11 +14,16 @@ import com.inspect.system.base.model.LoginUser;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class TokenController {
@Autowired
@ -76,4 +81,13 @@ public class TokenController {
mapApi.put("value", "Bearer " + map.get(TokenConst.ACCESS_TOKEN));
return Response.ok(mapApi, "操作成功");
}
@GetMapping({"/getToken"})
public Response<?> getToken(String username, String password, HttpServletRequest request) {
log.info("getToken username: {}, password: {}", username, password);
String md5Hex = DigestUtils.md5Hex(password);
LoginUser userInfo = this.sysLoginService.login(username, md5Hex, request);
Map<String, Object> map = this.tokenService.createToken(userInfo);
return Response.ok(map, "操作成功");
}
}

+ 2
- 0
inspect-admin/src/main/java/com/inspect/admin/service/SysLoginService.java View File

@ -127,6 +127,8 @@ public class SysLoginService {
String aollowIp = userInfo.getSysUser().getAollowIp();
if(StringUtils.isBlank(aollowIp)) {
throw new ServiceException("用户未绑定登录ip,不允许登录");
} else if (StringUtils.equals("*",aollowIp)) {
logger.info("允许用户任意ip登录");
} else if(!StringUtils.equals(requestIp, aollowIp)) {
try {
SysWarning sysWarning = this.generateLoginErrorLogVo("用户登录ip与绑定ip不同,已禁止登录", Integer.valueOf(2), username);


+ 1
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/RedisConst.java View File

@ -13,4 +13,5 @@ public class RedisConst {
public static final String TASK_RECORD_CNT = "TASK_RECORD_CNT";
public static final String TASK_CURRENT_CODE = "TASK_CURRENT_CODE@";
public static final String IMMEDIATELY_EXEC_TASK_TIME = "IMMEDIATELY_EXEC_TASK_TIME";
public static final String REPORT_PUSH_LOCK = "REPORT_PUSH_LOCK@";//任务完成自动归档+报告推送 防重锁
}

+ 1
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/TokenConst.java View File

@ -15,6 +15,7 @@ public class TokenConst {
public static final String LOGIN_TOKENS = "login_tokens:";
public static final String DOWN_TOKEN = "downToken";
public static final String ACCESS_TOKEN = "access_token";
public static final String TOKEN_TYPE = "token_type";
public static final String EXPIRES_IN = "expires_in";
public static final String ADMIN_FLAG = "admin_flag";
}

inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AnalysisTypeEnum.java → inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AnalysisType.java View File


+ 20
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java View File

@ -0,0 +1,20 @@
package com.inspect.base.core.enums;
import lombok.Getter;
@Getter
public enum DefectLevel {
// 0=无, 1=紧急, 2=严重, 3=一般
NONE(0, "无"),
URGENT(1, ""),
SERIOUS(2, ""),
NORMAL(3, "");
private final Integer code;
private final String info;
DefectLevel(Integer code, String info) {
this.code = code;
this.info = info;
}
}

+ 1
- 1
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/sftp/SftpClient.java View File

@ -113,7 +113,7 @@ public class SftpClient {
ftps.setFileType(FTPSClient.BINARY_FILE_TYPE);
sftpFactory.init();
}
ftps.setControlEncoding("UTF-8");
ftps.enterLocalPassiveMode();
ftps.enterLocalPassiveMode();
ftps.setFileTransferMode(FTPSClient.STREAM_TRANSFER_MODE);


+ 110
- 3
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/utils/HttpClientUtils.java View File

@ -75,6 +75,31 @@ public class HttpClientUtils {
return connection.getInputStream();
}
public static InputStream getStream(String url) throws Exception {
URL realUrl = new URL(url);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
return connection.getInputStream();
}
public static InputStream postStream(String url, String jsonParam) throws Exception {
URL realUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "utf-8");
writer.write(jsonParam);
writer.flush();
writer.close();
return connection.getInputStream();
}
public static URLConnection getConnection(String url, String param) throws Exception {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
@ -126,6 +151,76 @@ public class HttpClientUtils {
return result.toString();
}
public static String get(String url) throws Exception {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
URL realUrl = new URL(url);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
throw e;
} finally {
if (null != in) {
in.close();
}
}
return result.toString();
}
public static String delete(String url) throws Exception {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
HttpURLConnection connection = null;
try {
URL realUrl = new URL(url);
connection = (HttpURLConnection) realUrl.openConnection();
connection.setRequestMethod("DELETE");
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
try {
if (connection != null && connection.getErrorStream() != null) {
BufferedReader errorReader = new BufferedReader(new InputStreamReader(connection.getErrorStream(), "UTF-8"));
String line;
while ((line = errorReader.readLine()) != null) {
result.append(line);
}
errorReader.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
} finally {
if (null != in) {
in.close();
}
if (null != connection) {
connection.disconnect();
}
}
return result.toString();
}
public static String postJson(String requestUrl, String requestPram, String tokenKey, String accessToken) {
OutputStreamWriter outputStreamWriter = null;
BufferedReader bufferedReader = null;
@ -155,11 +250,23 @@ public class HttpClientUtils {
bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8"));
String line;
while ((line = bufferedReader.readLine()) != null) {
responseResult.append(line);
}
while ((line = bufferedReader.readLine()) != null) {
responseResult.append(line);
}
} catch (Exception var18) {
var18.printStackTrace();
try {
if (httpURLConnection != null && httpURLConnection.getErrorStream() != null) {
BufferedReader errorReader = new BufferedReader(new InputStreamReader(httpURLConnection.getErrorStream(), "utf-8"));
String line;
while ((line = errorReader.readLine()) != null) {
responseResult.append(line);
}
errorReader.close();
}
} catch (IOException var19) {
var19.printStackTrace();
}
} finally {
httpURLConnection.disconnect();


+ 4
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/web/domain/AjaxResult.java View File

@ -63,6 +63,10 @@ public class AjaxResult extends HashMap<String, Object> {
return new AjaxResult(code, msg, null);
}
public static AjaxResult error(int code, String msg, Object data) {
return new AjaxResult(code, msg, data);
}
public static AjaxResult fail(Integer code, String msg, Object data) {
return new AjaxResult(code, msg, data);
}


+ 51
- 0
inspect-base/inspect-base-redis/src/main/java/com/inspect/base/redis/service/RedisService.java View File

@ -2,6 +2,7 @@ package com.inspect.base.redis.service;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -11,8 +12,11 @@ import com.inspect.base.core.constant.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
@ -135,6 +139,53 @@ public class RedisService {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 游标分批扫描 key(非阻塞)。
*
* <p>禁止在业务代码里使用 {@link #keys(String)}:KEYS 是 O(N) 的阻塞命令,而 Redis 是单线程的,
* 一次 KEYS 执行期间其它所有客户端的命令都要排队。keyspace 较大时单次 KEYS 需要十几毫秒,
* 若在高频路径上循环调用,会直接把 Redis 及其上游服务拖死(已在生产环境造成过故障)。
* 需要按 pattern 查 key 时一律使用本方法。
*
* @param pattern 匹配模式,例如 TASK_CODE@279@*
* @return 匹配到的 key 集合(SCAN 可能重复返回同一个 key,方法内部已去重)
*/
public Collection<String> scan(String pattern) {
return scan(pattern, 500);
}
/**
* @param count 每批返回的 key 数量提示值(COUNT,非硬性上限)
*/
public Collection<String> scan(String pattern, int count) {
Set<String> keys = new LinkedHashSet<>();
if (pattern == null || pattern.isEmpty()) {
return keys;
}
long start = System.currentTimeMillis();
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(count).build();
redisTemplate.execute((RedisCallback<Void>) connection -> {
try (Cursor<byte[]> cursor = connection.scan(options)) {
while (cursor.hasNext()) {
Object key = redisTemplate.getKeySerializer().deserialize(cursor.next());
if (key != null) {
keys.add(String.valueOf(key));
}
}
} catch (Exception e) {
logger.error("RedisService.scan error, pattern: {}", pattern, e);
}
return null;
});
logger.info("RedisService.scan pattern: {}, size: {}, cost: {} ms", pattern, keys.size(), System.currentTimeMillis() - start);
return keys;
}
/**
* @deprecated KEYS 会阻塞整个 Redis(单线程),生产环境禁止调用,请改用 {@link #scan(String)}。
*/
@Deprecated
public Collection<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}


+ 4
- 3
inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/aspect/PreAuthorizeAspect.java View File

@ -47,10 +47,11 @@ public class PreAuthorizeAspect {
AuthUtil.checkRole(requiresRoles);
}
// TEMP 临时屏蔽 RequiresPermissions 注解
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if (requiresPermissions != null) {
AuthUtil.checkPermission(requiresPermissions);
}
// if (requiresPermissions != null) {
// AuthUtil.checkPermission(requiresPermissions);
// }
}
}

+ 2
- 3
inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/handler/GlobalExceptionHandler.java View File

@ -13,13 +13,12 @@ import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//@RestControllerAdvice
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ -51,7 +50,7 @@ public class GlobalExceptionHandler {
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
return StringUtils.isNotNull(code) ? AjaxResult.error(code, "服务器异常,请稍后再试", e.getMessage()) : AjaxResult.error("服务器异常,请稍后再试", e.getMessage());
}
@ExceptionHandler({RuntimeException.class})


+ 1
- 0
inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/service/TokenService.java View File

@ -53,6 +53,7 @@ public class TokenService {
Map<String, Object> rspMap = new HashMap<>();
rspMap.put(TokenConst.ACCESS_TOKEN, JwtUtils.createToken(claimsMap));
rspMap.put(TokenConst.EXPIRES_IN, expireTime);
rspMap.put(TokenConst.TOKEN_TYPE, "Bearer");
return rspMap;
}


+ 15
- 0
inspect-ivs/src/main/java/com/inspect/ivs/constant/IvsConst.java View File

@ -6,6 +6,7 @@ public class IvsConst {
public static final String URI_MODIFY_PASSWORD = "/users/userPassword/v1.0";
public static final String URI_KEEP_ALIVE = "/common/keepAlive";
public static final String URI_PTZ_CONTROL = "/device/ptzcontrol";
public static final String URI_POSITION_3D = "/device/position3d";
public static final String URI_DEVICE_LIST = "/device/deviceList/v1.0";
public static final String URI_PRESET_SET = "/ptz/presetposition/v1.0";
public static final String URI_START_REAL_PLAY = "/device/startrealplaybyipex";
@ -21,6 +22,20 @@ public class IvsConst {
public static final String URI_CONTROL_RESUME_RECORD = "/playback/resume/v1.0/{playHandle}";
public static final String URI_PLAYBACK_START = "/playback/startplatformplaybackbyip";
public static final String URI_PLAYBACK_STOP = "/playback/stopplatformplaybackbyip/{handle}";
public static final String URI_HLS_SESSIONS = "/hik/nvr/playback/hls/sessions";
public static final String URI_HLS_SESSION = "/hik/nvr/playback/hls/sessions/%s";
public static final String URI_HLS_SESSION_CONTROL = "/hik/nvr/playback/hls/sessions/%s/controls";
public static final String URI_HLS_UI_SESSIONS = "/hik/nvr/playback/hls/ui/sessions";
public static final String URI_HLS_UI_SESSION_CONTROL = "/hik/nvr/playback/hls/ui/sessions/%s/controls";
public static final String URI_UI_CONFIG = "/hik/nvr/playback/hls/ui/config";
public static final String URI_LIVE_START = "/hik/nvr/playback/hls/ui/actions/live/start";
public static final String URI_LIVE_STOP = "/hik/nvr/playback/hls/ui/actions/live/stop";
public static final String URI_LIVE_STATUS = "/hik/nvr/playback/hls/ui/actions/live/status";
public static final String URI_PRESET_LIST_PAGE = "/device/ptzpresetlist/%s/%s";
public static final String URI_PRESET_CREATE = "/hik/nvr/playback/hls/ui/actions/preset/create";
public static final String URI_SNAPSHOT_TASK = "/platform/platformSnapshot/%s/%s";
public static final String URI_SNAPSHOT_DOWNLOAD = "/platform/snapshot/%s";
public static final String URI_CLIP_DOWNLOAD = "/hik/nvr/playback/clips/download";
public static final String URI_PRESET_REMOVE = "/ptz/presetposition/{cameraCode}/{domainCode}/{presetIndex}/v1.0";
public static final String URI_PRESET_LIST = "/device/ptzpresetlist/{cameraCode}/{domainCode}";
public static final String IVS_TOKEN = "IVS_TOKEN";


+ 33
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsControlController.java View File

@ -6,6 +6,7 @@ import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.base.feign.domain.IvsResult;
import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.IvsResourceRetryableDelegate;
import com.inspect.ivs.util.UriUtils;
@ -19,8 +20,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import java.io.IOException;
@ -32,15 +37,18 @@ public class IvsControlController {
private final RedisService redisService;
private final IvsCommonService ivsCommonService;
private final IvsResourceRetryableDelegate ivsResourceRetryableDelegate;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Value("${ivs.version:3800}")
private String version;
public IvsControlController(
RedisService redisService,
IvsCommonService ivsCommonService, IvsResourceRetryableDelegate ivsResourceRetryableDelegate) {
IvsCommonService ivsCommonService, IvsResourceRetryableDelegate ivsResourceRetryableDelegate,
HlsPlaybackComponent hlsPlaybackComponent) {
this.redisService = redisService;
this.ivsCommonService = ivsCommonService;
this.ivsResourceRetryableDelegate = ivsResourceRetryableDelegate;
this.hlsPlaybackComponent = hlsPlaybackComponent;
}
@GetMapping({"ptz"})
@ -155,4 +163,28 @@ public class IvsControlController {
log.debug("[IVS] END");
return ivsPresetListView;
}
@PostMapping({"ptzControl"})
public ResponseEntity<JSONObject> ptzControl(@RequestBody JSONObject request) {
log.debug("[IVS] ptz control page param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_PTZ_CONTROL, request);
}
@PostMapping({"position3d"})
public ResponseEntity<JSONObject> position3d(@RequestBody JSONObject request) {
log.debug("[IVS] position3d page param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_POSITION_3D, request);
}
@GetMapping({"presetList/{deviceCode}/{domainCode}"})
public ResponseEntity<JSONObject> presetListPage(@PathVariable String deviceCode, @PathVariable String domainCode) {
log.debug("[IVS] preset list page deviceCode:{}, domainCode:{}", deviceCode, domainCode);
return this.hlsPlaybackComponent.get(String.format(IvsConst.URI_PRESET_LIST_PAGE, deviceCode, domainCode));
}
@PostMapping({"presetCreate"})
public ResponseEntity<JSONObject> presetCreate(@RequestBody JSONObject request) {
log.debug("[IVS] preset create param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_PRESET_CREATE, request);
}
}

+ 22
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsDeviceController.java View File

@ -9,6 +9,7 @@ import com.inspect.ivs.base.feign.view.SipbDeviceListView;
import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.domain.Temp;
import com.inspect.ivs.domain.TempConfiguration;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.IvsResourceRetryableDelegate;
import com.inspect.ivs.util.UriUtils;
@ -47,12 +48,14 @@ public class IvsDeviceController {
private boolean testMode;
private final IvsCommonService ivsCommonService;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Resource
private IvsResourceRetryableDelegate ivsResourceRetryableDelegate;
public IvsDeviceController(IvsCommonService ivsCommonService) {
public IvsDeviceController(IvsCommonService ivsCommonService, HlsPlaybackComponent hlsPlaybackComponent) {
this.ivsCommonService = ivsCommonService;
this.hlsPlaybackComponent = hlsPlaybackComponent;
}
@ -471,6 +474,24 @@ public class IvsDeviceController {
return ivsCommonService.getResultJson(ivsResourceRetryableDelegate.getLabel(), UriUtils.parse(IvsConst.URI_DEVICE_LIST, ivsDevChanListVo));
}
@GetMapping({"hls/config"})
public ResponseEntity<JSONObject> hlsConfig() {
log.debug("[DEVICE] hls config");
return this.hlsPlaybackComponent.get(IvsConst.URI_UI_CONFIG);
}
@GetMapping({"platformSnapshot/{deviceCode}/{domainCode}"})
public ResponseEntity<JSONObject> createSnapshotTask(@PathVariable String deviceCode, @PathVariable String domainCode) {
log.debug("[DEVICE] create snapshot task deviceCode:{}, domainCode:{}", deviceCode, domainCode);
return this.hlsPlaybackComponent.get(String.format(IvsConst.URI_SNAPSHOT_TASK, deviceCode, domainCode));
}
@GetMapping({"snapshot/{taskId}"})
public ResponseEntity<byte[]> downloadSnapshot(@PathVariable String taskId) {
log.debug("[DEVICE] download snapshot taskId:{}", taskId);
return this.hlsPlaybackComponent.getBinary(String.format(IvsConst.URI_SNAPSHOT_DOWNLOAD, taskId));
}
private static int calculateSum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {


+ 45
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsPlaybackController.java View File

@ -7,6 +7,7 @@ import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.domain.CameraCodeAndEndTime;
import com.inspect.ivs.enums.RtspType;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.PlaybackComponent;
import com.inspect.ivs.service.XinTongComponent;
@ -32,7 +33,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
@ -45,15 +51,17 @@ public class IvsPlaybackController {
private final ZlMediaComponent zlmediaComponent;
private final RedisService redisService;
private final PlaybackComponent playbackComponent;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Value("${rtsp.type:xintong}")
private RtspType rtspType;
private final XinTongComponent xinTongComponent;
public IvsPlaybackController(IvsCommonService ivsCommonService, ZlMediaComponent zlmediaComponent, RedisService redisService, PlaybackComponent playbackComponent, XinTongComponent xinTongComponent) {
public IvsPlaybackController(IvsCommonService ivsCommonService, ZlMediaComponent zlmediaComponent, RedisService redisService, PlaybackComponent playbackComponent, HlsPlaybackComponent hlsPlaybackComponent, XinTongComponent xinTongComponent) {
this.ivsCommonService = ivsCommonService;
this.zlmediaComponent = zlmediaComponent;
this.redisService = redisService;
this.playbackComponent = playbackComponent;
this.hlsPlaybackComponent = hlsPlaybackComponent;
this.xinTongComponent = xinTongComponent;
}
@ -116,6 +124,42 @@ public class IvsPlaybackController {
return Response.ok();
}
@PostMapping({"hls/sessions"})
public ResponseEntity<JSONObject> hlsCreateSession(@RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls create session param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_HLS_SESSIONS, request);
}
@DeleteMapping({"hls/sessions/{sessionId}"})
public ResponseEntity<JSONObject> hlsDeleteSession(@PathVariable String sessionId) {
log.debug("[PLAYBACK] hls delete session sessionId:{}", sessionId);
return this.hlsPlaybackComponent.delete(String.format(IvsConst.URI_HLS_SESSION, sessionId));
}
@GetMapping({"hls/sessions/{sessionId}"})
public ResponseEntity<JSONObject> hlsGetSession(@PathVariable String sessionId) {
log.debug("[PLAYBACK] hls query session sessionId:{}", sessionId);
return this.hlsPlaybackComponent.get(String.format(IvsConst.URI_HLS_SESSION, sessionId));
}
@PostMapping({"hls/sessions/{sessionId}/controls"})
public ResponseEntity<JSONObject> hlsControlSession(@PathVariable String sessionId, @RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls control session sessionId:{}, param:{}", sessionId, request);
return this.hlsPlaybackComponent.post(String.format(IvsConst.URI_HLS_SESSION_CONTROL, sessionId), request);
}
@PostMapping({"hls/ui/sessions"})
public ResponseEntity<JSONObject> hlsCreateUiSession(@RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls ui create session param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_HLS_UI_SESSIONS, request);
}
@PostMapping({"hls/ui/sessions/{sessionId}/controls"})
public ResponseEntity<JSONObject> hlsControlUiSession(@PathVariable String sessionId, @RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls ui control session sessionId:{}, param:{}", sessionId, request);
return this.hlsPlaybackComponent.post(String.format(IvsConst.URI_HLS_UI_SESSION_CONTROL, sessionId), request);
}
public String getLabel() {
return UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY);
}


+ 14
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsRecordController.java View File

@ -1,10 +1,12 @@
package com.inspect.ivs.controller;
import com.alibaba.fastjson.JSONObject;
import com.inspect.base.core.domain.Response;
import com.inspect.base.core.utils.DateUtils;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.ZlMediaComponent;
import com.inspect.ivs.util.UriUtils;
@ -22,7 +24,10 @@ import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -33,11 +38,13 @@ public class IvsRecordController {
private final IvsCommonService ivsCommonService;
private final RedisService redisService;
private final ZlMediaComponent zlmediaComponent;
private final HlsPlaybackComponent hlsPlaybackComponent;
public IvsRecordController(IvsCommonService ivsCommonService, RedisService redisService, ZlMediaComponent zlmediaComponent) {
public IvsRecordController(IvsCommonService ivsCommonService, RedisService redisService, ZlMediaComponent zlmediaComponent, HlsPlaybackComponent hlsPlaybackComponent) {
this.ivsCommonService = ivsCommonService;
this.redisService = redisService;
this.zlmediaComponent = zlmediaComponent;
this.hlsPlaybackComponent = hlsPlaybackComponent;
}
@GetMapping({"start"})
@ -83,6 +90,12 @@ public class IvsRecordController {
return ivsRecStopView;
}
@PostMapping({"clipDownload"})
public ResponseEntity<byte[]> clipDownload(@RequestBody JSONObject request) {
log.debug("[RECORD] clip download param:{}", request);
return this.hlsPlaybackComponent.postBinary(IvsConst.URI_CLIP_DOWNLOAD, request);
}
public String getLabel() {
return UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY);
}


+ 28
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java View File

@ -1,11 +1,13 @@
package com.inspect.ivs.controller;
import com.alibaba.fastjson.JSONObject;
import com.inspect.base.core.domain.Response;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.core.utils.uuid.UUID;
import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.enums.RtspType;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.XinTongComponent;
import com.inspect.ivs.service.ZlMediaComponent;
@ -16,12 +18,16 @@ import com.inspect.ivs.vo.IvsVideoRtspVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.net.URLEncoder;
@RestController
@RequestMapping({"/api/v1/stream"})
@ -30,6 +36,7 @@ public class IvsStreamController {
private final IvsCommonService ivsCommonService;
private final ZlMediaComponent zlMediaComponent;
private final XinTongComponent xinTongComponent;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Value("${ivs.test:false}")
private Boolean bIvsTestMode;
@Value("${rtsp.type:xintong}")
@ -37,10 +44,11 @@ public class IvsStreamController {
@Resource
private RedisService redisService;
public IvsStreamController(IvsCommonService ivsCommonService, ZlMediaComponent zlMediaComponent, XinTongComponent xinTongComponent) {
public IvsStreamController(IvsCommonService ivsCommonService, ZlMediaComponent zlMediaComponent, XinTongComponent xinTongComponent, HlsPlaybackComponent hlsPlaybackComponent) {
this.ivsCommonService = ivsCommonService;
this.zlMediaComponent = zlMediaComponent;
this.xinTongComponent = xinTongComponent;
this.hlsPlaybackComponent = hlsPlaybackComponent;
}
@GetMapping({"start"})
@ -122,4 +130,23 @@ public class IvsStreamController {
return ivsVideoRtspView;
}
@PostMapping({"live/start"})
public ResponseEntity<JSONObject> liveStart(@RequestBody JSONObject request) {
log.debug("[STREAM] live start param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_LIVE_START, request);
}
@PostMapping({"live/stop"})
public ResponseEntity<JSONObject> liveStop(@RequestBody JSONObject request) {
log.debug("[STREAM] live stop param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_LIVE_STOP, request);
}
@GetMapping({"live/status"})
public ResponseEntity<JSONObject> liveStatus(@RequestParam("cameraCode") String cameraCode) throws Exception {
log.debug("[STREAM] live status cameraCode:{}", cameraCode);
String uri = IvsConst.URI_LIVE_STATUS + "?cameraCode=" + URLEncoder.encode(cameraCode, "UTF-8");
return this.hlsPlaybackComponent.get(uri);
}
}

+ 102
- 0
inspect-ivs/src/main/java/com/inspect/ivs/service/HlsPlaybackComponent.java View File

@ -0,0 +1,102 @@
package com.inspect.ivs.service;
import com.alibaba.fastjson.JSONObject;
import com.inspect.ivs.util.WebClientUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientException;
@Component
public class HlsPlaybackComponent {
private static final Logger log = LoggerFactory.getLogger(HlsPlaybackComponent.class);
@Value("${ivs.nvr.hls.url:http://192.168.1.86:28081}")
private String url;
private final WebClient webClient = WebClientUtils.create();
public ResponseEntity<JSONObject> get(String uri) {
log.info("[NVR HLS] GET url: {}", url.concat(uri));
return exchange(webClient.get().uri(url.concat(uri)));
}
public ResponseEntity<JSONObject> post(String uri, Object body) {
log.info("[NVR HLS] POST url: {}, body: {}", url.concat(uri), JSONObject.toJSONString(body));
return exchange(webClient.post().uri(url.concat(uri))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body));
}
public ResponseEntity<JSONObject> delete(String uri) {
log.info("[NVR HLS] DELETE url: {}", url.concat(uri));
return exchange(webClient.delete().uri(url.concat(uri)));
}
public ResponseEntity<byte[]> getBinary(String uri) {
log.info("[NVR HLS] GET binary url: {}", url.concat(uri));
return exchangeBinary(webClient.get().uri(url.concat(uri)));
}
public ResponseEntity<byte[]> postBinary(String uri, Object body) {
log.info("[NVR HLS] POST binary url: {}, body: {}", url.concat(uri), JSONObject.toJSONString(body));
return exchangeBinary(webClient.post().uri(url.concat(uri))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body));
}
private ResponseEntity<JSONObject> exchange(WebClient.RequestHeadersSpec<?> spec) {
assert webClient != null;
try {
return spec.exchangeToMono(response ->
response.bodyToMono(String.class)
.defaultIfEmpty("")
.map(body -> {
int status = response.statusCode().value();
ResponseEntity<JSONObject> entity;
if (body == null || body.trim().isEmpty()) {
entity = new ResponseEntity<>((JSONObject) null, HttpStatus.valueOf(status));
} else {
entity = ResponseEntity.status(status)
.contentType(MediaType.APPLICATION_JSON)
.body(JSONObject.parseObject(body));
}
return entity;
}))
.block();
} catch (WebClientException e) {
log.error("[NVR HLS] request failed, url: {}", url, e);
JSONObject error = new JSONObject();
error.put("code", HttpStatus.BAD_GATEWAY.value());
error.put("message", "NVR HLS服务不可用");
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(error);
}
}
private ResponseEntity<byte[]> exchangeBinary(WebClient.RequestHeadersSpec<?> spec) {
assert webClient != null;
try {
return spec.exchangeToMono(response ->
response.bodyToMono(byte[].class)
.defaultIfEmpty(new byte[0])
.map(bytes -> {
MediaType mediaType = response.headers().contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
return ResponseEntity.status(response.statusCode().value())
.contentType(mediaType)
.body(bytes);
}))
.block();
} catch (WebClientException e) {
log.error("[NVR HLS] request failed, url: {}", url, e);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
.contentType(MediaType.APPLICATION_JSON)
.body(new byte[0]);
}
}
}

+ 3
- 0
inspect-ivs/src/main/resources/bootstrap.yml View File

@ -25,3 +25,6 @@ ivs:
password: Huawei@1234
newPassword: Huawei@12345
address: http://172.21.101.227:18530
nvr:
hls:
url: http://192.168.1.86:28081

+ 26
- 0
inspect-job/src/main/java/com/inspect/job/client/ReportArchiveClient.java View File

@ -0,0 +1,26 @@
package com.inspect.job.client;
import com.inspect.base.core.web.domain.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 巡检报告定时归档客户端(调用 inspect-main 的动环归档按时间窗口接口)。
*
* @author inspect
*/
@FeignClient(name = "inspect-main", contextId = "inspect-report-archive")
public interface ReportArchiveClient {
/**
* 归档 [startTime, endTime) 窗口内“已完成且未归档”的巡检执行:
* 合并生成一份动环报告,报告生成上传后自动推送外部平台;无待归档任务时不生成不推送。
*
* @param startTime 窗口开始时间 yyyy-MM-dd HH:mm:ss(含)
* @param endTime 窗口结束时间 yyyy-MM-dd HH:mm:ss(不含)
*/
@PostMapping({"/resultmain/archiveByTime"})
AjaxResult archiveByTime(@RequestParam("startTime") String startTime,
@RequestParam("endTime") String endTime);
}

+ 15
- 15
inspect-job/src/main/java/com/inspect/job/task/JobMainTask.java View File

@ -35,6 +35,7 @@ import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@ -1173,16 +1174,11 @@ public class JobMainTask {
.isEnable("1")
.build();
List<PatrolPresetPos> presetPosList = taskExecClient.selectPatrolPresetPosList(patrolPresetPos);
PatrolPresetPos presetPos = !presetPosList.isEmpty() ? presetPosList.get(0) :
PatrolPresetPos.builder().presetPosId("1")
.presetPosCode("1")
.channelCode("ffffffffffffffffff#ffffffffffffffffffffffffffffffff")
.channelId("999999")
.patrolPointId(1234567890L)
.channelType("vl")
.isEnable("1")
.eqpBookId("1")
.build();
if (presetPosList.isEmpty()) {
log.info("getInfosByRecord: {} PatrolPresetPos no found", patrolTaskInfo.getDeviceId());
continue;
}
PatrolPresetPos presetPos = presetPosList.get(0);
String deviceCode = "ffffffffffffffffff";
try {
String[] channelCodeList = presetPos.getChannelCode().split(StringUtils.HASHTAG);
@ -2039,12 +2035,14 @@ public class JobMainTask {
List<PatrolTask> patrolTaskControlList = new ArrayList<>();
for (PatrolTask patrolTask : patrolTaskList) {
log.debug("EXTRACT_REDIS_TASK devType: {}", patrolTask.getDevType());
if(StringUtils.isNotEmpty(patrolTask.getDevType())
// 添加可见光任务
if (StringUtils.isNotEmpty(patrolTask.getDevType())
&& patrolTask.getDevType().contains("2")) {
patrolTaskListEx.add(patrolTask);
}
if(StringUtils.isNotEmpty(patrolTask.getDevType())
// 添加无人机/机器狗任务
if (StringUtils.isNotEmpty(patrolTask.getDevType())
&& (patrolTask.getDevType().contains("0") || patrolTask.getDevType().contains("1"))) {
patrolTaskControlList.add(patrolTask);
}
@ -2076,7 +2074,7 @@ public class JobMainTask {
Date date = new Date();
log.info("JobMainTask fixedTimer clear taskInfos: {}", taskInfos.size());
taskInfos.clear();
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE_EX);
Collection<String> redisKeys = redisService.scan(RedisConst.TASK_CODE_EX);
for (String redisKey : redisKeys) {
String[] keywords = StringUtils.split(redisKey, StringUtils.AT);
if (keywords.length == 3) {
@ -2093,8 +2091,10 @@ public class JobMainTask {
@SuppressWarnings({"unused"})
public void execEveryDayTask() {
log.debug("***************************** JobTaskTimer execEveryDayTask *************************************");
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE_EX);
String today = LocalDate.now().toString();
String key = RedisConst.TASK_CODE_EX + today + "*";
Collection<String> redisKeys = redisService.scan(key);
log.info("TodayTask key:{}, size:{}", key, redisKeys.size());
for (String redisKey : redisKeys) {
try {
//redisKey = RedisConst.TASK_CODE + task.getTaskCode() + StringUtils.AT + DateUtils.format(DateUtils.yyyyMMddHHmmss2, task.getFixedStartTime())


+ 81
- 0
inspect-job/src/main/java/com/inspect/job/task/ReportArchiveScheduleTask.java View File

@ -0,0 +1,81 @@
package com.inspect.job.task;
import com.inspect.base.core.web.domain.AjaxResult;
import com.inspect.job.client.ReportArchiveClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 巡检报告定时归档任务(sys_job 在数据库灵活控制)。
* <p>
* 由 sql/sys_job_report_archive.sql 预置两条 sys_job 调用:
* <ul>
* <li>archiveMorning:每日 07:00,归档 前一日16:00 ~ 当日07:00 数据并推送平台;</li>
* <li>archiveEvening:每日 16:00,归档 当日07:00 ~ 当日16:00 数据并推送平台。</li>
* </ul>
* 窗口截止点(07:00 / 16:00)即为报告批次语义,若后续确认新的生成时间点,
* 需同时调整 sys_job 的 cron_expression 与下方 {@link #MORNING_END}/{@link #EVENING_END} 截止点。
*
* @author inspect
*/
@Slf4j
@Component("reportArchiveScheduleTask")
public class ReportArchiveScheduleTask {
/** 数据窗口截止时间:早晨批次 = 当日 07:00 */
private static final LocalTime MORNING_END = LocalTime.of(7, 0, 0);
/** 数据窗口截止时间:下午批次 = 当日 16:00 */
private static final LocalTime EVENING_END = LocalTime.of(16, 0, 0);
private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final ReportArchiveClient reportArchiveClient;
public ReportArchiveScheduleTask(ReportArchiveClient reportArchiveClient) {
this.reportArchiveClient = reportArchiveClient;
}
/**
* sys_job:每日 07:00 归档 前一日16:00 ~ 当日07:00 的巡检数据。
*/
public void archiveMorning() {
LocalDate today = LocalDate.now();
// 前一日 16:00
LocalDateTime start = LocalDateTime.of(today.minusDays(1), EVENING_END);
// 当日 07:00
LocalDateTime end = LocalDateTime.of(today, MORNING_END);
archiveByWindow("早晨批次(前一日16:00~当日07:00)", start, end);
}
/**
* sys_job:每日 16:00 归档 当日07:00 ~ 当日16:00 的巡检数据。
*/
public void archiveEvening() {
LocalDate today = LocalDate.now();
LocalDateTime start = LocalDateTime.of(today, MORNING_END);
LocalDateTime end = LocalDateTime.of(today, EVENING_END);
archiveByWindow("下午批次(当日07:00~当日16:00)", start, end);
}
/**
* 按窗口触发 inspect-main 定时归档;无待归档任务时主服务会跳过(不生成不推送)。
* 调用失败直接抛出异常,由 sys_job_log 记录失败。
*/
private void archiveByWindow(String batchName, LocalDateTime start, LocalDateTime end) {
String startTime = start.format(DATE_TIME);
String endTime = end.format(DATE_TIME);
log.info("[定时归档] {} 开始, 数据窗口: {} ~ {}", batchName, startTime, endTime);
AjaxResult result = reportArchiveClient.archiveByTime(startTime, endTime);
Object code = result == null ? null : result.get(AjaxResult.CODE_TAG);
Object msg = result == null ? null : result.get(AjaxResult.MSG_TAG);
log.info("[定时归档] {} 完成, code={}, msg={}", batchName, code, msg);
if (result == null || !(code instanceof Number) || ((Number) code).intValue() != 200) {
throw new RuntimeException("定时归档调用失败: " + (msg == null ? "无响应" : msg));
}
}
}

+ 64
- 0
inspect-main/inspect-main-api/pom.xml View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.inspect</groupId>
<artifactId>inspect-main</artifactId>
<version>3.4.0</version>
</parent>
<artifactId>inspect-main-api</artifactId>
<version>3.4.0</version>
<name>inspect-main-api</name>
<description>inspect-main-api</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket + STOMP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.inspect</groupId>
<artifactId>inspect-base-core</artifactId>
</dependency>
<dependency>
<groupId>com.inspect</groupId>
<artifactId>inspect-base-redis</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

+ 98
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/config/RabbitMQConfig.java View File

@ -0,0 +1,98 @@
package com.inspect.api.config;
import com.inspect.api.constant.YanyuanConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
/** 告警重试延迟(毫秒),重试队列消息的 TTL */
private static final int ALARM_RETRY_TTL_MS = 30_000;
@Bean
public TopicExchange alarmExchange() {
return new TopicExchange(YanyuanConstants.EXCHANGE, true, false);
}
@Bean("alarmQueue")
public Queue alarmQueue() {
return new Queue(YanyuanConstants.QUEUE, true);
}
@Bean
public Binding alarmBinding() {
return BindingBuilder.bind(alarmQueue()).to(alarmExchange()).with(YanyuanConstants.ROUTING_KEY);
}
// ============================ 告警重试/死信 ============================
@Bean
public TopicExchange alarmRetryExchange() {
return new TopicExchange(YanyuanConstants.ALARM_RETRY_EXCHANGE, true, false);
}
/** 重试队列:消息 TTL 到期后经死信交换机自动投回主队列 */
@Bean("alarmRetryQueue")
public Queue alarmRetryQueue() {
return QueueBuilder.durable(YanyuanConstants.ALARM_RETRY_QUEUE)
.ttl(ALARM_RETRY_TTL_MS)
.deadLetterExchange(YanyuanConstants.EXCHANGE)
.deadLetterRoutingKey(YanyuanConstants.ROUTING_KEY)
.build();
}
@Bean
public Binding alarmRetryBinding() {
return BindingBuilder.bind(alarmRetryQueue())
.to(alarmRetryExchange())
.with(YanyuanConstants.ALARM_RETRY_ROUTING_KEY);
}
@Bean
public TopicExchange alarmDeadExchange() {
return new TopicExchange(YanyuanConstants.ALARM_DEAD_EXCHANGE, true, false);
}
@Bean("alarmDeadQueue")
public Queue alarmDeadQueue() {
return QueueBuilder.durable(YanyuanConstants.ALARM_DEAD_QUEUE).build();
}
@Bean
public Binding alarmDeadBinding() {
return BindingBuilder.bind(alarmDeadQueue())
.to(alarmDeadExchange())
.with(YanyuanConstants.ALARM_DEAD_ROUTING_KEY);
}
// ============================ 巡检报告上传推送 MQ ============================
@Bean
public TopicExchange reportExchange() {
return new TopicExchange(YanyuanConstants.REPORT_EXCHANGE, true, false);
}
@Bean("reportUploadQueue")
public Queue reportUploadQueue() {
return new Queue(YanyuanConstants.REPORT_QUEUE, true);
}
@Bean
public Binding reportUploadBinding() {
return BindingBuilder.bind(reportUploadQueue()).to(reportExchange())
.with(YanyuanConstants.REPORT_ROUTING_KEY);
}
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}

+ 25
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/constant/YanyuanConstants.java View File

@ -0,0 +1,25 @@
package com.inspect.api.constant;
public class YanyuanConstants {
private YanyuanConstants() {}
public static final String EXCHANGE = "alarm.exchange";
public static final String QUEUE = "alarm.queue";
public static final String ROUTING_KEY = "alarm.routing-key";
/** 告警重试(WS发送失败后延迟重试) */
public static final String ALARM_RETRY_EXCHANGE = "alarm.retry.exchange";
public static final String ALARM_RETRY_QUEUE = "alarm.retry.queue";
public static final String ALARM_RETRY_ROUTING_KEY = "alarm.retry.routing-key";
/** 告警死信(重试耗尽后兜底) */
public static final String ALARM_DEAD_EXCHANGE = "alarm.dead.exchange";
public static final String ALARM_DEAD_QUEUE = "alarm.dead.queue";
public static final String ALARM_DEAD_ROUTING_KEY = "alarm.dead.routing-key";
/** 巡检报告上传推送(先上报MQ,再由消费者HTTP上传外部平台) */
public static final String REPORT_EXCHANGE = "report.exchange";
public static final String REPORT_QUEUE = "report.upload.queue";
public static final String REPORT_ROUTING_KEY = "report.upload.routing-key";
}

+ 63
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/controller/AlarmController.java View File

@ -0,0 +1,63 @@
package com.inspect.api.controller;
import com.inspect.api.domain.AlarmMessage;
import com.inspect.api.domain.ApiResult;
import com.inspect.api.domain.AuthTokenData;
import com.inspect.api.enums.AlarmCategory;
import com.inspect.api.enums.MessageType;
import com.inspect.api.enums.SystemCode;
import com.inspect.api.service.AlarmPublisher;
import com.inspect.api.service.ExtAuthService;
import com.inspect.base.core.web.controller.BaseController;
import com.inspect.base.core.web.domain.AjaxResult;
import com.inspect.base.core.web.page.TableDataInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/v1/api/alarm")
public class AlarmController extends BaseController {
private final ExtAuthService extAuthService;
private final AlarmPublisher alarmPublisher;
public AlarmController(ExtAuthService extAuthService, AlarmPublisher alarmPublisher) {
this.extAuthService = extAuthService;
this.alarmPublisher = alarmPublisher;
}
@PostMapping("/history")
@ResponseBody
public TableDataInfo list(AlarmMessage alarmMessage) {
startPage();
List<AlarmMessage> list = new ArrayList<>();
list.add(AlarmMessage.builder()
.messageType(MessageType.ALARM.getCode())
.alarmCategory(AlarmCategory.APPEARANCE_DEFECT.getCode())
.systemCode(SystemCode.PATROL.getCode())
.build());
// List<AlarmMessage> list = jobLogService.selectJobLogList(jobLog);
return getDataTable(list);
}
@GetMapping("/getToken")
public AjaxResult getToken(String clientId, String clientSecret) {
ApiResult<AuthTokenData> result = extAuthService.getAuthToken(clientId, clientSecret);
log.info("Result={}", result);
return AjaxResult.success(result.getData());
}
@PostMapping("/push")
@ResponseBody
public AjaxResult push(@RequestBody Map<String, Object> msg) {
if (msg != null) {
alarmPublisher.publish(msg);
return AjaxResult.success(msg);
}
return AjaxResult.error();
}
}

+ 19
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/controller/PatrolController.java View File

@ -0,0 +1,19 @@
package com.inspect.api.controller;
import com.inspect.base.core.web.controller.BaseController;
import com.inspect.base.core.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/v1/api/patrol")
public class PatrolController extends BaseController {
@GetMapping("/test")
public AjaxResult test() {
log.info("Test success!");
return AjaxResult.success("OK");
}
}

+ 19
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/AlarmMessage.java View File

@ -0,0 +1,19 @@
package com.inspect.api.domain;
import com.inspect.base.core.web.domain.BaseEntity;
import lombok.*;
@ToString
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AlarmMessage extends BaseEntity {
private String messageId;
private String messageType;
// 告警类型: messageType = alarm_push时传入
private String alarmCategory;
private String timestamp;
private String systemCode;
private Object data;
}

+ 12
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/ApiResult.java View File

@ -0,0 +1,12 @@
package com.inspect.api.domain;
import lombok.Data;
@Data
public class ApiResult<T> {
private boolean success;
private T data;
private String hintMessage;
private String type;
private int code;
}

+ 12
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/AuthTokenData.java View File

@ -0,0 +1,12 @@
package com.inspect.api.domain;
import lombok.Data;
@Data
public class AuthTokenData {
private String access_token;
private String token_type;
private String scope;
private String userId;
private String jti;
}

+ 23
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/ReportPushMessage.java View File

@ -0,0 +1,23 @@
package com.inspect.api.domain;
import lombok.Data;
import java.io.Serializable;
/**
* 巡检报告上传推送消息(先上报 report.upload.queue,由消费者异步 HTTP 上传外部平台)
*/
@Data
public class ReportPushMessage implements Serializable {
private static final long serialVersionUID = 1L;
/** 报告ID(inspection_report.line_id) */
private Long reportId;
/** 报告文件路径(SFTP,docx) */
private String filePath;
/** 报告详情(reportInfo JSON 字符串,发布时组装,消费者只负责下载文件并上传) */
private String reportInfo;
}

+ 17
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/enums/AlarmCategory.java View File

@ -0,0 +1,17 @@
package com.inspect.api.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AlarmCategory {
FIRE_SMOKE("fire_smoke", "烟火报警"),
NO_HELMET("no_helmet", "未佩戴安全帽报警"),
NO_UNIFORM("no_uniform", "未穿工装报警"),
METER_OVERRUN("meter_overrun", "红外温度超限报警"),
APPEARANCE_DEFECT("appearance_defect", "外观缺陷告警");
final private String code;
final private String value;
}

+ 14
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/enums/MessageType.java View File

@ -0,0 +1,14 @@
package com.inspect.api.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MessageType {
ALARM("alarm_push", "告警推送"),
REPORT("report_push", "报告推送");
final private String code;
final private String value;
}

+ 15
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/enums/SystemCode.java View File

@ -0,0 +1,15 @@
package com.inspect.api.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum SystemCode {
PE("power&environment", "动环"),
FIREFIGHTING("firefighting", "消防"),
PATROL("patrol", "巡检");
final private String code;
final private String value;
}

+ 15
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/props/AppProperties.java View File

@ -0,0 +1,15 @@
package com.inspect.api.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "yanyuan.auth")
public class AppProperties {
private String baseUrl;
private String wsUrl;
private String clientId;
private String clientSecret;
}

+ 78
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/AlarmConsumer.java View File

@ -0,0 +1,78 @@
package com.inspect.api.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inspect.api.constant.YanyuanConstants;
import com.inspect.api.websocket.AlarmWsConnection;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import java.util.Map;
@Slf4j
@Component
public class AlarmConsumer {
/** 最多重试次数(总尝试次数 = MAX_RETRY + 1) */
private static final int MAX_RETRY = 2;
private static final String RETRY_COUNT_HEADER = "alarm-retry-count";
private final AlarmWsConnection wsConnection;
private final ObjectMapper objectMapper;
private final RedisService redisService;
private final RabbitTemplate rabbitTemplate;
public AlarmConsumer(AlarmWsConnection wsConnection, ObjectMapper objectMapper,
RedisService redisService, RabbitTemplate rabbitTemplate) {
this.wsConnection = wsConnection;
this.objectMapper = objectMapper;
this.redisService = redisService;
this.rabbitTemplate = rabbitTemplate;
}
@RabbitListener(queues = "#{alarmQueue.name}")
public void onAlarm(Message message) {
try {
Map<String, Object> msg = objectMapper.readValue(message.getBody(),
new TypeReference<Map<String, Object>>() {});
String staticCode = (String) redisService.redisTemplate.opsForValue().get("sys_config:STATION_CODE");
if (StringUtils.isNotEmpty(staticCode)) {
msg.put("stationCode", staticCode);
}
String json = objectMapper.writeValueAsString(msg);
boolean sent = wsConnection.send(json);
if (sent) {
log.info("Alarm sent to WS: msg={}", msg);
} else {
retryOrDeadLetter(message);
}
} catch (Exception e) {
log.error("Alarm consumer error: {}", e.getMessage(), e);
retryOrDeadLetter(message);
}
}
private void retryOrDeadLetter(Message message) {
int retryCount = getRetryCount(message);
if (retryCount < MAX_RETRY) {
message.getMessageProperties().setHeader(RETRY_COUNT_HEADER, retryCount + 1);
rabbitTemplate.send(YanyuanConstants.ALARM_RETRY_EXCHANGE,
YanyuanConstants.ALARM_RETRY_ROUTING_KEY, message);
log.warn("Alarm WS send failed, retry {}/{} scheduled", retryCount + 1, MAX_RETRY);
} else {
rabbitTemplate.send(YanyuanConstants.ALARM_DEAD_EXCHANGE,
YanyuanConstants.ALARM_DEAD_ROUTING_KEY, message);
log.error("Alarm WS send failed after {} retries, moved to dead letter queue", MAX_RETRY);
}
}
private int getRetryCount(Message message) {
Object count = message.getMessageProperties().getHeader(RETRY_COUNT_HEADER);
return count instanceof Number ? ((Number) count).intValue() : 0;
}
}

+ 25
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/AlarmPublisher.java View File

@ -0,0 +1,25 @@
package com.inspect.api.service;
import com.inspect.api.constant.YanyuanConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class AlarmPublisher {
private final RabbitTemplate rabbitTemplate;
public AlarmPublisher(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void publish(Object msg) {
try {
log.info("Alert published: msg={}", msg);
rabbitTemplate.convertAndSend(YanyuanConstants.EXCHANGE, YanyuanConstants.ROUTING_KEY, msg);
} catch (Exception e) {
log.error("Failed to publish alert: {}", e.getMessage(), e);
}
}
}

+ 41
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ExtAuthService.java View File

@ -0,0 +1,41 @@
package com.inspect.api.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inspect.api.domain.ApiResult;
import com.inspect.api.domain.AuthTokenData;
import com.inspect.api.props.AppProperties;
import com.inspect.base.core.utils.HttpClientUtils;
import org.springframework.stereotype.Service;
@Service
public class ExtAuthService {
private static final String TOKEN_HEADER = "joinbright-token";
private final ObjectMapper objectMapper;
private final AppProperties appProperties;
public ExtAuthService(ObjectMapper objectMapper, AppProperties appProperties) {
this.objectMapper = objectMapper;
this.appProperties = appProperties;
}
public ApiResult<AuthTokenData> getAuthToken() {
return getAuthToken(appProperties.getClientId(), appProperties.getClientSecret());
}
public ApiResult<AuthTokenData> getAuthToken(String clientId, String clientSecret) {
String url = appProperties.getBaseUrl() + "/ext_auth/getAuthToken";
String param = "clientId=" + clientId + "&clientSecret=" + clientSecret;
return executeGet(url, param, new TypeReference<ApiResult<AuthTokenData>>() {});
}
private <T> ApiResult<T> executeGet(String url, String param, TypeReference<ApiResult<T>> typeRef) {
try {
String json = HttpClientUtils.get(url, param);
return objectMapper.readValue(json, typeRef);
} catch (Exception e) {
throw new RuntimeException("HTTP GET failed: " + url, e);
}
}
}

+ 34
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportPushPublisher.java View File

@ -0,0 +1,34 @@
package com.inspect.api.service;
import com.inspect.api.constant.YanyuanConstants;
import com.inspect.api.domain.ReportPushMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
/**
* 巡检报告上传消息发布者:报告生成后先发布到 report.exchange,
* 由 {@link ReportUploadConsumer} 异步 HTTP 上传到外部平台。
*/
@Slf4j
@Service
public class ReportPushPublisher {
private final RabbitTemplate rabbitTemplate;
public ReportPushPublisher(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void publish(ReportPushMessage message) {
try {
rabbitTemplate.convertAndSend(YanyuanConstants.REPORT_EXCHANGE,
YanyuanConstants.REPORT_ROUTING_KEY, message);
log.info("[REPORT_PUSH_MQ] 报告上传消息已发布: reportId={}, filePath={}",
message.getReportId(), message.getFilePath());
} catch (Exception e) {
log.error("[REPORT_PUSH_MQ] 报告上传消息发布失败: reportId={}, err: {}",
message.getReportId(), e.getMessage(), e);
}
}
}

+ 150
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportUploadConsumer.java View File

@ -0,0 +1,150 @@
package com.inspect.api.service;
import com.alibaba.fastjson.JSONObject;
import com.inspect.api.domain.ReportPushMessage;
import com.inspect.base.core.sftp.SftpClient;
import com.inspect.base.core.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* 巡检报告上传消费者:从 report.upload.queue 取消息,下载报告 docx,
* 通过 {@link ReportUploadService} HTTP 上传到外部平台(POST /jb-nessp-external/api/uploadReport)。
* <p>
* 重试策略:上传失败就在当前消费里重试,最多 {@code inspect.report-push.mq-max-retries} 次(含首次);
* 仍然失败则正常返回(消息 ack 出队 = 丢弃),<b>不抛异常</b>,避免 MQ 自动重投递造成无限重投递。
*/
@Slf4j
@Component
public class ReportUploadConsumer {
/** 两次尝试之间的间隔(毫秒) */
private static final long RETRY_INTERVAL_MILLIS = 3000;
/** 上传失败最大尝试次数(含首次),超过后直接丢弃消息 */
@Value("${inspect.report-push.mq-max-retries:3}")
private int maxRetries;
/** 报告文件(SFTP)下载重试次数/间隔(毫秒),防止异步上传尚未完成 */
@Value("${inspect.report-push.download-retry-times:3}")
private int downloadRetryTimes;
@Value("${inspect.report-push.download-retry-interval-millis:1000}")
private long downloadRetryIntervalMillis;
private final ReportUploadService reportUploadService;
private final SftpClient sftpClient;
public ReportUploadConsumer(ReportUploadService reportUploadService, SftpClient sftpClient) {
this.reportUploadService = reportUploadService;
this.sftpClient = sftpClient;
}
@RabbitListener(queues = "#{reportUploadQueue.name}")
public void onReport(ReportPushMessage message) {
if (message == null || message.getReportId() == null) {
log.warn("[REPORT_PUSH_MQ] 报告上传消息为空,丢弃");
return;
}
int maxAttempts = Math.max(1, maxRetries);
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
if (upload(message)) {
log.info("[REPORT_PUSH_MQ] 报告上传成功: reportId={}, 第{}/{}次尝试",
message.getReportId(), attempt, maxAttempts);
return;
}
log.warn("[REPORT_PUSH_MQ] 报告上传失败, 第{}/{}次尝试: reportId={}",
attempt, maxAttempts, message.getReportId());
} catch (Exception e) {
log.warn("[REPORT_PUSH_MQ] 报告上传异常, 第{}/{}次尝试: reportId={}, err={}",
attempt, maxAttempts, message.getReportId(), e.getMessage());
}
if (attempt < maxAttempts) {
try {
Thread.sleep(RETRY_INTERVAL_MILLIS);
} catch (InterruptedException ie) {
// 应用停机等中断场景:抛出让消息重回队列,避免丢消息
Thread.currentThread().interrupt();
throw new RuntimeException("report upload retry interrupted", ie);
}
}
}
// 超过最大尝试次数:正常返回即 ack 出队,消息被丢弃,不会再被投递
log.error("[REPORT_PUSH_MQ] 报告上传超过最大尝试次数({}), 丢弃消息: reportId={}, filePath={}",
maxAttempts, message.getReportId(), message.getFilePath());
}
/**
* 下载报告 docx + HTTP 上传(reportInfo 由发布方组装好随消息传递)
*/
private boolean upload(ReportPushMessage message) throws Exception {
String filePath = message.getFilePath();
if (StringUtils.isEmpty(filePath)) {
log.warn("[REPORT_PUSH_MQ] 报告文件路径为空, 丢弃: reportId={}", message.getReportId());
return true;
}
byte[] fileBytes = downloadFromSftp(filePath);
if (fileBytes == null || fileBytes.length == 0) {
log.warn("[REPORT_PUSH_MQ] 下载报告文件失败: {}", filePath);
return false;
}
JSONObject reportInfo = JSONObject.parseObject(message.getReportInfo());
if (reportInfo == null) {
log.warn("[REPORT_PUSH_MQ] reportInfo 为空, 丢弃: reportId={}", message.getReportId());
return true;
}
String fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
boolean ok = reportUploadService.uploadReport(fileBytes, fileName, reportInfo);
log.info("[REPORT_PUSH_MQ] 报告上传结果: {}, reportId={}, fileName={}",
ok, message.getReportId(), fileName);
return ok;
}
/**
* 从 SFTP 下载报告文件(导出为异步上传,下载失败自动重试等待)
*/
private byte[] downloadFromSftp(String filePath) {
for (int i = 0; i < Math.max(1, downloadRetryTimes); i++) {
try {
final byte[][] holder = new byte[1][];
sftpClient.downLoad(filePath, inputStream -> holder[0] = readAll(inputStream));
if (holder[0] != null && holder[0].length > 0) {
return holder[0];
}
log.warn("[REPORT_PUSH_MQ] 报告文件尚未就绪, 重试 {}/{}: {}", i + 1, downloadRetryTimes, filePath);
} catch (Exception e) {
log.warn("[REPORT_PUSH_MQ] 下载报告文件异常, 重试 {}/{}: {}, err: {}",
i + 1, downloadRetryTimes, filePath, e.getMessage());
}
if (i < Math.max(1, downloadRetryTimes) - 1 && downloadRetryIntervalMillis > 0) {
try {
Thread.sleep(downloadRetryIntervalMillis);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
break;
}
}
}
return null;
}
private byte[] readAll(InputStream inputStream) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int n;
try {
while ((n = inputStream.read(buf)) != -1) {
bos.write(buf, 0, n);
}
} finally {
inputStream.close();
}
return bos.toByteArray();
}
}

+ 184
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportUploadService.java View File

@ -0,0 +1,184 @@
package com.inspect.api.service;
import com.alibaba.fastjson.JSONObject;
import com.inspect.api.domain.ApiResult;
import com.inspect.api.domain.AuthTokenData;
import com.inspect.base.core.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* 巡检报告上传服务(外部平台《巡检报告接口文档 v1.0.0》)
* <p>
* 1. Joinbright-Token 复用 {@link ExtAuthService}(GET /ext_auth/getAuthToken,配置 yanyuan.auth.*)
* 2. POST {upload-url} 上传巡检报告:
* - 请求头携带 joinbright-token
* - multipart/form-data:file(报告文件) + reportInfo(报告详情 JSON 字符串)
*/
@Slf4j
@Service
public class ReportUploadService {
/** Token 请求头名称(外部平台固定) */
private static final String TOKEN_HEADER = "joinbright-token";
/** 是否启用报告上传推送 */
@Value("${inspect.report-push.enabled:true}")
private boolean enabled;
/** 巡检报告上传接口完整地址 */
@Value("${inspect.report-push.upload-url:http://192.168.129.2:32799/jb-gateway/jb-nessp-external/api/uploadReport}")
private String uploadUrl;
/** http 连接/读取超时(秒) */
@Value("${inspect.report-push.http-timeout-seconds:60}")
private int httpTimeoutSeconds;
private final ExtAuthService extAuthService;
public ReportUploadService(ExtAuthService extAuthService) {
this.extAuthService = extAuthService;
}
/**
* 获取 Joinbright-Token
*/
public String getJoinbrightToken() {
try {
ApiResult<AuthTokenData> result = extAuthService.getAuthToken();
if (result == null || !result.isSuccess() || result.getData() == null) {
log.error("[REPORT-PUSH] 获取 Joinbright-Token 失败: {}", result);
return null;
}
String token = result.getData().getAccess_token();
log.info("[REPORT-PUSH] 获取 Joinbright-Token 成功: {}...",
StringUtils.isEmpty(token) ? "" : token.substring(0, Math.min(12, token.length())));
return token;
} catch (Exception e) {
log.error("[REPORT-PUSH] 获取 Joinbright-Token 异常", e);
return null;
}
}
/**
* 上传巡检报告文件及元数据到外部平台
*
* @param fileBytes 报告文件字节(docx)
* @param fileName 报告文件名(含扩展名)
* @param reportInfo 报告详情对象(序列化为 JSON 字符串传递)
* @return 是否上传成功
*/
public boolean uploadReport(byte[] fileBytes, String fileName, JSONObject reportInfo) {
if (!enabled) {
log.warn("[REPORT-PUSH] 报告上传未启用(inspect.report-push.enabled=false)");
return false;
}
if (fileBytes == null || fileBytes.length == 0) {
log.warn("[REPORT-PUSH] 报告文件为空, 无法上传");
return false;
}
if (reportInfo == null) {
log.warn("[REPORT-PUSH] reportInfo 为空, 无法上传");
return false;
}
// 上传失败时(token可能失效/超时)重新获取token重试一次
String token = getJoinbrightToken();
boolean ok = doUpload(token, fileBytes, fileName, reportInfo);
if (!ok && StringUtils.isNotEmpty(token)) {
log.warn("[REPORT-PUSH] 首次上传失败, 重新获取token后重试");
ok = doUpload(getJoinbrightToken(), fileBytes, fileName, reportInfo);
}
return ok;
}
private boolean doUpload(String token, byte[] fileBytes, String fileName, JSONObject reportInfo) {
if (StringUtils.isEmpty(token)) {
log.error("[REPORT-PUSH] Joinbright-Token 为空, 无法上传");
return false;
}
String boundary = "----InspectReportBoundary" + System.currentTimeMillis();
HttpURLConnection conn = null;
try {
URL url = new URL(uploadUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(httpTimeoutSeconds * 1000);
conn.setReadTimeout(httpTimeoutSeconds * 1000);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty(TOKEN_HEADER, token);
String reportInfoJson = reportInfo.toJSONString();
try (DataOutputStream out = new DataOutputStream(conn.getOutputStream())) {
// reportInfo 字段
out.writeBytes("--" + boundary + "\r\n");
out.writeBytes("Content-Disposition: form-data; name=\"reportInfo\"\r\n");
out.writeBytes("Content-Type: text/plain; charset=UTF-8\r\n\r\n");
out.write(reportInfoJson.getBytes("UTF-8"));
out.writeBytes("\r\n");
// file 字段
out.write(("--" + boundary + "\r\n").getBytes(StandardCharsets.UTF_8));
String fileHeader = "Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n";
out.write(fileHeader.getBytes(StandardCharsets.UTF_8));
out.writeBytes("Content-Type: application/octet-stream\r\n\r\n");
try (InputStream in = new ByteArrayInputStream(fileBytes)) {
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) != -1) {
out.write(buf, 0, n);
}
}
out.writeBytes("\r\n");
out.writeBytes("--" + boundary + "--\r\n");
out.flush();
}
int responseCode = conn.getResponseCode();
String response = readResponse(conn, responseCode);
log.info("[REPORT-PUSH] 上传响应 code: {}, body: {}", responseCode, response);
if (responseCode == 200) {
JSONObject resp = JSONObject.parseObject(response);
if (resp != null && Boolean.TRUE.equals(resp.getBoolean("success"))) {
return true;
}
log.error("[REPORT-PUSH] 上传业务失败: {}", response);
} else {
log.error("[REPORT-PUSH] 上传HTTP失败: {}, body: {}", responseCode, response);
}
return false;
} catch (Exception e) {
log.error("[REPORT-PUSH] 报告上传异常, url: {}", uploadUrl, e);
return false;
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private String readResponse(HttpURLConnection conn, int responseCode) throws Exception {
InputStream stream = responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream();
if (stream == null) {
return "";
}
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
}
return result.toString();
}
}

+ 133
- 0
inspect-main/inspect-main-api/src/main/java/com/inspect/api/websocket/AlarmWsConnection.java View File

@ -0,0 +1,133 @@
package com.inspect.api.websocket;
import com.inspect.api.domain.ApiResult;
import com.inspect.api.domain.AuthTokenData;
import com.inspect.api.props.AppProperties;
import com.inspect.api.service.ExtAuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@Component
public class AlarmWsConnection {
private final ExtAuthService extAuthService;
private final AppProperties appProperties;
private final StandardWebSocketClient wsClient;
private final AtomicReference<WebSocketSession> sessionRef = new AtomicReference<>();
private final AtomicInteger reconnectBackoff = new AtomicInteger(5);
private volatile boolean connecting = false;
public AlarmWsConnection(ExtAuthService extAuthService, AppProperties appProperties) {
this.extAuthService = extAuthService;
this.appProperties = appProperties;
this.wsClient = new StandardWebSocketClient();
}
public void connect() {
if (connecting) {
return;
}
connecting = true;
try {
ApiResult<AuthTokenData> result = extAuthService.getAuthToken();
if (!result.isSuccess() || result.getData() == null) {
log.error("Alarm WS: failed to get auth token, retry in {}s", reconnectBackoff.get());
connecting = false;
return;
}
String token = result.getData().getAccess_token();
String wsUrl = appProperties.getWsUrl() + "?Joinbright-Token=" + token;
wsClient.doHandshake(new AlarmWsHandler(), wsUrl)
.addCallback(
session -> {
sessionRef.set(session);
reconnectBackoff.set(5);
connecting = false;
log.info("Alarm WS connected: {}", session.getId());
},
ex -> {
connecting = false;
log.error("Alarm WS handshake failed: {}", ex.getMessage());
}
);
} catch (Exception e) {
connecting = false;
log.error("Alarm WS connect error: {}", e.getMessage());
}
}
@Scheduled(fixedDelay = 1000)
public void reconnect() {
WebSocketSession session = sessionRef.get();
if (session != null && session.isOpen()) {
return;
}
if (connecting) {
return;
}
int delay = reconnectBackoff.get();
log.info("Alarm WS reconnecting in {}s", delay);
try {
Thread.sleep(delay * 1000L);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
return;
}
int nextDelay = Math.min(delay * 2, 60);
reconnectBackoff.set(nextDelay);
connect();
}
public boolean send(String message) {
WebSocketSession session = sessionRef.get();
if (session == null || !session.isOpen()) {
log.warn("Alarm WS not connected, message dropped: {}", message);
return false;
}
try {
synchronized (session) {
session.sendMessage(new TextMessage(message));
}
return true;
} catch (Exception e) {
log.error("Alarm WS send failed: {}", e.getMessage());
return false;
}
}
public boolean isConnected() {
WebSocketSession session = sessionRef.get();
return session != null && session.isOpen();
}
private class AlarmWsHandler extends TextWebSocketHandler {
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
log.warn("Alarm WS closed: {}", status);
sessionRef.compareAndSet(session, null);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
log.info("Alarm WS received: {}", message.getPayload());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable ex) {
log.error("Alarm WS transport error: {}", ex.getMessage());
sessionRef.compareAndSet(session, null);
}
}
}

+ 4
- 0
inspect-main/inspect-main-start/pom.xml View File

@ -66,6 +66,10 @@
<groupId>com.inspect</groupId>
<artifactId>inspect-main-task-exec</artifactId>
</dependency>
<dependency>
<groupId>com.inspect</groupId>
<artifactId>inspect-main-api</artifactId>
</dependency>
</dependencies>
<build>


+ 1
- 1
inspect-main/inspect-main-start/src/main/java/com/inspect/InspectStartApplication.java View File

@ -18,7 +18,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
@EnableRyFeignClients
@EnableDiscoveryClient
@SpringBootApplication
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableAsync
public class InspectStartApplication {
private final static Logger log = LoggerFactory.getLogger(InspectStartApplication.class);


+ 40
- 1
inspect-main/inspect-main-start/src/main/resources/application.yml View File

@ -64,4 +64,43 @@ sftp:
port: 21
username: ftpuser
password: atia2018
isImplicit: 1
isImplicit: 1
# 盐源外部平台认证/推送(复用 inspect-main-api 的 ExtAuthService / AlarmWsConnection)
# client-id/client-secret 为现场认证凭据,请按现场配置填写(可在nacos配置中心覆盖)
yanyuan:
auth:
base-url: http://192.168.129.2:32799/jb-gateway/jb-base
ws-url: ws://192.168.129.2:32799/jb-gateway/jb-base/jb-base-socket
client-id: ""
client-secret: ""
# 任务巡检完成后自动归档 + 生成报告 + 推送(先上报 RabbitMQ,再由消费者 HTTP 上传外部平台)
# 见《盐源新能源智慧感知平台_巡检报告接口文档 v1.0.0》:POST /jb-nessp-external/api/uploadReport
inspect:
report-push:
# 是否启用自动归档+推送
enabled: true
# 巡检报告上传接口完整地址
upload-url: http://192.168.129.2:32799/jb-gateway/jb-nessp-external/api/uploadReport
# http 连接/读取超时(秒)
http-timeout-seconds: 60
# 自动归档审核人(报告发起人 username 为空时的兜底)
check-person: admin
# 自动归档巡视结论
task-result: 巡检完成,自动归档
# 任务状态更新事务提交等待时间(毫秒)
after-commit-delay-millis: 2000
# 归档防重锁过期时间(秒)
lock-expire-seconds: 86400
# 上传报告的场站ID(为空时取 basedata_station.station_no)
station-id: ""
# 上传报告的巡检类型(风机巡检/光伏巡检/线路巡检/山火巡检/机器人巡检;为空时按任务预置位类型推断)
inspect-type: ""
# 上传报告的巡检设备名称(机库名称、机器人名称、摄像头名称,可选)
inspect-device-name: ""
# 报告文件(SFTP)下载重试次数/间隔(毫秒)
download-retry-times: 10
download-retry-interval-millis: 1000
# 上传失败 MQ 重投递最大次数(超过后丢弃,避免无限重试)
mq-max-retries: 3

+ 5
- 5
inspect-main/inspect-main-start/src/main/resources/bootstrap.yaml View File

@ -6,15 +6,15 @@ server:
spring:
application:
# 应用名称
name: dliip-patrol
name: inspect-main
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
server-addr: 192.168.0.115:8848
namespace: 69445dc4-9c24-4814-ac93-ff14f1438c36
server-addr: http://192.168.1.116:8848
namespace: cf5b4192-f657-4012-b158-566325bae171
#server-addr: 192.168.0.114:8848
#namespace: 744b9c6a-5855-44cf-b537-808c0166d8f5
config:
@ -26,10 +26,10 @@ spring:
group: DEFAULT_GROUP
# 共享配置
shared-configs:
- dliip-patrol-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
- inspect-main-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
#dev-llc
# namespace: 6b358531-5084-4f9c-84f2-f732d0b2af39
main:
allow-bean-definition-overriding: true
allow-circular-references: true
allow-circular-references: true

+ 39
- 19
inspect-main/inspect-main-task-exec/src/main/java/com/inspect/exec/controller/PatrolTaskExecController.java View File

@ -546,7 +546,7 @@ public class PatrolTaskExecController extends BaseController {
task.setFixedStartTime(DateUtils.parse(DateUtils.yyyyMMddHHmmss2, (DateUtils.format(DateUtils.yyyyMMdd2, new Date()) + " " + cycleTimes[i])));
final String taskType = "CYCLE-BY-WEEK";
parseTaskToRedis(taskType, task, null);
parseTaskToRedis(taskType, task);
}
} else if (isCycleTaskByMonth(task)) {
String[] monthList = task.getCycleMonth().split(StringUtils.COMMA);
@ -559,7 +559,7 @@ public class PatrolTaskExecController extends BaseController {
task.setFixedStartTime(DateUtils.parse(DateUtils.yyyyMMddHHmmss2, (DateUtils.format(DateUtils.yyyyMMdd2, new Date()) + " " + cycleTimes[i])));
final String taskType = "CYCLE-BY-MONTH";
parseTaskToRedis(taskType, task, null);
parseTaskToRedis(taskType, task);
}
}
} else if (isInterTask(task)) {
@ -571,11 +571,13 @@ public class PatrolTaskExecController extends BaseController {
if (intervalNumber > 0) {
List<Date> exeTimes = getExecTimes(task.getIntervalExecuteTime(), DateUtils.format(DateUtils.yyyyMMddHHmmss2, task.getIntervalStartTime()), DateUtils.format(DateUtils.yyyyMMddHHmmss2, task.getIntervalEndTime()), intervalNumber);
// List<Date> exeTimes = getExecTimesOld(task.getIntervalExecuteTime(), intervalNumber);
// 每个任务的策略 key 只清理一次(原实现在每个执行时刻都做一次 KEYS 全库扫描)
cleanTaskKeysAfter(task, task.getIntervalEndTime());
for (Date exeTime : exeTimes) {
logger.debug("[TASK] {}, isInterTaskByHour exeTime: {}", task.getTaskCode(), DateUtils.format(DateUtils.yyyyMMddHHmmss2, exeTime));
task.setFixedStartTime(exeTime);
final String taskType = "INTER-BY-HOUR";
parseTaskToRedis(taskType, task, task.getIntervalEndTime());
parseTaskToRedis(taskType, task);
}
} else {
log.info("[TASK] isInterTaskByHour intervalNumber error: {}", intervalNumber);
@ -589,10 +591,15 @@ public class PatrolTaskExecController extends BaseController {
List<Date> exeTimes = getExecTimesByMinute(task.getIntervalExecuteTime(), DateUtils.format(DateUtils.yyyyMMddHHmmss2, task.getIntervalStartTime()), endTime, intervalNumber);
if (exeTimes.size() > 0) {
// Date date = exeTimes.get(exeTimes.size() - 1);
cleanTaskKeysAfter(task, task.getIntervalEndTime());
for (Date exeTime : exeTimes) {
// 晚于 intervalEndTime 的时刻不生成策略 key(保持"只保留 finalDate 之内的 key"的原意)
if (task.getIntervalEndTime() != null && exeTime.after(task.getIntervalEndTime())) {
continue;
}
task.setFixedStartTime(exeTime);
final String taskType = "INTER-BY-MINUTE";
parseTaskToRedis(taskType, task, task.getIntervalEndTime());
parseTaskToRedis(taskType, task);
}
}
} else {
@ -607,7 +614,7 @@ public class PatrolTaskExecController extends BaseController {
String time = intervalExecuteTime[0] + ":" + DateUtils.getMinuteInt() + ":" + intervalExecuteTime[2];
task.setFixedStartTime(DateUtils.parse(DateUtils.yyyyMMddHHmmss2, (DateUtils.format(DateUtils.yyyyMMdd2, new Date()) + " " + time)));
final String taskType = "INTER-BY-DATE";
parseTaskToRedis(taskType, task, null);
parseTaskToRedis(taskType, task);
}
}
}
@ -619,7 +626,7 @@ public class PatrolTaskExecController extends BaseController {
}
}
private void parseTaskToRedis(final String taskType, PatrolTask task, Date finalDate) {
private void parseTaskToRedis(final String taskType, PatrolTask task) {
if (StringUtils.isNotEmpty(task.getDevNo())) {
String[] devNos = task.getDevNo().split(StringUtils.COMMA);
List<String> devNoList = new ArrayList<>();
@ -646,18 +653,6 @@ public class PatrolTaskExecController extends BaseController {
if (taskExecRecord == null) {
List<PatrolTask> patrolTasks = getPatrolTasks(task);
if (!patrolTasks.isEmpty()) {
if(finalDate != null) {
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE + task.getTaskCode() + StringUtils.AT + "*");
for (String redisKey : redisKeys) {
String[] keywords = StringUtils.split(redisKey, StringUtils.AT);
if (keywords.length == 3) {
String fixedStartTime = keywords[2];
if (DateUtils.parse(DateUtils.yyyyMMddHHmmss2, fixedStartTime).after(finalDate)) {
redisService.deleteObject(redisKey);
}
}
}
}
logger.debug(Color.GREEN + "[TASK] TYPE: {}, CYCLE BY WEEK key: {}, patrolId: {}" + Color.END, taskType, key, taskPatrolledId);
redisService.setCacheObject(key, JSONArray.toJSONString(patrolTasks));
}
@ -667,6 +662,31 @@ public class PatrolTaskExecController extends BaseController {
}
}
/**
* 清理该任务下"计划执行时间晚于 finalDate"的策略 key。
*
* <p>历史实现放在 parseTaskToRedis 里,而 parseTaskToRedis 是按"每个执行时刻"调用的,
* 于是一次 makeCurrentDayTask 会对 Redis 发起成百上千次 KEYS 全库扫描(TASK_CODE@taskCode@*),
* 单线程 Redis 被阻塞,导致所有业务请求排队、tomcat 线程池被占满、容器健康检查连续超时。
* 现在改为:① 用 SCAN 替代 KEYS(不阻塞其它客户端);② 每个任务只清理一次。
*/
private void cleanTaskKeysAfter(PatrolTask task, Date finalDate) {
if (task == null || finalDate == null || StringUtils.isEmpty(task.getTaskCode())) {
return;
}
Collection<String> redisKeys = redisService.scan(RedisConst.TASK_CODE + task.getTaskCode() + StringUtils.AT + "*");
for (String redisKey : redisKeys) {
String[] keywords = StringUtils.split(redisKey, StringUtils.AT);
if (keywords.length == 3) {
Date fixedStartTime = DateUtils.parse(DateUtils.yyyyMMddHHmmss2, keywords[2]);
if (fixedStartTime != null && fixedStartTime.after(finalDate)) {
redisService.deleteObject(redisKey);
}
}
}
}
private List<Date> getExecTimesOld(String exeTime, int num) {
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@ -795,7 +815,7 @@ public class PatrolTaskExecController extends BaseController {
// this.log.info(" 【当前正在执行的任务:{}, 其它任务不能执行!!!】", taskCodeStr);
// } else {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (String key : this.redisService.keys(RedisConst.TASK_CODE_EX)) {
for (String key : this.redisService.scan(RedisConst.TASK_CODE_EX)) {
String fixedStartTime = key.split(StringUtils.AT)[2];
long currentMinutes = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis());
long fixedStartMinutes = TimeUnit.MILLISECONDS.toMinutes(sdf.parse(fixedStartTime).getTime());


+ 15
- 1
inspect-main/inspect-main-task/pom.xml View File

@ -13,6 +13,11 @@
<artifactId>inspect-main-task</artifactId>
<version>3.4.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -63,10 +68,13 @@
</dependency>
<!-- POI 流式处理 -->
<!-- 版本必须与运行环境一致:base-core/statistics 均走根 pom 的 4.1.2,
实际部署 target/lib 也是 4.1.2;若此处用 5.2.3 会出现编译期与运行期
API 不一致(如 XWPFRun.setFontSize(double) 在 4.1.2 不存在) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
@ -79,5 +87,11 @@
<artifactId>metadata-extractor</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>com.inspect</groupId>
<artifactId>inspect-main-api</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
</project>

+ 152
- 5
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/controller/ResultAnalysisController.java View File

@ -2,9 +2,12 @@ package com.inspect.analysis.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageInfo;
import com.inspect.analysis.domain.*;
import com.inspect.analysis.service.IResultAnalysisService;
import com.inspect.analysis.service.DefectListAggregator;
import com.inspect.api.service.AlarmPublisher;
import com.inspect.base.core.constant.AlgConstants;
import com.inspect.base.core.domain.Response;
import com.inspect.base.core.enums.PointStatusEnum;
@ -644,14 +647,12 @@ public class ResultAnalysisController extends BaseController {
/**
* 缺陷记录(将确认缺陷的数据展示在这)
* @param query 查询条件:点位名称、算法名称(下拉,选项见 /defect/algNameOptions)、采集时间范围
* @return
*/
@GetMapping({"/defect/list"})
public TableDataInfo defectList() {
ResultAnalysis resultAnalysis = new ResultAnalysis();
resultAnalysis.setPointStatusList(Arrays.asList(PointStatusEnum.CONFIRMED.getCode(), PointStatusEnum.TRACKING.getCode()));
// 查出全部数据
List<PatrolData> allList = patrolResultService.selectPatrolDataResultByTaskCodeV2(resultAnalysis);
public TableDataInfo defectList(DefectListQuery query) {
List<PatrolData> allList = patrolResultService.selectDefectList(query);
Map<String, String> imageNormalMap = allList.parallelStream()
.filter(patrolData -> StringUtils.isNotEmpty(patrolData.getImageNormalUrlPath()))
@ -739,9 +740,15 @@ public class ResultAnalysisController extends BaseController {
patrolData.setResStatus(value.stream().map(item -> item.getResStatus() != null ? item.getResStatus() : "").distinct().findFirst().orElse(""));
patrolData.setSuggestion(value.stream().map(item -> item.getSuggestion() != null ? item.getSuggestion() : "").distinct().findFirst().orElse(""));
patrolData.setWarnStatus(value.stream().map(item -> item.getWarnStatus() != null ? item.getWarnStatus() : "").distinct().findFirst().orElse(""));
patrolData.setTaskName(value.stream().map(item -> item.getTaskName() != null ? item.getTaskName() : "").distinct().findFirst().orElse(""));
resultList.add(patrolData);
});
// 算法名称筛选:algType 属于聚合分组维度,按聚合记录的 algName 精确匹配,命中记录整条保留(多条缺陷描述不会丢失)
if (query != null && StringUtils.isNotEmpty(query.getAlgName())) {
String selectedAlgName = query.getAlgName();
resultList.removeIf(item -> !selectedAlgName.equals(item.getAlgName()));
}
// 非 null 的值按降序排列(最新的时间在前),null的在最后面排列
resultList.sort(Comparator.comparing(PatrolData::getPatrolTime,
Comparator.nullsLast(Comparator.reverseOrder())));
@ -761,6 +768,40 @@ public class ResultAnalysisController extends BaseController {
return rspData;
}
/**
* 缺陷记录-算法名称下拉选项(返回去重后的 algName 值,供 /defect/list 的算法名称下拉筛选使用)
* @param query 查询条件:点位名称、采集时间范围(algName 不参与查询,避免选项被自身过滤)
* @return
*/
@GetMapping({"/defect/algNameOptions"})
public AjaxResult defectAlgNameOptions(DefectListQuery query) {
List<PatrolData> allList = patrolResultService.selectDefectList(query);
List<AlgInfo> algInfos = patrolResultService.selectAlgInfo(new PatrolResult());
// 仅取缺陷数据(与 /defect/list 口径一致),按 algType 解析出 algName 后去重、按字典序返回
List<String> algNameOptions = allList.stream()
.filter(data -> "0".equals(data.getWarnStatus()))
.map(data -> resolveAlgName(algInfos, data.getAlgType()))
.filter(StringUtils::isNotEmpty)
.distinct()
.sorted()
.collect(Collectors.toList());
return AjaxResult.success(algNameOptions);
}
/**
* 按算法类型解析算法名称,口径与 /defect/list 明细行分配的 algName 一致(未匹配到时返回空串)。
*/
private String resolveAlgName(List<AlgInfo> algInfos, String algType) {
if (algInfos == null) {
return "";
}
return algInfos.stream()
.filter(alg -> alg.getAlgSubtypeCode() != null && alg.getAlgSubtypeCode().equals(algType))
.map(AlgInfo::getAlgSubtypeName)
.findFirst()
.orElse("");
}
/**
* 新的关系树
* @param areaIdS
@ -884,6 +925,30 @@ public class ResultAnalysisController extends BaseController {
return rspData;
}
/**
* 缺陷列表查询:同一任务、点位对象和算法聚合为一条记录。
*/
@GetMapping({"/defect/summary"})
public TableDataInfo defectSummary(DefectListQuery query) {
logger.info("LIST_DEFECT_SUMMARY param: {}", query);
List<PatrolData> candidates = patrolResultService.selectDefectList(query);
logger.info("缺陷汇总原始数据 candidates size:{}",candidates == null ? 0 : candidates.size());
List<AlgInfo> algInfos = patrolResultService.selectAlgInfo(new PatrolResult());
List<PatrolData> resultList = DefectListAggregator.aggregate(candidates, algInfos);
logger.info("缺陷汇总 resultList size:{}",resultList.size());
PageDomain pageDomain = TableSupport.buildPageRequest();
List<PatrolData> pageList = DefectListAggregator.page(
resultList, pageDomain.getPageNum(), pageDomain.getPageSize());
TableDataInfo response = new TableDataInfo();
response.setCode(200);
response.setRows(pageList);
response.setTotal(resultList.size());
response.setMsg("查询成功");
return response;
}
private static class GroupKey {
private final String objectId;
private final String algType;
@ -1055,4 +1120,86 @@ public class ResultAnalysisController extends BaseController {
return AjaxResult.success(total);
}
@Resource
private AlarmPublisher alarmPublisher;
@GetMapping("/test/test")
public AjaxResult testTest() {
String json = "{\n" +
" \"lineId\": 12880539,\n" +
" \"resultType\": \"1\",\n" +
" \"businessId\": \"2514487\",\n" +
" \"resultContent\": \"{\\\"code\\\":\\\"2000\\\",\\\"conf\\\":\\\"0.95\\\",\\\"defect\\\":true,\\\"pos\\\":[],\\\"resImageUrl\\\":\\\"/1/2026/09/09/1776/CCD/1776_20260909142246152_2093939_SVG1.jpg\\\",\\\"type\\\":\\\"meter\\\",\\\"value\\\":\\\"1\\\",\\\"extra\\\":{\\\"avgValue\\\":12.092737,\\\"minValue\\\":4.4596844,\\\"maxValue\\\":25.46292}}\",\n" +
" \"resStatus\": \"0\",\n" +
" \"pointStatus\": \"1\",\n" +
" \"pointStatusList\": null,\n" +
" \"beginTime\": null,\n" +
" \"endTime\": null,\n" +
" \"suggestion\": null,\n" +
" \"objectId\": \"2024070\",\n" +
" \"description\": \"正常\",\n" +
" \"requestId\": \"5403fc4309ee48658f01d8922a9fa83c\",\n" +
" \"areaId\": null,\n" +
" \"mainDevId\": null,\n" +
" \"resValue\": \"1\",\n" +
" \"algType\": \"meter\",\n" +
" \"filter\": \"0\",\n" +
" \"taskPatrolId\": \"1671_20260709170248562\",\n" +
" \"resCode\": \"2000\",\n" +
" \"resImgUrl\": \"/1/2026/09/09/1776/CCD/1776_20260909142246152_2093939_SVG1.jpg\",\n" +
" \"resConf\": \"0.95\",\n" +
" \"resDesc\": null,\n" +
" \"resPos\": \"[]\",\n" +
" \"result\": \"1\",\n" +
" \"algName\": null,\n" +
" \"originalValue\": null,\n" +
" \"imageNormalUrlPath\": null,\n" +
" \"taskPatrolledId\": null,\n" +
" \"posType\": null,\n" +
" \"csQxNum\": 0,\n" +
" \"csYcNum\": 0,\n" +
" \"aiQxNum\": 0,\n" +
" \"aiYcNum\": 0,\n" +
" \"pointName\": null,\n" +
" \"deviceName\": null,\n" +
" \"abnormal\": null,\n" +
" \"channelName\": null,\n" +
" \"deviceSource\": null,\n" +
" \"filterDesc\": null,\n" +
" \"desc\": null\n" +
"}";
try {
ObjectMapper objectMapper = new ObjectMapper();
ResultAnalysis resultAnalysis = objectMapper.readValue(json, ResultAnalysis.class);
PatrolData patrolData = patrolResultService.selectPatrolData(resultAnalysis);
if (patrolData != null) {
if (true || AlgConstants.ALG_INFRARED_LIST.contains(resultAnalysis.getAlgType())) {
JSONObject jsonObject = JSONObject.parseObject(resultAnalysis.getResultContent());
JSONObject extraObject = jsonObject == null ? null : jsonObject.getJSONObject("extra");
if (extraObject != null) {
Object maxTemp = extraObject.get("maxValue");
if (maxTemp != null) {
patrolData.setMaxValue(String.valueOf(maxTemp));
}
Object minTemp = extraObject.get("minValue");
if (minTemp != null) {
patrolData.setMinValue(String.valueOf(minTemp));
}
Object avgTemp = extraObject.get("avgValue");
if (avgTemp != null) {
patrolData.setAvgValue(String.valueOf(avgTemp));
patrolData.setResValue(String.valueOf(avgTemp));
}
}
}
alarmPublisher.publish(patrolData);
return AjaxResult.success();
}
} catch (Exception e) {
logger.error("testTest", e);
}
return AjaxResult.error();
}
}

+ 9
- 1
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/AnalyseResPoint.java View File

@ -1,11 +1,14 @@
package com.inspect.analysis.domain;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.inspect.analysis.constant.AnalyseConstants;
import com.inspect.base.core.utils.StringUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class AnalyseResPoint implements Serializable {
@ -20,7 +23,8 @@ public class AnalyseResPoint implements Serializable {
private String conf;
private String desc;
private List<Pos> pos;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, Object> extra = new HashMap<>();
// public boolean checkSuccess() {
// return "2000".equals(this.code) && "1".equals(this.value);
// }
@ -127,6 +131,10 @@ public class AnalyseResPoint implements Serializable {
this.value2 = value2;
}
public Map<String, Object> getExtra() { return extra; }
public void setExtra(Map<String, Object> extra) { this.extra = extra; }
@Override
public boolean equals(Object object) {
if (this == object) return true;


+ 1
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/AnalyseResult.java View File

@ -25,6 +25,7 @@ public class AnalyseResult implements Serializable {
private boolean isTest = false;
private int totalNumber;
private boolean isCompensate = false; // false-normal; true-compensate
private boolean completed = false; // 分析流程是否结束
public void setResultsList(List<AnalyseResItem> resultsList) {
this.resultsList = resultsList;


+ 54
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/DefectListQuery.java View File

@ -0,0 +1,54 @@
package com.inspect.analysis.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
/**
* 缺陷列表查询条件。
*/
@Getter
@Setter
@ToString
public class DefectListQuery {
/** 巡视任务执行 ID。 */
private String taskPatrolledId;
/** 服务层根据主任务展开后的任务执行 ID。 */
private List<String> taskPatrolIds;
/** 结果来源:0-大模型,1-初筛。 */
private String filter;
/** 算法类型。 */
private String algType;
/** 算法名称(前端下拉筛选,精确匹配聚合记录的 algName),选项见 /defect/algNameOptions。 */
private String algName;
/** 区域 ID。 */
private String areaId;
/** 主设备 ID。 */
private String deviceId;
/** 点位 ID。 */
private String pointId;
/** 任务编码。 */
private String taskCode;
/** 点位名称。 */
private String pointName;
/** 任务名称。 */
private String taskName;
/** 主设备名称。 */
private String deviceName;
/** 相机/巡检设备名称。 */
private String channelName;
/** 系统/设备来源。 */
private String deviceSource;
/** 点位状态。 */
private String pointStatus;
/** 缺陷描述关键字。 */
private String desc;
/** 开始日期,格式 yyyy-MM-dd。 */
private String beginTime;
/** 结束日期,格式 yyyy-MM-dd。 */
private String endTime;
/** 预置位类型:0-视频,其他-机器人/无人机;未传时按视频处理。 */
private String posType = "0";
}

+ 5
- 55
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/ResultAnalysis.java View File

@ -3,19 +3,16 @@ package com.inspect.analysis.domain;
import com.inspect.base.core.annotation.Excel;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.core.web.domain.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
import java.util.List;
@Setter
@Getter
@Data
@ToString
public class ResultAnalysis extends BaseEntity {
private static final long serialVersionUID = 1L;
private final Logger log = LoggerFactory.getLogger(getClass());
private Long lineId;
@Excel(
@ -69,6 +66,7 @@ public class ResultAnalysis extends BaseEntity {
private String deviceName;
private String abnormal;
private String channelName;
private String channelCode;
private String deviceSource;
private String filterDesc;
private String desc;
@ -439,52 +437,4 @@ public class ResultAnalysis extends BaseEntity {
// this.setDescription(defaultDesc);
// this.setCreateTime(new Date());
// }
@Override
public String toString() {
return "ResultAnalysis{" +
"lineId=" + lineId +
", resultType='" + resultType + '\'' +
", businessId='" + businessId + '\'' +
", resultContent='" + resultContent + '\'' +
", resStatus='" + resStatus + '\'' +
", pointStatus='" + pointStatus + '\'' +
", pointStatusList='" + pointStatusList + '\'' +
", beginTime='" + beginTime + '\'' +
", endTime='" + endTime + '\'' +
", createTime='" + super.getCreateTime() + '\'' +
", suggestion='" + suggestion + '\'' +
", objectId='" + objectId + '\'' +
", description='" + description + '\'' +
", requestId='" + requestId + '\'' +
", areaId='" + areaId + '\'' +
", mainDevId='" + mainDevId + '\'' +
", resValue='" + resValue + '\'' +
", algType='" + algType + '\'' +
", filter='" + filter + '\'' +
", taskPatrolId='" + taskPatrolId + '\'' +
", resCode='" + resCode + '\'' +
", resImgUrl='" + resImgUrl + '\'' +
", resConf='" + resConf + '\'' +
", resDesc='" + resDesc + '\'' +
", resPos='" + resPos + '\'' +
", result='" + result + '\'' +
", algName='" + algName + '\'' +
", originalValue='" + originalValue + '\'' +
", imageNormalUrlPath='" + imageNormalUrlPath + '\'' +
", taskPatrolledId='" + taskPatrolledId + '\'' +
", posType='" + posType + '\'' +
", csQxNum=" + csQxNum +
", csYcNum=" + csYcNum +
", aiQxNum=" + aiQxNum +
", aiYcNum=" + aiYcNum +
", pointName='" + pointName + '\'' +
", deviceName='" + deviceName + '\'' +
", abnormal='" + abnormal + '\'' +
", channelName='" + channelName + '\'' +
", deviceSource='" + deviceSource + '\'' +
", filterDesc='" + filterDesc + '\'' +
", desc='" + desc + '\'' +
'}';
}
}

+ 206
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/service/DefectListAggregator.java View File

@ -0,0 +1,206 @@
package com.inspect.analysis.service;
import com.inspect.analysis.domain.AlgValue;
import com.inspect.base.core.constant.AlgConstants;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.partrolresult.domain.AlgInfo;
import com.inspect.task.domain.PatrolData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 将缺陷分析明细按任务、对象和算法聚合为列表展示记录。
*/
public final class DefectListAggregator {
private DefectListAggregator() {
}
public static List<PatrolData> aggregate(List<PatrolData> source, List<AlgInfo> algInfos) {
if (source == null || source.isEmpty()) {
return new ArrayList<>();
}
Map<String, String> algNameMap = algInfos == null ? Collections.emptyMap() : algInfos.stream()
.filter(Objects::nonNull)
.filter(item -> StringUtils.isNotEmpty(item.getAlgSubtypeCode()))
.collect(Collectors.toMap(AlgInfo::getAlgSubtypeCode, AlgInfo::getAlgSubtypeName,
(first, ignored) -> first));
Map<GroupKey, List<PatrolData>> groups = new LinkedHashMap<>();
for (PatrolData item : source) {
if (item == null || !"0".equals(item.getWarnStatus())
|| AlgConstants.CORRECTION.equals(item.getAlgType())) {
continue;
}
item.setAlgName(algNameMap.getOrDefault(item.getAlgType(), ""));
prepareItem(item);
GroupKey key = new GroupKey(item.getTaskPatrolId(), item.getObjectId(), item.getAlgType());
groups.computeIfAbsent(key, ignored -> new ArrayList<>()).add(item);
}
List<PatrolData> result = new ArrayList<>();
for (Map.Entry<GroupKey, List<PatrolData>> entry : groups.entrySet()) {
result.add(merge(entry.getKey(), entry.getValue()));
}
result.sort(Comparator.comparingInt(item -> statusRank(item.getResStatus())));
return result;
}
public static List<PatrolData> page(List<PatrolData> source, Integer pageNum, Integer pageSize) {
if (source == null || source.isEmpty()) {
return new ArrayList<>();
}
int safePageNum = pageNum == null || pageNum < 1 ? 1 : pageNum;
int safePageSize = pageSize == null || pageSize < 1 ? 10 : pageSize;
long from = (long) (safePageNum - 1) * safePageSize;
if (from >= source.size()) {
return new ArrayList<>();
}
int fromIndex = (int) from;
long to = Math.min((long) source.size(), from + safePageSize);
int toIndex = (int) to;
return source.subList(fromIndex, toIndex);
}
private static void prepareItem(PatrolData item) {
if ("1".equals(item.getFilter()) && StringUtils.isNotEmpty(item.getImageNormalUrlPath())) {
item.setImg(item.getImageNormalUrlPath());
}
if (item.getResValue() != null && "meter".equals(item.getAlgType())) {
String value = new AlgValue().formatValue(item.getResValue());
if ("-1".equals(value)) {
item.setResValue("空值");
}
}
}
private static PatrolData merge(GroupKey key, List<PatrolData> values) {
PatrolData result = new PatrolData();
result.setTaskPatrolId(key.taskPatrolId);
result.setObjectId(key.objectId);
result.setAlgType(key.algType);
result.setDesc(joinDistinct(values, PatrolData::getDesc));
for (PatrolData item : values) {
if ("1".equals(item.getFilter())) {
// if (StringUtils.isEmpty(result.getImgAnalyse())) {
// result.setImgAnalyse(item.getImgAnalyse());
// }
if (StringUtils.isEmpty(result.getImg())) {
result.setImg(item.getImg());
}
}
if ("0".equals(item.getFilter())) {
if (StringUtils.isNotEmpty(item.getImgAnalyse())) {
result.setImgAnalyse(item.getImgAnalyse());
}
// if (StringUtils.isNotEmpty(item.getImg())) {
// result.setImg(item.getImg());
// }
}
// if (StringUtils.isEmpty(result.getImg())) {
// if (StringUtils.isNotEmpty(item.getImgAnalyse())) {
// result.setImgAnalyse(item.getImgAnalyse());
// }
// if (StringUtils.isNotEmpty(item.getImg())) {
// result.setImg(item.getImg());
// }
// }
}
result.setImageNormalUrlPath(firstNonEmpty(values, PatrolData::getImageNormalUrlPath));
result.setFilePath(firstNonEmpty(values, PatrolData::getFilePath));
result.setAlgName(firstNonEmpty(values, PatrolData::getAlgName));
result.setAreaName(firstNonEmpty(values, PatrolData::getAreaName));
// result.setBrightDesc(firstNonEmpty(values, PatrolData::getBrightDesc));
// result.setBrightImgAnalyse(firstNonEmpty(values, PatrolData::getBrightImgAnalyse));
// result.setBrightResStatus(firstNonEmpty(values, PatrolData::getBrightResStatus));
result.setChannelName(firstNonEmpty(values, PatrolData::getChannelName));
result.setDataType(firstNonEmpty(values, PatrolData::getDataType));
result.setDeviceName(firstNonEmpty(values, PatrolData::getDeviceName));
result.setDeviceSource(firstNonEmpty(values, PatrolData::getDeviceSource));
result.setImgType(firstNonEmpty(values, PatrolData::getImgType));
result.setLineId(joinDistinct(values, PatrolData::getLineId));
result.setPatrolTime(firstNonEmpty(values, PatrolData::getPatrolTime));
result.setPointId(firstNonEmpty(values, PatrolData::getPointId));
result.setPointName(firstNonEmpty(values, PatrolData::getPointName));
result.setPointStatus(firstNonEmpty(values, PatrolData::getPointStatus));
result.setRequestId(firstNonEmpty(values, PatrolData::getRequestId));
result.setResValue(firstNonEmpty(values, PatrolData::getResValue));
result.setResStatus(firstNonEmpty(values, PatrolData::getResStatus));
result.setSuggestion(firstNonEmpty(values, PatrolData::getSuggestion));
result.setWarnStatus("0");
result.setTaskName(firstNonEmpty(values,PatrolData::getTaskName));
return result;
}
private static String firstNonEmpty(List<PatrolData> values, Function<PatrolData, String> getter) {
return values.stream().map(getter).filter(StringUtils::isNotEmpty).findFirst().orElse("");
}
private static String joinDistinct(List<PatrolData> values, Function<PatrolData, String> getter) {
Set<String> distinct = new LinkedHashSet<>();
for (PatrolData value : values) {
String item = getter.apply(value);
if (StringUtils.isNotEmpty(item)) {
distinct.add(item);
}
}
return String.join(",", distinct);
}
private static int statusRank(String status) {
if ("1".equals(status)) {
return 1;
}
if ("3".equals(status)) {
return 2;
}
if ("2".equals(status)) {
return 3;
}
return 10;
}
private static final class GroupKey {
private final String taskPatrolId;
private final String objectId;
private final String algType;
private GroupKey(String taskPatrolId, String objectId, String algType) {
this.taskPatrolId = taskPatrolId;
this.objectId = objectId;
this.algType = algType;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof GroupKey)) {
return false;
}
GroupKey that = (GroupKey) other;
return Objects.equals(taskPatrolId, that.taskPatrolId)
&& Objects.equals(objectId, that.objectId)
&& Objects.equals(algType, that.algType);
}
@Override
public int hashCode() {
return Objects.hash(taskPatrolId, objectId, algType);
}
}
}

+ 63
- 98
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/service/impl/AnalyseResponseServiceImpl.java View File

@ -12,10 +12,12 @@ import com.inspect.analysis.service.IAnalyseLogService;
import com.inspect.analysis.service.IAnalyseResponseService;
import com.inspect.analysis.service.IResultAnalysisService;
import com.inspect.analysis.utils.ResultAnalysisUtils;
import com.inspect.api.service.AlarmPublisher;
import com.inspect.base.core.constant.AlgConstants;
import com.inspect.base.core.constant.Color;
import com.inspect.base.core.constant.RedisConst;
import com.inspect.base.core.constant.Tags;
import com.inspect.base.core.enums.AnalysisType;
import com.inspect.base.core.enums.TaskStatus;
import com.inspect.base.core.utils.BigDecimalUtil;
import com.inspect.base.core.utils.DateUtils;
@ -31,6 +33,7 @@ import com.inspect.partrolresult.service.IPatrolResultService;
import com.inspect.resultmain.domain.PatrolTaskResultMain;
import com.inspect.resultmain.service.IPatrolTaskResultMainService;
import com.inspect.system.base.domain.SysDictData;
import com.inspect.task.domain.PatrolData;
import com.inspect.taskinfo.domain.PatrolTaskInfo;
import com.inspect.taskinfo.service.IPatrolTaskInfoService;
import com.inspect.taskstatus.domain.PatrolTaskStatus;
@ -41,6 +44,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
@ -81,6 +85,28 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
@Resource
private DelayQueueService<String> delayQueueService;
@Resource
private AlarmPublisher alarmPublisher;
public static List<AnalyseResPoint> mergeAnalyseResItem(List<AnalyseResPoint> analyseResPointList) {
String mergedValue = analyseResPointList.stream()
.map(AnalyseResPoint::getValue)
.filter(Objects::nonNull)
.collect(Collectors.joining(StringUtils.COMMA));
AnalyseResPoint first = analyseResPointList.get(0);
AnalyseResPoint mergedPoint = new AnalyseResPoint();
mergedPoint.setType(first.getType());
mergedPoint.setCode(first.getCode());
mergedPoint.setResImageUrl(first.getResImageUrl());
mergedPoint.setImageNormalUrlPath(first.getImageNormalUrlPath());
mergedPoint.setConf(first.getConf());
mergedPoint.setDesc(first.getDesc());
mergedPoint.setPos(first.getPos());
mergedPoint.setValue(mergedValue);
return Collections.singletonList(mergedPoint);
}
@Override
public void picAnalyseRetNotify(AnalyseResult analyseResult) {
@ -234,6 +260,7 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
requestId,
objectId,
StringUtils.EMPTY));
analyseResult.setCompleted(bBigModelExecFlag ? false : true);
repositAnalyseAlgorithmResult(analyseResult);
} catch (Exception e) {
log.error("error", e);
@ -258,7 +285,6 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
requestId);
}
public synchronized void doAlgorithmAnalysis(AnalyseResult analyseResult) {
log.info("doAlgorithmAnalysis analyseResult: {}", analyseResult);
int size = 0;
@ -829,6 +855,9 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
} catch (Exception e) {
log.error("error", e);
}
// ws
uploadResultWS(resultAnalysis, analyseResult.isCompleted());
}
patrolResult.setValue(valueJoiner.toString());
@ -871,6 +900,38 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
//this.senWebsocket(websocketDataList);
}
private void uploadResultWS(ResultAnalysis resultAnalysis, boolean completed) {
log.info("uploadResultWS patrolTaskId:{}, objectId:{}, algType:{}, isCompleted:{}", resultAnalysis.getTaskPatrolId(), resultAnalysis.getObjectId(), resultAnalysis.getAlgType(), completed);
if (completed) {
PatrolData patrolData = patrolResultService.selectPatrolData(resultAnalysis);
if (patrolData != null) {
if (AlgConstants.ALG_INFRARED_LIST.contains(resultAnalysis.getAlgType())) {
JSONObject jsonObject = JSONObject.parseObject(resultAnalysis.getResultContent());
JSONObject extraObject = jsonObject == null ? null : jsonObject.getJSONObject("extra");
if (extraObject != null) {
BigDecimal maxValue = extraObject.getBigDecimal("maxValue");
if (maxValue != null) {
patrolData.setMaxValue(maxValue.setScale(1, RoundingMode.HALF_UP).toPlainString());
}
BigDecimal minValue = extraObject.getBigDecimal("minValue");
if (minValue != null) {
patrolData.setMinValue(minValue.setScale(1, RoundingMode.HALF_UP).toPlainString());
}
BigDecimal avgValue = extraObject.getBigDecimal("avgValue");
if (avgValue != null) {
String formattedAvgValue = avgValue.setScale(1, RoundingMode.HALF_UP).toPlainString();
patrolData.setAvgValue(formattedAvgValue);
patrolData.setResValue(formattedAvgValue);
}
}
}
alarmPublisher.publish(patrolData);
}
}
}
private ResultAnalysis combineAnalyse(
AnalyseResult analyseResult,
String patrolResultId,
@ -905,7 +966,7 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
// 处理正常结果、以及兼容处理无机人表计gis_meter2、纠偏correction的异常结果
if (resultAnalysisUtils.checkCode(analyseResPoint.getCode(), analyseResPoint.getDesc(), resultAnalysis)
|| (AlgConstants.GIS_METER_2.equals(analyseResPoint.getType()))
|| (AlgConstants.CORRECTION.equals(analyseResPoint.getType()) && Arrays.asList("2001", "2002", "2003").contains(analyseResPoint.getCode()) )) {
|| (AlgConstants.CORRECTION.equals(analyseResPoint.getType()) && Arrays.asList("2001", "2002", "2003").contains(analyseResPoint.getCode()))) {
AlgValue algValue = selectAlgMap(objectId, analyseResPoint.getType());
resultAnalysisUtils.parseAlg(analyseResPoint.getType(), algValue, value, analyseResPoint.getDesc(), resultAnalysis);
// 合并辅助值字段
@ -1019,100 +1080,4 @@ public class AnalyseResponseServiceImpl implements IAnalyseResponseService {
return algValue;
}
public static final String mergeString = "{\n" +
"\t\"requestId\": \"b00f2fbd1fcc49c6a47ed2b3a77e5028\",\n" +
"\t\"resultsList\": [{\n" +
"\t\t\"objectId\": \"2079991\",\n" +
"\t\t\"results\": [{\n" +
"\t\t\t\"type\": \"switch\",\n" +
"\t\t\t\"value\": \"和\",\n" +
"\t\t\t\"code\": \"2000\",\n" +
"\t\t\t\"resImageUrl\": \"2/2025/12/12/1531/CCD/3038_R100-001_20251212140756.jpg\",\n" +
"\t\t\t\"pos\": [],\n" +
"\t\t\t\"conf\": 0.99,\n" +
"\t\t\t\"desc\": \"\",\n" +
"\t\t\t\"imageNormalUrlPath\": \"1/2025/08/29/1584/CCD/3038_R100-001_20250829123953.jpg\"\n" +
"\t\t}, {\n" +
"\t\t\t\"type\": \"switch\",\n" +
"\t\t\t\"value\": \"和\",\n" +
"\t\t\t\"code\": \"2000\",\n" +
"\t\t\t\"resImageUrl\": \"2/2025/12/12/1531/CCD/3038_R100-001_20251212140756.jpg\",\n" +
"\t\t\t\"pos\": [],\n" +
"\t\t\t\"conf\": 0.99,\n" +
"\t\t\t\"desc\": \"\",\n" +
"\t\t\t\"imageNormalUrlPath\": \"1/2025/08/29/1584/CCD/3038_R100-001_20250829123953.jpg\"\n" +
"\t\t}, {\n" +
"\t\t\t\"type\": \"switch\",\n" +
"\t\t\t\"value\": \"分\",\n" +
"\t\t\t\"code\": \"2000\",\n" +
"\t\t\t\"resImageUrl\": \"2/2025/12/12/1531/CCD/3038_R100-001_20251212140756.jpg\",\n" +
"\t\t\t\"pos\": [],\n" +
"\t\t\t\"conf\": 0.99,\n" +
"\t\t\t\"desc\": \"\",\n" +
"\t\t\t\"imageNormalUrlPath\": \"1/2025/08/29/1584/CCD/3038_R100-001_20250829123953.jpg\"\n" +
"\t\t}, {\n" +
"\t\t\t\"type\": \"switch\",\n" +
"\t\t\t\"value\": \"和\",\n" +
"\t\t\t\"code\": \"2000\",\n" +
"\t\t\t\"resImageUrl\": \"2/2025/12/12/1531/CCD/3038_R100-001_20251212140756.jpg\",\n" +
"\t\t\t\"pos\": [],\n" +
"\t\t\t\"conf\": 0.99,\n" +
"\t\t\t\"desc\": \"\",\n" +
"\t\t\t\"imageNormalUrlPath\": \"1/2025/08/29/1584/CCD/3038_R100-001_20250829123953.jpg\"\n" +
"\t\t}, {\n" +
"\t\t\t\"type\": \"switch\",\n" +
"\t\t\t\"value\": \"分\",\n" +
"\t\t\t\"code\": \"2000\",\n" +
"\t\t\t\"resImageUrl\": \"2/2025/12/12/1531/CCD/3038_R100-001_20251212140756.jpg\",\n" +
"\t\t\t\"pos\": [],\n" +
"\t\t\t\"conf\": 0.99,\n" +
"\t\t\t\"desc\": \"\",\n" +
"\t\t\t\"imageNormalUrlPath\": \"1/2025/08/29/1584/CCD/3038_R100-001_20250829123953.jpg\"\n" +
"\t\t}, {\n" +
"\t\t\t\"type\": \"switch\",\n" +
"\t\t\t\"value\": \"分\",\n" +
"\t\t\t\"code\": \"2000\",\n" +
"\t\t\t\"resImageUrl\": \"2/2025/12/12/1531/CCD/3038_R100-001_20251212140756.jpg\",\n" +
"\t\t\t\"pos\": [],\n" +
"\t\t\t\"conf\": 0.99,\n" +
"\t\t\t\"desc\": \"\",\n" +
"\t\t\t\"imageNormalUrlPath\": \"1/2025/08/29/1584/CCD/3038_R100-001_20250829123953.jpg\"\n" +
"\t\t}],\n" +
"\t\t\"algFactory\": \"yd\"\n" +
"\t}]\n" +
"}";
public static List<AnalyseResPoint> mergeAnalyseResItem(List<AnalyseResPoint> analyseResPointList) {
String mergedValue = analyseResPointList.stream()
.map(AnalyseResPoint::getValue)
.filter(Objects::nonNull)
.collect(Collectors.joining(StringUtils.COMMA));
AnalyseResPoint first = analyseResPointList.get(0);
AnalyseResPoint mergedPoint = new AnalyseResPoint();
mergedPoint.setType(first.getType());
mergedPoint.setCode(first.getCode());
mergedPoint.setResImageUrl(first.getResImageUrl());
mergedPoint.setImageNormalUrlPath(first.getImageNormalUrlPath());
mergedPoint.setConf(first.getConf());
mergedPoint.setDesc(first.getDesc());
mergedPoint.setPos(first.getPos());
mergedPoint.setValue(mergedValue);
return Collections.singletonList(mergedPoint);
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
AnalyseResult analyseResult;
try {
analyseResult = mapper.readValue(mergeString, new TypeReference<AnalyseResult>() {
});
log.info("mergeAnalyseResItem analyseResult: {}", analyseResult);
List<AnalyseResPoint> result = mergeAnalyseResItem(analyseResult.getResultsList().get(0).getResults());
log.info("mergeAnalyseResItem result: {}", result);
} catch (Exception e) {
log.error("mergeAnalyseResItem exception", e);
}
}
}

+ 3
- 3
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java View File

@ -175,7 +175,7 @@ public class ResultAnalysisUtils {
} else if (algType.equals("meter") || algType.equals("gis_meter")) {
log.info("------------------parseAlg : meter|gis_meter value: {}, originalValue: {}", value, correctionValue);
if ("open".equalsIgnoreCase(value)
|| "closed".equalsIgnoreCase(value) || "Off".equalsIgnoreCase(value) || "On".equalsIgnoreCase(value)) {
|| "closed".equalsIgnoreCase(value) || "Off".equalsIgnoreCase(value) || "On".equalsIgnoreCase(value) || "正常".equalsIgnoreCase(value)) {
resultAnalysis.setResStatus("0");
resultAnalysis.setPointStatus("1");
resultAnalysis.setResultType("1");
@ -508,7 +508,7 @@ public class ResultAnalysisUtils {
if (!NumberUtils.isCreatable(val)) {
return val;
}
// 红外算法,保留1位小数(四舍五入)
if (AlgConstants.ALG_INFRARED_LIST.contains(algType)) {
String formatted = BigDecimalUtil.toOneDecimal(val, 1);
return shouldAddUnit ? formatted + unit : formatted;
@ -521,7 +521,7 @@ public class ResultAnalysisUtils {
}
return val;
}
// 所有值都加单位(如果有单位的话)
return shouldAddUnit ? val + unit : val;
})
.collect(Collectors.joining(","));


+ 7
- 2
inspect-main/inspect-main-task/src/main/java/com/inspect/aspect/TaskStatusChangeAspect.java View File

@ -1,6 +1,7 @@
package com.inspect.aspect;
import com.inspect.annotation.TaskStatusChange;
import com.inspect.reportpush.service.ReportAutoArchiveService;
import com.inspect.task.service.IPatrolTaskService;
import com.inspect.taskstatus.domain.PatrolTaskStatus;
import lombok.extern.slf4j.Slf4j;
@ -18,6 +19,9 @@ public class TaskStatusChangeAspect {
@Autowired
private IPatrolTaskService patrolTaskService;
@Autowired
private ReportAutoArchiveService reportAutoArchiveService;
/**
* 拦截带有 @TaskStatusChange 注解的方法
@ -46,8 +50,9 @@ public class TaskStatusChangeAspect {
// 执行切面逻辑
if (taskStatusChange.async()) {
log.info("异步执行区域任务合并报告,任务执行id: {}", taskStatus.getTaskPatrolledId());
patrolTaskService.executeAsync(taskStatus);
log.info("异步执行自动归档,任务执行id: {}", taskStatus.getTaskPatrolledId());
// 任务巡检完成自动归档 + 生成报告 + websocket 推送(盐源外部平台)
reportAutoArchiveService.onTaskCompleted(taskStatus);
} else {
executeSync(taskStatus);
}


+ 3
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/img/domain/InspectionReportImg.java View File

@ -28,6 +28,9 @@ public class InspectionReportImg extends BaseEntity {
name = "缩略图路径"
)
private String imgSrc;
private String imageNormalUrlPath;
@JsonFormat(
pattern = "yyyy-MM-dd"
)


+ 27
- 36
inspect-main/inspect-main-task/src/main/java/com/inspect/insreport/controller/InspectionReportController.java View File

@ -19,6 +19,7 @@ import com.inspect.insreport.domain.InspectionReport;
import com.inspect.insreport.service.IInspectionReportService;
import com.inspect.insreportdata.domain.InspectionReportData;
import com.inspect.insreportdata.service.IInspectionReportDataService;
import com.inspect.insreporttask.service.IInspectionReportTaskService;
import com.inspect.message.MessageUtils;
import com.inspect.taskftp.domain.PatrolTaskFtp;
import com.inspect.taskftp.service.IPatrolTaskFtpService;
@ -39,7 +40,6 @@ import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
@ -52,6 +52,7 @@ public class InspectionReportController extends BaseController {
private final IInspectionReportService inspectionReportService;
private final IInspectionReportDataService inspectionReportDataService;
private final IInspectionReportImgService inspectionReportImgService;
private final IInspectionReportTaskService inspectionReportTaskService;
private final SftpClient sftpClient;
private final IPatrolTaskFtpService patrolTaskFtpService;
@ -61,46 +62,22 @@ public class InspectionReportController extends BaseController {
@Autowired
private MessageUtils MessageUtils;
public InspectionReportController(IInspectionReportService inspectionReportService, IInspectionReportDataService inspectionReportDataService, IInspectionReportImgService inspectionReportImgService, SftpClient sftpClient, IPatrolTaskFtpService patrolTaskFtpService) {
public InspectionReportController(IInspectionReportService inspectionReportService, IInspectionReportDataService inspectionReportDataService, IInspectionReportImgService inspectionReportImgService, IInspectionReportTaskService inspectionReportTaskService, SftpClient sftpClient, IPatrolTaskFtpService patrolTaskFtpService) {
this.inspectionReportService = inspectionReportService;
this.inspectionReportDataService = inspectionReportDataService;
this.inspectionReportImgService = inspectionReportImgService;
this.inspectionReportTaskService = inspectionReportTaskService;
this.sftpClient = sftpClient;
this.patrolTaskFtpService = patrolTaskFtpService;
}
@GetMapping({"/list"})
public TableDataInfo list(InspectionReport inspectionReport) {
// startPage();
startPage();
List<InspectionReport> inspectionReportList = inspectionReportService.selectByAllList(inspectionReport);
logger.info("[REPORT] inspectionReportList size: {}", inspectionReportList.size());
// 根据 inspectionTaskName 和 inspectionDate 组合去重
List<InspectionReport> distinctList = inspectionReportList.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
report -> report.getInspectionTaskName() + "_" +
(report.getInspectionDate() != null ? DateUtils.format("yyyy-MM-dd",report.getInspectionDate()) : "null"),
Function.identity(),
(existing, replacement) -> existing
),
map -> map.values().stream()
.sorted(Comparator.comparing(InspectionReport::getInspectionDate,
Comparator.nullsLast(Comparator.reverseOrder())))
.collect(Collectors.toList())
));
PageDomain pageDomain = TableSupport.buildPageRequest();
int pageNum = pageDomain.getPageNum();
int pageSize = pageDomain.getPageSize();
int toNum = Math.min(distinctList.size(), pageNum * pageSize);
List<InspectionReport> pageList = distinctList.subList((pageNum - 1) * pageSize, toNum);
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(200);
rspData.setRows(pageList);
rspData.setMsg("查询成功");
rspData.setTotal((new PageInfo<>(distinctList)).getTotal());
return rspData;
return getDataTable(inspectionReportList);
}
public Map<String, String> getImgMap() {
@ -135,11 +112,18 @@ public class InspectionReportController extends BaseController {
// 暂时这么处理(进度条报告入口,lineId传的是patrolTaskStatus的lineId)
PatrolTaskStatus patrolTaskStatus = patrolTaskStatusService.selectPatrolTaskStatusByLineId(lineId);
String taskPatrolledId = patrolTaskStatus.getTaskPatrolledId();
inspectionReport = new InspectionReport();
inspectionReport.setTaskPatrolledId(taskPatrolledId);
inspectionReport.setFilter(filter);
logger.info("进度条报告查看,filter:{},taskPatrolledId:{}", filter, taskPatrolledId);
List<InspectionReport> inspectionReports = inspectionReportService.selectInspectionReportList(inspectionReport);
// 合并归档后一次归档只生成一条报告,多个任务经 inspection_report_task 关联同一条报告,
// 故先按任务执行标识查关联报告
List<InspectionReport> inspectionReports =
inspectionReportTaskService.selectInspectionReportsByTaskPatrolledId(taskPatrolledId, filter);
if (inspectionReports.isEmpty()) {
// 兼容历史数据:旧归档按任务各生成一条报告,回退按报告头 task_patrolled_id 等值匹配
inspectionReport = new InspectionReport();
inspectionReport.setTaskPatrolledId(taskPatrolledId);
inspectionReport.setFilter(filter);
inspectionReports = inspectionReportService.selectInspectionReportList(inspectionReport);
}
if (inspectionReports.size() > 0) {
inspectionReport = inspectionReports.get(0);
lineId = inspectionReport.getLineId();
@ -203,10 +187,17 @@ public class InspectionReportController extends BaseController {
// 暂时这么处理(进度条报告入口,lineId传的是patrolTaskStatus的lineId)
PatrolTaskStatus patrolTaskStatus = patrolTaskStatusService.selectPatrolTaskStatusByLineId(lineId);
String taskPatrolledId = patrolTaskStatus.getTaskPatrolledId();
inspectionReport.setTaskPatrolledId(taskPatrolledId);
inspectionReport.setFilter(filter);
logger.info("进度条报告查看,filter:{},taskPatrolledId:{}", filter, taskPatrolledId);
List<InspectionReport> inspectionReports = inspectionReportService.selectInspectionReportList(inspectionReport);
// 合并归档后一次归档只生成一条报告,多个任务经 inspection_report_task 关联同一条报告,
// 故先按任务执行标识查关联报告
List<InspectionReport> inspectionReports =
inspectionReportTaskService.selectInspectionReportsByTaskPatrolledId(taskPatrolledId, filter);
if (inspectionReports.isEmpty()) {
// 兼容历史数据:旧归档按任务各生成一条报告,回退按报告头 task_patrolled_id 等值匹配
inspectionReport.setTaskPatrolledId(taskPatrolledId);
inspectionReport.setFilter(filter);
inspectionReports = inspectionReportService.selectInspectionReportList(inspectionReport);
}
if (inspectionReports.size() > 0) {
inspectionReport = inspectionReports.get(0);
lineId = inspectionReport.getLineId();


+ 5
- 2
inspect-main/inspect-main-task/src/main/java/com/inspect/insreport/domain/InspectionReport.java View File

@ -92,6 +92,8 @@ public class InspectionReport extends BaseEntity {
private String filePath;
private String taskId;
private String taskResultId;
/** 位置(省份 + 地市拼接,如 四川省成都市) */
private String location;
private String areaName;
private String areaType;
private String deviceType;
@ -118,12 +120,12 @@ public class InspectionReport extends BaseEntity {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
InspectionReport that = (InspectionReport) object;
return Objects.equals(lineId, that.lineId) && Objects.equals(stationName, that.stationName) && Objects.equals(voltLevel, that.voltLevel) && Objects.equals(stationType, that.stationType) && Objects.equals(inspectionDate, that.inspectionDate) && Objects.equals(inspectionTaskName, that.inspectionTaskName) && Objects.equals(envInfo, that.envInfo) && Objects.equals(inspectionStartTime, that.inspectionStartTime) && Objects.equals(inspectionEndTime, that.inspectionEndTime) && Objects.equals(patrolStatistics, that.patrolStatistics) && Objects.equals(checkPerson, that.checkPerson) && Objects.equals(checkTime, that.checkTime) && Objects.equals(description, that.description) && Objects.equals(filePath, that.filePath) && Objects.equals(taskId, that.taskId) && Objects.equals(taskResultId, that.taskResultId) && Objects.equals(areaName, that.areaName) && Objects.equals(areaType, that.areaType) && Objects.equals(deviceType, that.deviceType) && Objects.equals(type, that.type) && Objects.equals(cycleStartTime, that.cycleStartTime) && Objects.equals(cycleEndTime, that.cycleEndTime) && Objects.equals(taskPatrolledId, that.taskPatrolledId) && Objects.equals(ReportDatalist, that.ReportDatalist) && Objects.equals(info, that.info);
return Objects.equals(lineId, that.lineId) && Objects.equals(stationName, that.stationName) && Objects.equals(voltLevel, that.voltLevel) && Objects.equals(stationType, that.stationType) && Objects.equals(inspectionDate, that.inspectionDate) && Objects.equals(inspectionTaskName, that.inspectionTaskName) && Objects.equals(envInfo, that.envInfo) && Objects.equals(inspectionStartTime, that.inspectionStartTime) && Objects.equals(inspectionEndTime, that.inspectionEndTime) && Objects.equals(patrolStatistics, that.patrolStatistics) && Objects.equals(checkPerson, that.checkPerson) && Objects.equals(checkTime, that.checkTime) && Objects.equals(description, that.description) && Objects.equals(filePath, that.filePath) && Objects.equals(taskId, that.taskId) && Objects.equals(taskResultId, that.taskResultId) && Objects.equals(location, that.location) && Objects.equals(areaName, that.areaName) && Objects.equals(areaType, that.areaType) && Objects.equals(deviceType, that.deviceType) && Objects.equals(type, that.type) && Objects.equals(cycleStartTime, that.cycleStartTime) && Objects.equals(cycleEndTime, that.cycleEndTime) && Objects.equals(taskPatrolledId, that.taskPatrolledId) && Objects.equals(ReportDatalist, that.ReportDatalist) && Objects.equals(info, that.info);
}
@Override
public int hashCode() {
return Objects.hash(lineId, stationName, voltLevel, stationType, inspectionDate, inspectionTaskName, envInfo, inspectionStartTime, inspectionEndTime, patrolStatistics, checkPerson, checkTime, description, filePath, taskId, taskResultId, areaName, areaType, deviceType, type, cycleStartTime, cycleEndTime, taskPatrolledId, ReportDatalist, info);
return Objects.hash(lineId, stationName, voltLevel, stationType, inspectionDate, inspectionTaskName, envInfo, inspectionStartTime, inspectionEndTime, patrolStatistics, checkPerson, checkTime, description, filePath, taskId, taskResultId, location, areaName, areaType, deviceType, type, cycleStartTime, cycleEndTime, taskPatrolledId, ReportDatalist, info);
}
@Override
@ -145,6 +147,7 @@ public class InspectionReport extends BaseEntity {
", filePath='" + filePath + '\'' +
", taskId='" + taskId + '\'' +
", taskResultId='" + taskResultId + '\'' +
", location='" + location + '\'' +
", areaName='" + areaName + '\'' +
", areaType='" + areaType + '\'' +
", deviceType='" + deviceType + '\'' +


+ 5
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/insreportdata/domain/InspectionReportData.java View File

@ -58,6 +58,11 @@ public class InspectionReportData extends BaseEntity {
name = "巡视结果"
)
private String inspectionResults;
@Excel(
name = "巡视读数"
)
private String inspectionValue;
@Excel(
name = "点位状态:正常、异常、待人工确 认三种状态"
)


+ 32
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/domain/InspectionReportTask.java View File

@ -0,0 +1,32 @@
package com.inspect.insreporttask.domain;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* 巡检报告-任务关联表。
* <p>
* 归档改造后,一次归档(单个任务 / 多个任务合并归档)只生成一条
* {@link com.inspect.insreport.domain.InspectionReport},被归档的每个任务执行
* (patrol_task_result_main.line_id)都通过本表关联到同一条报告。
*
* @author inspect
*/
@Getter
@Setter
public class InspectionReportTask {
/** 主键 */
private Long id;
/** 报告主键 inspection_report.line_id */
private Long reportLineId;
/** 归档任务执行主键 patrol_task_result_main.line_id */
private Long taskResultLineId;
/** 任务执行标识 patrol_task_result_main.task_patrolled_id */
private String taskPatrolledId;
/** 任务名称 patrol_task_result_main.task_name */
private String taskName;
/** 创建时间 */
private Date createTime;
}

+ 36
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/mapper/InspectionReportTaskMapper.java View File

@ -0,0 +1,36 @@
package com.inspect.insreporttask.mapper;
import com.inspect.insreport.domain.InspectionReport;
import com.inspect.insreporttask.domain.InspectionReportTask;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface InspectionReportTaskMapper {
/**
* 批量写入 报告-任务 关联。
*
* @param inspectionReportTasks 关联记录(一次归档一条报告,任务逐条关联)
*/
int batchInsertInspectionReportTask(@Param("list") List<InspectionReportTask> inspectionReportTasks);
/**
* 按任务执行标识查询其关联的报告(合并归档时多个任务关联同一条报告)。
*
* @param taskPatrolledId 任务执行标识 patrol_task_result_main.task_patrolled_id
* @param filter 报告类型过滤(可为空/不传,表示不过滤)
*/
List<InspectionReport> selectInspectionReportsByTaskPatrolledId(@Param("taskPatrolledId") String taskPatrolledId,
@Param("filter") String filter);
/**
* 按报告主键查询该报告关联的所有任务执行标识。
*
* @param reportLineId 报告主键 inspection_report.line_id
* @return 任务执行标识列表 inspection_report_task.task_patrolled_id
*/
List<String> selectTaskPatrolledIdsByReportLineId(@Param("reportLineId") Long reportLineId);
}

+ 24
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/service/IInspectionReportTaskService.java View File

@ -0,0 +1,24 @@
package com.inspect.insreporttask.service;
import com.inspect.insreport.domain.InspectionReport;
import com.inspect.insreporttask.domain.InspectionReportTask;
import java.util.List;
public interface IInspectionReportTaskService {
/**
* 写入 报告-任务 关联记录。
*
* @param inspectionReportTasks 关联记录列表
*/
int batchInsert(List<InspectionReportTask> inspectionReportTasks);
/**
* 按任务执行标识查询其关联的报告(多个任务可能关联同一条合并报告)。
*
* @param taskPatrolledId 任务执行标识
* @param filter 报告类型过滤(可空)
*/
List<InspectionReport> selectInspectionReportsByTaskPatrolledId(String taskPatrolledId, String filter);
}

+ 32
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/insreporttask/service/impl/InspectionReportTaskServiceImpl.java View File

@ -0,0 +1,32 @@
package com.inspect.insreporttask.service.impl;
import com.inspect.insreport.domain.InspectionReport;
import com.inspect.insreporttask.domain.InspectionReportTask;
import com.inspect.insreporttask.mapper.InspectionReportTaskMapper;
import com.inspect.insreporttask.service.IInspectionReportTaskService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class InspectionReportTaskServiceImpl implements IInspectionReportTaskService {
private final InspectionReportTaskMapper inspectionReportTaskMapper;
public InspectionReportTaskServiceImpl(InspectionReportTaskMapper inspectionReportTaskMapper) {
this.inspectionReportTaskMapper = inspectionReportTaskMapper;
}
@Override
public int batchInsert(List<InspectionReportTask> inspectionReportTasks) {
if (inspectionReportTasks == null || inspectionReportTasks.isEmpty()) {
return 0;
}
return this.inspectionReportTaskMapper.batchInsertInspectionReportTask(inspectionReportTasks);
}
@Override
public List<InspectionReport> selectInspectionReportsByTaskPatrolledId(String taskPatrolledId, String filter) {
return this.inspectionReportTaskMapper.selectInspectionReportsByTaskPatrolledId(taskPatrolledId, filter);
}
}

+ 2
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/message/MessageUtils.java View File

@ -235,6 +235,8 @@ public class MessageUtils {
return "summary";
case "缺陷":
return "defect";
case "本次巡检未发现缺陷项":
return "No defects were found during this inspection.";
case"分析失败":
return "analysis failed";
default:


+ 5
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/mapper/PatrolResultMapper.java View File

@ -1,6 +1,7 @@
package com.inspect.partrolresult.mapper;
import com.inspect.analysis.domain.ResultAnalysis;
import com.inspect.analysis.domain.DefectListQuery;
import com.inspect.partrolresult.domain.*;
import com.inspect.statistics.domain.TaskStatisticsConfig;
import com.inspect.system.base.openDomain.BasedataEqpBookMoMain;
@ -34,6 +35,8 @@ public interface PatrolResultMapper {
List<PatrolData> selectPatrolDataResultByTaskCodeV2(ResultAnalysis resultAnalysis);
List<PatrolData> selectDefectList(DefectListQuery query);
List<PatrolData> selectPatrolDataResultByTaskCodeList(PatrolTaskStatus patrolTaskStatus);
List<BasedataEqpBookMoMain> selectBasedataEqpBookList(BasedataEqpBookMoMain basedataEqpBookMoMain);
@ -127,4 +130,6 @@ public interface PatrolResultMapper {
List<AlgInfo> selectPointAlgInfo(String patrolPointId);
List<AlgInfo> selectAlgInfoByCode(String codes);
PatrolData selectPatrolData(ResultAnalysis resultAnalysis);
}

+ 5
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/service/IPatrolResultService.java View File

@ -1,6 +1,7 @@
package com.inspect.partrolresult.service;
import com.inspect.analysis.domain.ResultAnalysis;
import com.inspect.analysis.domain.DefectListQuery;
import com.inspect.insreport.domain.InspectionReport;
import com.inspect.partrolresult.domain.*;
import com.inspect.resultmain.domain.PatrolTaskResultMain;
@ -30,6 +31,8 @@ public interface IPatrolResultService {
List<PatrolData> selectPatrolDataResultByTaskCodeV2(ResultAnalysis resultAnalysis);
List<PatrolData> selectDefectList(DefectListQuery query);
List<PatrolData> selectPatrolDataResultByTaskCodeList(PatrolTaskStatus patrolTaskStatus);
List<BasedataEqpBookMoMain> selectBasedataEqpBookList(BasedataEqpBookMoMain basedataEqpbookMomain);
@ -109,4 +112,6 @@ public interface IPatrolResultService {
List<AlgInfo> selectAlgInfoByCode(String codes);
List<String> selectAlgList(String taskCode, String sendCode, String specifiedAlg);
PatrolData selectPatrolData(ResultAnalysis resultAnalysis);
}

+ 122
- 63
inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/service/impl/PatrolResultServiceImpl.java View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.inspect.analysis.domain.AlgValue;
import com.inspect.analysis.domain.AnalyseResPoint;
import com.inspect.analysis.domain.DefectListQuery;
import com.inspect.analysis.domain.ResultAnalysis;
import com.inspect.analysis.mapper.ResultAnalysisMapper;
import com.inspect.analysis.utils.ResultAnalysisUtils;
@ -21,6 +22,8 @@ import com.inspect.insreport.domain.InspectionReport;
import com.inspect.insreport.mapper.InspectionReportMapper;
import com.inspect.insreportdata.domain.InspectionReportData;
import com.inspect.insreportdata.mapper.InspectionReportDataMapper;
import com.inspect.insreporttask.domain.InspectionReportTask;
import com.inspect.insreporttask.mapper.InspectionReportTaskMapper;
import com.inspect.message.MessageUtils;
import com.inspect.partrolresult.domain.*;
import com.inspect.partrolresult.mapper.PatrolResultMapper;
@ -75,6 +78,7 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
private final InspectionReportMapper inspectionReportMapper;
private final InspectionReportImgMapper inspectionReportImgMapper;
private final InspectionReportDataMapper inspectionReportDataMapper;
private final InspectionReportTaskMapper inspectionReportTaskMapper;
private final ResultAnalysisMapper resultAnalysisMapper;
private final FeignBasedataAreaService feignBasedataAreaService;
@Value("${inspect.task.report.voltage:±800kv}")
@ -86,7 +90,7 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
@Resource
private ResultAnalysisUtils resultAnalysisUtils;
public PatrolResultServiceImpl(PatrolResultMapper patrolResultMapper, PatrolResultDefaultValueMapper patrolResultDefaultvalueMapper, PatrolTaskResultMainMapper patrolTaskResultMainMapper, PatrolTaskStatusMapper patrolTaskStatusMapper, PatrolTaskMapper patrolTaskMapper, InspectionReportMapper inspectionReportMapper, InspectionReportImgMapper inspectionReportImgMapper, InspectionReportDataMapper inspectionReportDataMapper, ResultAnalysisMapper resultAnalysisMapper, FeignBasedataAreaService feignBasedataAreaService) {
public PatrolResultServiceImpl(PatrolResultMapper patrolResultMapper, PatrolResultDefaultValueMapper patrolResultDefaultvalueMapper, PatrolTaskResultMainMapper patrolTaskResultMainMapper, PatrolTaskStatusMapper patrolTaskStatusMapper, PatrolTaskMapper patrolTaskMapper, InspectionReportMapper inspectionReportMapper, InspectionReportImgMapper inspectionReportImgMapper, InspectionReportDataMapper inspectionReportDataMapper, InspectionReportTaskMapper inspectionReportTaskMapper, ResultAnalysisMapper resultAnalysisMapper, FeignBasedataAreaService feignBasedataAreaService) {
this.patrolResultMapper = patrolResultMapper;
this.patrolResultDefaultvalueMapper = patrolResultDefaultvalueMapper;
this.patrolTaskResultMainMapper = patrolTaskResultMainMapper;
@ -95,6 +99,7 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
this.inspectionReportMapper = inspectionReportMapper;
this.inspectionReportImgMapper = inspectionReportImgMapper;
this.inspectionReportDataMapper = inspectionReportDataMapper;
this.inspectionReportTaskMapper = inspectionReportTaskMapper;
this.resultAnalysisMapper = resultAnalysisMapper;
this.feignBasedataAreaService = feignBasedataAreaService;
}
@ -147,6 +152,11 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
return this.patrolResultMapper.selectPatrolDataResultByTaskCodeV3(resultAnalysis);
}
@Override
public List<PatrolData> selectDefectList(DefectListQuery query) {
return patrolResultMapper.selectDefectList(query);
}
public List<PatrolData> selectPatrolDataResultByTaskCodeList(PatrolTaskStatus patrolTaskStatus) {
return patrolResultMapper.selectPatrolDataResultByTaskCodeList(patrolTaskStatus);
}
@ -528,6 +538,8 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
String stationName = stationMap.get("station_name");
String stationType = stationMap.get("station_type");
String voltLevel = stationMap.get("volt_level");
String cityName = stationMap.get("city_name"); // 地市
String provinceName = stationMap.get("province_name"); // 省份
startTime = PrintUtil.useTime("导出报告:查询站点信息", startTime);
AjaxResult ajaxResult = feignBasedataAreaService.evnList();
@ -735,62 +747,98 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
} else {
patrolStatistics = String.format(messageUtils.get("本次任务巡视总结果数量:%d个,正常结果数量:%d个,缺陷结果数量:%d个"), total, okNum, defectNum);
}
for (Long lineId : lineIds) {
// 一次归档(单个任务 / 多个任务合并归档)只生成一份报告:
// 1) 报告头(inspection_report)只插一条,明细数据(inspection_report_data)覆盖本次归档的全部任务;
// 2) 被归档的每个任务执行都通过 inspection_report_task 关联到这一条报告,
// 任务粒度与明细行中的 task_patrolled_id / task_name 保持一致,
// 便于从任意任务/进度入口定位到这条合并报告。
List<InspectionReportTask> reportTasks = new ArrayList<>();
Long reportLineId = null;
for (int idx = 0; idx < lineIds.size(); idx++) {
Long lineId = lineIds.get(idx);
resultMain.setLineId(lineId);
resultMain.setCheckTime(new Date());
resultMain.setFileStatus("1");
String mainId = String.valueOf(resultMain.getLineId());
// 查询当前的结果信息
PatrolResult patrolResult = resultList.stream().filter(item -> mainId.equals(item.getMainId())).findFirst().orElse(null);
assert patrolResult != null;
String taskCode = patrolResult.getTaskCode();
PatrolTaskResultMain main = patrolTaskResultMainMapper.selectPatrolTaskResultMainByLineId(lineId);
String taskPatrolId = main.getTaskPatrolledId();
if (StringUtils.isEmpty(taskPatrolId)) {
throw new ServiceException("LACK PLAN ID: " + mainId);
} else {
}
if (reportLineId == null) {
// 以本次归档第一个执行任务的信息生成报告头
PatrolResult patrolResult = resultList.stream().filter(item -> mainId.equals(item.getMainId())).findFirst().orElse(null);
assert patrolResult != null;
String taskCode = patrolResult.getTaskCode();
List<PatrolTaskStatus> taskStatuses = this.patrolTaskStatusMapper.selectPatrolTaskStatusList(PatrolTaskStatus.builder().taskPatrolledId(taskPatrolId).build());
if (taskStatuses.isEmpty()) {
throw new ServiceException("LACKING STATUS: " + mainId);
}
List<PatrolTask> list = patrolTaskMapper.selectPatrolTaskList(PatrolTask.builder().taskCode(taskCode).build());
PatrolTask task = new PatrolTask();
if (list.isEmpty()) {
log.error("TASK PLAN LOST: " + taskCode);
} else {
List<PatrolTask> list = patrolTaskMapper.selectPatrolTaskList(PatrolTask.builder().taskCode(taskCode).build());
PatrolTask task = new PatrolTask();
if (list.isEmpty()) {
log.error("TASK PLAN LOST: " + taskCode);
} else {
task = list.get(0);
}
InspectionReport report = new InspectionReport();
report.setPatrolStatistics(patrolStatistics);
report.setInspectionDate(new Date());
report.setTaskResultId(mainId);
report.setInspectionStartTime(startTimes.get(0));
report.setInspectionEndTime(DateUtils.parseDate(endTimes.get(endTimes.size() - 1)));
report.setTaskId(task.getTaskId() == null ? "" : String.valueOf(task.getTaskId()));
report.setEnvInfo(StringUtils.join(envoList, ","));
report.setTaskPatrolledId(taskPatrolId);
report.setInspectionTaskName(taskName);
report.setDescription(resultMain.getTaskResult());
report.setCheckPerson(resultMain.getCheckPerson());
report.setCheckTime(resultMain.getCheckTime());
report.setStationName(stationName);
report.setVoltLevel(StringUtils.isEmpty(voltLevel) ? voltage : voltLevel);
report.setFilter("0");
report.setStationType(stationType);
inspectionReportMapper.insertInspectionReport(report);
// 为每个报告生成数据
batchInsertReportData_shaoxing(String.valueOf(report.getLineId()), patrolResultRefs, basePointAreaInfoList, lineIds);
reportIds.add(report.getLineId());
task = list.get(0);
}
InspectionReport report = new InspectionReport();
report.setPatrolStatistics(patrolStatistics);
report.setInspectionDate(startTimes.get(0));
report.setTaskResultId(mainId);
report.setInspectionStartTime(startTimes.get(0));
report.setInspectionEndTime(DateUtils.parseDate(endTimes.get(endTimes.size() - 1)));
report.setTaskId(task.getTaskId() == null ? "" : String.valueOf(task.getTaskId()));
report.setEnvInfo(StringUtils.join(envoList, ","));
report.setTaskPatrolledId(taskPatrolId);
report.setInspectionTaskName(taskName);
report.setDescription(resultMain.getTaskResult());
report.setCheckPerson(resultMain.getCheckPerson());
report.setCheckTime(resultMain.getCheckTime());
report.setStationName(stationName);
report.setVoltLevel(StringUtils.isEmpty(voltLevel) ? voltage : voltLevel);
report.setFilter("0");
report.setStationType(stationType);
// 位置 = 省份 + 地市(如 四川省成都市)
report.setLocation(buildLocation(provinceName, cityName));
inspectionReportMapper.insertInspectionReport(report);
// 为这一份报告生成明细数据(覆盖本次归档的所有任务)
batchInsertReportData_shaoxing(String.valueOf(report.getLineId()), patrolResultRefs, basePointAreaInfoList, lineIds);
reportLineId = report.getLineId();
reportIds.add(reportLineId);
}
this.patrolTaskResultMainMapper.updatePatrolTaskResultMain(resultMain);
this.patrolResultMapper.updatePatrolResultByMainId(String.valueOf(lineId));
// 记录 任务-报告 关联(同一份报告下每个任务一条)
InspectionReportTask reportTask = new InspectionReportTask();
reportTask.setReportLineId(reportLineId);
reportTask.setTaskResultLineId(lineId);
reportTask.setTaskPatrolledId(taskPatrolId);
reportTask.setTaskName(main.getTaskName());
reportTask.setCreateTime(new Date());
reportTasks.add(reportTask);
}
if (!reportTasks.isEmpty()) {
inspectionReportTaskMapper.batchInsertInspectionReportTask(reportTasks);
}
startTime = PrintUtil.useTime("导出报告:全部数据入库", startTime);
return reportIds;
}
/** 拼接报告位置:省份 + 地市(如 四川省成都市;缺省时只显示存在的部分) */
private String buildLocation(String provinceName, String cityName) {
StringBuilder sb = new StringBuilder();
if (StringUtils.isNotEmpty(provinceName)) {
sb.append(provinceName);
}
if (StringUtils.isNotEmpty(cityName)) {
sb.append(cityName);
}
return sb.toString();
}
@Transactional
public List saveReportV2(PatrolTaskResultMain resultMain, List<PatrolResult> resultList) {
long startTime = System.currentTimeMillis();
@ -1321,31 +1369,33 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
reportData.setTaskName(resultTaskName);
reportData.setTaskPatrolledId(taskPatrolledId);
reportData.setTaskResultId(String.valueOf(mainId));
if ("1".equals(patrolResultRef.getResultType()) && validAlgTypes.contains(patrolResultRef.getAlgType())) {
reportData.setInspectionResults(patrolResultRef.getValue());
} else {
reportData.setInspectionResults(patrolResultRef.getDescription());
}
// if ("1".equals(patrolResultRef.getResultType()) && validAlgTypes.contains(patrolResultRef.getAlgType())) {
// reportData.setInspectionResults(patrolResultRef.getValue());
// } else {
// reportData.setInspectionResults(patrolResultRef.getDescription());
// }
reportData.setInspectionResults(patrolResultRef.getDescription());
reportData.setInspectionValue(patrolResultRef.getValue());
reportData.setAlgName(patrolResultRef.getAlgName());
String threshold = patrolResultRef.getThreshold();
if (StringUtils.isBlank(threshold)) {
threshold = "";
}
if ("1".equals(patrolResultRef.getResultType()) && threshold.contains("sub_53")) {
reportData.setInspectionResults(patrolResultRef.getValue());
}
if ("1".equals(patrolResultRef.getResultType()) && (threshold.contains("sub_52") || threshold.contains("sub_53"))
&& !"0".equals(patrolResultRef.getValue())
&& !"1".equals(patrolResultRef.getValue())
&& !"-1".equals(patrolResultRef.getValue())
&& !"check image".equals(patrolResultRef.getValue())) {
reportData.setInspectionResults(patrolResultRef.getValue());
}
// String threshold = patrolResultRef.getThreshold();
// if (StringUtils.isBlank(threshold)) {
// threshold = "";
// }
// if ("1".equals(patrolResultRef.getResultType()) && threshold.contains("sub_53")) {
// reportData.setInspectionResults(patrolResultRef.getValue());
// }
//
// if ("1".equals(patrolResultRef.getResultType()) && (threshold.contains("sub_52") || threshold.contains("sub_53"))
// && !"0".equals(patrolResultRef.getValue())
// && !"1".equals(patrolResultRef.getValue())
// && !"-1".equals(patrolResultRef.getValue())
// && !"check image".equals(patrolResultRef.getValue())) {
// reportData.setInspectionResults(patrolResultRef.getValue());
// }
// 给读数增加单位
String resValue = resultAnalysisUtils.addUnit2Value(reportData.getInspectionResults(), patrolResultRef.getAlgType(), patrolResultRef.getUnit());
reportData.setInspectionResults(resValue);
String resValue = resultAnalysisUtils.addUnit2Value(reportData.getInspectionValue(), patrolResultRef.getAlgType(), patrolResultRef.getUnit());
reportData.setInspectionValue(resValue);
String pointStatus = patrolResultRef.getPointStatus();
String resultType = patrolResultRef.getResultType();
@ -1395,16 +1445,20 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
//List<resultList> results = JSONObject.parseObject(resultContent).getJSONArray("resultList").toJavaList(resultList.class);
//String imgUrl = results.get(0).getResults().get(0).getResImageUrl();
AnalyseResPoint analyseResPoint = JSONObject.parseObject(resultContent, AnalyseResPoint.class);
String imgUrl = analyseResPoint.getResImageUrl();
String resImageUrl = analyseResPoint.getResImageUrl();
String imageNormalUrlPath = analyseResPoint.getImageNormalUrlPath();
if (StringUtils.isEmpty(imgUrl)) {
imgUrl = patrolResultRef.getFilePath().split(StringUtils.COMMA)[0];
if (StringUtils.isEmpty(resImageUrl)) {
resImageUrl = patrolResultRef.getFilePath().split(StringUtils.COMMA)[0];
}
if (StringUtils.isEmpty(imageNormalUrlPath)) {
imageNormalUrlPath = patrolResultRef.getFilePath().split(StringUtils.COMMA)[0];
}
InspectionReportImg reportImg = new InspectionReportImg();
reportImg.setReportInfoId(reportDataId);
reportImg.setImg(imgUrl);
reportImg.setImgSrc(imgUrl);
reportImg.setImg(resImageUrl);
reportImg.setImgSrc(patrolResultRef.getFilePath().split(StringUtils.COMMA)[0]);
reportImg.setImageNormalUrlPath(imageNormalUrlPath);
reportImg.setImgType("0");
reportImg.setCreatTime(new Date());
reportImages.add(reportImg);
@ -2222,6 +2276,11 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
return algList;
}
@Override
public PatrolData selectPatrolData(ResultAnalysis resultAnalysis) {
return patrolResultMapper.selectPatrolData(resultAnalysis);
}
private PatrolResultRef mergePatrolResultRef(PatrolResultRef filterResult, PatrolResultRef AIResult, String resultType) {
PatrolResultRef merged = new PatrolResultRef();
merged.setDeviceId(filterResult.getDeviceId());


+ 283
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/reportfont/ReportFontEmbedder.java View File

@ -0,0 +1,283 @@
package com.inspect.reportfont;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFSettings;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLanguage;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSettings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
/**
* 动环巡检报告 Word 字体嵌入。
* <p>
* 原理(Word 的"嵌入字体"机制,让查看方电脑无需安装字体):
* <ol>
* <li>docx 包内的字体"注册表"是 {@code word/fontTable.xml}:按字体名(w:name)列出字体,
* 每个条目通过 {@code embedRegular/embedBold} 属性(r:id)引用一个字体部件;</li>
* <li>字体部件({@code word/fonts/*.odttf})是混淆后的字体数据——按 OOXML §17.8.1,
* 字体前 32 字节与混淆 key 异或,key 由条目中的 {@code fontKey}(GUID)推导;
* Word 打开文档时按同样算法还原后使用;</li>
* <li>正文 run 只写字体名(w:eastAsia),Word 按名字查 fontTable 命中嵌入字体。</li>
* </ol>
* 实现要点:
* <ul>
* <li>{@code fontTable.xml} 直接使用 Word 自身生成的模板(fontKey 固定),
* 兼容性最稳(实测动态生成的 fontTable Word 不识别嵌入字体);</li>
* <li>字体部件按模板固定的 font1..font5 命名,混淆 key 与模板条目一一对应,
* 且按序 addRelationship 保证 rId 映射正确。</li>
* </ul>
*/
@Slf4j
@Component
public class ReportFontEmbedder {
// ============================ 部件/关系类型常量 ============================
private static final String REL_FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
private static final String REL_FONT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
private static final String REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
private static final String REL_THEME = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
private static final String REL_WEB_SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings";
private static final String CT_FONT_TABLE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml";
private static final String CT_FONT = "application/vnd.openxmlformats-officedocument.obfuscatedFont";
private static final String CT_STYLES = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
private static final String CT_THEME = "application/vnd.openxmlformats-officedocument.theme+xml";
private static final String CT_WEB_SETTINGS = "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml";
/** 模板文件所在 classpath 目录 */
private static final String TEMPLATE_DIR = "report/wordparts/";
/**
* 报告用到的 5 个嵌入字体部件(与 fontTable 模板的条目一一对应):
* 部件名 -> {字体文件, 混淆 key}。字体文件位于 classpath fonts/ 或外部目录。
* 注意:fontTable 模板中「仿宋_GB2312」有 regular/bold/italic 三个样式条目
* (rId1..3,对应 font1..3),正文/表头加粗/签署表斜体都引用同一份仿宋_GB2312;
* 方正小标宋简体只嵌 bold(rId4/font4,标题使用);
* 黑体只嵌 bold(rId5/font5,标题字体)。
*/
private static final List<EmbeddedFont> EMBEDDED_FONTS = Arrays.asList(
new EmbeddedFont("font1", "仿宋_GB2312.ttf", "DAF739B1-18F1-4906-820A-C106608C2B73"),
new EmbeddedFont("font2", "仿宋_GB2312.ttf", "3039BACA-5140-4C4C-8AFB-F7B61E374105"),
new EmbeddedFont("font3", "仿宋_GB2312.ttf", "73410ACE-E9FA-46ED-BA3D-9ECB6C9FEF4E"),
new EmbeddedFont("font4", "方正小标宋简体.ttf", "4D5F1B72-2D28-435A-B7BC-848B95F30EBB"),
new EmbeddedFont("font5", "simhei.ttf", "818E8C25-A7E2-4610-9234-360D3B3511E5"));
/**
* 外部字体目录({@code report.donghuan.fonts-dir}),
* 留空则从 classpath {@code fonts/} 加载(随 jar 打包)。
*/
@Value("${report.donghuan.fonts-dir:}")
private String fontsDir;
/** 一个待嵌入字体:部件名 + 字体文件 + 混淆 key(fontKey) */
private static final class EmbeddedFont {
final String partName;
final String fileName;
final String fontKey;
EmbeddedFont(String partName, String fileName, String fontKey) {
this.partName = partName;
this.fileName = fileName;
this.fontKey = fontKey;
}
}
/**
* 把报告所需字体嵌入 docx(在生成正文内容前调用)。
* 字体缺失时不阻断归档,报告退化为不带嵌入字体。
*/
public void embed(XWPFDocument doc) {
try {
OPCPackage pkg = doc.getPackage();
PackagePart documentPart = pkg.getPart(PackagingURIHelper.createPartName("/word/document.xml"));
// 1. fontTable.xml(静态模板,含字体注册信息)
addPart(pkg, "/word/fontTable.xml", CT_FONT_TABLE, "fontTable.xml");
// 2. 字体部件 font1..font5.odttf,关系挂到 fontTable 上(rId 按序分配,与模板一致)
embedFontParts(pkg, pkg.getPart(PackagingURIHelper.createPartName("/word/fontTable.xml")));
// 3. 补齐 Word 常规部件(styles/theme/webSettings,缺了 Word 不解析嵌入字体)
addPart(pkg, "/word/styles.xml", CT_STYLES, "styles.xml");
addPart(pkg, "/word/theme/theme1.xml", CT_THEME, "theme1.xml");
addPart(pkg, "/word/webSettings.xml", CT_WEB_SETTINGS, "webSettings.xml");
// 4. document.xml 引用 fontTable/styles/theme/webSettings
addDocumentRelations(documentPart, pkg);
// 5. settings.xml 写入嵌入字体标志
addSettingsFlags(doc);
log.info("[FONT_EMBED] 报告字体嵌入完成");
} catch (Exception e) {
log.error("[FONT_EMBED] 字体嵌入失败,本次报告不带嵌入字体", e);
}
}
/** 从模板目录创建部件并写入内容,同时挂到 document.xml */
private PackagePart addPart(OPCPackage pkg, String partName, String contentType, String templateName) throws Exception {
PackagePart part = pkg.createPart(PackagingURIHelper.createPartName(partName), contentType);
byte[] bytes = readTemplate(templateName);
if (bytes != null) {
try (OutputStream os = part.getOutputStream()) {
os.write(bytes);
}
}
log.info("[FONT_EMBED] 已创建部件: {} ({} bytes)", partName, bytes == null ? 0 : bytes.length);
return part;
}
/** 把 document.xml 与 fontTable/styles/theme/webSettings 建立关系 */
private void addDocumentRelations(PackagePart documentPart, OPCPackage pkg) throws Exception {
documentPart.addRelationship(
PackagingURIHelper.createPartName("/word/fontTable.xml"), TargetMode.INTERNAL, REL_FONT_TABLE);
documentPart.addRelationship(
PackagingURIHelper.createPartName("/word/styles.xml"), TargetMode.INTERNAL, REL_STYLES);
documentPart.addRelationship(
PackagingURIHelper.createPartName("/word/theme/theme1.xml"), TargetMode.INTERNAL, REL_THEME);
documentPart.addRelationship(
PackagingURIHelper.createPartName("/word/webSettings.xml"), TargetMode.INTERNAL, REL_WEB_SETTINGS);
}
/**
* 依次创建 font1..font5.odttf 部件(完整字体按固定 key 混淆),
* 并把 font 关系挂到 fontTable 部件(addRelationship 按序生成 rId1..rId5,
* 与静态模板中的引用一一对应)。
*/
private void embedFontParts(OPCPackage pkg, PackagePart fontTablePart) throws Exception {
for (EmbeddedFont font : EMBEDDED_FONTS) {
byte[] fontBytes = loadFontBytes(font.fileName);
if (fontBytes == null) {
log.warn("[FONT_EMBED] 字体文件缺失,跳过部件 {}: {}", font.partName, font.fileName);
continue;
}
PackagePart fontPart = pkg.createPart(
PackagingURIHelper.createPartName("/word/fonts/" + font.partName + ".odttf"), CT_FONT);
try (OutputStream os = fontPart.getOutputStream()) {
os.write(obfuscateFont(fontBytes, font.fontKey));
}
fontTablePart.addRelationship(fontPart.getPartName(), TargetMode.INTERNAL, REL_FONT);
log.info("[FONT_EMBED] 已嵌入字体部件: {} <- {} ({} bytes)", font.partName, font.fileName, fontBytes.length);
}
}
/**
* 在 settings.xml 写入嵌入字体标志,Word 靠它识别"文档带嵌入字体":
* {@code <w:embedTrueTypeFonts/>}、{@code <w:saveSubsetFonts/>}、
* {@code <w:themeFontLang w:val="en-US" w:eastAsia="zh-CN"/>}。
* <p>
* 注意:POI 4.1.2 保存 settings 时从内部缓存的 CTSettings 写回(直接改部件内容会被覆盖),
* 且不能整体替换缓存对象(类型不兼容),因此反射取出缓存的 CTSettings 用其 API 添加标志。
*/
private void addSettingsFlags(XWPFDocument doc) {
try {
for (POIXMLDocumentPart part : doc.getRelations()) {
if (part instanceof XWPFSettings) {
java.lang.reflect.Field ctField = XWPFSettings.class.getDeclaredField("ctSettings");
ctField.setAccessible(true);
CTSettings ctSettings = (CTSettings) ctField.get(part);
if (!ctSettings.isSetEmbedTrueTypeFonts()) {
ctSettings.addNewEmbedTrueTypeFonts();
}
if (!ctSettings.isSetSaveSubsetFonts()) {
ctSettings.addNewSaveSubsetFonts();
}
if (!ctSettings.isSetThemeFontLang()) {
CTLanguage lang = ctSettings.addNewThemeFontLang();
lang.setVal("en-US");
lang.setEastAsia("zh-CN");
}
log.info("[FONT_EMBED] settings.xml 已写入嵌入字体标志");
return;
}
}
log.warn("[FONT_EMBED] 未找到 settings 部件,跳过嵌入字体标志");
} catch (Exception e) {
log.warn("[FONT_EMBED] 写入 settings.xml 嵌入字体标志失败", e);
}
}
/** 读取 classpath 模板内容(report/wordparts/) */
private byte[] readTemplate(String name) {
try (InputStream is = getClass().getClassLoader().getResourceAsStream(TEMPLATE_DIR + name)) {
if (is == null) {
log.warn("[FONT_EMBED] 模板缺失: {}", TEMPLATE_DIR + name);
return null;
}
return readAll(is);
} catch (Exception e) {
log.warn("[FONT_EMBED] 读取模板失败: {}", name, e);
return null;
}
}
/**
* 加载字体文件:优先外部目录(report.donghuan.fonts-dir),
* 其次 classpath {@code fonts/}(随 jar 打包)。
*/
private byte[] loadFontBytes(String fileName) {
try {
if (fontsDir != null && !fontsDir.isEmpty()) {
Path fontPath = Paths.get(fontsDir, fileName);
if (Files.exists(fontPath)) {
return Files.readAllBytes(fontPath);
}
}
try (InputStream is = getClass().getClassLoader().getResourceAsStream("fonts/" + fileName)) {
if (is != null) {
return readAll(is);
}
}
log.warn("[FONT_EMBED] 字体文件不存在: {} (fontsDir={}, classpath=fonts/)", fileName, fontsDir);
} catch (Exception e) {
log.warn("[FONT_EMBED] 读取字体文件失败: {}", fileName, e);
}
return null;
}
/**
* OOXML §17.8.1 字体混淆(ODTTF):字体前 32 字节与混淆 key 异或。
* key 由 fontKey(GUID 去掉横线、32 位十六进制)每 2 字符转 1 字节后整体反转得到。
* Word 打开文档时按同样算法还原字体。
*/
private byte[] obfuscateFont(byte[] fontBytes, String fontKey) {
byte[] out = fontBytes.clone();
byte[] key = buildObfuscationKey(fontKey);
int len = Math.min(32, out.length);
for (int i = 0; i < len; i++) {
out[i] ^= key[i % 16];
}
return out;
}
/** 由 fontKey(GUID)生成 16 字节混淆 key:十六进制转字节后整体反转 */
private byte[] buildObfuscationKey(String fontKey) {
String hex = fontKey.replace("-", "");
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) Integer.parseInt(hex.substring((15 - i) * 2, (16 - i) * 2), 16);
}
return key;
}
private byte[] readAll(InputStream is) throws java.io.IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int n;
while ((n = is.read(buf)) != -1) {
bos.write(buf, 0, n);
}
return bos.toByteArray();
}
}

+ 46
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/config/ReportPushProperties.java View File

@ -0,0 +1,46 @@
package com.inspect.reportpush.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 任务巡检完成自动归档 + 生成报告 + websocket 推送配置
* <p>
* 对应 application.yml 中 {@code inspect.report-push.*} 配置块
* (可在 nacos 配置中心按现场覆盖)。
*
* @author inspect
*/
@Data
@Component
@ConfigurationProperties(prefix = "inspect.report-push")
public class ReportPushProperties {
/** 是否启用推送 */
private boolean enabled = true;
/** 是否启用自动归档 */
private boolean archive = true;
/** 自动归档审核人 */
private String checkPerson = "admin";
/** 自动归档巡视结论 */
private String taskResult = "巡检完成,自动归档";
/** 任务状态更新事务提交等待时间(毫秒) */
private long afterCommitDelayMillis = 2000;
/** 归档防重锁过期时间(秒) */
private long lockExpireSeconds = 86400;
/** 上传报告的场站ID(为空时取 basedata_station.station_no) */
private String stationId = "";
/** 上传报告的巡检类型(风机巡检/光伏巡检/线路巡检/山火巡检/机器人巡检) */
private String inspectType = "动环智能巡检";
/** 上传报告的巡检设备名称(机库名称、机器人名称、摄像头名称,可选) */
private String inspectDeviceName = "";
}

+ 64
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/controller/ReportPushController.java View File

@ -0,0 +1,64 @@
package com.inspect.reportpush.controller;
import com.alibaba.fastjson.JSONObject;
import com.inspect.api.service.ReportUploadService;
import com.inspect.base.core.utils.DateUtils;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.core.web.controller.BaseController;
import com.inspect.base.core.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* 报告推送调试接口(对接外部平台-盐源新能源智慧感知平台《巡检报告接口文档》)
* <ul>
* <li>GET /reportpush/getToken 获取 Joinbright-Token(复用 ExtAuthService,验证认证连通性)</li>
* <li>GET /reportpush/test 上传一条测试报告(验证 /uploadReport 上传接口连通性)</li>
* </ul>
*/
@Slf4j
@RestController
@RequestMapping("/reportpush")
public class ReportPushController extends BaseController {
@Autowired
private ReportUploadService reportUploadService;
/**
* 获取 Joinbright-Token(调试用)
*/
@GetMapping("/getToken")
public AjaxResult getToken() {
String token = reportUploadService.getJoinbrightToken();
return StringUtils.isNotEmpty(token) ? AjaxResult.success(token) : AjaxResult.error("获取Joinbright-Token失败");
}
/**
* 上传测试报告(调试用)
*/
@GetMapping("/test")
public AjaxResult test(String message) {
byte[] dummyFile = "inspect report upload test".getBytes(StandardCharsets.UTF_8);
String now = DateUtils.parseDateToStr(DateUtils.yyyyMMddHHmmss2, new Date());
JSONObject reportInfo = new JSONObject();
reportInfo.put("id", "TEST-" + System.currentTimeMillis());
reportInfo.put("name", "测试报告");
reportInfo.put("fileType", "docx");
reportInfo.put("stationId", "");
reportInfo.put("stationName", "测试电站");
reportInfo.put("inspectTime", now);
reportInfo.put("createTime", now);
reportInfo.put("inspectType", "机器人巡检");
reportInfo.put("inspectContent", StringUtils.isEmpty(message) ? "巡检报告上传接口联调测试" : message);
reportInfo.put("taskId", "TEST");
reportInfo.put("taskName", "测试任务");
boolean ok = reportUploadService.uploadReport(dummyFile, "test_report.docx", reportInfo);
return ok ? AjaxResult.success("上传成功") : AjaxResult.error("上传失败, 请查看日志");
}
}

+ 191
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/service/ReportAutoArchiveService.java View File

@ -0,0 +1,191 @@
package com.inspect.reportpush.service;
import com.inspect.base.core.constant.RedisConst;
import com.inspect.base.core.enums.TaskStatus;
import com.inspect.base.core.enums.TaskType;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService;
import com.inspect.analysis.domain.ResultAnalysis;
import com.inspect.analysis.service.IResultAnalysisService;
import com.inspect.reportpush.config.ReportPushProperties;
import com.inspect.resultmain.controller.DonghuanReportExportController;
import com.inspect.resultmain.domain.PatrolTaskResultMain;
import com.inspect.resultmain.service.IPatrolTaskResultMainService;
import com.inspect.taskstatus.domain.PatrolTaskStatus;
import com.inspect.taskstatus.service.IPatrolTaskStatusService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 任务巡检完成 → 自动归档 + 生成报告(动环 Word) → websocket 推送
* <p>
* 由 {@code TaskStatusChangeAspect} 在任务状态变更为「已完成(1)」时异步触发,
* 仅处理主任务/普通任务(子任务由主任务统一归档),
* 通过 Redis 防重锁避免重复归档。
*
* @author inspect
*/
@Slf4j
@Service
public class ReportAutoArchiveService {
private final ReportPushProperties properties;
private final RedisService redisService;
private final IPatrolTaskStatusService patrolTaskStatusService;
private final IPatrolTaskResultMainService patrolTaskResultMainService;
private final IResultAnalysisService resultAnalysisService;
private final DonghuanReportExportController donghuanReportExportController;
public ReportAutoArchiveService(ReportPushProperties properties,
RedisService redisService,
IPatrolTaskStatusService patrolTaskStatusService,
IPatrolTaskResultMainService patrolTaskResultMainService,
IResultAnalysisService resultAnalysisService,
DonghuanReportExportController donghuanReportExportController) {
this.properties = properties;
this.redisService = redisService;
this.patrolTaskStatusService = patrolTaskStatusService;
this.patrolTaskResultMainService = patrolTaskResultMainService;
this.resultAnalysisService = resultAnalysisService;
this.donghuanReportExportController = donghuanReportExportController;
}
/**
* 任务状态变更为已完成时触发(异步)。
*
* @param taskStatus 变更后的任务状态(切面入参)
*/
@Async
public void onTaskCompleted(PatrolTaskStatus taskStatus) {
try {
if (!properties.isArchive()) {
log.info("[AUTO_ARCHIVE] 未启用 inspect.report-push.archive,跳过自动归档: taskPatrolledId={}",
taskStatus == null ? null : taskStatus.getTaskPatrolledId());
return;
}
if (taskStatus == null || StringUtils.isEmpty(taskStatus.getTaskPatrolledId())) {
log.warn("[AUTO_ARCHIVE] 任务状态参数为空,跳过自动归档");
return;
}
// 子任务不单独归档,由主任务统一归档
if (TaskType.SUB.getCode().equals(taskStatus.getTaskType())) {
log.debug("[AUTO_ARCHIVE] 子任务不触发归档: {}", taskStatus.getTaskPatrolledId());
return;
}
// 归档单元:主任务(联合任务)或普通任务自身
PatrolTaskStatus mainTask = patrolTaskStatusService.selectPatrolTaskStatusMain(taskStatus.getTaskPatrolledId());
String archiveTaskPatrolledId = mainTask != null
? mainTask.getTaskPatrolledId() : taskStatus.getTaskPatrolledId();
if (StringUtils.isEmpty(archiveTaskPatrolledId)) {
log.warn("[AUTO_ARCHIVE] 无法确定归档任务ID,跳过: {}", taskStatus.getTaskPatrolledId());
return;
}
// 防重锁:同一归档单元只允许一次自动归档
String lockKey = RedisConst.REPORT_PUSH_LOCK + archiveTaskPatrolledId;
Boolean locked = redisService.redisTemplate.opsForValue()
.setIfAbsent(lockKey, archiveTaskPatrolledId,
properties.getLockExpireSeconds(), TimeUnit.SECONDS);
if (!Boolean.TRUE.equals(locked)) {
log.info("[AUTO_ARCHIVE] 任务 {} 已在归档/推送中(防重锁),跳过", archiveTaskPatrolledId);
return;
}
log.info("[AUTO_ARCHIVE] 获取归档防重锁: {}", lockKey);
// 等待任务状态事务提交、结果数据落库
long waitMillis = properties.getAfterCommitDelayMillis();
if (waitMillis > 0) {
try {
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
releaseLock(lockKey);
return;
}
}
// 二次校验:任务仍为已完成状态
PatrolTaskStatus freshTask = patrolTaskStatusService.selectPatrolTaskStatusByTaskPatrolledId(archiveTaskPatrolledId);
if (freshTask == null || !TaskStatus.DONE.getCode().equals(freshTask.getTaskState())) {
log.info("[AUTO_ARCHIVE] 任务 {} 当前状态 {},非已完成,跳过自动归档",
archiveTaskPatrolledId, freshTask == null ? "null" : freshTask.getTaskState());
releaseLock(lockKey);
return;
}
// 二次校验:已归档(file_status=1)的任务不重复归档
PatrolTaskResultMain existMain = patrolTaskResultMainService
.selectPatrolTaskResultMainByTaskPatrolledId(archiveTaskPatrolledId);
if (existMain != null && "1".equals(existMain.getFileStatus())) {
log.info("[AUTO_ARCHIVE] 任务 {} 已归档(fileStatus=1),跳过", archiveTaskPatrolledId);
releaseLock(lockKey);
return;
}
// 归档范围:主任务 + 其下所有子任务(联合任务)
List<String> taskPatrolledIds = new ArrayList<>();
taskPatrolledIds.add(archiveTaskPatrolledId);
List<PatrolTaskStatus> subTasks = patrolTaskStatusService.selectPatrolTaskStatusListByMainId(archiveTaskPatrolledId);
if (subTasks != null) {
for (PatrolTaskStatus subTask : subTasks) {
if (StringUtils.isNotEmpty(subTask.getTaskPatrolledId())) {
taskPatrolledIds.add(subTask.getTaskPatrolledId());
}
}
}
// 触发归档(动环 Word 报告),报告生成后由 exportDonghuanReport 回调推送
PatrolTaskResultMain resultMain = new PatrolTaskResultMain();
resultMain.setTaskPatrolledIds(taskPatrolledIds);
resultMain.setCheckPerson(properties.getCheckPerson());
resultMain.setTaskResult(properties.getTaskResult());
log.info("[AUTO_ARCHIVE] 触发自动归档: taskPatrolledIds={}, checkPerson={}, taskResult={}",
taskPatrolledIds, resultMain.getCheckPerson(), resultMain.getTaskResult());
donghuanReportExportController.updateInfo_donghuan(resultMain);
} catch (Exception e) {
log.error("[AUTO_ARCHIVE] 自动归档异常: taskPatrolledId={}",
taskStatus == null ? null : taskStatus.getTaskPatrolledId(), e);
}
}
/**
* 校验任务(含子任务)是否存在缺陷数据(初筛缺陷 + 大模型缺陷)
*/
private boolean hasDefect(List<String> taskPatrolledIds) {
try {
Map<String, ResultAnalysis> abnormalMap = resultAnalysisService.selectTaskAbnormalDataBatch(taskPatrolledIds);
if (abnormalMap == null || abnormalMap.isEmpty()) {
return false;
}
int csQxNum = 0, aiQxNum = 0;
for (ResultAnalysis analysis : abnormalMap.values()) {
if (analysis == null) {
continue;
}
csQxNum += analysis.getCsQxNum();
aiQxNum += analysis.getAiQxNum();
}
return csQxNum + aiQxNum > 0;
} catch (Exception e) {
log.error("[AUTO_ARCHIVE] 查询缺陷数据异常,按存在缺陷处理: taskPatrolledIds={}", taskPatrolledIds, e);
return true;
}
}
private void releaseLock(String lockKey) {
try {
redisService.deleteObject(lockKey);
} catch (Exception e) {
log.warn("[AUTO_ARCHIVE] 释放防重锁失败: {}", lockKey, e);
}
}
}

+ 148
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/service/ReportPushService.java View File

@ -0,0 +1,148 @@
package com.inspect.reportpush.service;
import com.alibaba.fastjson.JSONObject;
import com.inspect.api.domain.ReportPushMessage;
import com.inspect.api.service.ReportPushPublisher;
import com.inspect.base.core.utils.DateUtils;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.insreport.domain.InspectionReport;
import com.inspect.partrolresult.mapper.PatrolResultMapper;
import com.inspect.reportpush.config.ReportPushProperties;
import com.inspect.task.mapper.PatrolTaskMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Map;
/**
* 巡检报告推送服务(先上报 RabbitMQ,由 inspect-main-api 的
* {@link com.inspect.api.service.ReportUploadConsumer} 异步 HTTP 上传到外部平台,
* 见《盐源新能源智慧感知平台_巡检报告接口文档 v1.0.0》)。
* <p>
* 报告归档(Word)生成完毕后,组装 reportInfo 并发布 report.upload.queue 消息,
* 消费者下载报告 docx 后 POST /jb-nessp-external/api/uploadReport 上传
* (multipart/form-data:file + reportInfo,请求头携带 joinbright-token)。
*
* @author inspect
*/
@Slf4j
@Service
public class ReportPushService {
private final ReportPushProperties properties;
private final ReportPushPublisher reportPushPublisher;
private final PatrolTaskMapper patrolTaskMapper;
private final PatrolResultMapper patrolResultMapper;
public ReportPushService(ReportPushProperties properties,
ReportPushPublisher reportPushPublisher,
PatrolTaskMapper patrolTaskMapper,
PatrolResultMapper patrolResultMapper) {
this.properties = properties;
this.reportPushPublisher = reportPushPublisher;
this.patrolTaskMapper = patrolTaskMapper;
this.patrolResultMapper = patrolResultMapper;
}
/**
* 报告生成并上传完成后推送。自动归档与手动归档均会触发。
*
* @param report 已保存 filePath 的巡检报告
*/
public void pushReport(InspectionReport report) {
pushReport(report, null);
}
/**
* 报告生成并上传完成后推送(带发起人)。
*
* @param report 已保存 filePath 的巡检报告
* @param username 报告发起人(当前登录账号,可为空)
*/
public void pushReport(InspectionReport report, String username) {
if (report == null) {
return;
}
if (!properties.isEnabled()) {
log.info("[REPORT_PUSH] 未启用 report-push 配置,跳过报告推送: reportId={}", report.getLineId());
return;
}
// 报告文件路径(动环导出保存的是 .docx,兼容 .xls 结尾路径)
String filePath = report.getFilePath();
if (StringUtils.isEmpty(filePath)) {
log.warn("[REPORT_PUSH] 报告文件路径为空, 无法推送: reportId={}", report.getLineId());
return;
}
String fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
// 报告元数据(《盐源新能源智慧感知平台_巡检报告接口文档_v1.0.0(2)》reportInfo 字段,发布时组装)
JSONObject reportInfo = new JSONObject();
reportInfo.put("id", report.getLineId() == null
? String.valueOf(System.currentTimeMillis()) : String.valueOf(report.getLineId()));
reportInfo.put("name", StringUtils.isNotEmpty(fileName)
? fileName : "巡检报告");
reportInfo.put("fileType", "docx");
reportInfo.put("stationId", resolveStationId());
reportInfo.put("stationName", resolveStationName(report));
reportInfo.put("inspectTime", report.getInspectionStartTime() == null ? null
: DateUtils.parseDateToStr(DateUtils.yyyyMMddHHmmss2, report.getInspectionStartTime()));
reportInfo.put("createTime", DateUtils.parseDateToStr(DateUtils.yyyyMMddHHmmss2, new Date()));
reportInfo.put("inspectType", resolveInspectType());
reportInfo.put("inspectDeviceName", properties.getInspectDeviceName());
reportInfo.put("inspectContent", StringUtils.isNotEmpty(report.getPatrolStatistics())
? report.getPatrolStatistics() : report.getInspectionTaskName());
reportInfo.put("taskId", StringUtils.isNotEmpty(report.getTaskPatrolledId()) ? report.getTaskPatrolledId() : "");
reportInfo.put("taskName", report.getInspectionTaskName());
// 报告发起人:归档入口捕获的当前登录账号,为空时取配置审核人
reportInfo.put("username", StringUtils.isNotEmpty(username) ? username : properties.getCheckPerson());
ReportPushMessage message = new ReportPushMessage();
message.setReportId(report.getLineId());
message.setFilePath(filePath);
message.setReportInfo(reportInfo.toJSONString());
reportPushPublisher.publish(message);
log.info("[REPORT_PUSH] 报告推送消息已发布: reportId={}, filePath={}, username={}, message={}",
report.getLineId(), filePath, username, message);
}
/**
* 场站ID:配置优先,其次取 basedata_station.station_no
*/
private String resolveStationId() {
if (StringUtils.isNotEmpty(properties.getStationId())) {
return properties.getStationId();
}
try {
String stationNo = patrolTaskMapper.selectStationNo();
return stationNo == null ? "" : stationNo;
} catch (Exception e) {
log.warn("[REPORT_PUSH] 查询场站编号失败: {}", e.getMessage());
return "";
}
}
/**
* 电站名称:报告记录优先,其次 basedata_station.station_name
*/
private String resolveStationName(InspectionReport report) {
if (report != null && StringUtils.isNotEmpty(report.getStationName())) {
return report.getStationName();
}
try {
Map<String, String> stationMap = patrolResultMapper.selectBasedataStation();
if (stationMap != null && stationMap.get("station_name") != null) {
return stationMap.get("station_name");
}
} catch (Exception e) {
log.warn("[REPORT_PUSH] 查询电站名称失败: {}", e.getMessage());
}
return "";
}
/**
* 巡检类型:配置优先,默认动环智能巡检
*/
private String resolveInspectType() {
return StringUtils.isNotEmpty(properties.getInspectType()) ? properties.getInspectType() : "动环智能巡检";
}
}

+ 1835
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/controller/DonghuanReportExportController.java
File diff suppressed because it is too large
View File


+ 10
- 5
inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/controller/PatrolTaskResultMainController.java View File

@ -263,7 +263,7 @@ public class PatrolTaskResultMainController extends BaseController {
}
@PostMapping({"/updateInfo_shaoxing"})
@PostMapping({"/updateInfo_shaoxing1"})
public AjaxResult updateInfo_shaoxing(
@RequestBody PatrolTaskResultMain resultMain) {
long startTime = System.currentTimeMillis();
@ -304,7 +304,12 @@ public class PatrolTaskResultMainController extends BaseController {
return;
}
CompletableFuture.runAsync(() -> exportExcelWordAndZipShaoxing(reportIds), executor);
CompletableFuture.runAsync(() -> exportExcelWordAndZipShaoxing(reportIds), executor)
.whenComplete((v, ex) -> {
if (ex != null) {
logger.error("Async export failed for reportIds: {}", reportIds, ex);
}
});
}, executor).exceptionally(ex -> {
logger.error("Error occurred during saveReport: ", ex);
@ -1486,7 +1491,7 @@ public class PatrolTaskResultMainController extends BaseController {
}
public void exportExcelWordAndZipShaoxing(List<Long> reportIds) {
// 合并报告,所有报告都是一样的,所以随便取一个报告导出,这样就避免多次生成同样报告,影响性能
// 一次归档(单个任务 / 多个任务合并)只生成一条报告,多个任务经 inspection_report_task 关联同一条报告
Long lineId = reportIds.get(0);
logMemoryUsage("START exportExcelWordAndZipShaoxing");
List<String> images = new ArrayList<>();
@ -1627,8 +1632,8 @@ public class PatrolTaskResultMainController extends BaseController {
}
});
} catch (Exception e) {
logger.error("生成巡视文件异常:{}", e);
} catch (Throwable e) {
logger.error("生成巡视文件异常", e);
closeResources(sxssfWorkbook, wordDocument, tempFiles);
}
logMemoryUsage("END exportExcelWordAndZipShaoxing");


+ 14
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/mapper/PatrolTaskResultMainMapper.java View File

@ -4,6 +4,7 @@ import com.inspect.resultmain.domain.PatrolTaskResultMain;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
@Mapper
@ -34,5 +35,18 @@ public interface PatrolTaskResultMainMapper {
List<Long> selectLineIdsByList(@Param("lineIds") List<Long> lineIds);
/**
* 定时归档用:查询指定时间窗口内“已完成且未归档”的巡检执行(patrol_task_result_main.line_id)。
* <p>
* 判定口径(按任务状态开始时间归批次):
* 任务状态已完成(patrol_task_status.task_state='1')且开始时间落在 [startTime, endTime),
* 且该执行尚未归档(file_status 不为 1)。
*
* @param startTime 窗口开始时间(含)
* @param endTime 窗口结束时间(不含)
*/
List<Long> selectNotArchivedDoneLineIdsByStartWindow(@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
PatrolTaskResultMain selectPatrolTaskResultMainOne(String patrolTaskId);
}

+ 9
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/service/IPatrolTaskResultMainService.java View File

@ -2,6 +2,7 @@ package com.inspect.resultmain.service;
import com.inspect.resultmain.domain.PatrolTaskResultMain;
import java.util.Date;
import java.util.List;
public interface IPatrolTaskResultMainService {
@ -31,5 +32,13 @@ public interface IPatrolTaskResultMainService {
List<Long> selectLineIdsByList(List<Long> lineIds);
/**
* 定时归档:查询指定时间窗口内“已完成且未归档”的巡检执行 lineId 列表。
*
* @param startTime 窗口开始时间(含)
* @param endTime 窗口结束时间(不含)
*/
List<Long> selectNotArchivedDoneLineIdsByStartWindow(Date startTime, Date endTime);
PatrolTaskResultMain selectPatrolTaskResultMainOne(String patrolTaskId);
}

+ 6
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/service/impl/PatrolTaskResultMainServiceImpl.java View File

@ -5,6 +5,7 @@ import com.inspect.resultmain.domain.PatrolTaskResultMain;
import com.inspect.resultmain.mapper.PatrolTaskResultMainMapper;
import com.inspect.resultmain.service.IPatrolTaskResultMainService;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
@ -74,6 +75,11 @@ public class PatrolTaskResultMainServiceImpl implements IPatrolTaskResultMainSer
return this.patrolTaskResultMainMapper.selectLineIdsByList(lineIds);
}
@Override
public List<Long> selectNotArchivedDoneLineIdsByStartWindow(Date startTime, Date endTime) {
return this.patrolTaskResultMainMapper.selectNotArchivedDoneLineIdsByStartWindow(startTime, endTime);
}
@Override
public PatrolTaskResultMain selectPatrolTaskResultMainOne(String patrolTaskId) {
return this.patrolTaskResultMainMapper.selectPatrolTaskResultMainOne(patrolTaskId);


+ 5
- 3
inspect-main/inspect-main-task/src/main/java/com/inspect/task/controller/PatrolTaskController.java View File

@ -586,8 +586,10 @@ public class PatrolTaskController extends BaseController {
patrolTaskService.deletePatrolTaskByTaskIds(taskIds);
patrolTaskInfoService.deletePatrolTaskInfoByMajorIds(taskIds);
// 原来每个 taskId 都做一次 KEYS 全库扫描,改为只扫描一次(SCAN 非阻塞)
Collection<String> taskCodeRedisKeys = redisService.scan(RedisConst.TASK_CODE_EX);
for (Long taskId : taskIds) {
for (String key : redisService.keys(RedisConst.TASK_CODE_EX)) {
for (String key : taskCodeRedisKeys) {
logger.info("[CONTROLLER] remove task redis key: {}", key);
if (key.contains(String.valueOf(taskId))) {
redisService.deleteObject(key);
@ -1118,7 +1120,7 @@ public class PatrolTaskController extends BaseController {
issueTask(patrolTask);
// 修改任务,删除原有的任务执行策略,根据新的策略重新生成
if (StringUtils.isNotEmpty(patrolTask.getTaskCode())) {
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE + patrolTask.getTaskCode() + StringUtils.AT + "*");
Collection<String> redisKeys = redisService.scan(RedisConst.TASK_CODE + patrolTask.getTaskCode() + StringUtils.AT + "*");
for (String redisKey : redisKeys) {
redisService.deleteObject(redisKey);
}
@ -1344,7 +1346,7 @@ public class PatrolTaskController extends BaseController {
issueTask(patrolTask);
// 修改任务,删除原有的任务执行策略,根据新的策略重新生成
if (StringUtils.isNotEmpty(patrolTask.getTaskCode())) {
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE + patrolTask.getTaskCode() + StringUtils.AT + "*");
Collection<String> redisKeys = redisService.scan(RedisConst.TASK_CODE + patrolTask.getTaskCode() + StringUtils.AT + "*");
for (String redisKey : redisKeys) {
redisService.deleteObject(redisKey);
}


+ 25
- 250
inspect-main/inspect-main-task/src/main/java/com/inspect/task/domain/PatrolData.java View File

@ -1,10 +1,14 @@
package com.inspect.task.domain;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.inspect.base.core.annotation.Excel;
import com.inspect.base.core.enums.DefectLevel;
import lombok.Data;
import lombok.ToString;
import java.util.Objects;
@ToString
@Data
public class PatrolData implements Comparable<PatrolData> {
@Excel(
name = "设备"
@ -14,6 +18,7 @@ public class PatrolData implements Comparable<PatrolData> {
name = "相机名"
)
private String channelName;
private String channelCode;
@Excel(
name = "系统"
)
@ -87,6 +92,26 @@ public class PatrolData implements Comparable<PatrolData> {
private String taskPatrolId;
private String taskName;
// 缺陷等级
private Integer defectLevel = DefectLevel.NONE.getCode();
@JsonInclude(JsonInclude.Include.NON_NULL)
private String maxValue;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String minValue;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String avgValue;
public void setWarnStatus(String warnStatus) {
this.warnStatus = warnStatus;
if ("0".equals(warnStatus)) {
this.defectLevel = DefectLevel.SERIOUS.getCode();
} else if ("2".equals(warnStatus)) {
this.defectLevel = DefectLevel.NORMAL.getCode();
} else {
this.defectLevel = DefectLevel.NONE.getCode();
}
}
public String warnStatus(String ws) {
byte var3 = -1;
@ -135,260 +160,10 @@ public class PatrolData implements Comparable<PatrolData> {
return this.requestId != null && this.algType != null && this.requestId.equals(pd.getRequestId()) && this.algType.equals(pd.algType);
}
public String getDeviceName() {
return this.deviceName;
}
public String getChannelName() {
return this.channelName;
}
public String getDeviceSource() {
return this.deviceSource;
}
public String getPointName() {
return this.pointName;
}
public String getPointId() {
return pointId;
}
public String getWarnStatus() {
return this.warnStatus;
}
public String getAlgName() {
return this.algName;
}
public String getResValue() {
return this.resValue;
}
public String getImg() {
return this.img;
}
public String getFilterDesc() {
return this.filterDesc;
}
public String getBrightDesc() {
return this.brightDesc;
}
public String getImgAnalyse() {
return this.imgAnalyse;
}
public String getBrightImgAnalyse() {
return this.brightImgAnalyse;
}
public String getDesc() {
return this.desc;
}
public String getLineId() {
return this.lineId;
}
public String getPatrolTime() {
return this.patrolTime;
}
public String getPointStatus() {
return this.pointStatus;
}
public String getImgType() {
return this.imgType;
}
public String getDataType() {
return this.dataType;
}
public String getFilter() {
return this.filter;
}
public String getRequestId() {
return this.requestId;
}
public String getResStatus() {
return this.resStatus;
}
public String getBrightResStatus() {
return this.brightResStatus;
}
public String getAreaName() {
return this.areaName;
}
public String getAlgType() {
return this.algType;
}
public String getObjectId() {
return this.objectId;
}
public String getImageNormalUrlPath() {
return this.imageNormalUrlPath;
}
public String getFilePath() {
return this.filePath;
}
public String getSuggestion() {
return this.suggestion;
}
public String getOriginalValue() {
return originalValue;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setChannelName(String channelName) {
this.channelName = channelName;
}
public void setDeviceSource(String deviceSource) {
this.deviceSource = deviceSource;
}
public void setPointName(String pointName) {
this.pointName = pointName;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public void setWarnStatus(String warnStatus) {
this.warnStatus = warnStatus;
}
public void setAlgName(String algName) {
this.algName = algName;
}
public void setResValue(String resValue) {
this.resValue = resValue;
}
public void setImg(String img) {
this.img = img;
}
public void setFilterDesc(String filterDesc) {
this.filterDesc = filterDesc;
}
public void setBrightDesc(String brightDesc) {
this.brightDesc = brightDesc;
}
public void setImgAnalyse(String imgAnalyse) {
this.imgAnalyse = imgAnalyse;
}
public void setBrightImgAnalyse(String brightImgAnalyse) {
this.brightImgAnalyse = brightImgAnalyse;
}
public void setDesc(String desc) {
this.desc = desc;
}
public void setLineId(String lineId) {
this.lineId = lineId;
}
public void setPatrolTime(String patrolTime) {
this.patrolTime = patrolTime;
}
public void setPointStatus(String pointStatus) {
this.pointStatus = pointStatus;
}
public void setImgType(String imgType) {
this.imgType = imgType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public void setFilter(String filter) {
this.filter = filter;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public void setResStatus(String resStatus) {
this.resStatus = resStatus;
}
public void setBrightResStatus(String brightResStatus) {
this.brightResStatus = brightResStatus;
}
public void setAreaName(String areaName) {
this.areaName = areaName;
}
public void setAlgType(String algType) {
this.algType = algType;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public void setImageNormalUrlPath(String imageNormalUrlPath) {
this.imageNormalUrlPath = imageNormalUrlPath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public void setSuggestion(String suggestion) {
this.suggestion = suggestion;
}
public void setOriginalValue(String originalValue) {
this.originalValue = originalValue;
}
public String getTaskPatrolId() {
return taskPatrolId;
}
public void setTaskPatrolId(String taskPatrolId) {
this.taskPatrolId = taskPatrolId;
}
public String getAreaId() {
return areaId;
}
public void setAreaId(String areaId) {
this.areaId = areaId;
}
protected boolean canEqual(Object other) {
return other instanceof PatrolData;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;


+ 7
- 4
inspect-main/inspect-main-task/src/main/java/com/inspect/taskstatus/service/impl/PatrolTaskStatusServiceImpl.java View File

@ -22,6 +22,7 @@ import com.inspect.taskstatus.mapper.PatrolTaskStatusMapper;
import com.inspect.taskstatus.service.IPatrolTaskStatusService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -102,6 +103,7 @@ public class PatrolTaskStatusServiceImpl implements IPatrolTaskStatusService {
return this.patrolTaskStatusMapper.updatePatrolTaskStatusOfTaskState(patrolTaskStatus);
}
@TaskStatusChange(targetState = "1", async = true) // "1"表示已完成状态
public int updatePatrolTaskStatusOfTaskStateAndEndTime(PatrolTaskStatus patrolTaskStatus) {
return this.patrolTaskStatusMapper.updatePatrolTaskStatusOfTaskStateAndEndTime(patrolTaskStatus);
}
@ -119,6 +121,7 @@ public class PatrolTaskStatusServiceImpl implements IPatrolTaskStatusService {
}
@Override
@TaskStatusChange(targetState = "1", async = true) // "1"表示已完成状态
public int insertIgnorePatrolTaskStatus(PatrolTaskStatus patrolTaskStatusMain) {
return this.patrolTaskStatusMapper.insertIgnorePatrolTaskStatus(patrolTaskStatusMain);
}
@ -158,8 +161,8 @@ public class PatrolTaskStatusServiceImpl implements IPatrolTaskStatusService {
patrolTaskStatus.setTaskState(state);
setNormalTaskStatus(patrolTaskStatus);
// 更新子任务或普通任务
updatePatrolTaskStatus(patrolTaskStatus);
// 更新子任务或普通任务(走代理调用,确保 @TaskStatusChange 切面在状态变为完成时触发)
((IPatrolTaskStatusService) AopContext.currentProxy()).updatePatrolTaskStatus(patrolTaskStatus);
log.info("updateTaskProgress: {}", patrolTaskStatus);
// 如果是子任务,需要同步更新主任务
if (TaskType.SUB.getCode().equals(patrolTaskStatus.getTaskType()) && patrolTaskStatus.getMainId() != null) {
@ -239,8 +242,8 @@ public class PatrolTaskStatusServiceImpl implements IPatrolTaskStatusService {
mainTask.setTaskEstimatedTime(mainAlgorithmProgress.toString());
setNormalTaskStatus(mainTask);
// 更新主任务
patrolTaskStatusMapper.updatePatrolTaskStatus(mainTask);
// 更新主任务(走代理调用,确保 @TaskStatusChange 切面在联合任务完成时触发)
((IPatrolTaskStatusService) AopContext.currentProxy()).updatePatrolTaskStatus(mainTask);
}
// 生成巡视归档记录


BIN
inspect-main/inspect-main-task/src/main/resources/fonts/simfang.ttf View File


BIN
inspect-main/inspect-main-task/src/main/resources/fonts/simhei.ttf View File


BIN
inspect-main/inspect-main-task/src/main/resources/fonts/仿宋_GB2312.ttf View File


BIN
inspect-main/inspect-main-task/src/main/resources/fonts/方正小标宋简体.ttf View File


+ 4
- 2
inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportDataMapper.xml View File

@ -15,6 +15,7 @@
<result property="dataSources" column="data_sources"/>
<result property="acquisitionTime" column="acquisition_time"/>
<result property="inspectionResults" column="inspection_results"/>
<result property="inspectionValue" column="inspection_value"/>
<result property="pointStatus" column="point_status"/>
<result property="inspectionImg" column="inspection_img"/>
<result property="reportId" column="report_id"/>
@ -36,6 +37,7 @@
data_sources,
acquisition_time,
inspection_results,
inspection_value,
point_status,
inspection_img,
alg_name,
@ -164,11 +166,11 @@
</delete>
<insert id="batchInsertInspectionReportData" parameterType="java.util.List">
insert into inspection_report_data
(line_id,code,report_id,area,time_interval,eq_name,parts,point_name,data_sources,acquisition_time,inspection_results,point_status,inspection_img,alg_name,task_name,task_patrolled_id,task_result_id)
(line_id,code,report_id,area,time_interval,eq_name,parts,point_name,data_sources,acquisition_time,inspection_results,inspection_value,point_status,inspection_img,alg_name,task_name,task_patrolled_id,task_result_id)
values
<foreach collection="list" item="t" index="index" separator=",">
(#{t.lineId}, #{t.code}, #{t.reportId}, #{t.area}, #{t.timeInterval}, #{t.eqName}, #{t.parts},
#{t.pointName},#{t.dataSources},#{t.acquisitionTime},#{t.inspectionResults},#{t.pointStatus},#{t.inspectionImg},#{t.algName},#{t.taskName},#{t.taskPatrolledId},#{t.taskResultId})
#{t.pointName},#{t.dataSources},#{t.acquisitionTime},#{t.inspectionResults},#{t.inspectionValue},#{t.pointStatus},#{t.inspectionImg},#{t.algName},#{t.taskName},#{t.taskPatrolledId},#{t.taskResultId})
</foreach>
</insert>
</mapper>

+ 4
- 3
inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportImgMapper.xml View File

@ -9,12 +9,13 @@
<result property="reportInfoId" column="report_info_id"/>
<result property="img" column="img"/>
<result property="imgSrc" column="img_src"/>
<result property="imageNormalUrlPath" column="image_normal_url_path"/>
<result property="creatTime" column="creat_time"/>
<result property="imgType" column="img_type"/>
</resultMap>
<sql id="selectInspectionReportImgVo">
select line_id, report_info_id, img, img_src, creat_time, img_type
select line_id, report_info_id, img, img_src, image_normal_url_path, creat_time, img_type
from inspection_report_img
</sql>
@ -87,9 +88,9 @@
</foreach>
</delete>
<insert id="batchInsertInspectionReportImg" parameterType="java.util.List">
insert into inspection_report_img (line_id, report_info_id, img, img_src, creat_time, img_type) values
insert into inspection_report_img (line_id, report_info_id, img, img_src, image_normal_url_path, creat_time, img_type) values
<foreach collection="list" item="t" index="index" separator=",">
(#{t.lineId}, #{t.reportInfoId}, #{t.img}, #{t.imgSrc}, #{t.creatTime}, #{t.imgType})
(#{t.lineId}, #{t.reportInfoId}, #{t.img}, #{t.imgSrc}, #{t.imageNormalUrlPath}, #{t.creatTime}, #{t.imgType})
</foreach>
</insert>
</mapper>

+ 5
- 0
inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportMapper.xml View File

@ -23,6 +23,7 @@
<result property="taskResultId" column="task_result_id"/>
<result property="filePath" column="file_path"/>
<result property="filter" column="filter"/>
<result property="location" column="location"/>
</resultMap>
<sql id="selectInspectionReportVo">
@ -34,6 +35,7 @@
station_name,
volt_level,
station_type,
location,
inspection_date,
inspection_task_name,
env_info,
@ -113,6 +115,7 @@
<if test="stationName != null">station_name,</if>
<if test="voltLevel != null">volt_level,</if>
<if test="stationType != null">station_type,</if>
<if test="location != null">location,</if>
<if test="inspectionDate != null">inspection_date,</if>
<if test="inspectionTaskName != null">inspection_task_name,</if>
<if test="envInfo != null">env_info,</if>
@ -132,6 +135,7 @@
<if test="stationName != null">#{stationName},</if>
<if test="voltLevel != null">#{voltLevel},</if>
<if test="stationType != null">#{stationType},</if>
<if test="location != null">#{location},</if>
<if test="inspectionDate != null">#{inspectionDate},</if>
<if test="inspectionTaskName != null">#{inspectionTaskName},</if>
<if test="envInfo != null">#{envInfo},</if>
@ -155,6 +159,7 @@
<if test="stationName != null">station_name = #{stationName},</if>
<if test="voltLevel != null">volt_level = #{voltLevel},</if>
<if test="stationType != null">station_type = #{stationType},</if>
<if test="location != null">location = #{location},</if>
<if test="inspectionDate != null">inspection_date = #{inspectionDate},</if>
<if test="inspectionTaskName != null">inspection_task_name = #{inspectionTaskName},</if>
<if test="envInfo != null">env_info = #{envInfo},</if>


+ 69
- 0
inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportTaskMapper.xml View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inspect.insreporttask.mapper.InspectionReportTaskMapper">
<resultMap type="com.inspect.insreport.domain.InspectionReport" id="InspectionReportResult">
<result property="lineId" column="line_id"/>
<result property="stationName" column="station_name"/>
<result property="voltLevel" column="volt_level"/>
<result property="stationType" column="station_type"/>
<result property="inspectionDate" column="inspection_date"/>
<result property="inspectionTaskName" column="inspection_task_name"/>
<result property="envInfo" column="env_info"/>
<result property="inspectionStartTime" column="inspection_start_time"/>
<result property="inspectionEndTime" column="inspection_end_time"/>
<result property="patrolStatistics" column="patrol_statistics"/>
<result property="checkPerson" column="check_person"/>
<result property="checkTime" column="check_time"/>
<result property="description" column="description"/>
<result property="taskId" column="task_id"/>
<result property="taskPatrolledId" column="task_patrolled_id"/>
<result property="taskResultId" column="task_result_id"/>
<result property="filePath" column="file_path"/>
<result property="filter" column="filter"/>
<result property="location" column="location"/>
</resultMap>
<insert id="batchInsertInspectionReportTask" parameterType="java.util.List">
insert into inspection_report_task
(report_line_id, task_result_line_id, task_patrolled_id, task_name)
values
<foreach collection="list" item="t" index="index" separator=",">
(#{t.reportLineId}, #{t.taskResultLineId}, #{t.taskPatrolledId}, #{t.taskName})
</foreach>
</insert>
<select id="selectInspectionReportsByTaskPatrolledId" resultMap="InspectionReportResult">
select r.line_id,
r.task_id,
r.task_patrolled_id,
r.file_path,
r.task_result_id,
r.station_name,
r.volt_level,
r.station_type,
r.location,
r.inspection_date,
r.inspection_task_name,
r.env_info,
r.inspection_start_time,
r.inspection_end_time,
r.patrol_statistics,
r.check_person,
r.check_time,
r.description,
r.filter
from inspection_report_task t
join inspection_report r on r.line_id = t.report_line_id
where t.task_patrolled_id = #{taskPatrolledId}
<if test="filter != null and filter != ''">and r.filter = #{filter}</if>
order by r.line_id desc
</select>
<select id="selectTaskPatrolledIdsByReportLineId" resultType="java.lang.String">
select task_patrolled_id from inspection_report_task
where report_line_id = #{reportLineId} order by id
</select>
</mapper>

+ 161
- 0
inspect-main/inspect-main-task/src/main/resources/mapper/task/PatrolResultMapper.xml View File

@ -182,10 +182,12 @@
<result property="resValue" column="res_value"/>
<result property="deviceSource" column="device_source"/>
<result property="channelName" column="channel_name"/>
<result property="channelCode" column="channel_code"/>
<result property="imageNormalUrlPath" column="image_normal_url_path"/>
<result property="filePath" column="file_path"/>
<result property="originalValue" column="original_value"/>
<result property="taskPatrolId" column="task_patrol_id"/>
<result property="taskName" column="task_name"/>
</resultMap>
<select id="selectPatrolDataResultByTaskCode" parameterType="PatrolResult" resultMap="PatrolDataResult">
@ -346,6 +348,76 @@
</where>
ORDER BY line_id desc
</select>
<select id="selectDefectList" parameterType="com.inspect.analysis.domain.DefectListQuery"
resultMap="PatrolDataResult">
select
b.line_id,
a.device_name as point_name,
a.device_id as point_id,
a.create_time as patrol_time,
b.point_status,
b.result_type as warn_status,
b.description as 'desc',
case when b.filter = '0' then a.file_path else b.res_img_url end as img,
b.image_normal_url_path,
a.file_path,
a.file_type as img_type,
a.data_type,
b.filter,
b.request_id,
b.res_img_url as img_analysis,
b.original_value,
b.res_status,
d.device_name,
e.area_name,
d.area_id,
b.alg_type,
b.objectId,
b.suggestion,
b.res_value,
b.task_patrol_id,
a.task_name
from patrol_result a
inner join result_analysis b
on a.line_id = b.business_id
and a.task_patrolled_id = b.task_patrol_id
and a.device_id = b.objectId
left join basedata_patrolpoint c on a.device_id = c.patrol_point_id
left join basedata_device d on c.device_id = d.device_id
left join basedata_area e on e.area_id = d.area_id
<where>
<choose>
<when test="taskPatrolIds != null and !taskPatrolIds.isEmpty()">
and a.task_patrolled_id in
<foreach collection="taskPatrolIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
and b.task_patrol_id in
<foreach collection="taskPatrolIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</when>
</choose>
and b.result_type = '0'
<if test="filter != null and filter != ''">and b.filter = #{filter}</if>
<if test="algType != null and algType != ''">and b.alg_type = #{algType}</if>
<if test="areaId != null and areaId != ''">and d.area_id = #{areaId}</if>
<if test="deviceId != null and deviceId != ''">and d.device_id = #{deviceId}</if>
<if test="pointId != null and pointId != ''">and a.device_id = #{pointId}</if>
<if test="taskCode != null and taskCode != ''">and a.task_code = #{taskCode}</if>
<if test="pointName != null and pointName != ''">and a.device_name like concat('%', #{pointName}, '%')</if>
<if test="taskName != null and taskName != ''">and a.task_name like concat('%', #{taskName}, '%')</if>
<if test="deviceName != null and deviceName != ''">and d.device_name like concat('%', #{deviceName}, '%')</if>
<if test="pointStatus != null and pointStatus != ''">and b.point_status = #{pointStatus}</if>
<if test="beginTime != null and beginTime != ''">and a.create_time &gt;= concat(#{beginTime}, ' 00:00:00')</if>
<if test="endTime != null and endTime != ''">and a.create_time &lt;= concat(#{endTime}, ' 23:59:59')</if>
</where>
order by b.task_patrol_id, b.objectId, b.alg_type,
case b.filter when '1' then 1 when '0' then 2 else 3 end,
a.create_time, b.line_id
</select>
<resultMap type="com.inspect.system.base.openDomain.BasedataEqpBookMoMain" id="BasedataEqpBookResult">
<result property="eqpBookId" column="eqp_book_id"/>
<result property="stationName" column="station_name"/>
@ -1369,6 +1441,8 @@
ELSE h.patrol_device_name
END AS channel_name,
IF(s.pos_type = '0', g.channel_code, NULL) AS channel_code,
CASE
WHEN s.pos_type = '0' THEN g.device_source
ELSE h.device_source
@ -1451,6 +1525,8 @@
<if test="pointName != null and pointName != ''">and a.device_name like concat('%', #{pointName}, '%')</if>
<if test="deviceName != null and deviceName != ''">and d.device_name like concat('%', #{deviceName}, '%')
</if>
<if test="beginTime != null and beginTime != ''">and a.create_time &gt;= concat(#{beginTime}, ' 00:00:00')</if>
<if test="endTime != null and endTime != ''">and a.create_time &lt;= concat(#{endTime}, ' 23:59:59')</if>
</where>
<if test="abnormal == 0">ORDER BY d.device_name,a.device_name</if>
<if test="abnormal == 1">ORDER BY b.point_status</if>
@ -1470,4 +1546,89 @@
LEFT JOIN basedata_alg a ON s.alg_id = a.alg_id
WHERE FIND_IN_SET(s.alg_subtype_code, #{codes})
</select>
<select id="selectChannelByPointId" parameterType="java.lang.String" resultType="java.util.HashMap">
SELECT
a.eqp_book_id,
a.area_id,
a.patrol_device_code,
a.patrol_device_name,
a.use_mode,
b.channel_code,
b.channel_id,
b.channel_name,
a.device_source
FROM
basedata_eqpbook a
LEFT JOIN basedata_eqpbook_channel b ON a.eqp_book_id = b.eqp_book_id
LEFT JOIN patrol_preset_pos pp ON pp.channel_id = b.channel_id
LEFT JOIN basedata_patrolpoint p ON p.patrol_point_id = pp.patrol_point_id
WHERE
p.patrol_point_id = #{pointId}
</select>
<select id="selectPatrolData" parameterType="ResultAnalysis" resultMap="PatrolDataResult">
SELECT *
FROM (
SELECT
t.*,
ROW_NUMBER() OVER (
PARTITION BY t.point_id, t.task_patrol_id, t.objectId, t.alg_type
ORDER BY t.filter ASC
) AS rn
FROM (
SELECT
b.line_id,
a.device_name AS point_name,
a.device_id AS point_id,
a.create_time AS patrol_time,
b.point_status,
b.result_type AS warn_status,
b.description AS `desc`,
CASE WHEN b.filter = 0 THEN a.file_path ELSE b.res_img_url END AS img,
b.image_normal_url_path,
a.file_path,
a.file_type AS img_type,
a.data_type,
b.filter,
b.request_id,
b.res_img_url AS img_analysis,
b.original_value,
b.res_status,
d.device_name,
e.area_name,
e.area_id,
b.alg_type,
b.objectId,
b.suggestion,
b.res_value,
s.pos_type,
b.task_patrol_id,
CASE
WHEN s.pos_type = '0' THEN g.channel_name
ELSE h.patrol_device_name
END AS channel_name,
IF(s.pos_type = '0', g.channel_code, NULL) AS channel_code,
CASE
WHEN s.pos_type = '0' THEN g.device_source
ELSE h.device_source
END AS device_source
FROM patrol_result a
INNER JOIN result_analysis b ON a.line_id = b.business_id
LEFT JOIN basedata_patrolpoint c ON a.device_id = c.patrol_point_id
LEFT JOIN basedata_device d ON c.device_id = d.device_id
LEFT JOIN basedata_area e ON e.area_id = d.area_id
LEFT JOIN patrol_task_status s ON s.task_patrolled_id = a.task_patrolled_id
LEFT JOIN patrol_preset_pos f1 ON s.pos_type = '0' AND c.patrol_point_id = f1.patrol_point_id
LEFT JOIN basedata_eqpbook_channel g ON f1.channel_id = g.channel_id
LEFT JOIN basedata_patrolpoint_preset f2 ON s.pos_type != '0' AND c.patrol_point_id = f2.patrol_point_id
LEFT JOIN basedata_eqpbook h ON f2.eqp_book_id = h.eqp_book_id
WHERE (g.channel_id IS NOT NULL OR h.eqp_book_id IS NOT NULL)
AND a.task_patrolled_id IN (#{taskPatrolId})
AND b.task_patrol_id IN (#{taskPatrolId})
AND b.objectId = #{objectId}
AND b.alg_type = #{algType}
) t
) ranked
WHERE rn = 1;
</select>
</mapper>

+ 12
- 0
inspect-main/inspect-main-task/src/main/resources/mapper/task/PatrolTaskResultMainMapper.xml View File

@ -162,6 +162,18 @@
</foreach>)
</select>
<!-- 定时归档:按任务状态开始时间归批次,取窗口内已完成且未归档的巡检执行 -->
<select id="selectNotArchivedDoneLineIdsByStartWindow" resultType="java.lang.Long">
select distinct m.line_id
from patrol_task_result_main m
join patrol_task_status s on s.task_patrolled_id = m.task_patrolled_id
where s.task_state = '1'
and (m.file_status is null or m.file_status &lt;&gt; '1')
and s.start_time &gt;= #{startTime}
and s.start_time &lt; #{endTime}
order by m.line_id
</select>
<select id="selectPatrolTaskResultMainByTaskPatrolledId" resultMap="PatrolTaskResultMainResult">
<include refid="selectPatrolTaskResultMainVo"/>
where task_patrolled_id = #{taskPatrolledId}


BIN
inspect-main/inspect-main-task/src/main/resources/report/logo.png View File

Before After
Width: 406  |  Height: 68  |  Size: 15 KiB

+ 2
- 0
inspect-main/inspect-main-task/src/main/resources/report/wordparts/fontTable.xml View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:fonts xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du"><w:font w:name="等线"><w:altName w:val="DengXian"/><w:panose1 w:val="02010600030101010101"/><w:charset w:val="86"/><w:family w:val="auto"/><w:pitch w:val="variable"/><w:sig w:usb0="A00002BF" w:usb1="38CF7CFA" w:usb2="00000016" w:usb3="00000000" w:csb0="0004000F" w:csb1="00000000"/></w:font><w:font w:name="Times New Roman"><w:panose1 w:val="02020603050405020304"/><w:charset w:val="00"/><w:family w:val="roman"/><w:pitch w:val="variable"/><w:sig w:usb0="E0002EFF" w:usb1="C000785B" w:usb2="00000009" w:usb3="00000000" w:csb0="000001FF" w:csb1="00000000"/></w:font><w:font w:name="仿宋_GB2312"><w:panose1 w:val="02010609060101010101"/><w:charset w:val="86"/><w:family w:val="modern"/><w:pitch w:val="fixed"/><w:sig w:usb0="800002BF" w:usb1="38CF7CFA" w:usb2="00000016" w:usb3="00000000" w:csb0="00040001" w:csb1="00000000"/><w:embedRegular r:id="rId1" w:subsetted="1" w:fontKey="{DAF739B1-18F1-4906-820A-C106608C2B73}"/><w:embedBold r:id="rId2" w:subsetted="1" w:fontKey="{3039BACA-5140-4C4C-8AFB-F7B61E374105}"/><w:embedItalic r:id="rId3" w:subsetted="1" w:fontKey="{73410ACE-E9FA-46ED-BA3D-9ECB6C9FEF4E}"/></w:font><w:font w:name="方正小标宋简体"><w:panose1 w:val="02000000000000000000"/><w:charset w:val="86"/><w:family w:val="auto"/><w:pitch w:val="variable"/><w:sig w:usb0="A00002BF" w:usb1="184F6CFA" w:usb2="00000012" w:usb3="00000000" w:csb0="00040001" w:csb1="00000000"/><w:embedBold r:id="rId4" w:subsetted="1" w:fontKey="{4D5F1B72-2D28-435A-B7BC-848B95F30EBB}"/></w:font><w:font w:name="黑体"><w:altName w:val="SimHei"/><w:panose1 w:val="02010609060101010101"/><w:charset w:val="86"/><w:family w:val="modern"/><w:pitch w:val="fixed"/><w:sig w:usb0="800002BF" w:usb1="38CF7CFA" w:usb2="00000016" w:usb3="00000000" w:csb0="00040001" w:csb1="00000000"/><w:embedBold r:id="rId5" w:subsetted="1" w:fontKey="{818E8C25-A7E2-4610-9234-360D3B3511E5}"/></w:font><w:font w:name="等线 Light"><w:panose1 w:val="02010600030101010101"/><w:charset w:val="86"/><w:family w:val="auto"/><w:pitch w:val="variable"/><w:sig w:usb0="A00002BF" w:usb1="38CF7CFA" w:usb2="00000016" w:usb3="00000000" w:csb0="0004000F" w:csb1="00000000"/></w:font></w:fonts>

+ 2
- 0
inspect-main/inspect-main-task/src/main/resources/report/wordparts/styles.xml
File diff suppressed because it is too large
View File


+ 2
- 0
inspect-main/inspect-main-task/src/main/resources/report/wordparts/theme1.xml
File diff suppressed because it is too large
View File


+ 2
- 0
inspect-main/inspect-main-task/src/main/resources/report/wordparts/webSettings.xml View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:webSettings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du"/>

+ 17
- 0
inspect-main/inspect-main-video/src/main/java/com/inspect/patrol/contants/AccessApiConstant.java View File

@ -13,5 +13,22 @@ public class AccessApiConstant {
public static final String PLAYBACK_START = "/api/v1/playback/start";
public static final String PLAYBACK_STOP = "/api/v1/playback/stop";
public static final String PLAYBACK_CONTROL = "/api/v1/playback/control";
public static final String HLS_SESSION_CREATE = "/api/v1/playback/hls/sessions";
public static final String HLS_SESSION_DELETE = "/api/v1/playback/hls/sessions/%s";
public static final String HLS_SESSION_QUERY = "/api/v1/playback/hls/sessions/%s";
public static final String HLS_SESSION_CONTROL = "/api/v1/playback/hls/sessions/%s/controls";
public static final String HLS_UI_SESSION_CREATE = "/api/v1/playback/hls/ui/sessions";
public static final String HLS_UI_SESSION_CONTROL = "/api/v1/playback/hls/ui/sessions/%s/controls";
public static final String HLS_CONFIG = "/api/v1/device/hls/config";
public static final String LIVE_START = "/api/v1/stream/live/start";
public static final String LIVE_STOP = "/api/v1/stream/live/stop";
public static final String LIVE_STATUS = "/api/v1/stream/live/status";
public static final String PTZ_CONTROL_PAGE = "/api/v1/control/ptzControl";
public static final String POSITION_3D_PAGE = "/api/v1/control/position3d";
public static final String PRESET_LIST_PAGE = "/api/v1/control/presetList/%s/%s";
public static final String PRESET_CREATE = "/api/v1/control/presetCreate";
public static final String SNAPSHOT_TASK = "/api/v1/device/platformSnapshot/%s/%s";
public static final String SNAPSHOT_DOWNLOAD = "/api/v1/device/snapshot/%s";
public static final String CLIP_DOWNLOAD = "/api/v1/record/clipDownload";
public static final String PTZ_LIST = "/api/v1/control/presetList";
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save