xin
2 days ago debb7b1ef8bb4e96e09b2d878ad821b4b2f3a110
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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());
        }
    }
}