liujie
2026-06-09 c96fbe6b0aeb413a17c429133ee6d81d138144b9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package com.xiaopeng.order.utils;
 
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.order.config.MinistryTransportProperties;
import com.xiaopeng.order.model.dto.RegulationOrderCreateInfoDTO;
import com.xiaopeng.order.model.dto.RegulationOrderMatchInfoDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
 
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
 
@Slf4j
@Component
public class MinistryOfTransportOrderReporter {
 
    public static final DateTimeFormatter DATA_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
 
    private static final DateTimeFormatter FILE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-ddHHmmssSSS");
 
    private static final String TOKEN_CACHE_KEY = "ministry_of_transport:token";
 
    private static final long TOKEN_EXPIRE_SECONDS = 20 * 60;
 
    private static final long TOKEN_REFRESH_THRESHOLD_SECONDS = 5 * 60;
 
    private final RedisService redisService;
 
    private final MinistryTransportProperties ministryTransportProperties;
 
    private final RestTemplate restTemplate;
 
    public MinistryOfTransportOrderReporter(RedisService redisService,
                                            MinistryTransportProperties ministryTransportProperties) {
        this.redisService = redisService;
        this.ministryTransportProperties = ministryTransportProperties;
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(10000);
        requestFactory.setReadTimeout(10000);
        this.restTemplate = new RestTemplate(requestFactory);
    }
 
    public String pushOrderCreateInfo(RegulationOrderCreateInfoDTO item) {
        return pushOrderInfo(item, "DDFQ");
    }
 
    public String pushOrderMatchInfo(RegulationOrderMatchInfoDTO item) {
        return pushOrderInfo(item, "DDCG");
    }
 
    private String pushOrderInfo(Object item, String command) {
        String token = getToken();
        if (StringUtils.isBlank(token)) {
            throw new RuntimeException("交通部token为空");
        }
        JSONObject body = new JSONObject();
        JSONArray items = new JSONArray();
        items.add(JSON.toJSON(item));
        body.put("items", items);
        String filename = ministryTransportProperties.getPlatform() + "_ORDER_" + command + "_REQ_" + LocalDateTime.now().format(FILE_TIME_FORMATTER) + ".json";
        return uploadJsonFile(filename, body.toJSONString(), token);
    }
 
    private String getToken() {
        String cachedToken = redisService.getCacheObject(TOKEN_CACHE_KEY);
        if (StringUtils.isNotBlank(cachedToken)) {
            Long expireTime = redisService.getExpire(TOKEN_CACHE_KEY);
            if (expireTime != null && expireTime > TOKEN_REFRESH_THRESHOLD_SECONDS) {
                return cachedToken;
            }
        }
        JSONObject body = new JSONObject();
        body.put("platform", ministryTransportProperties.getPlatform());
        body.put("key", ministryTransportProperties.getKey());
        String filename = ministryTransportProperties.getPlatform() + "_TOKEN_ACCESS_REQ_" + LocalDateTime.now().format(FILE_TIME_FORMATTER) + ".json";
        String response = uploadJsonFile(filename, body.toJSONString(), ministryTransportProperties.getPlatform());
        String token = parseToken(response);
        redisService.setCacheObject(TOKEN_CACHE_KEY, token, TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return token;
    }
 
    private String uploadJsonFile(String filename, String jsonBody, String auth) {
        byte[] bytes = jsonBody.getBytes(StandardCharsets.UTF_8);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        headers.add("binfile-md5", md5Hex(bytes));
        headers.add("binfile-auth", auth);
        headers.add("binfile-gzip", "false");
        headers.add("binfile-reqlen", String.valueOf(bytes.length));
 
        HttpHeaders fileHeaders = new HttpHeaders();
        fileHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(new ByteArrayResource(bytes) {
            @Override
            public String getFilename() {
                return filename;
            }
        }, fileHeaders);
 
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("binFile", fileEntity);
        body.add("filename", filename);
 
        ResponseEntity<String> response = restTemplate.postForEntity(
                ministryTransportProperties.getUrl(),
                new HttpEntity<>(body, headers),
                String.class
        );
        String responseBody = response.getBody() == null ? "" : response.getBody();
        log.info("交通部订单接口响应 status={}, filename={}, responseLength={}",
                response.getStatusCodeValue(), filename, responseBody.length());
        return responseBody;
    }
 
    private String parseToken(String tokenResponse) {
        if (StringUtils.isBlank(tokenResponse)) {
            throw new RuntimeException("交通部token响应为空");
        }
        JSONObject response = JSON.parseObject(tokenResponse);
        String token = response.getString("token");
        if (StringUtils.isNotBlank(token)) {
            return token;
        }
        JSONArray items = response.getJSONArray("items");
        if (items != null && !items.isEmpty()) {
            token = items.getJSONObject(0).getString("token");
            if (StringUtils.isNotBlank(token)) {
                return token;
            }
        }
        throw new RuntimeException("交通部token响应中未找到token: " + tokenResponse);
    }
 
    private String md5Hex(byte[] bytes) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] digest = md5.digest(bytes);
            StringBuilder sb = new StringBuilder(digest.length * 2);
            for (byte b : digest) {
                sb.append(Character.forDigit((b >> 4) & 0xF, 16));
                sb.append(Character.forDigit(b & 0xF, 16));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException("计算交通部请求MD5失败", e);
        }
    }
}