package com.oying.modules.winnerlook.client;
|
|
import cn.hutool.core.util.IdUtil;
|
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONObject;
|
import com.oying.exception.BadRequestException;
|
import com.oying.modules.winnerlook.util.Md5Util;
|
import lombok.extern.slf4j.Slf4j;
|
import org.w3c.dom.Document;
|
import org.w3c.dom.Element;
|
import org.w3c.dom.NodeList;
|
import shade.okhttp3.*;
|
|
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
import java.io.ByteArrayInputStream;
|
import java.nio.charset.StandardCharsets;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.TreeMap;
|
import java.util.UUID;
|
|
/**
|
* @author MinaWu
|
* @description
|
* @date 2025/5/14 16:16
|
*/
|
|
@Slf4j
|
public class OkHttpService {
|
private static final String SECRET_KEY = "X-Winner-Secret-Key";
|
private static final String TIMESTAMP_HEADER = "X-Winner-Timestamp";
|
private static final String SIGNATURE_HEADER = "X-Winner-Signature";
|
private static final String REQUEST_ID = "X-Winner-Request-Id";
|
private static final String TOKEN = "X-Winner-token";
|
private static final String PARAMS = "params";
|
|
/**
|
* 生成 requestId
|
*
|
* @param timestamp 时间戳
|
* @return requestId
|
*/
|
private static String generateRequestId(String userName, long timestamp) {
|
return userName.replace("_", "") + "_" + IdUtil.simpleUUID() + "_" + timestamp;
|
}
|
|
public static String doPost(Map<String, Object> params, String baseUrl, String userName, String token) {
|
String secretKey = UUID.randomUUID().toString().replace("-", "");
|
String boundary = UUID.randomUUID().toString().replace("-", "");
|
|
// 生成时间戳
|
long timestamp = System.currentTimeMillis();
|
String timestampStr = String.valueOf(timestamp);
|
// 生成 requestId
|
String requestId = generateRequestId(userName, timestamp);
|
|
// 生成签名
|
String signature = generateSignature(params, timestampStr, secretKey, requestId, token);
|
|
// 创建请求体
|
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder(boundary)
|
.setType(MultipartBody.FORM);
|
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue().toString());
|
}
|
// 创建请求
|
Request request = new Request.Builder()
|
.url(baseUrl)
|
.header(TIMESTAMP_HEADER, timestampStr)
|
.header(SIGNATURE_HEADER, signature)
|
.header(REQUEST_ID, requestId)
|
.header(SECRET_KEY, secretKey)
|
.header("Content-Type", "multipart/form-data;charset=UTF-8; boundary=" + boundary)
|
.post(bodyBuilder.build())
|
.build();
|
// 发送请求
|
try {
|
return "response";
|
} catch (Exception e) {
|
log.info("doPost requestId:{},body:{},headers:{} ", requestId, request.body().toString(), request.headers().toString());
|
throw new BadRequestException("短信POST:" + e.getMessage());
|
}
|
}
|
|
/**
|
* 对 treeMap 的键进行排序
|
*/
|
private static void sortParams(Map<String, Object> treeMap) {
|
for (String key : treeMap.keySet()) {
|
Object value = treeMap.get(key);
|
if (value instanceof JSONObject) {
|
// 如果是 JSONObject,递归排序
|
treeMap.put(key, sortJsonObject((JSONObject) value));
|
} else if (value instanceof JSONArray) {
|
// 如果是 JSONArray,对数组中的每个对象排序
|
treeMap.put(key, sortJsonArray((JSONArray) value));
|
} else {
|
// 其他类型直接放入
|
treeMap.put(key, value);
|
}
|
}
|
}
|
|
/**
|
* 对 JSONObject 的键进行排序
|
*/
|
private static JSONObject sortJsonObject(JSONObject jsonObject) {
|
Map<String, Object> treeMap = new TreeMap<>();
|
Set<String> keys = jsonObject.keySet();
|
for (String key : keys) {
|
Object value = jsonObject.get(key);
|
if (value instanceof JSONObject) {
|
// 如果是 JSONObject,递归排序
|
treeMap.put(key, sortJsonObject((JSONObject) value));
|
} else if (value instanceof JSONArray) {
|
// 如果是 JSONArray,对数组中的每个对象排序
|
treeMap.put(key, sortJsonArray((JSONArray) value));
|
} else {
|
// 其他类型直接放入
|
treeMap.put(key, value);
|
}
|
}
|
return new JSONObject(treeMap);
|
}
|
|
/**
|
* 对 JSONArray 中的每个 JSONObject 进行排序
|
*/
|
private static JSONArray sortJsonArray(JSONArray jsonArray) {
|
JSONArray sortedJsonArray = new JSONArray();
|
for (Object item : jsonArray) {
|
if (item instanceof JSONObject) {
|
// 如果是 JSONObject,递归排序
|
sortedJsonArray.add(sortJsonObject((JSONObject) item));
|
} else {
|
// 其他类型直接放入
|
sortedJsonArray.add(item);
|
}
|
}
|
return sortedJsonArray;
|
}
|
|
|
/**
|
* 生成签名
|
*
|
* @param map 请求参数
|
* @param timestamp 时间戳
|
* @param secretKey 密钥
|
* @return 签名
|
*/
|
private static String generateSignature(Map<String, Object> map, String timestamp, String secretKey, String requestId, String token) {
|
try {
|
// 对参数进行排序 将params转成字符串再转成map是为了将其中的对象和集合转成jsonObject和jsonArray 方便排序处理
|
// Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(map), Map.class);
|
Map<String, Object> sortedParams = new TreeMap<>(map);
|
sortParams(sortedParams);
|
// 将参数转换为 JSON 字符串
|
String jsonString = JSONObject.toJSONString(sortedParams);
|
// 计算参数的 MD5
|
String paramsMD5 = Md5Util.getMD5ByCharset(jsonString, StandardCharsets.UTF_8);
|
log.info("generateSignature requestId:{} jsonString:{} paramsMD5:{}", requestId, jsonString, paramsMD5);
|
// 生成待签名的数据
|
String dataToSign = PARAMS + "=" + paramsMD5 +
|
"&" + TIMESTAMP_HEADER + "=" + timestamp +
|
"&" + REQUEST_ID + "=" + requestId +
|
"&" + TOKEN + "=" + token;
|
|
// 使用 HMAC-SHA256 生成签名
|
return Md5Util.hmacSha256(dataToSign, secretKey);
|
} catch (Exception e) {
|
throw new BadRequestException("短信生成签名:" + e.getMessage());
|
}
|
}
|
|
/**
|
* 解析 XML 响应并提取 <string> 标签中的值
|
*
|
* @param xmlResponse XML 响应字符串
|
* @return <string> 标签中的值
|
*/
|
public static String parseXmlResponse(String xmlResponse) {
|
try {
|
// 创建 DocumentBuilder
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
|
// 将 XML 字符串转换为 Document 对象
|
Document document = builder.parse(new ByteArrayInputStream(xmlResponse.getBytes(StandardCharsets.UTF_8)));
|
|
// 获取 <string> 标签
|
NodeList nodeList = document.getElementsByTagName("string");
|
Element stringElement = (Element) nodeList.item(0);
|
// 提取标签中的文本内容
|
return stringElement.getTextContent();
|
} catch (Exception e) {
|
throw new BadRequestException("短信解析XML:" + e.getMessage());
|
}
|
}
|
}
|