configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration();
if (!configResponse.isSuccess()) {
return configResponse;
}
diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/HttpResult.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/HttpResult.java
new file mode 100644
index 0000000..9cce8e3
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/dtos/HttpResult.java
@@ -0,0 +1,69 @@
+package com.mgm.mcrm.apigateway.daylight.common.dtos;
+
+import lombok.Data;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.message.StatusLine;
+
+import java.io.Serializable;
+
+@Data
+public class HttpResult implements Serializable {
+ /**
+ * 执行结果-body
+ */
+ private String result;
+
+ /**
+ * 状态码-statusCode
+ */
+ private int statusCode;
+
+ /**
+ * 状态行-StatusLine
+ */
+ private StatusLine statusLine;
+
+ /**
+ * 请求头信息
+ */
+ private Header[] reqHeaders;
+
+ /**
+ * 响应头信息
+ */
+ private Header[] respHeaders;
+
+ /**
+ * 协议版本
+ */
+ private ProtocolVersion protocolVersion;
+
+ /**
+ * HttpResponse结果对象
+ */
+ private HttpResponse resp;
+
+ public HttpResult(HttpResponse resp) {
+ // HttpClient 5 API: 使用 new StatusLine(response) 创建 StatusLine
+ this.statusLine = new StatusLine(resp);
+ // HttpClient 5 API: 使用 getHeaders() 获取所有响应头
+ this.respHeaders = resp.getHeaders();
+ // HttpClient 5 API: 从 StatusLine 获取协议版本
+ this.protocolVersion = this.statusLine.getProtocolVersion();
+ // HttpClient 5 API: 使用 getCode() 获取状态码
+ this.statusCode = resp.getCode();
+ this.resp = resp;
+ }
+
+ /**
+ * 从返回的头信息中查询指定头信息
+ *
+ * @param name 头信息名称
+ */
+ public Header getHeaders(final String name) {
+ Header[] headers = this.resp.getHeaders(name);
+ return headers!=null && headers.length>0?headers[0]:null;
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/enums/HttpMethods.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/enums/HttpMethods.java
new file mode 100644
index 0000000..e45fbf7
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/enums/HttpMethods.java
@@ -0,0 +1,74 @@
+package com.mgm.mcrm.apigateway.daylight.common.enums;
+
+
+/**
+ * 枚举HttpMethods方法
+ *
+ * @author Anka
+ */
+public enum HttpMethods {
+
+ /**
+ * 求获取Request-URI所标识的资源
+ */
+ GET(0, "GET"),
+
+ /**
+ * 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。
+ * POST请求可能会导致新的资源的建立和/或已有资源的修改
+ */
+ POST(1, "POST"),
+
+ /**
+ * 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。
+ * 这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息
+ * 只获取响应信息报头
+ */
+ HEAD(2, "HEAD"),
+
+ /**
+ * 向指定资源位置上传其最新内容(全部更新,操作幂等)
+ */
+ PUT (3, "PUT"),
+
+ /**
+ * 请求服务器删除Request-URI所标识的资源
+ */
+ DELETE (4, "DELETE"),
+
+ /**
+ * 请求服务器回送收到的请求信息,主要用于测试或诊断
+ */
+ TRACE(5, "TRACE"),
+
+ /**
+ * 向指定资源位置上传其最新内容(部分更新,非幂等)
+ */
+ PATCH (6, "PATCH"),
+
+ /**
+ * 返回服务器针对特定资源所支持的HTTP请求方法。
+ * 也可以利用向Web服务器发送'*'的请求来测试服务器的功能性
+ */
+ OPTIONS (7, "OPTIONS"),
+
+// /**
+// * HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
+// */
+// CONNECT(99, "CONNECT"),
+ ;
+
+ private int code;
+ private String name;
+
+ private HttpMethods(int code, String name){
+ this.code = code;
+ this.name = name;
+ }
+ public String getName() {
+ return name;
+ }
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpClientExecutor.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpClientExecutor.java
new file mode 100644
index 0000000..72ff4b4
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpClientExecutor.java
@@ -0,0 +1,519 @@
+package com.mgm.mcrm.apigateway.daylight.common.httpclient;
+
+import com.mgm.mcrm.apigateway.daylight.common.dtos.HttpResult;
+import com.mgm.mcrm.apigateway.daylight.common.enums.HttpMethods;
+import org.apache.hc.client5.http.classic.methods.*;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * HTTP 客户端工具类(基于 Apache HttpClient 5)
+ *
+ * 提供便捷的 HTTP 请求方法,支持:
+ * - GET、POST、PUT、DELETE 请求
+ * - 自定义请求头和参数
+ * - Cookie 管理
+ * - 详细响应信息获取
+ *
+ * 使用示例:
+ *
+ * // 配置请求
+ * HttpConfig config = HttpConfig.custom()
+ * .url("https://api.example.com/data")
+ * .timeout(requestConfig)
+ * .map(params)
+ * .encoding("utf-8")
+ * .client(httpClient);
+ *
+ * // 配置请求头
+ * Header[] headers = HttpHeaderUtil.custom()
+ * .userAgent("MyApp")
+ * .build();
+ *
+ * // 执行 GET 请求
+ * String result = HttpClientUtil.get(config, headers);
+ *
+ * // 执行 POST 请求
+ * String result = HttpClientUtil.post(config, headers);
+ *
+ * // 获取详细响应信息
+ * HttpResult httpResult = HttpClientUtil.sendAndGetResp(config, headers);
+ *
+ *
+ * @author Aaka
+ */
+public class HttpClientExecutor {
+
+ private HttpClientExecutor() {
+ // 工具类,禁止实例化
+ }
+
+ /**
+ * 执行 GET 请求
+ *
+ * @param config HTTP 请求配置
+ * @param headers 请求头数组(可选,可为 null)
+ * @return 响应体字符串
+ * @throws Exception 请求执行异常
+ */
+ public static String get(HttpRequestOptions config, Header[] headers) throws Exception {
+ return executeGet(config, headers);
+ }
+
+ /**
+ * 执行 POST 请求
+ *
+ * @param config HTTP 请求配置
+ * @param headers 请求头数组(可选,可为 null)
+ * @return 响应体字符串
+ * @throws Exception 请求执行异常
+ */
+ public static String post(HttpRequestOptions config, Header[] headers) throws Exception {
+ return executePost(config, headers);
+ }
+
+ /**
+ * 执行通用请求(根据 HttpConfig 中的 method 自动选择)
+ *
+ * @param config HTTP 请求配置
+ * @param headers 请求头数组(可选,可为 null)
+ * @return 响应体字符串
+ * @throws Exception 请求执行异常
+ */
+ public static String send(HttpRequestOptions config, Header[] headers) throws Exception {
+ return execute(config, headers);
+ }
+
+ /**
+ * 执行请求并返回详细响应信息
+ *
+ * @param config HTTP 请求配置
+ * @param headers 请求头数组(可选,可为 null)
+ * @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
+ * @throws Exception 请求执行异常
+ */
+ public static HttpResult sendAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
+ return executeAndGetResp(config, headers);
+ }
+
+ /**
+ * 执行请求并返回详细响应信息
+ *
+ * @param config HTTP 请求配置
+ * @param headers 请求头数组(可选,可为 null)
+ * @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
+ * @throws Exception 请求执行异常
+ */
+ public static HttpResult sendAndPostResp(HttpRequestOptions config, Header[] headers) throws Exception {
+ return executePostRes(config, headers);
+ }
+
+ /**
+ * 执行 PATCH 请求并返回详细响应信息
+ *
+ * @param config HTTP 请求配置
+ * @param headers 请求头数组(可选,可为 null)
+ * @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
+ * @throws Exception 请求执行异常
+ */
+ public static HttpResult sendAndPatchResp(HttpRequestOptions config, Header[] headers) throws Exception {
+ return executePatchRes(config, headers);
+ }
+
+ /**
+ * 执行 GET 请求(内部方法)
+ */
+ private static String executeGet(HttpRequestOptions config, Header[] headers) throws Exception {
+ String url = config.url();
+ List nvps = new ArrayList<>();
+ // 1. 先解析 URL 中原本就带的查询参数(如果有)
+ // 2. 再把 HttpConfig.map() 里的参数也加入到 nvps 中
+ // 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
+ Map map = config.map();
+ if (map != null && !map.isEmpty()) {
+ url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
+ // 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
+ HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
+ }
+
+ // 3. 如果 nvps 中有参数,手动构建查询字符串
+ // 注意:某些服务器(如接口测试工具)可能期望参数值中的特殊字符(如 == 和 ;)不被编码
+ // 因此我们只对参数名进行编码,参数值保持原样或只编码必要的字符
+ if (!nvps.isEmpty()) {
+ StringBuilder queryBuilder = new StringBuilder();
+ for (int i = 0; i < nvps.size(); i++) {
+ NameValuePair nvp = nvps.get(i);
+ if (i > 0) {
+ queryBuilder.append("&");
+ }
+ try {
+ // 只对参数名进行编码
+ String encodedName = encodeQueryParam(nvp.getName(), config.inenc());
+ // 参数值保持原样,不进行编码(与接口测试工具行为一致)
+ // 如果参数值中包含需要编码的字符(如中文、空格等),可以在这里添加编码逻辑
+ String paramValue = nvp.getValue();
+ queryBuilder.append(encodedName).append("=").append(paramValue);
+ } catch (Exception ex) {
+ // 如果编码失败,使用原始值
+ queryBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
+ }
+ }
+ String query = queryBuilder.toString();
+ if (url.contains("?")) {
+ url = url + "&" + query;
+ } else {
+ url = url + "?" + query;
+ }
+ }
+// System.out.println(url);
+ HttpGet httpGet = new HttpGet(url);
+ setHeaders(httpGet, headers);
+ setRequestConfig(httpGet, config);
+
+ // 如果配置了 context,使用 context 执行请求以支持 Cookie
+ // 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
+ CloseableHttpResponse response;
+ if (config.context() != null) {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet, config.context());
+ response = tempResponse;
+ } else {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet);
+ response = tempResponse;
+ }
+ try (response) {
+ return extractResponseBody(response, config);
+ }
+ }
+
+ /**
+ * 执行 POST 请求(内部方法)
+ */
+ private static String executePost(HttpRequestOptions config, Header[] headers) throws Exception {
+ String url = config.url();
+ List nvps = new ArrayList<>();
+ url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
+
+ HttpPost httpPost = new HttpPost(url);
+ setHeaders(httpPost, headers);
+ setRequestConfig(httpPost, config);
+
+ // 设置请求体
+ Map map = config.map();
+ if (map != null && !map.isEmpty()) {
+ HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
+ httpPost.setEntity(entity);
+ }
+
+ // 如果配置了 context,使用 context 执行请求以支持 Cookie
+ // 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
+ CloseableHttpResponse response;
+ if (config.context() != null) {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost, config.context());
+ response = tempResponse;
+ } else {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost);
+ response = tempResponse;
+ }
+ try (response) {
+ return extractResponseBody(response, config);
+ }
+ }
+
+ /**
+ * 执行 POST 请求(内部方法)
+ */
+ private static HttpResult executePostRes(HttpRequestOptions config, Header[] headers) throws Exception {
+ String url = config.url();
+ List nvps = new ArrayList<>();
+ url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
+
+ HttpPost httpPost = new HttpPost(url);
+ setHeaders(httpPost, headers);
+ setRequestConfig(httpPost, config);
+
+ // 设置请求体
+ Map map = config.map();
+ if (map != null && !map.isEmpty()) {
+ HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
+ httpPost.setEntity(entity);
+ }
+
+ // 如果配置了 context,使用 context 执行请求以支持 Cookie
+ // 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
+ CloseableHttpResponse response;
+ if (config.context() != null) {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost, config.context());
+ response = tempResponse;
+ } else {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost);
+ response = tempResponse;
+ }
+ try (response) {
+ return buildHttpResult(response, config);
+ }
+ }
+
+ /**
+ * 执行 PATCH 请求(内部方法)
+ */
+ private static HttpResult executePatchRes(HttpRequestOptions config, Header[] headers) throws Exception {
+ String url = config.url();
+ List nvps = new ArrayList<>();
+ url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
+
+ HttpPatch httpPatch = new HttpPatch(url);
+ setHeaders(httpPatch, headers);
+ setRequestConfig(httpPatch, config);
+
+ // 设置请求体
+ Map map = config.map();
+ if (map != null && !map.isEmpty()) {
+ HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
+ httpPatch.setEntity(entity);
+ }
+
+ // 如果配置了 context,使用 context 执行请求以支持 Cookie
+ // 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
+ CloseableHttpResponse response;
+ if (config.context() != null) {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPatch, config.context());
+ response = tempResponse;
+ } else {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPatch);
+ response = tempResponse;
+ }
+ try (response) {
+ return buildHttpResult(response, config);
+ }
+ }
+
+ /**
+ * 执行通用请求(根据 HttpMethods 自动选择)(内部方法)
+ */
+ private static String execute(HttpRequestOptions config, Header[] headers) throws Exception {
+ HttpMethods method = config.method();
+ String url = config.url();
+ List nvps = new ArrayList<>();
+ url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
+
+ ClassicHttpRequest request = createRequest(method, url);
+ setHeaders(request, headers);
+ setRequestConfig(request, config);
+
+ // 设置请求体(仅 POST 和 PUT)
+ Map map = config.map();
+ if (map != null && !map.isEmpty() && (request instanceof HttpPost || request instanceof HttpPut)) {
+ HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
+ if (request instanceof HttpPost) {
+ ((HttpPost) request).setEntity(entity);
+ } else if (request instanceof HttpPut) {
+ ((HttpPut) request).setEntity(entity);
+ }
+ }
+
+ // 如果配置了 context,使用 context 执行请求以支持 Cookie
+ // 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
+ CloseableHttpResponse response;
+ if (config.context() != null) {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(request, config.context());
+ response = tempResponse;
+ } else {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(request);
+ response = tempResponse;
+ }
+ try (response) {
+ return extractResponseBody(response, config);
+ }
+ }
+
+ /**
+ * 执行请求并返回详细响应信息(内部方法)
+ */
+ private static HttpResult executeAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
+ String url = config.url();
+ List nvps = new ArrayList<>();
+ // 1. 先解析 URL 中原本就带的查询参数(如果有)
+ url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
+
+ // 2. 再把 HttpConfig.map() 里的参数也加入到 nvps 中
+ // 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
+ Map map = config.map();
+ if (map != null && !map.isEmpty()) {
+ // 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
+ HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
+ }
+
+ // 3. 如果 nvps 中有参数,手动构建查询字符串
+ // 注意:某些服务器(如接口测试工具)可能期望参数值中的特殊字符(如 == 和 ;)不被编码
+ // 因此我们只对参数名进行编码,参数值保持原样或只编码必要的字符
+ if (!nvps.isEmpty()) {
+ StringBuilder queryBuilder = new StringBuilder();
+ for (int i = 0; i < nvps.size(); i++) {
+ NameValuePair nvp = nvps.get(i);
+ if (i > 0) {
+ queryBuilder.append("&");
+ }
+ try {
+ // 只对参数名进行编码
+ String encodedName = encodeQueryParam(nvp.getName(), config.inenc());
+ // 参数值保持原样,不进行编码(与接口测试工具行为一致)
+ // 如果参数值中包含需要编码的字符(如中文、空格等),可以在这里添加编码逻辑
+ String paramValue = nvp.getValue();
+ queryBuilder.append(encodedName).append("=").append(paramValue);
+ } catch (Exception ex) {
+ // 如果编码失败,使用原始值
+ queryBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
+ }
+ }
+ String query = queryBuilder.toString();
+ if (url.contains("?")) {
+ url = url + "&" + query;
+ } else {
+ url = url + "?" + query;
+ }
+ }
+// System.out.println(url);
+ HttpGet httpGet = new HttpGet(url);
+ setHeaders(httpGet, headers);
+ setRequestConfig(httpGet, config);
+
+ // 如果配置了 context,使用 context 执行请求以支持 Cookie
+ // 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
+ CloseableHttpResponse response;
+ if (config.context() != null) {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet, config.context());
+ response = tempResponse;
+ } else {
+ @SuppressWarnings("deprecation")
+ CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet);
+ response = tempResponse;
+ }
+ try (response) {
+ return buildHttpResult(response, config);
+ }
+ }
+
+ /**
+ * 创建 HTTP 请求对象
+ */
+ private static ClassicHttpRequest createRequest(HttpMethods method, String url) {
+ switch (method) {
+ case GET:
+ return new HttpGet(url);
+ case POST:
+ return new HttpPost(url);
+ case PUT:
+ return new HttpPut(url);
+ case DELETE:
+ return new HttpDelete(url);
+ default:
+ return new HttpGet(url);
+ }
+ }
+
+ /**
+ * 设置请求头
+ */
+ private static void setHeaders(ClassicHttpRequest request, Header[] headers) {
+ if (headers != null) {
+ for (Header header : headers) {
+ request.addHeader(header.getName(), header.getValue());
+ }
+ }
+ }
+
+ /**
+ * 设置请求配置
+ */
+ private static void setRequestConfig(ClassicHttpRequest request, HttpRequestOptions config) {
+ if (config.requestConfig() == null) {
+ return;
+ }
+
+ if (request instanceof HttpGet) {
+ ((HttpGet) request).setConfig(config.requestConfig());
+ } else if (request instanceof HttpPost) {
+ ((HttpPost) request).setConfig(config.requestConfig());
+ } else if (request instanceof HttpPut) {
+ ((HttpPut) request).setConfig(config.requestConfig());
+ } else if (request instanceof HttpPatch) {
+ ((HttpPatch) request).setConfig(config.requestConfig());
+ } else if (request instanceof HttpDelete) {
+ ((HttpDelete) request).setConfig(config.requestConfig());
+ }
+ }
+
+ /**
+ * 提取响应体内容
+ */
+ private static String extractResponseBody(CloseableHttpResponse response, HttpRequestOptions config) throws Exception {
+ HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ return "";
+ }
+ try {
+ String result = EntityUtils.toString(entity, Charset.forName(config.outenc()));
+ return result;
+ } finally {
+ EntityUtils.consume(entity);
+ }
+ }
+
+ /**
+ * 构建 HttpResult 对象
+ */
+ private static HttpResult buildHttpResult(CloseableHttpResponse response, HttpRequestOptions config) throws Exception {
+ // 直接使用 HttpResult 构造函数,自动设置状态行、响应头、协议版本和状态码
+ HttpResult result = new HttpResult(response);
+
+ // 读取响应体内容
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ String responseBody = EntityUtils.toString(entity, Charset.forName(config.outenc()));
+ result.setResult(responseBody);
+ EntityUtils.consume(entity);
+ }
+
+ return result;
+ }
+
+ /**
+ * 编码查询参数名(只编码参数名,不编码参数值)
+ * 这样可以保持与接口测试工具的行为一致
+ *
+ * @param param 参数名
+ * @param encoding 编码格式
+ * @return 编码后的参数名
+ */
+ private static String encodeQueryParam(String param, String encoding) {
+ if (param == null) {
+ return "";
+ }
+ try {
+ // 只对参数名进行标准 URL 编码
+ return java.net.URLEncoder.encode(param, encoding).replace("+", "%20");
+ } catch (java.io.UnsupportedEncodingException e) {
+ return param;
+ }
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpEntityBuilder.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpEntityBuilder.java
new file mode 100644
index 0000000..b899070
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpEntityBuilder.java
@@ -0,0 +1,310 @@
+package com.mgm.mcrm.apigateway.daylight.common.httpclient;
+
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
+import org.apache.hc.core5.http.io.entity.FileEntity;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.Map.Entry;
+
+
+public class HttpEntityBuilder {
+
+ //传入参数特定类型
+ public static final String ENTITY_STRING="$ENTITY_STRING$";
+ public static final String ENTITY_JSON="$ENTITY_JSON$";
+ public static final String ENTITY_FILE="$ENTITY_FILEE$";
+ public static final String ENTITY_BYTES="$ENTITY_BYTES$";
+ public static final String ENTITY_INPUTSTREAM="$ENTITY_INPUTSTREAM$";
+ public static final String ENTITY_SERIALIZABLE="$ENTITY_SERIALIZABLE$";
+ public static final String ENTITY_MULTIPART="$ENTITY_MULTIPART$";
+ private static final List SPECIAL_ENTITIY = Arrays.asList(ENTITY_STRING, ENTITY_JSON, ENTITY_BYTES, ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART);
+
+ /**
+ * 是否开启debug,
+ */
+ private static boolean debug = false;
+
+ private static final Logger log = LoggerFactory.getLogger(HttpEntityBuilder.class);
+
+
+ /**
+ * 检测url是否含有参数,如果有,则把参数加到参数列表中
+ *
+ * @param url 资源地址
+ * @param nvps 参数列表
+ * @param encoding 编码
+ * @return 返回去掉参数的url
+ * @throws UnsupportedEncodingException 不支持的编码异常
+ */
+ public static String checkHasParas(String url, List nvps, String encoding) throws UnsupportedEncodingException {
+ // 检测url中是否存在参数
+ if (url.contains("?") && url.indexOf("?") < url.indexOf("=")) {
+ String[] paras = url.substring(url.indexOf("?") + 1).split("&");
+ for (String para : paras) {
+ int pos = para.indexOf("=");
+ if (pos >= 0) {
+ nvps.add(new BasicNameValuePair(para.substring(0, pos), para.substring(pos + 1)));
+ } else {
+ nvps.add(new BasicNameValuePair(para, ""));
+ }
+ }
+ url = url.substring(0, url.indexOf("?"));
+ }
+ return url;
+ }
+
+ /**
+ *
+ * 参数转换,将map中的参数,转到参数列表中
+ *
+ * @param nvps 参数列表
+ * @param map 参数列表(map)
+ * @param encoding 编码
+ * @return 返回HttpEntity
+ * @throws UnsupportedEncodingException 不支持的编码异常
+ */
+ public static HttpEntity map2HttpEntity(List nvps, Map map, String encoding) throws UnsupportedEncodingException {
+ HttpEntity entity = null;
+ if(map!=null && map.size()>0){
+ boolean isSpecial = false;
+ // 拼接参数
+ for (Entry entry : map.entrySet()) {
+ if(SPECIAL_ENTITIY.contains(entry.getKey())){//判断是否在之中
+ isSpecial = true;
+ if(ENTITY_STRING.equals(entry.getKey())){//string
+ entity = new StringEntity(String.valueOf(entry.getValue()), Charset.forName(encoding));
+ break;
+ }else if(ENTITY_JSON.equals(entry.getKey())){//json
+ ContentType jsonContentType;
+
+ entity = new StringEntity(String.valueOf(entry.getValue()), Charset.forName(encoding));
+ String contentType = "application/json";
+ if (encoding != null && !encoding.trim().isEmpty()) {
+ // 创建带有编码的JSON ContentType
+ jsonContentType = ContentType.APPLICATION_JSON.withCharset(Charset.forName(encoding));
+ }else {
+ // 使用默认的JSON ContentType(通常UTF-8)
+ jsonContentType = ContentType.APPLICATION_JSON;
+ }
+ // 创建实体时直接传入ContentType
+ entity = new StringEntity(String.valueOf(entry.getValue()), jsonContentType);
+ break;
+ }else if(ENTITY_BYTES.equals(entry.getKey())){//file
+ byte[] data = (byte[]) entry.getValue();
+ entity = new ByteArrayEntity(data, ContentType.APPLICATION_OCTET_STREAM);
+ break;
+ }else if(ENTITY_FILE.equals(entry.getKey())){//file
+ if(File.class.isAssignableFrom(entry.getValue().getClass())){
+ entity = new FileEntity((File)entry.getValue(), ContentType.APPLICATION_OCTET_STREAM);
+ }else if(entry.getValue().getClass()==String.class){
+ entity = new FileEntity(new File((String) entry.getValue()), ContentType.create("text/plain", "UTF-8"));
+ }
+ break;
+ }else if(ENTITY_INPUTSTREAM.equals(entry.getKey())){//inputstream
+// entity = new InputStreamEntity();
+ break;
+ }else if(ENTITY_SERIALIZABLE.equals(entry.getKey())){//serializeable
+// entity = new SerializableEntity()
+ break;
+ }else if(ENTITY_MULTIPART.equals(entry.getKey())){//MultipartEntityBuilder
+ File[] files = null;
+ if(File.class.isAssignableFrom(entry.getValue().getClass().getComponentType())){
+ files=(File[])entry.getValue();
+ }else if(entry.getValue().getClass().getComponentType()==String.class){
+ String[] names = (String[]) entry.getValue();
+ files = new File[names.length];
+ for (int i = 0; i < names.length; i++) {
+ files[i] = new File(names[i]);
+ }
+ }
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ builder.setCharset(Charset.forName(encoding));// 设置请求的编码格式
+ builder.setMode(HttpMultipartMode.LEGACY);// 设置浏览器兼容模式
+ int count = 0;
+ for (File file : files) {
+// //把文件转换成流对象FileBody
+// FileBody fileBody = new FileBody(file);
+// builder.addPart(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++, fileBody);
+ builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++,file);
+ }
+ boolean forceRemoveContentTypeCharset = (boolean)map.get(ENTITY_MULTIPART+".rmCharset");
+ Map m = new HashMap();
+ m.putAll(map);
+ m.remove(ENTITY_MULTIPART);
+ m.remove(ENTITY_MULTIPART+".name");
+ m.remove(ENTITY_MULTIPART+".rmCharset");
+ Iterator> iterator = m.entrySet().iterator();
+ // 发送的数据
+ while (iterator.hasNext()) {
+ Entry e = iterator.next();
+ builder.addTextBody(e.getKey(), String.valueOf(e.getValue()), ContentType.create("text/plain", encoding));
+ }
+ entity = builder.build();// 生成 HTTP POST 实体
+
+ //强制去除contentType中的编码设置,否则,在某些情况下会导致上传失败
+ if(forceRemoveContentTypeCharset){
+ removeContentTypeCharset(encoding, entity);
+ }
+ break;
+ }else {
+ nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
+ }
+ }else{
+ nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
+ }
+ }
+ if(!isSpecial) {
+ entity = new UrlEncodedFormEntity(nvps, Charset.forName(encoding));
+ }
+ }
+ return entity;
+ }
+
+ /**
+ * 移除content-type中的charset
+ *
+ * @param encoding 编码
+ * @param entity 请求参数及数据信息
+ */
+ private static void removeContentTypeCharset(String encoding, HttpEntity entity) {
+ try {
+ Class> clazz = entity.getClass();
+ Field field = clazz.getDeclaredField("contentType");
+ field.setAccessible(true); //将字段的访问权限设为true:即去除private修饰符的影响
+ if(Modifier.isFinal(field.getModifiers())){
+ Field modifiersField = Field.class.getDeclaredField("modifiers"); //去除final修饰符的影响,将字段设为可修改的
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ }
+ BasicHeader o = (BasicHeader) field.get(entity);
+ field.set(entity, new BasicHeader(HttpHeaders.CONTENT_TYPE, o.getValue().replace("; charset="+encoding,"")));
+ } catch (NoSuchFieldException e) {
+ HttpEntityBuilder.exception(e);
+ } catch (SecurityException e) {
+ HttpEntityBuilder.exception(e);
+ } catch (IllegalArgumentException e) {
+ HttpEntityBuilder.exception(e);
+ } catch (IllegalAccessException e) {
+ HttpEntityBuilder.exception(e);
+ }
+ }
+
+
+ /**
+ * 生成参数
+ * 参数格式:k1=v1&k2=v2
+ *
+ * @param paras 参数列表
+ * @return 返回参数列表(map)
+ */
+ public static Map buildParas(String paras){
+ String[] p = paras.split("&");
+ String[][] ps = new String[p.length][2];
+ int pos = 0;
+ for (int i = 0; i < p.length; i++) {
+ pos = p[i].indexOf("=");
+ ps[i][0]=p[i].substring(0,pos);
+ ps[i][1]=p[i].substring(pos+1);
+ pos = 0;
+ }
+ return buildParas(ps);
+ }
+
+ /**
+ * 生成参数
+ * 参数类型:{{"k1","v1"},{"k2","v2"}}
+ *
+ * @param paras 参数列表
+ * @return 返回参数列表(map)
+ */
+ public static Map buildParas(String[][] paras){
+ // 创建参数队列
+ Map map = new HashMap();
+ for (String[] para: paras) {
+ map.put(para[0], para[1]);
+ }
+ return map;
+ }
+
+ /**
+ * 打印消息
+ *
+ * @param msg 消息
+ */
+ public static void info(String msg){
+ if(debug){
+ log.info(msg);
+ }
+ }
+
+ /**
+ * 打印消息和异常堆栈
+ *
+ * @param msg 异常消息
+ * @param t 异常
+ */
+ public static void infoException(String msg, Throwable t){
+ if(debug){
+ log.info(msg,t);
+ }
+ }
+
+ /**
+ * 打印错误消息
+ *
+ * @param msg 异常消息
+ */
+ public static void error(String msg){
+ log.error(msg);
+ }
+
+ /**
+ * 打印错误消息和异常堆栈
+ *
+ * @param msg 异常消息
+ * @param t 异常
+ */
+ public static void errorException(String msg, Throwable t){
+ log.error(msg, t);
+ }
+
+ /**
+ * 打印异常堆栈
+ *
+ * @param t 异常
+ */
+ public static void exception(Throwable t){
+ log.error(String.valueOf(t));
+ }
+
+ /**
+ * 开启打印日志
+ */
+ public static void debug() {
+ debug(true);
+ }
+ /**
+ * 开启或关闭打印日志
+ * @param debug 是否开启debug
+ */
+ public static void debug(boolean debug) {
+ HttpEntityBuilder.debug = debug;
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpRequestOptions.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpRequestOptions.java
new file mode 100644
index 0000000..68dea0b
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/httpclient/HttpRequestOptions.java
@@ -0,0 +1,375 @@
+package com.mgm.mcrm.apigateway.daylight.common.httpclient;
+
+import com.mgm.mcrm.apigateway.daylight.common.enums.HttpMethods;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.mime.Header;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Timeout;
+
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpRequestOptions {
+
+ private HttpRequestOptions(){};
+
+ /**
+ * 获取实例
+ * @return 返回当前对象
+ */
+ public static HttpRequestOptions create(){
+ return new HttpRequestOptions();
+ }
+
+
+ /**
+ * HttpClient对象
+ */
+ private HttpClient client;
+
+ /**
+ * Header头信息
+ */
+ private Header[] headers;
+
+ /**
+ * 是否返回response的headers
+ */
+ private boolean isReturnRespHeaders;
+
+ /**
+ * 请求方法
+ */
+ private HttpMethods method=HttpMethods.POST;
+
+ /**
+ * 请求方法名称
+ */
+ private String methodName;
+
+ /**
+ * 用于cookie操作
+ */
+ private HttpContext context;
+
+ // private Map map;
+
+ /**
+ * 以json格式作为输入参数
+ */
+ private String json;
+
+ /**
+ * 输入输出编码
+ */
+ private String encoding= Charset.defaultCharset().displayName();
+
+ /**
+ * 输入编码
+ */
+ private String inenc;
+
+ /**
+ * 输出编码
+ */
+ private String outenc;
+
+ /**
+ * 设置RequestConfig
+ */
+ private RequestConfig requestConfig;
+
+ /**
+ * 解决多线程下载时,strean被close的问题
+ */
+ private static final ThreadLocal outs = new ThreadLocal();
+
+ /**
+ * 解决多线程处理时,url被覆盖问题
+ */
+ private static final ThreadLocal urls = new ThreadLocal();
+
+ /**
+ * 解决多线程处理时,url被覆盖问题
+ */
+ private static final ThreadLocal