MesSyncService.java 11.7 KB
package com.iot.scheduler.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iot.scheduler.dto.MesSyncResponse;
import com.iot.scheduler.entity.DeviceState;
import com.iot.scheduler.repository.DeviceStateRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * MES设备状态同步服务
 */
@Slf4j
@Service
public class MesSyncService {

    @Value("${mes.sync.url:http://192.168.1.203/SMES_Production_Services/InvokeJSON}")
    private String mesSyncUrl;

    @Value("${mes.sync.method:ServicesCUS.Module_CUS.CUS_EquipmentStateSync}")
    private String mesSyncMethod;

    @Autowired
    private DeviceStateRepository deviceStateRepository;

    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private final ObjectMapper objectMapper = new ObjectMapper();

    private static final Integer STATUS_200 = 200;

    /**
     * 查询设备状态并批量同步到MES系统(一次请求发送全部设备)
     */
    public void syncDeviceStatesToMes() {
        log.info("========== 开始同步设备状态到MES ==========");
        long startTime = System.currentTimeMillis();

        try {
            // 1. 从数据库查询所有设备状态
            List<DeviceState> deviceStates = deviceStateRepository.findAllDeviceStates();
            log.info("查询到 {} 条设备状态记录", deviceStates.size());

            if (deviceStates.isEmpty()) {
                log.warn("未查询到任何设备数据,跳过同步");
                return;
            }

            // 2. 构建完整的请求报文(header + content)
            LocalDateTime now = LocalDateTime.now();
            String requestBody = buildFullRequestBody(deviceStates, now);

            // 3. 构建HTTP Header(只需要Content-Type,header已放在body中)
            Map<String, String> httpHeaderMap = new HashMap<>();
            httpHeaderMap.put("Content-Type", "application/json; charset=UTF-8");
            httpHeaderMap.put("Accept", "application/json");

            // ====== 打印请求详情 ======
            log.info("========== [请求参数] ==========");
            log.info("请求URL: {}", mesSyncUrl);
            log.info("请求方法: POST");
            log.info("请求Header: {}", httpHeaderMap);
            log.info("请求Body ({} bytes): \n{}", requestBody.getBytes().length, prettyPrintJson(requestBody));

            // 4. 发送POST请求
            String responseJson = sendPost(mesSyncUrl, requestBody, httpHeaderMap, null);

            // ====== 打印返回值详情 ======
            log.info("========== [返回值] ==========");
            log.info("MES原始响应: {}", responseJson);
            if (responseJson != null && !responseJson.matches("\\d+")) {
                log.info("格式化响应:\n{}", prettyPrintJson(responseJson));
            }

            // 5. 解析响应并记录结果
            if (responseJson != null && !responseJson.matches("\\d+")) {
                try {
                    MesSyncResponse response = objectMapper.readValue(responseJson, MesSyncResponse.class);
                    if (Boolean.TRUE.equals(response.getIsSuccess())) {
                        log.info("========== 设备状态同步成功: 共{}条, message={} ==========",
                                deviceStates.size(), response.getMessage());
                    } else {
                        log.warn("========== 设备状态同步失败: code={}, message={}, exception={} ==========",
                                response.getCode(), response.getMessage(), response.getExceptionMessage());
                    }
                } catch (Exception e) {
                    log.warn("MES响应JSON解析失败, 原始响应: {}, 异常: {}", responseJson, e.getMessage(), e);
                }
            } else if (responseJson != null && responseJson.equals("400")) {
                log.error("========== MES接口返回400错误,请检查:1.请求格式是否正确 2.接口地址是否正确 3.header是否在body中 ==========");
            } else if (responseJson != null && responseJson.equals("404")) {
                log.error("========== MES接口返回404错误,接口地址不存在,请检查URL配置 ==========");
            } else if (responseJson != null && responseJson.equals("500")) {
                log.error("========== MES接口返回500错误,服务器内部错误 ==========");
            } else {
                log.error("========== 设备状态同步失败: 共{}条, 响应: {} ==========", deviceStates.size(), responseJson);
            }

        } catch (Exception e) {
            log.error("设备状态同步异常", e);
        }

        long endTime = System.currentTimeMillis();
        log.info("========== 设备状态同步结束,耗时: {}ms ==========", (endTime - startTime));
    }

    /**
     * 构建完整的请求报文(header + content)
     * 符合MES接口规范:{"header": {...}, "content": [...]}
     */
    private String buildFullRequestBody(List<DeviceState> devices, LocalDateTime now) throws Exception {
        // 1. 构建 header 对象
        ObjectNode header = objectMapper.createObjectNode();
        header.put("method", mesSyncMethod);
        header.put("is_debug", "true");
        header.put("lang", "zh_CN");
        header.put("platform", "web");

        // 2. 构建 content 对象(MES要求 content 为JSON对象,内含 equipmentstatelist 数组)
        ObjectNode contentObj = objectMapper.createObjectNode();
        ArrayNode equipmentStateList = contentObj.putArray("equipmentstatelist");
        for (DeviceState device : devices) {
            ObjectNode deviceNode = equipmentStateList.addObject();
            deviceNode.put("equipmentno", device.getEquipmentNo());
            deviceNode.put("equipmentstate", device.getState() != null ? device.getState().toString() : "0");
            deviceNode.put("createdate", now.format(DATE_FORMAT));
        }

        // 3. 组装完整的请求体
        ObjectNode requestBody = objectMapper.createObjectNode();
        requestBody.set("header", header);
        requestBody.set("content", contentObj);

        return objectMapper.writeValueAsString(requestBody);
    }

    /**
     * 发送POST请求
     */
    public static String sendPost(String url, String bodyJson, Map<String, String> headerMap,
                                  Map<String, String> queryMap) {
        String response = null;

        // 处理查询参数
        if (MapUtils.isNotEmpty(queryMap)) {
            url = packQueryParamToUrl(url, queryMap);
        }

        CloseableHttpClient httpclient = null;
        CloseableHttpResponse httpResponse = null;

        try {
            httpclient = HttpClients.createDefault();
            HttpPost httppost = new HttpPost(url);

            // 设置请求体
            StringEntity stringentity = new StringEntity(bodyJson, ContentType.create("application/json", "UTF-8"));
            httppost.setEntity(stringentity);

            // 设置请求头
            if (MapUtils.isNotEmpty(headerMap)) {
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    httppost.setHeader(entry.getKey(), entry.getValue());
                }
            }

            // 执行请求
            httpResponse = httpclient.execute(httppost);
            int statusCode = httpResponse.getStatusLine().getStatusCode();

            if (statusCode == STATUS_200) {
                response = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
            } else {
                response = String.valueOf(statusCode);
                log.error("HTTP请求失败,状态码: {}", statusCode);
            }

        } catch (Exception e) {
            log.error("HttpClientUtil sendPost error", e);
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
                if (httpclient != null) {
                    httpclient.close();
                }
            } catch (Exception e) {
                log.error("关闭HTTP连接失败", e);
            }
        }

        return response;
    }

    /**
     * 发送GET请求
     */
    public static String sendGet(String url, Map<String, String> headerMap, Map<String, String> queryMap) {
        String response = null;

        if (MapUtils.isNotEmpty(queryMap)) {
            url = packQueryParamToUrl(url, queryMap);
        }

        CloseableHttpClient httpclient = null;
        CloseableHttpResponse httpResponse = null;

        try {
            httpclient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);

            if (MapUtils.isNotEmpty(headerMap)) {
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    httpGet.setHeader(entry.getKey(), entry.getValue());
                }
            }

            httpResponse = httpclient.execute(httpGet);
            int statusCode = httpResponse.getStatusLine().getStatusCode();

            if (statusCode == STATUS_200) {
                response = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
            } else {
                response = String.valueOf(statusCode);
            }

        } catch (Exception e) {
            log.error("HttpClientUtil sendGet error", e);
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
                if (httpclient != null) {
                    httpclient.close();
                }
            } catch (Exception e) {
                log.error("关闭HTTP连接失败", e);
            }
        }

        return response;
    }

    /**
     * 拼接查询参数到URL
     */
    private static String packQueryParamToUrl(String url, Map<String, String> queryMap) {
        StringBuilder queryBuilder = new StringBuilder(url);
        if (!url.contains("?")) {
            queryBuilder.append("?");
        } else {
            queryBuilder.append("&");
        }
        for (Map.Entry<String, String> entry : queryMap.entrySet()) {
            try {
                String encode = URLEncoder.encode(entry.getValue(), "UTF-8");
                queryBuilder.append(entry.getKey()).append("=").append(encode).append("&");
            } catch (Exception e) {
                log.error("HttpClientUtil encode url query param error", e);
            }
        }
        queryBuilder.setLength(queryBuilder.length() - 1);
        return queryBuilder.toString();
    }

    /**
     * JSON字符串格式化(美化打印)
     */
    private String prettyPrintJson(String json) {
        try {
            JsonNode node = objectMapper.readTree(json);
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
        } catch (Exception e) {
            return json;
        }
    }
}