From 2e9f75803fb1f7332e1deddecdffd6c91f9f46b4 Mon Sep 17 00:00:00 2001 From: yinhuaiwei Date: Fri, 29 May 2026 17:51:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=8D=97=E7=91=9E?= =?UTF-8?q?=E7=A7=91=E6=8A=80=E7=BA=A2=E5=A4=96=E6=95=B0=E6=8D=AE=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=B8=8A=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 10 +- .../mapper/ResultAnalysisMapper.java | 9 +- .../naritech/config/AuthProperties.java | 16 ++ .../naritech/config/NaritechProperties.java | 23 +++ .../naritech/config/RestTemplateConfig.java | 50 +++++++ .../controller/NaritechController.java | 91 ++++++++++++ .../naritech/dto/NaritechPatrolData.java | 42 ++++++ .../naritech/dto/NaritechPatrolResponse.java | 15 ++ .../naritech/dto/NaritechTokenResponse.java | 32 ++++ .../service/NaritechPatrolDataService.java | 140 ++++++++++++++++++ .../service/NaritechTokenService.java | 77 ++++++++++ .../resources/mapper/ResultAnalysisMapper.xml | 43 ++++++ 12 files changed, 546 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/inspect/simulator/naritech/config/AuthProperties.java create mode 100644 src/main/java/com/inspect/simulator/naritech/config/NaritechProperties.java create mode 100644 src/main/java/com/inspect/simulator/naritech/config/RestTemplateConfig.java create mode 100644 src/main/java/com/inspect/simulator/naritech/controller/NaritechController.java create mode 100644 src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolData.java create mode 100644 src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolResponse.java create mode 100644 src/main/java/com/inspect/simulator/naritech/dto/NaritechTokenResponse.java create mode 100644 src/main/java/com/inspect/simulator/naritech/service/NaritechPatrolDataService.java create mode 100644 src/main/java/com/inspect/simulator/naritech/service/NaritechTokenService.java diff --git a/pom.xml b/pom.xml index 7428a99..54ac37e 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,15 @@ mybatis-plus-boot-starter 3.4.3 - + + org.springframework.boot + spring-boot-starter-validation + + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.4.1 + net.java.jna jna diff --git a/src/main/java/com/inspect/simulator/mapper/ResultAnalysisMapper.java b/src/main/java/com/inspect/simulator/mapper/ResultAnalysisMapper.java index f247611..ad3ae90 100644 --- a/src/main/java/com/inspect/simulator/mapper/ResultAnalysisMapper.java +++ b/src/main/java/com/inspect/simulator/mapper/ResultAnalysisMapper.java @@ -1,11 +1,12 @@ package com.inspect.simulator.mapper; -import com.inspect.simulator.domain.analysis.vo.AnalysisResult; import com.inspect.simulator.domain.bigmodelr.ContentJson; import com.inspect.simulator.domain.visual.VisualJson; +import com.inspect.simulator.naritech.dto.NaritechPatrolData; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.time.LocalDateTime; import java.util.List; @Mapper @@ -20,4 +21,10 @@ public interface ResultAnalysisMapper { int addDmtModelInfo(@Param("list") List contentJsonList); int addVisualModelInfo(@Param("list") List visualJsonList); + + List selectNaritechNotUploaded(); + + int batchInsertUploadLog(@Param("logIds") List logIds, + @Param("type") String type, + @Param("uploadTime") LocalDateTime uploadTime); } diff --git a/src/main/java/com/inspect/simulator/naritech/config/AuthProperties.java b/src/main/java/com/inspect/simulator/naritech/config/AuthProperties.java new file mode 100644 index 0000000..36bb5bb --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/config/AuthProperties.java @@ -0,0 +1,16 @@ +package com.inspect.simulator.naritech.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "naritech.auth") +public class AuthProperties { + + private String grantType; + private String scope; + private String clientId; + private String clientSecret; +} diff --git a/src/main/java/com/inspect/simulator/naritech/config/NaritechProperties.java b/src/main/java/com/inspect/simulator/naritech/config/NaritechProperties.java new file mode 100644 index 0000000..2238646 --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/config/NaritechProperties.java @@ -0,0 +1,23 @@ +package com.inspect.simulator.naritech.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "naritech.api") +public class NaritechProperties { + + private String baseUrl; + private String tokenPath; + private String patrolReceivePath; + + public String getFullTokenUrl() { + return baseUrl + tokenPath; + } + + public String getFullPatrolReceiveUrl() { + return baseUrl + patrolReceivePath; + } +} diff --git a/src/main/java/com/inspect/simulator/naritech/config/RestTemplateConfig.java b/src/main/java/com/inspect/simulator/naritech/config/RestTemplateConfig.java new file mode 100644 index 0000000..c2cb63a --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/config/RestTemplateConfig.java @@ -0,0 +1,50 @@ +package com.inspect.simulator.naritech.config; + +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(null, (chain, authType) -> true) + .build(); + + SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( + sslContext, NoopHostnameVerifier.INSTANCE); + + Registry registry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); + connectionManager.setMaxTotal(50); + connectionManager.setDefaultMaxPerRoute(20); + + CloseableHttpClient httpClient = HttpClients.custom() + .setConnectionManager(connectionManager) + .build(); + + return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient)); + } +} diff --git a/src/main/java/com/inspect/simulator/naritech/controller/NaritechController.java b/src/main/java/com/inspect/simulator/naritech/controller/NaritechController.java new file mode 100644 index 0000000..a4afe51 --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/controller/NaritechController.java @@ -0,0 +1,91 @@ +package com.inspect.simulator.naritech.controller; + +import com.inspect.simulator.naritech.dto.NaritechPatrolData; +import com.inspect.simulator.naritech.dto.NaritechPatrolResponse; +import com.inspect.simulator.naritech.service.NaritechPatrolDataService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@RestController +@RequestMapping("/naritech") +public class NaritechController { + private static final int BATCH_SIZE = 500; + @Resource + private NaritechPatrolDataService naritechPatrolDataService; + + @GetMapping("/uploadPatrolData") + public void uploadPatrolData() { + log.info("===== 巡视数据上送任务开始, 当前时间: {} =====", LocalDateTime.now()); + try { + List dataList = naritechPatrolDataService.collectPatrolData(); + if (dataList.isEmpty()) { + log.info("无待上传数据,任务结束"); + return; + } + int total = dataList.size(); + int totalBatches = (total - 1) / BATCH_SIZE + 1; + log.info("本次共需上送 {} 条数据, 共 {} 个批次, 每批 {} 条", total, totalBatches, BATCH_SIZE); + List uploadedIds = new ArrayList<>(); + + for (int i = 0; i < total; i+=BATCH_SIZE) { + int end = Math.min(i + BATCH_SIZE, total); + List subDataList = dataList.subList(i, end); + int batchNum = (i / BATCH_SIZE) + 1; + + try { + NaritechPatrolResponse response = naritechPatrolDataService.uploadPatrolData(subDataList); + if (response != null && Boolean.TRUE.equals(response.getResult())) { + List ids = dataList.stream().map(NaritechPatrolData::getLogId).collect(Collectors.toList()); + naritechPatrolDataService.recordUploadSuccess(ids); + uploadedIds.addAll(ids); + log.info("第 {}/{} 批上送成功, {} 条", batchNum, totalBatches, subDataList.size()); + } else { + log.error("第 {}/{} 批上送失败, 远端响应: {}", + batchNum, totalBatches, + response != null ? response.getMessage() : "无"); + } + } catch (Exception e) { + log.error("第 {}/{} 批上送异常", batchNum, totalBatches, e); + } + } + + log.info("===== 任务结束, 成功: {}/{}, 失败: {} =====", uploadedIds.size(), total, total - uploadedIds.size()); + } catch (Exception e) { + log.error("===== 巡视数据上送任务异常 =====", e); + } + } + + @GetMapping("/test") + public void test() { + log.info("===== 巡视数据测试上送任务开始, 当前时间: {} =====", LocalDateTime.now()); + try { + List dataList = new ArrayList<>(); + NaritechPatrolData data = NaritechPatrolData.builder() + .logId(1L) + .lineId(12736374) + .areaName("极Ⅱ") + .deviceName("极Ⅱ高Y/Y-C相") + .patroldeviceName("") + .patrolpointName("极Ⅱ高Y/Y-C相电容器与管母接头测温") + .value("36.7℃") + .time("2026-05-26 01:01:03") + .unit("℃") + .build(); + dataList.add(data); + NaritechPatrolResponse response = naritechPatrolDataService.uploadPatrolData(dataList); + log.info("===== 巡视数据测试上送任务完成, 结果: {} =====", + response != null ? response.getMessage() : "无数据"); + } catch (Exception e) { + log.error("===== 巡视数据测试上送任务异常 =====", e); + } + } +} diff --git a/src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolData.java b/src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolData.java new file mode 100644 index 0000000..e4909bb --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolData.java @@ -0,0 +1,42 @@ +package com.inspect.simulator.naritech.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +@Data +@Builder +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +public class NaritechPatrolData { + /** result_analysis 主键(用于记录,不上送) */ + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private Long logId; + + /** 唯一标识ID */ + private Integer lineId; + + /** 区域 */ + private String areaName; + + /** 设备 */ + private String deviceName; + + /** 相别 */ + private String patroldeviceName; + + /** 点位 */ + private String patrolpointName; + + /** 值 */ + private String value; + + /** 生成时间(格式:yyyy-MM-dd HH:mm:ss) */ + private String time; + + /** 单位(用于SQL映射,不上送) */ + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String unit; +} diff --git a/src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolResponse.java b/src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolResponse.java new file mode 100644 index 0000000..6b4d114 --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/dto/NaritechPatrolResponse.java @@ -0,0 +1,15 @@ +package com.inspect.simulator.naritech.dto; + +import lombok.Data; + +@Data +public class NaritechPatrolResponse { + + private Boolean result; + + private Integer code; + + private String message; + + private String body; +} diff --git a/src/main/java/com/inspect/simulator/naritech/dto/NaritechTokenResponse.java b/src/main/java/com/inspect/simulator/naritech/dto/NaritechTokenResponse.java new file mode 100644 index 0000000..472f392 --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/dto/NaritechTokenResponse.java @@ -0,0 +1,32 @@ +package com.inspect.simulator.naritech.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class NaritechTokenResponse { + + @JsonProperty("access_token") + private String accessToken; + + private String ver; + + private String code; + + private String scope; + + private String iss; + + @JsonProperty("token_type") + private String tokenType; + + private String message; + + @JsonProperty("expires_in") + private Integer expiresIn; + + @JsonProperty("client_id") + private String clientId; + + private String jti; +} diff --git a/src/main/java/com/inspect/simulator/naritech/service/NaritechPatrolDataService.java b/src/main/java/com/inspect/simulator/naritech/service/NaritechPatrolDataService.java new file mode 100644 index 0000000..13ff52c --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/service/NaritechPatrolDataService.java @@ -0,0 +1,140 @@ +package com.inspect.simulator.naritech.service; + +import com.inspect.simulator.mapper.ResultAnalysisMapper; +import com.inspect.simulator.naritech.config.NaritechProperties; +import com.inspect.simulator.naritech.dto.NaritechPatrolData; +import com.inspect.simulator.naritech.dto.NaritechPatrolResponse; +import com.inspect.simulator.utils.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +public class NaritechPatrolDataService { + + private static final Logger log = LoggerFactory.getLogger(NaritechPatrolDataService.class); + + private final RestTemplate restTemplate; + private final NaritechProperties apiProperties; + private final NaritechTokenService tokenService; + private final ResultAnalysisMapper resultAnalysisMapper; + + public NaritechPatrolDataService(RestTemplate restTemplate, + NaritechProperties apiProperties, + NaritechTokenService tokenService, + ResultAnalysisMapper resultAnalysisMapper) { + this.restTemplate = restTemplate; + this.apiProperties = apiProperties; + this.tokenService = tokenService; + this.resultAnalysisMapper = resultAnalysisMapper; + } + + /** + * 收集巡视数据。 + * 此处需要根据实际业务替换为真实的数据来源(数据库查询、文件读取等)。 + */ + public List collectPatrolData() { + // 查询当天未上报的红外算法巡视记录 + // infrared: filter=1 其他算法: filter=0 + List dataList = resultAnalysisMapper.selectNaritechNotUploaded(); + // 去掉读数单位 + if (dataList != null && !dataList.isEmpty()) { + dataList.forEach(NaritechPatrolDataService::removeUnitFromValue); + } + return dataList; + } + + /** + * 去掉读数单位 + */ + private static void removeUnitFromValue(NaritechPatrolData data) { + String value = data.getValue(); + if (StringUtils.isEmpty(value)) { + return; + } + + String unit = data.getUnit(); + String[] vals = value.split(",", -1); + List cleanedValues = new ArrayList<>(); + for (String val : vals) { + if (val.endsWith(unit)) { + val = val.substring(0, val.length() - unit.length()); + } + // 非数字值,直接返回空值 + if (!NumberUtils.isCreatable(val)) { + data.setValue(""); + return; + } + cleanedValues.add(val); + } + + data.setValue(String.join(",", cleanedValues)); + } + + /** + * 上送巡视数据到远端接口 + */ + public NaritechPatrolResponse uploadPatrolData(List dataList) { + if (dataList == null || dataList.isEmpty()) { + log.warn("无巡视数据需要上送"); + return null; + } + + String token = tokenService.getAccessToken(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(token); + + HttpEntity> requestEntity = new HttpEntity<>(dataList, headers); + + String url = apiProperties.getFullPatrolReceiveUrl(); + log.info("上送巡视数据到: {}, 数据条数: {}", url, dataList.size()); + + try { + NaritechPatrolResponse response = restTemplate.postForObject(url, requestEntity, NaritechPatrolResponse.class); + if (response != null && Boolean.TRUE.equals(response.getResult())) { + log.info("巡视数据上送成功: {}", response.getMessage()); + } else { + log.error("巡视数据上送失败: {}", response != null ? response.getMessage() : "无响应"); + } + return response; + } catch (Exception e) { + log.error("巡视数据上送异常", e); + throw new RuntimeException("巡视数据上送失败: " + e.getMessage(), e); + } + } + + public void recordUploadSuccess(List logIds) { + if (logIds == null || logIds.isEmpty()) { + log.warn("无巡视数据需要上送"); + return; + } + resultAnalysisMapper.batchInsertUploadLog(logIds, "naritech#infrared", LocalDateTime.now()); + log.info("已记录 {} 条上传成功数据", logIds.size()); + } + + public static void main(String[] args) { + NaritechPatrolData data = NaritechPatrolData.builder() + .lineId(12736374) + .areaName("极Ⅱ") + .deviceName("极Ⅱ高Y/Y-C相") + .patroldeviceName("") + .patrolpointName("极Ⅱ高Y/Y-C相电容器与管母接头测温") + .value("27,2℃") + .time("2026-05-26 01:01:03") + .unit("℃") + .build(); + removeUnitFromValue(data); + System.out.println(data); + } +} diff --git a/src/main/java/com/inspect/simulator/naritech/service/NaritechTokenService.java b/src/main/java/com/inspect/simulator/naritech/service/NaritechTokenService.java new file mode 100644 index 0000000..a36d63f --- /dev/null +++ b/src/main/java/com/inspect/simulator/naritech/service/NaritechTokenService.java @@ -0,0 +1,77 @@ +package com.inspect.simulator.naritech.service; + +import com.inspect.simulator.naritech.config.AuthProperties; +import com.inspect.simulator.naritech.config.NaritechProperties; +import com.inspect.simulator.naritech.dto.NaritechTokenResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.time.Instant; + +@Slf4j +@Service +public class NaritechTokenService { + private final RestTemplate restTemplate; + private final NaritechProperties apiProperties; + private final AuthProperties authProperties; + + private volatile String cachedToken; + private volatile long tokenExpireTime = 0; + + public NaritechTokenService(RestTemplate restTemplate, + NaritechProperties apiProperties, + AuthProperties authProperties) { + this.restTemplate = restTemplate; + this.apiProperties = apiProperties; + this.authProperties = authProperties; + } + + public synchronized String getAccessToken() { + // Token未过期则复用(提前5分钟刷新) + if (cachedToken != null && System.currentTimeMillis() < tokenExpireTime - 300_000) { + return cachedToken; + } + return fetchToken(); + } + + private String fetchToken() { + log.info("开始获取访问Token..."); + try { + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("grant_type", authProperties.getGrantType()); + body.add("scope", authProperties.getScope()); + body.add("client_id", authProperties.getClientId()); + body.add("client_secret", authProperties.getClientSecret()); + + String url = apiProperties.getFullTokenUrl(); + log.info("请求Token接口: {}", url); + + NaritechTokenResponse response = restTemplate.postForObject(url, body, NaritechTokenResponse.class); + + if (response != null && response.getAccessToken() != null) { + cachedToken = response.getAccessToken(); + if (response.getExpiresIn() != null && response.getExpiresIn() > 0) { + tokenExpireTime = System.currentTimeMillis() + response.getExpiresIn() * 1000L; + } else { + // 默认24小时过期 + tokenExpireTime = System.currentTimeMillis() + 86400_000L; + } + log.info("Token获取成功, 过期时间: {}", Instant.ofEpochMilli(tokenExpireTime)); + return cachedToken; + } + log.error("Token获取失败: 响应为空或缺少access_token"); + throw new RuntimeException("Token获取失败"); + } catch (Exception e) { + log.error("Token获取异常", e); + throw new RuntimeException("Token获取失败: " + e.getMessage(), e); + } + } + + public void clearToken() { + cachedToken = null; + tokenExpireTime = 0; + } +} diff --git a/src/main/resources/mapper/ResultAnalysisMapper.xml b/src/main/resources/mapper/ResultAnalysisMapper.xml index 20eac38..2d7f31b 100644 --- a/src/main/resources/mapper/ResultAnalysisMapper.xml +++ b/src/main/resources/mapper/ResultAnalysisMapper.xml @@ -73,4 +73,47 @@ + + + + INSERT INTO patrol_upload_log (log_id, type, upload_time) + VALUES + + (#{id}, #{type}, #{uploadTime}) + + ON DUPLICATE KEY UPDATE upload_time = VALUES(upload_time) + \ No newline at end of file