Commit 624b1fb8a165de8c4e2abfac490d5d2e92890635

Authored by 杨鸣坤
1 parent 7220147f

feat: 添加PostgreSQL、JPA及MES同步配置

... ... @@ -35,6 +35,47 @@
35 35 <artifactId>spring-boot-starter-test</artifactId>
36 36 <scope>test</scope>
37 37 </dependency>
  38 + <!-- PostgreSQL -->
  39 + <dependency>
  40 + <groupId>org.postgresql</groupId>
  41 + <artifactId>postgresql</artifactId>
  42 + <scope>runtime</scope>
  43 + </dependency>
  44 + <!-- Spring Data JPA -->
  45 + <dependency>
  46 + <groupId>org.springframework.boot</groupId>
  47 + <artifactId>spring-boot-starter-data-jpa</artifactId>
  48 + </dependency>
  49 + <!-- WebClient for HTTP calls -->
  50 + <dependency>
  51 + <groupId>org.springframework.boot</groupId>
  52 + <artifactId>spring-boot-starter-webflux</artifactId>
  53 + </dependency>
  54 + <!-- Jackson for JSON -->
  55 + <dependency>
  56 + <groupId>com.fasterxml.jackson.core</groupId>
  57 + <artifactId>jackson-databind</artifactId>
  58 + </dependency>
  59 +
  60 + <!--工具类-->
  61 + <dependency>
  62 + <groupId>org.apache.commons</groupId>
  63 + <artifactId>commons-lang3</artifactId>
  64 + <version>3.6</version>
  65 + </dependency>
  66 +
  67 + <!--工具类-->
  68 + <dependency>
  69 + <groupId>org.apache.commons</groupId>
  70 + <artifactId>commons-collections4</artifactId>
  71 + <version>4.2</version>
  72 + </dependency>
  73 +
  74 + <dependency>
  75 + <groupId>org.apache.httpcomponents</groupId>
  76 + <artifactId>httpclient</artifactId>
  77 + <version>4.5.8</version>
  78 + </dependency>
38 79 </dependencies>
39 80
40 81 <build>
... ...
  1 +package com.iot.scheduler.config;
  2 +
  3 +import org.springframework.context.annotation.Bean;
  4 +import org.springframework.context.annotation.Configuration;
  5 +import org.springframework.http.client.reactive.ReactorClientHttpConnector;
  6 +import org.springframework.web.reactive.function.client.WebClient;
  7 +import reactor.netty.http.client.HttpClient;
  8 +
  9 +import java.time.Duration;
  10 +
  11 +/**
  12 + * WebClient配置
  13 + */
  14 +@Configuration
  15 +public class WebClientConfig {
  16 +
  17 + @Bean
  18 + public WebClient webClient() {
  19 + HttpClient httpClient = HttpClient.create()
  20 + .responseTimeout(Duration.ofSeconds(30))
  21 + .followRedirect(true); // 自动跟随301/302重定向
  22 +
  23 + return WebClient.builder()
  24 + .clientConnector(new ReactorClientHttpConnector(httpClient))
  25 + .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
  26 + .build();
  27 + }
  28 +}
... ...
  1 +package com.iot.scheduler.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +/**
  10 + * MES同步请求报文
  11 + */
  12 +@Data
  13 +@Builder
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class MesSyncRequest {
  17 +
  18 + @JsonProperty("header")
  19 + private Header header;
  20 +
  21 + @JsonProperty("content")
  22 + private Content content;
  23 +
  24 + @Data
  25 + @Builder
  26 + @NoArgsConstructor
  27 + @AllArgsConstructor
  28 + public static class Header {
  29 + private String method;
  30 + private String isDebug;
  31 + private String lang;
  32 + private String platform;
  33 + }
  34 +
  35 + @Data
  36 + @Builder
  37 + @NoArgsConstructor
  38 + @AllArgsConstructor
  39 + public static class Content {
  40 + private String equipmentno;
  41 + private String equipmentstate;
  42 + private String createdate;
  43 + }
  44 +}
... ...
  1 +package com.iot.scheduler.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * MES同步响应报文
  8 + */
  9 +@Data
  10 +public class MesSyncResponse {
  11 + @JsonProperty("isSuccess")
  12 + private Boolean isSuccess;
  13 +
  14 + private Integer Code;
  15 +
  16 + private String uri;
  17 +
  18 + private String Message;
  19 +
  20 + private String Exception;
  21 +
  22 + private String ExceptionMessage;
  23 +
  24 + private String StackTrace;
  25 +
  26 + @JsonProperty("Content")
  27 + private Object Content;
  28 +}
... ...
  1 +package com.iot.scheduler.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 设备状态实体类(对应SQL查询结果)
  7 + */
  8 +@Data
  9 +public class DeviceState {
  10 + private String productType; // 所属产品
  11 + private String equipmentNo; // 设备名称 (equipmentno)
  12 + private Integer state; // 状态编号 (equipmentstate)
  13 +}
... ...
  1 +package com.iot.scheduler.repository;
  2 +
  3 +import com.iot.scheduler.entity.DeviceState;
  4 +import org.springframework.beans.factory.annotation.Autowired;
  5 +import org.springframework.jdbc.core.JdbcTemplate;
  6 +import org.springframework.jdbc.core.RowMapper;
  7 +import org.springframework.stereotype.Repository;
  8 +
  9 +import java.sql.ResultSet;
  10 +import java.sql.SQLException;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * 设备状态数据访问层
  15 + */
  16 +@Repository
  17 +public class DeviceStateRepository {
  18 +
  19 + @Autowired
  20 + private JdbcTemplate jdbcTemplate;
  21 +
  22 + private final RowMapper<DeviceState> rowMapper = new RowMapper<>() {
  23 + @Override
  24 + public DeviceState mapRow(ResultSet rs, int rowNum) throws SQLException {
  25 + DeviceState device = new DeviceState();
  26 + device.setProductType(rs.getString("所属产品"));
  27 + device.setEquipmentNo(rs.getString("设备名称"));
  28 + Object stateObj = rs.getObject("状态");
  29 + if (stateObj != null) {
  30 + device.setState(((Number) stateObj).intValue());
  31 + }
  32 + return device;
  33 + }
  34 + };
  35 +
  36 + /**
  37 + * 查询所有设备状态
  38 + */
  39 + public List<DeviceState> findAllDeviceStates() {
  40 + String sql = """
  41 + select
  42 + de.type as 所属产品,
  43 + de.name as 设备名称,
  44 + CASE
  45 + WHEN attrA0.bool_v != true THEN 0
  46 + WHEN tk.long_v = 0 THEN 2
  47 + WHEN tk.long_v = 1 THEN 3
  48 + END as 状态
  49 + from
  50 + device de
  51 + left join ts_kv_latest tk on de.id = tk.entity_id
  52 + and tk.key = 103
  53 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  54 + AND attrA0.entity_type = 'DEVICE'
  55 + AND attrA0.attribute_key = 'active'
  56 + where
  57 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  58 + and (
  59 + de.device_profile_id = '3bedb640-1f29-11f0-95bb-f76fd8ecf988'
  60 + or de.device_profile_id = '47f59320-1f26-11f0-95bb-f76fd8ecf988'
  61 + )
  62 +
  63 + UNION ALL
  64 +
  65 + select
  66 + de.type as 所属产品,
  67 + de.name as 设备名称,
  68 + CASE
  69 + WHEN attrA0.bool_v != true THEN 0
  70 + WHEN tk.long_v = 0 THEN 2
  71 + WHEN tk.long_v = 1 THEN 3
  72 + END as 状态
  73 + from
  74 + device de
  75 + left join ts_kv_latest tk on de.id = tk.entity_id
  76 + and tk.key = 103
  77 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  78 + AND attrA0.entity_type = 'DEVICE'
  79 + AND attrA0.attribute_key = 'active'
  80 + where
  81 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  82 + and de.device_profile_id = 'ee71f340-1f2d-11f0-95bb-f76fd8ecf988'
  83 +
  84 + UNION ALL
  85 +
  86 + select
  87 + de.type as 所属产品,
  88 + de.name as 设备名称,
  89 + CASE
  90 + WHEN attrA0.bool_v != true THEN 0
  91 + WHEN MAX(tk.long_v) = 0 THEN 2
  92 + WHEN MAX(tk.long_v) = 1 THEN 3
  93 + END as 状态
  94 + from
  95 + device de
  96 + left join ts_kv_latest tk on de.id = tk.entity_id
  97 + and tk.key IN (103, 102, 101, 100)
  98 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  99 + AND attrA0.entity_type = 'DEVICE'
  100 + AND attrA0.attribute_key = 'active'
  101 + where
  102 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  103 + and de.device_profile_id = 'a5fbfe30-1f2d-11f0-95bb-f76fd8ecf988'
  104 + GROUP BY
  105 + de.name,
  106 + de.type,
  107 + attrA0.bool_v
  108 +
  109 + UNION ALL
  110 +
  111 + select
  112 + de.type as 所属产品,
  113 + de.name as 设备名称,
  114 + CASE
  115 + WHEN attrA0.bool_v != true THEN 0
  116 + WHEN tk1.str_v != '***(Others)' THEN 1
  117 + WHEN tk.str_v = 'STaRT' THEN 3
  118 + ELSE 2
  119 + END as 状态
  120 + from
  121 + device de
  122 + left join ts_kv_latest tk on de.id = tk.entity_id
  123 + and tk.key = 231
  124 + left join ts_kv_latest tk1 on de.id = tk1.entity_id
  125 + and tk1.key = 221
  126 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  127 + AND attrA0.entity_type = 'DEVICE'
  128 + AND attrA0.attribute_key = 'active'
  129 + where
  130 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  131 + and de.device_profile_id = 'a3a188e0-1f2c-11f0-95bb-f76fd8ecf988'
  132 +
  133 + UNION ALL
  134 +
  135 + select
  136 + de.type as 所属产品,
  137 + de.name as 设备名称,
  138 + CASE
  139 + WHEN attrA0.bool_v != true THEN 0
  140 + WHEN tk1.long_v = 1 THEN 1
  141 + WHEN tk.long_v = 1 THEN 3
  142 + ELSE 2
  143 + END as 状态
  144 + from
  145 + device de
  146 + left join ts_kv_latest tk on de.id = tk.entity_id
  147 + and tk.key = 81
  148 + left join ts_kv_latest tk1 on de.id = tk1.entity_id
  149 + and tk1.key = 119
  150 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  151 + AND attrA0.entity_type = 'DEVICE'
  152 + AND attrA0.attribute_key = 'active'
  153 + where
  154 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  155 + and de.device_profile_id = '8c247660-1f2b-11f0-95bb-f76fd8ecf988'
  156 +
  157 + UNION ALL
  158 +
  159 + select
  160 + de.type as 所属产品,
  161 + de.name as 设备名称,
  162 + CASE
  163 + WHEN attrA0.bool_v != true THEN 0
  164 + WHEN tk1.long_v = 1 THEN 1
  165 + WHEN tk.long_v = 1 THEN 3
  166 + ELSE 2
  167 + END as 状态
  168 + from
  169 + device de
  170 + left join ts_kv_latest tk on de.id = tk.entity_id
  171 + and tk.key = 86
  172 + left join ts_kv_latest tk1 on de.id = tk1.entity_id
  173 + and tk1.key = 147
  174 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  175 + AND attrA0.entity_type = 'DEVICE'
  176 + AND attrA0.attribute_key = 'active'
  177 + where
  178 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  179 + and de.device_profile_id = '312d1be0-1f2b-11f0-95bb-f76fd8ecf988'
  180 +
  181 + UNION ALL
  182 +
  183 + select
  184 + de.type as 所属产品,
  185 + de.name as 设备名称,
  186 + CASE
  187 + WHEN attrA0.bool_v != true THEN 0
  188 + WHEN tk1.long_v = 1 THEN 1
  189 + WHEN tk.long_v = 1 THEN 3
  190 + ELSE 2
  191 + END as 状态
  192 + from
  193 + device de
  194 + left join ts_kv_latest tk on de.id = tk.entity_id
  195 + and tk.key = 97
  196 + left join ts_kv_latest tk1 on de.id = tk1.entity_id
  197 + and tk1.key = 111
  198 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  199 + AND attrA0.entity_type = 'DEVICE'
  200 + AND attrA0.attribute_key = 'active'
  201 + where
  202 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  203 + and de.device_profile_id = 'aaee40f0-1f24-11f0-95bb-f76fd8ecf988'
  204 +
  205 + UNION ALL
  206 +
  207 + -- 子设备固安力冲压机
  208 + select
  209 + de.type as 所属产品,
  210 + de.name as 设备名称,
  211 + CASE
  212 + WHEN attrA0.bool_v != true THEN 0
  213 + WHEN tk1.long_v != 1 and tk.long_v = 1 THEN 3
  214 + WHEN tk1.long_v != 1 and tk.long_v = 0 THEN 2
  215 + WHEN tk1.long_v = 1 THEN 1
  216 + END as 状态
  217 + from
  218 + device de
  219 + left join ts_kv_latest tk on de.id = tk.entity_id
  220 + and tk.key = 98
  221 + left join ts_kv_latest tk1 on de.id = tk1.entity_id
  222 + and tk1.key = 103
  223 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  224 + AND attrA0.entity_type = 'DEVICE'
  225 + AND attrA0.attribute_key = 'active'
  226 + where
  227 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  228 + and de.device_profile_id = '01e3db90-1f20-11f0-95bb-f76fd8ecf988'
  229 +
  230 + UNION ALL
  231 +
  232 + -- 子设备海天注塑机
  233 + select
  234 + de.type as 所属产品,
  235 + de.name as 设备名称,
  236 + CASE
  237 + WHEN attrA0.bool_v != true THEN 0
  238 + WHEN tk.long_v = 1 THEN 2
  239 + WHEN tk.long_v = 2 THEN 3
  240 + END as 状态
  241 + from
  242 + device de
  243 + left join ts_kv_latest tk on de.id = tk.entity_id
  244 + and tk.key = 65
  245 + LEFT JOIN attribute_kv attrA0 on de.id = attrA0.entity_id
  246 + AND attrA0.entity_type = 'DEVICE'
  247 + AND attrA0.attribute_key = 'active'
  248 + where
  249 + de.tenant_id = '606bd430-1b6d-11f0-916e-45391b6ab681'
  250 + and de.device_profile_id = 'ffe73360-1e7e-11f0-95bb-f76fd8ecf988'
  251 + """;
  252 +
  253 + return jdbcTemplate.query(sql, rowMapper);
  254 + }
  255 +}
... ...
  1 +package com.iot.scheduler.service;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.fasterxml.jackson.databind.node.ArrayNode;
  6 +import com.fasterxml.jackson.databind.node.ObjectNode;
  7 +import com.iot.scheduler.dto.MesSyncResponse;
  8 +import com.iot.scheduler.entity.DeviceState;
  9 +import com.iot.scheduler.repository.DeviceStateRepository;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.apache.commons.collections4.MapUtils;
  12 +import org.apache.http.client.methods.CloseableHttpResponse;
  13 +import org.apache.http.client.methods.HttpGet;
  14 +import org.apache.http.client.methods.HttpPost;
  15 +import org.apache.http.entity.ContentType;
  16 +import org.apache.http.entity.StringEntity;
  17 +import org.apache.http.impl.client.CloseableHttpClient;
  18 +import org.apache.http.impl.client.HttpClients;
  19 +import org.apache.http.util.EntityUtils;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.beans.factory.annotation.Value;
  22 +import org.springframework.stereotype.Service;
  23 +
  24 +import java.net.URLEncoder;
  25 +import java.time.LocalDateTime;
  26 +import java.time.format.DateTimeFormatter;
  27 +import java.util.HashMap;
  28 +import java.util.List;
  29 +import java.util.Map;
  30 +
  31 +/**
  32 + * MES设备状态同步服务
  33 + */
  34 +@Slf4j
  35 +@Service
  36 +public class MesSyncService {
  37 +
  38 + @Value("${mes.sync.url:http://192.168.1.203/SMES_Test_Services/ServicesCUS.Module_CUS.CUS_EquipmentStateSync}")
  39 + private String mesSyncUrl;
  40 +
  41 + @Value("${mes.sync.method:ServicesCUS.Module_CUS.CUS_EquipmentStateSync}")
  42 + private String mesSyncMethod;
  43 +
  44 + @Autowired
  45 + private DeviceStateRepository deviceStateRepository;
  46 +
  47 + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  48 + private final ObjectMapper objectMapper = new ObjectMapper();
  49 +
  50 + private static final Integer STATUS_200 = 200;
  51 +
  52 + /**
  53 + * 查询设备状态并批量同步到MES系统(一次请求发送全部设备)
  54 + */
  55 + public void syncDeviceStatesToMes() {
  56 + log.info("========== 开始同步设备状态到MES ==========");
  57 + long startTime = System.currentTimeMillis();
  58 +
  59 + try {
  60 + // 1. 从数据库查询所有设备状态
  61 + List<DeviceState> deviceStates = deviceStateRepository.findAllDeviceStates();
  62 + log.info("查询到 {} 条设备状态记录", deviceStates.size());
  63 +
  64 + if (deviceStates.isEmpty()) {
  65 + log.warn("未查询到任何设备数据,跳过同步");
  66 + return;
  67 + }
  68 +
  69 + // 2. 构建完整的请求报文(header + content)
  70 + LocalDateTime now = LocalDateTime.now();
  71 + String requestBody = buildFullRequestBody(deviceStates, now);
  72 +
  73 + // 3. 构建HTTP Header(只需要Content-Type,header已放在body中)
  74 + Map<String, String> httpHeaderMap = new HashMap<>();
  75 + httpHeaderMap.put("Content-Type", "application/json; charset=UTF-8");
  76 + httpHeaderMap.put("Accept", "application/json");
  77 +
  78 + // ====== 打印请求详情 ======
  79 + log.info("========== [请求参数] ==========");
  80 + log.info("请求URL: {}", mesSyncUrl);
  81 + log.info("请求方法: POST");
  82 + log.info("请求Header: {}", httpHeaderMap);
  83 + log.info("请求Body ({} bytes): \n{}", requestBody.getBytes().length, prettyPrintJson(requestBody));
  84 +
  85 + // 4. 发送POST请求
  86 + String responseJson = sendPost(mesSyncUrl, requestBody, httpHeaderMap, null);
  87 +
  88 + // ====== 打印返回值详情 ======
  89 + log.info("========== [返回值] ==========");
  90 + log.info("MES原始响应: {}", responseJson);
  91 + if (responseJson != null && !responseJson.matches("\\d+")) {
  92 + log.info("格式化响应:\n{}", prettyPrintJson(responseJson));
  93 + }
  94 +
  95 + // 5. 解析响应并记录结果
  96 + if (responseJson != null && !responseJson.matches("\\d+")) {
  97 + try {
  98 + MesSyncResponse response = objectMapper.readValue(responseJson, MesSyncResponse.class);
  99 + if (Boolean.TRUE.equals(response.getIsSuccess())) {
  100 + log.info("========== 设备状态同步成功: 共{}条, message={} ==========",
  101 + deviceStates.size(), response.getMessage());
  102 + } else {
  103 + log.warn("========== 设备状态同步失败: code={}, message={}, exception={} ==========",
  104 + response.getCode(), response.getMessage(), response.getExceptionMessage());
  105 + }
  106 + } catch (Exception e) {
  107 + log.warn("MES响应JSON解析失败, 原始响应: {}", responseJson);
  108 + }
  109 + } else if (responseJson != null && responseJson.equals("400")) {
  110 + log.error("========== MES接口返回400错误,请检查:1.请求格式是否正确 2.接口地址是否正确 3.header是否在body中 ==========");
  111 + } else if (responseJson != null && responseJson.equals("404")) {
  112 + log.error("========== MES接口返回404错误,接口地址不存在,请检查URL配置 ==========");
  113 + } else if (responseJson != null && responseJson.equals("500")) {
  114 + log.error("========== MES接口返回500错误,服务器内部错误 ==========");
  115 + } else {
  116 + log.error("========== 设备状态同步失败: 共{}条, 响应: {} ==========", deviceStates.size(), responseJson);
  117 + }
  118 +
  119 + } catch (Exception e) {
  120 + log.error("设备状态同步异常", e);
  121 + }
  122 +
  123 + long endTime = System.currentTimeMillis();
  124 + log.info("========== 设备状态同步结束,耗时: {}ms ==========", (endTime - startTime));
  125 + }
  126 +
  127 + /**
  128 + * 构建完整的请求报文(header + content)
  129 + * 符合MES接口规范:{"header": {...}, "content": [...]}
  130 + */
  131 + private String buildFullRequestBody(List<DeviceState> devices, LocalDateTime now) throws Exception {
  132 + // 1. 构建 header 对象
  133 + ObjectNode header = objectMapper.createObjectNode();
  134 + header.put("method", mesSyncMethod);
  135 + header.put("is_debug", "true");
  136 + header.put("lang", "zh_CN");
  137 + header.put("platform", "web");
  138 +
  139 + // 2. 构建 content 数组
  140 + ArrayNode contentArray = objectMapper.createArrayNode();
  141 + for (DeviceState device : devices) {
  142 + ObjectNode deviceNode = objectMapper.createObjectNode();
  143 + deviceNode.put("equipmentno", device.getEquipmentNo());
  144 + deviceNode.put("equipmentstate", device.getState() != null ? device.getState().toString() : "0");
  145 + deviceNode.put("createdate", now.format(DATE_FORMAT));
  146 + contentArray.add(deviceNode);
  147 + }
  148 +
  149 + // 3. 组装完整的请求体
  150 + ObjectNode requestBody = objectMapper.createObjectNode();
  151 + requestBody.set("header", header);
  152 + requestBody.set("content", contentArray);
  153 +
  154 + return objectMapper.writeValueAsString(requestBody);
  155 + }
  156 +
  157 + /**
  158 + * 发送POST请求
  159 + */
  160 + public static String sendPost(String url, String bodyJson, Map<String, String> headerMap,
  161 + Map<String, String> queryMap) {
  162 + String response = null;
  163 +
  164 + // 处理查询参数
  165 + if (MapUtils.isNotEmpty(queryMap)) {
  166 + url = packQueryParamToUrl(url, queryMap);
  167 + }
  168 +
  169 + CloseableHttpClient httpclient = null;
  170 + CloseableHttpResponse httpResponse = null;
  171 +
  172 + try {
  173 + httpclient = HttpClients.createDefault();
  174 + HttpPost httppost = new HttpPost(url);
  175 +
  176 + // 设置请求体
  177 + StringEntity stringentity = new StringEntity(bodyJson, ContentType.create("application/json", "UTF-8"));
  178 + httppost.setEntity(stringentity);
  179 +
  180 + // 设置请求头
  181 + if (MapUtils.isNotEmpty(headerMap)) {
  182 + for (Map.Entry<String, String> entry : headerMap.entrySet()) {
  183 + httppost.setHeader(entry.getKey(), entry.getValue());
  184 + }
  185 + }
  186 +
  187 + // 执行请求
  188 + httpResponse = httpclient.execute(httppost);
  189 + int statusCode = httpResponse.getStatusLine().getStatusCode();
  190 +
  191 + if (statusCode == STATUS_200) {
  192 + response = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
  193 + } else {
  194 + response = String.valueOf(statusCode);
  195 + log.error("HTTP请求失败,状态码: {}", statusCode);
  196 + }
  197 +
  198 + } catch (Exception e) {
  199 + log.error("HttpClientUtil sendPost error", e);
  200 + } finally {
  201 + try {
  202 + if (httpResponse != null) {
  203 + httpResponse.close();
  204 + }
  205 + if (httpclient != null) {
  206 + httpclient.close();
  207 + }
  208 + } catch (Exception e) {
  209 + log.error("关闭HTTP连接失败", e);
  210 + }
  211 + }
  212 +
  213 + return response;
  214 + }
  215 +
  216 + /**
  217 + * 发送GET请求
  218 + */
  219 + public static String sendGet(String url, Map<String, String> headerMap, Map<String, String> queryMap) {
  220 + String response = null;
  221 +
  222 + if (MapUtils.isNotEmpty(queryMap)) {
  223 + url = packQueryParamToUrl(url, queryMap);
  224 + }
  225 +
  226 + CloseableHttpClient httpclient = null;
  227 + CloseableHttpResponse httpResponse = null;
  228 +
  229 + try {
  230 + httpclient = HttpClients.createDefault();
  231 + HttpGet httpGet = new HttpGet(url);
  232 +
  233 + if (MapUtils.isNotEmpty(headerMap)) {
  234 + for (Map.Entry<String, String> entry : headerMap.entrySet()) {
  235 + httpGet.setHeader(entry.getKey(), entry.getValue());
  236 + }
  237 + }
  238 +
  239 + httpResponse = httpclient.execute(httpGet);
  240 + int statusCode = httpResponse.getStatusLine().getStatusCode();
  241 +
  242 + if (statusCode == STATUS_200) {
  243 + response = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
  244 + } else {
  245 + response = String.valueOf(statusCode);
  246 + }
  247 +
  248 + } catch (Exception e) {
  249 + log.error("HttpClientUtil sendGet error", e);
  250 + } finally {
  251 + try {
  252 + if (httpResponse != null) {
  253 + httpResponse.close();
  254 + }
  255 + if (httpclient != null) {
  256 + httpclient.close();
  257 + }
  258 + } catch (Exception e) {
  259 + log.error("关闭HTTP连接失败", e);
  260 + }
  261 + }
  262 +
  263 + return response;
  264 + }
  265 +
  266 + /**
  267 + * 拼接查询参数到URL
  268 + */
  269 + private static String packQueryParamToUrl(String url, Map<String, String> queryMap) {
  270 + StringBuilder queryBuilder = new StringBuilder(url);
  271 + if (!url.contains("?")) {
  272 + queryBuilder.append("?");
  273 + } else {
  274 + queryBuilder.append("&");
  275 + }
  276 + for (Map.Entry<String, String> entry : queryMap.entrySet()) {
  277 + try {
  278 + String encode = URLEncoder.encode(entry.getValue(), "UTF-8");
  279 + queryBuilder.append(entry.getKey()).append("=").append(encode).append("&");
  280 + } catch (Exception e) {
  281 + log.error("HttpClientUtil encode url query param error", e);
  282 + }
  283 + }
  284 + queryBuilder.setLength(queryBuilder.length() - 1);
  285 + return queryBuilder.toString();
  286 + }
  287 +
  288 + /**
  289 + * JSON字符串格式化(美化打印)
  290 + */
  291 + private String prettyPrintJson(String json) {
  292 + try {
  293 + JsonNode node = objectMapper.readTree(json);
  294 + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
  295 + } catch (Exception e) {
  296 + return json;
  297 + }
  298 + }
  299 +}
\ No newline at end of file
... ...
  1 +package com.iot.scheduler.task;
  2 +
  3 +import com.iot.scheduler.service.MesSyncService;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.beans.factory.annotation.Value;
  7 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  8 +import org.springframework.scheduling.annotation.Scheduled;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +/**
  12 + * MES设备状态同步定时任务
  13 + * 每分钟执行一次,将设备状态同步到MES系统
  14 + */
  15 +@Slf4j
  16 +@Component
  17 +@ConditionalOnProperty(name = "mes.sync.enabled", havingValue = "true", matchIfMissing = true)
  18 +public class MesSyncTask {
  19 +
  20 + @Autowired
  21 + private MesSyncService mesSyncService;
  22 +
  23 + @Value("${mes.sync.cron:0 */1 * * * ?}")
  24 + private String cronExpression;
  25 +
  26 + /**
  27 + * 定时任务:每分钟同步设备状态到MES
  28 + * 默认cron: (每分钟的第0秒执行)
  29 + */
  30 + @Scheduled(cron = "${mes.sync.cron:0 */1 * * * ?}")
  31 + public void syncDeviceStates() {
  32 + log.info("[MesSyncTask] 开始执行设备状态同步任务...");
  33 + long startTime = System.currentTimeMillis();
  34 + try {
  35 + mesSyncService.syncDeviceStatesToMes();
  36 + } catch (Exception e) {
  37 + log.error("[MesSyncTask] 设备状态同步任务执行异常", e);
  38 + } finally {
  39 + long costTime = System.currentTimeMillis() - startTime;
  40 + log.info("[MesSyncTask] 设备状态同步任务结束,耗时: {}ms", costTime);
  41 + }
  42 + }
  43 +}
... ...
... ... @@ -3,6 +3,16 @@ spring:
3 3 name: iot-scheduler
4 4 main:
5 5 banner-mode: off
  6 + datasource:
  7 + url: jdbc:postgresql://60.175.90.27:5433/thingskit
  8 + username: postgres
  9 + password: thingskit
  10 + driver-class-name: org.postgresql.Driver
  11 + jpa:
  12 + database-platform: org.hibernate.dialect.PostgreSQLDialect
  13 + show-sql: false
  14 + hibernate:
  15 + ddl-auto: none
6 16
7 17 server:
8 18 port: 8080
... ... @@ -24,3 +34,11 @@ scheduler:
24 34 panji:
25 35 pull: "0 0/5 * * * ?"
26 36 push: "0 0/10 * * * ?"
  37 +
  38 +# MES Sync Configuration
  39 +mes:
  40 + sync:
  41 + enabled: true
  42 + url: http://192.168.1.203/SMES_Test_Services/***
  43 + method: ServicesCUS.Module_CUS.CUS_EquipmentStateSync
  44 + cron: "0 */1 * * * ?" # Every 1 minute
... ...