Browse Source

Merge branch 'yanyuan' of https://git.ht-atia.cn/qinyanlei/inspect into yanyuan

yanyuan
yinhuaiwei 3 weeks ago
parent
commit
c10550aaef
46 changed files with 2946 additions and 54 deletions
  1. +1
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/RedisConst.java
  2. +19
    -1
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/config/RabbitMQConfig.java
  3. +5
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/constant/YanyuanConstants.java
  4. +2
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/AlarmMessage.java
  5. +26
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/domain/ReportPushMessage.java
  6. +34
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportPushPublisher.java
  7. +137
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportUploadConsumer.java
  8. +182
    -0
      inspect-main/inspect-main-api/src/main/java/com/inspect/api/service/ReportUploadService.java
  9. +1
    -1
      inspect-main/inspect-main-start/src/main/java/com/inspect/InspectStartApplication.java
  10. +40
    -1
      inspect-main/inspect-main-start/src/main/resources/application.yml
  11. +15
    -1
      inspect-main/inspect-main-task/pom.xml
  12. +25
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/controller/ResultAnalysisController.java
  13. +52
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/domain/DefectListQuery.java
  14. +205
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/service/DefectListAggregator.java
  15. +2
    -2
      inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java
  16. +7
    -2
      inspect-main/inspect-main-task/src/main/java/com/inspect/aspect/TaskStatusChangeAspect.java
  17. +3
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/img/domain/InspectionReportImg.java
  18. +5
    -2
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreport/domain/InspectionReport.java
  19. +5
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/insreportdata/domain/InspectionReportData.java
  20. +2
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/message/MessageUtils.java
  21. +3
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/mapper/PatrolResultMapper.java
  22. +3
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/service/IPatrolResultService.java
  23. +56
    -28
      inspect-main/inspect-main-task/src/main/java/com/inspect/partrolresult/service/impl/PatrolResultServiceImpl.java
  24. +281
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportfont/ReportFontEmbedder.java
  25. +43
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/config/ReportPushProperties.java
  26. +64
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/controller/ReportPushController.java
  27. +190
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/service/ReportAutoArchiveService.java
  28. +148
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/reportpush/service/ReportPushService.java
  29. +1221
    -0
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/controller/DonghuanReportExportController.java
  30. +1
    -1
      inspect-main/inspect-main-task/src/main/java/com/inspect/resultmain/controller/PatrolTaskResultMainController.java
  31. +7
    -4
      inspect-main/inspect-main-task/src/main/java/com/inspect/taskstatus/service/impl/PatrolTaskStatusServiceImpl.java
  32. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/simfang.ttf
  33. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/simhei.ttf
  34. BIN
      inspect-main/inspect-main-task/src/main/resources/fonts/方正小标宋简体.ttf
  35. +4
    -2
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportDataMapper.xml
  36. +4
    -3
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportImgMapper.xml
  37. +5
    -0
      inspect-main/inspect-main-task/src/main/resources/mapper/task/InspectionReportMapper.xml
  38. +110
    -0
      inspect-main/inspect-main-task/src/main/resources/mapper/task/PatrolResultMapper.xml
  39. BIN
      inspect-main/inspect-main-task/src/main/resources/report/logo.png
  40. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/fontTable.xml
  41. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/styles.xml
  42. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/theme1.xml
  43. +2
    -0
      inspect-main/inspect-main-task/src/main/resources/report/wordparts/webSettings.xml
  44. +2
    -0
      inspect-metadata/src/main/java/com/inspect/metadata/eqpbook/domain/BasedataEqpBookChannel.java
  45. +8
    -6
      inspect-metadata/src/main/resources/mapper/eqpbook/BasedataEqpBookChannelMapper.xml
  46. +20
    -0
      pom.xml

+ 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@";//任务完成自动归档+报告推送 防重锁
}

+ 19
- 1
inspect-main/inspect-main-api/src/main/java/com/inspect/api/config/RabbitMQConfig.java View File

@ -25,7 +25,25 @@ public class RabbitMQConfig {
@Bean
public Binding alarmBinding() {
return BindingBuilder.bind(alarmQueue()).to(alarmExchange()).with(YanyuanConstants.EXCHANGE);
return BindingBuilder.bind(alarmQueue()).to(alarmExchange()).with(YanyuanConstants.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


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

@ -7,4 +7,9 @@ public class YanyuanConstants {
public static final String EXCHANGE = "alarm.exchange";
public static final String QUEUE = "alarm.queue";
public static final String ROUTING_KEY = "alarm.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";
}

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

@ -6,6 +6,8 @@ import lombok.*;
@ToString
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AlarmMessage extends BaseEntity {
private String messageId;
private String messageType;


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

@ -0,0 +1,26 @@
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;
/** 消费重试次数(上传失败后 MQ 重投递计数) */
private int retryCount;
}

+ 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);
}
}
}

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

@ -0,0 +1,137 @@
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>
* 上传失败抛出异常使消息重投递重试次数由消息 retryCount 控制超过 {@code mq-max-retries} 丢弃
*/
@Slf4j
@Component
public class ReportUploadConsumer {
/** 上传失败最大重试次数(超过后丢弃,避免无限重投) */
@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;
}
try {
boolean ok = upload(message);
if (ok) {
log.info("[REPORT_PUSH_MQ] 报告上传成功: reportId={}", message.getReportId());
} else {
throw new RuntimeException("report upload failed, will be requeued");
}
} catch (Exception e) {
message.setRetryCount(message.getRetryCount() + 1);
if (message.getRetryCount() > maxRetries) {
log.error("[REPORT_PUSH_MQ] 报告上传超过最大重试次数({}), 丢弃: reportId={}, err: {}",
maxRetries, message.getReportId(), e.getMessage());
return;
}
log.warn("[REPORT_PUSH_MQ] 报告上传失败(第{}次), 消息重投递: reportId={}, err: {}",
message.getRetryCount(), message.getReportId(), e.getMessage());
throw new RuntimeException("report upload failed, will be requeued", e);
}
}
/**
* 下载报告 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();
}
}

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

@ -0,0 +1,182 @@
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;
/**
* 巡检报告上传服务外部平台巡检报告接口文档 v1.0.0
* <p>
* 1. Joinbright-Token 复用 {@link ExtAuthService}GET /ext_auth/getAuthToken配置 yanyuan.auth.*
* 2. POST {upload-url} 上传巡检报告
* - 请求头携带 joinbright-token
* - multipart/form-datafile(报告文件) + 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.writeBytes("--" + boundary + "\r\n");
out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n");
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();
}
}

+ 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

+ 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>

+ 25
- 0
inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/controller/ResultAnalysisController.java View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
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.base.core.constant.AlgConstants;
import com.inspect.base.core.domain.Response;
import com.inspect.base.core.enums.PointStatusEnum;
@ -884,6 +885,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;


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

@ -0,0 +1,52 @@
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;
/** 区域 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";
}

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

@ -0,0 +1,205 @@
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");
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);
}
}
}

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

@ -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"
)


+ 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 = "点位状态:正常、异常、待人工确 认三种状态"
)


+ 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:


+ 3
- 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);


+ 3
- 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);


+ 56
- 28
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;
@ -147,6 +148,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 +534,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();
@ -778,6 +786,8 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
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);
@ -791,6 +801,18 @@ public class PatrolResultServiceImpl implements IPatrolResultService {
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 +1343,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 +1419,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);


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

@ -0,0 +1,281 @@
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:eastAsiaWord 按名字查 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/ 或外部目录
* 注意黑体在模板中有 regular/bold/italic 三个样式条目均引用同一份黑体
* 方正小标宋仿宋各只嵌 bold模板如此标题/表头均加粗使用
*/
private static final List<EmbeddedFont> EMBEDDED_FONTS = Arrays.asList(
new EmbeddedFont("font1", "simhei.ttf", "DAF739B1-18F1-4906-820A-C106608C2B73"),
new EmbeddedFont("font2", "simhei.ttf", "3039BACA-5140-4C4C-8AFB-F7B61E374105"),
new EmbeddedFont("font3", "simhei.ttf", "73410ACE-E9FA-46ED-BA3D-9ECB6C9FEF4E"),
new EmbeddedFont("font4", "方正小标宋简体.ttf", "4D5F1B72-2D28-435A-B7BC-848B95F30EBB"),
new EmbeddedFont("font5", "simfang.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 fontKeyGUID 去掉横线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();
}
}

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

@ -0,0 +1,43 @@
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 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("上传失败, 请查看日志");
}
}

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

@ -0,0 +1,190 @@
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.isEnabled()) {
log.info("[AUTO_ARCHIVE] 未启用 inspect.report-push.enabled,跳过自动归档: 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-datafile + 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.getTaskId()) ? report.getTaskId() : "");
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={}",
report.getLineId(), filePath, username);
}
/**
* 场站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() : "动环智能巡检";
}
}

+ 1221
- 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


+ 1
- 1
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();


+ 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/方正小标宋简体.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>


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

@ -346,6 +346,116 @@
</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
<choose>
<when test="posType != null and posType != '' and posType == '0'.toString">
,g.channel_name,
g.device_source
</when>
<when test="posType != null and posType != '' and posType != '0'.toString">
,h.patrol_device_name as channel_name,
h.device_source
</when>
</choose>
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
<choose>
<when test="posType != null and posType != '' and posType != '0'.toString">
left join basedata_patrolpoint_preset f on c.patrol_point_id = f.patrol_point_id
left join basedata_eqpbook h on f.eqp_book_id = h.eqp_book_id
</when>
<otherwise>
left join patrol_preset_pos f on c.patrol_point_id = f.patrol_point_id
left join basedata_eqpbook_channel g on f.channel_id = g.channel_id
left join basedata_eqpbook h on 1 = 0
</otherwise>
</choose>
<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="channelName != null and channelName != ''">
<choose>
<when test="posType != null and posType != '' and posType == '0'.toString">
and g.channel_name like concat('%', #{channelName}, '%')
</when>
<when test="posType != null and posType != '' and posType != '0'.toString">
and h.patrol_device_name like concat('%', #{channelName}, '%')
</when>
</choose>
</if>
<if test="deviceSource != null and deviceSource != ''">
<choose>
<when test="posType != null and posType != '' and posType == '0'.toString">
and g.device_source like concat('%', #{deviceSource}, '%')
</when>
<when test="posType != null and posType != '' and posType != '0'.toString">
and h.device_source like concat('%', #{deviceSource}, '%')
</when>
</choose>
</if>
<if test="pointStatus != null and pointStatus != ''">and b.point_status = #{pointStatus}</if>
<if test="desc != null and desc != ''">and b.description like concat('%', #{desc}, '%')</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"/>


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="仿宋"><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"/>

+ 2
- 0
inspect-metadata/src/main/java/com/inspect/metadata/eqpbook/domain/BasedataEqpBookChannel.java View File

@ -93,6 +93,8 @@ public class BasedataEqpBookChannel extends BaseEntity {
private String areaName;
private Long areaId;
public String toString() {
return (new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)).append("channelId", this.getChannelId()).append("channelCode", this.getChannelCode()).append("channelName", this.getChannelName()).append("parentChannelId", this.getParentChannelId()).append("eqpbookId", this.getEqpBookId()).append("channelType", this.getChannelType()).append("onlineState", this.getOnlineState()).append("channelContent", this.getChannelContent()).append("provider", this.getProvider()).append("address", this.getAddress()).append("host", this.getHost()).append("port", this.getPort()).append("createBy", this.getCreateBy()).append("createTime", this.getCreateTime()).append("updateBy", this.getUpdateBy()).append("updateTime", this.getUpdateTime()).append("deleteFlag", this.getDeleteFlag()).append("patroldeviceCode", this.getPatrolDeviceCode()).append("username", this.getUsername()).append("password", this.getPassword()).toString();
}


+ 8
- 6
inspect-metadata/src/main/resources/mapper/eqpbook/BasedataEqpBookChannelMapper.xml View File

@ -5,6 +5,10 @@
<mapper namespace="com.inspect.metadata.eqpbook.mapper.BasedataEqpBookChannelMapper">
<resultMap type="BasedataEqpBookChannel" id="BasedataEqpBookChannelResult">
<result property="areaId" column="area_id"/>
<result property="areaName" column="area_name"/>
<result property="stationName" column="station_name"/>
<result property="stationCode" column="station_code"/>
<result property="channelId" column="channel_id"/>
<result property="channelCode" column="channel_code"/>
<result property="upSystemDeviceCode" column="up_system_device_code"/>
@ -58,6 +62,10 @@
<select id="selectBasedataEqpBookChannelList" parameterType="BasedataEqpBookChannel"
resultMap="BasedataEqpBookChannelResult">
select
be.area_id,
be.area_name,
be.station_name,
be.station_code,
bec.channel_id,
bec.channel_code,
bec.up_system_device_code,
@ -88,12 +96,6 @@
<if test="channelName != null and channelName != ''">and bec.channel_name like concat('%', #{channelName},
'%')
</if>
<if test="deviceCode != null and deviceCode != ''">and bec.patrol_device_code like concat('%', #{deviceCode},
'%')
</if>
<if test="deviceName != null and deviceName != ''">and bec.channel_name like concat('%', #{deviceName},
'%')
</if>
<if test="parentChannelId != null and parentChannelId != ''">and bec.parent_channel_id = #{parentChannelId}
</if>
<if test="eqpBookId != null">and bec.eqp_book_id = #{eqpBookId}</if>


+ 20
- 0
pom.xml View File

@ -390,6 +390,26 @@
</resource>
</resources>
<plugins>
<!-- filtering=true 会把 ttf 等二进制资源按文本做变量替换而破坏文件,
这里声明这些扩展名不做过滤(如巡检报告嵌入的字体文件) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>ttc</nonFilteredFileExtension>
<nonFilteredFileExtension>otf</nonFilteredFileExtension>
<nonFilteredFileExtension>png</nonFilteredFileExtension>
<nonFilteredFileExtension>jpg</nonFilteredFileExtension>
<nonFilteredFileExtension>jpeg</nonFilteredFileExtension>
<nonFilteredFileExtension>docx</nonFilteredFileExtension>
<nonFilteredFileExtension>doc</nonFilteredFileExtension>
<nonFilteredFileExtension>pdf</nonFilteredFileExtension>
<nonFilteredFileExtension>zip</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
</project>

Loading…
Cancel
Save