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 fileEntity = new HttpEntity<>(new ByteArrayResource(bytes) { @Override public String getFilename() { return filename; } }, fileHeaders); MultiValueMap body = new LinkedMultiValueMap<>(); body.add("binFile", fileEntity); body.add("filename", filename); ResponseEntity 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); } } }