first commit

This commit is contained in:
Mauve
2026-09-08 16:56:12 +08:00
commit 6ce4e4a166
898 changed files with 234303 additions and 0 deletions
@@ -0,0 +1,525 @@
package com.mgm.mcrm.common.httpclient;
import com.mgm.mcrm.common.dtos.HttpResult;
import com.mgm.mcrm.common.enums.HttpMethods;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
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;
import com.mgm.mcrm.common.httpclient.HttpEntityBuilder;
/**
* HTTP 客户端执行器(基于 Apache HttpClient 5)
*
* 这是 HTTP 客户端模块的主要门面类,提供便捷的 HTTP 请求执行方法。
* 支持:
* - GET、POST、PUT、DELETE 请求
* - 自定义请求头和参数
* - Cookie 管理
* - 详细响应信息获取
*
* 使用示例:
* <pre>
* // 配置请求选项
* HttpRequestOptions options = HttpRequestOptions.create()
* .url("https://api.example.com/data")
* .timeout(requestConfig)
* .map(params)
* .encoding("utf-8")
* .client(httpClient);
*
* // 配置请求头
* Header[] headers = HttpHeaderBuilder.create()
* .userAgent("MyApp")
* .build();
*
* // 执行 GET 请求
* String result = HttpClientExecutor.get(options, headers);
*
* // 执行 POST 请求
* String result = HttpClientExecutor.post(options, headers);
*
* // 获取详细响应信息
* HttpResult httpResult = HttpClientExecutor.execute(options, headers);
* </pre>
*
* @author Aaka
*/
public class HttpClientExecutor {
private HttpClientExecutor() {
// 工具类,禁止实例化
}
/**
* 执行 GET 请求
*
* @param options HTTP 请求选项
* @param headers 请求头数组(可选,可为 null)
* @return 响应体字符串
* @throws Exception 请求执行异常
*/
public static String get(HttpRequestOptions options, Header[] headers) throws Exception {
return executeGet(options, headers);
}
/**
* 执行 POST 请求
*
* @param options HTTP 请求选项
* @param headers 请求头数组(可选,可为 null)
* @return 响应体字符串
* @throws Exception 请求执行异常
*/
public static String post(HttpRequestOptions options, Header[] headers) throws Exception {
return executePost(options, headers);
}
/**
* 执行通用请求(根据 HttpRequestOptions 中的 method 自动选择)
*
* @param options HTTP 请求选项
* @param headers 请求头数组(可选,可为 null)
* @return 响应体字符串
* @throws Exception 请求执行异常
*/
public static String execute(HttpRequestOptions options, Header[] headers) throws Exception {
return executeRequest(options, headers);
}
/**
* 执行请求并返回详细响应信息
*
* @param options HTTP 请求选项
* @param headers 请求头数组(可选,可为 null)
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
* @throws Exception 请求执行异常
*/
public static HttpResult executeWithResult(HttpRequestOptions options, Header[] headers) throws Exception {
return executeRequestWithResult(options, 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);
}
/**
* 执行请求并返回详细响应信息
*
* @param config HTTP 请求配置
* @param headers 请求头数组(可选,可为 null)
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
* @throws Exception 请求执行异常
*/
public static HttpResult sendAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
return executeAndGetResp(config, headers);
}
/**
* 执行请求并返回详细响应信息(内部方法)
*/
private static HttpResult executeAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
// 1. 先解析 URL 中原本就带的查询参数(如果有)
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
// 2. 再把 HttpConfig.map() 里的参数也加入到 nvps 中
// 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
Map<String, Object> 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);
}
}
/**
* 执行 POST 请求(内部方法)
*/
private static HttpResult executePostRes(HttpRequestOptions config, Header[] headers) throws Exception {
String url = config.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
HttpPost httpPost = new HttpPost(url);
setHeaders(httpPost, headers);
setRequestConfig(httpPost, config);
// 设置请求体
Map<String, Object> 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);
}
}
/**
* 执行 GET 请求(内部方法)
*/
private static String executeGet(HttpRequestOptions options, Header[] headers) throws Exception {
String url = options.url();
List<NameValuePair> nvps = new ArrayList<>();
// 1. 先解析 URL 中原本就带的查询参数(如果有)
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
// 2. 再把 HttpRequestOptions.map() 里的参数也加入到 nvps 中
// 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
Map<String, Object> map = options.map();
if (map != null && !map.isEmpty()) {
// 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
HttpEntityBuilder.map2HttpEntity(nvps, map, options.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(), options.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, options);
// 如果配置了 context,使用 context 执行请求以支持 Cookie
CloseableHttpResponse response;
if (options.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpGet, options.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpGet);
response = tempResponse;
}
try (response) {
return extractResponseBody(response, options);
}
}
/**
* 执行 POST 请求(内部方法)
*/
private static String executePost(HttpRequestOptions options, Header[] headers) throws Exception {
String url = options.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
HttpPost httpPost = new HttpPost(url);
setHeaders(httpPost, headers);
setRequestConfig(httpPost, options);
// 设置请求体
Map<String, Object> map = options.map();
if (map != null && !map.isEmpty()) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
httpPost.setEntity(entity);
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
CloseableHttpResponse response;
if (options.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpPost, options.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpPost);
response = tempResponse;
}
try (response) {
return extractResponseBody(response, options);
}
}
/**
* 执行通用请求(根据 HttpMethods 自动选择)(内部方法)
*/
private static String executeRequest(HttpRequestOptions options, Header[] headers) throws Exception {
HttpMethods method = options.method();
String url = options.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
ClassicHttpRequest request = createRequest(method, url);
setHeaders(request, headers);
setRequestConfig(request, options);
// 设置请求体(仅 POST 和 PUT)
Map<String, Object> map = options.map();
if (map != null && !map.isEmpty() && (request instanceof HttpPost || request instanceof HttpPut)) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
if (request instanceof HttpPost) {
((HttpPost) request).setEntity(entity);
} else if (request instanceof HttpPut) {
((HttpPut) request).setEntity(entity);
}
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
CloseableHttpResponse response;
if (options.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request, options.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request);
response = tempResponse;
}
try (response) {
return extractResponseBody(response, options);
}
}
/**
* 执行请求并返回详细响应信息(内部方法)
*/
private static HttpResult executeRequestWithResult(HttpRequestOptions options, Header[] headers) throws Exception {
HttpMethods method = options.method();
String url = options.url();
List<NameValuePair> nvps = new ArrayList<>();
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
ClassicHttpRequest request = createRequest(method, url);
setHeaders(request, headers);
setRequestConfig(request, options);
// 设置请求体(仅 POST 和 PUT)
Map<String, Object> map = options.map();
if (map != null && !map.isEmpty() && (request instanceof HttpPost || request instanceof HttpPut)) {
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
if (request instanceof HttpPost) {
((HttpPost) request).setEntity(entity);
} else if (request instanceof HttpPut) {
((HttpPut) request).setEntity(entity);
}
}
// 如果配置了 context,使用 context 执行请求以支持 Cookie
CloseableHttpResponse response;
if (options.context() != null) {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request, options.context());
response = tempResponse;
} else {
@SuppressWarnings("deprecation")
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request);
response = tempResponse;
}
try (response) {
return buildHttpResult(response, options);
}
}
/**
* 创建 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 options) {
if (options.requestConfig() == null) {
return;
}
if (request instanceof HttpGet) {
((HttpGet) request).setConfig(options.requestConfig());
} else if (request instanceof HttpPost) {
((HttpPost) request).setConfig(options.requestConfig());
} else if (request instanceof HttpPut) {
((HttpPut) request).setConfig(options.requestConfig());
} else if (request instanceof HttpDelete) {
((HttpDelete) request).setConfig(options.requestConfig());
}
}
/**
* 提取响应体内容
*/
private static String extractResponseBody(CloseableHttpResponse response, HttpRequestOptions options) throws Exception {
HttpEntity entity = response.getEntity();
if (entity == null) {
return "";
}
try {
String result = EntityUtils.toString(entity, Charset.forName(options.outenc()));
return result;
} finally {
EntityUtils.consume(entity);
}
}
/**
* 构建 HttpResult 对象
*/
private static HttpResult buildHttpResult(CloseableHttpResponse response, HttpRequestOptions options) throws Exception {
// 直接使用 HttpResult 构造函数,自动设置状态行、响应头、协议版本和状态码
HttpResult result = new HttpResult(response);
// 读取响应体内容
HttpEntity entity = response.getEntity();
if (entity != null) {
String responseBody = EntityUtils.toString(entity, Charset.forName(options.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;
}
}
}
@@ -0,0 +1,54 @@
package com.mgm.mcrm.common.httpclient;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.protocol.HttpClientContext;
/**
* HTTP Cookie 上下文管理类
*
* 用于设置和携带 Cookie,配合 {@link HttpClientExecutor} 使用。
*
* @author Aaka
*/
public class HttpCookieContext {
/**
* 使用httpcontext,用于设置和携带Cookie
*/
private HttpClientContext context ;
/**
* 储存Cookie
*/
private CookieStore cookieStore;
public static HttpCookieContext create(){
return new HttpCookieContext();
}
private HttpCookieContext(){
this.context = new HttpClientContext();
this.cookieStore = new BasicCookieStore();
this.context.setCookieStore(cookieStore);
}
public HttpClientContext getContext() {
return context;
}
public HttpCookieContext setContext(HttpClientContext context) {
this.context = context;
return this;
}
public CookieStore getCookieStore() {
return cookieStore;
}
public HttpCookieContext setCookieStore(CookieStore cookieStore) {
this.cookieStore = cookieStore;
return this;
}
}
@@ -0,0 +1,245 @@
package com.mgm.mcrm.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.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* HTTP 实体构建器
*
* 用于构建 HTTP 请求实体(HttpEntity),支持:
* - URL 编码表单(form-urlencoded)
* - JSON 实体
* - 文件上传(multipart/form-data)
* - 字节数组实体
* - 文件实体
*
* @author Aaka
*/
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<String> SPECIAL_ENTITIY = Arrays.asList(ENTITY_STRING, ENTITY_JSON, ENTITY_BYTES, ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART);
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<NameValuePair> nvps, String encoding) throws UnsupportedEncodingException {
// 检测url中是否存在参数
if (url.contains("?") && url.indexOf("?") < url.indexOf("=")) {
Map<String, Object> map = buildParas(url.substring(url.indexOf("?") + 1));
map2HttpEntity(nvps, map, encoding);
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<NameValuePair> nvps, Map<String, Object> map, String encoding) throws UnsupportedEncodingException {
HttpEntity entity = null;
if(map!=null && map.size()>0){
boolean isSpecial = false;
// 拼接参数
for (Entry<String, Object> 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;
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<String, Object> m = new HashMap<String, Object>();
m.putAll(map);
m.remove(ENTITY_MULTIPART);
m.remove(ENTITY_MULTIPART+".name");
m.remove(ENTITY_MULTIPART+".rmCharset");
Iterator<Entry<String, Object>> iterator = m.entrySet().iterator();
// 发送的数据
while (iterator.hasNext()) {
Entry<String, Object> 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) {
log.error("Failed to remove content type charset", e);
} catch (SecurityException e) {
log.error("Failed to remove content type charset", e);
} catch (IllegalArgumentException e) {
log.error("Failed to remove content type charset", e);
} catch (IllegalAccessException e) {
log.error("Failed to remove content type charset", e);
}
}
/**
* 生成参数
* 参数格式:k1=v1&amp;k2=v2
*
* @param paras 参数列表
* @return 返回参数列表(map)
*/
public static Map<String,Object> 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<String,Object> buildParas(String[][] paras){
// 创建参数队列
Map<String,Object> map = new HashMap<String, Object>();
for (String[] para: paras) {
map.put(para[0], para[1]);
}
return map;
}
}
@@ -0,0 +1,698 @@
package com.mgm.mcrm.common.httpclient;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.nio.charset.StandardCharsets; // 替代 Consts
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.message.BasicHeader;
/**
* HTTP 请求头构建器
*
* 用于构建 HTTP 请求头,支持链式调用。
*
* @author Aaka
*/
public class HttpHeaderBuilder {
private HttpHeaderBuilder() {}
public static HttpHeaderBuilder create() {
return new HttpHeaderBuilder();
}
//记录head头信息
private Map<String, Header> headerMaps = new HashMap<String, Header>();
/**
* 自定义header头信息
*
* @param key header-key
* @param value header-value
* @return 返回当前对象
*/
public HttpHeaderBuilder other(String key, String value) {
headerMaps.put(key, new BasicHeader(key, value));
return this;
}
/**
* 指定客户端能够接收的内容类型
* 例如:Accept: text/plain, text/html
*
* @param accept accept
* @return 返回当前对象
*/
public HttpHeaderBuilder accept(String accept) {
headerMaps.put(HttpReqHead.ACCEPT,
new BasicHeader(HttpReqHead.ACCEPT, accept));
return this;
}
/**
* 浏览器可以接受的字符编码集
* 例如:Accept-Charset: iso-8859-5
*
* @param acceptCharset accept-charset
* @return 返回当前对象
*/
public HttpHeaderBuilder acceptCharset(String acceptCharset) {
headerMaps.put(HttpReqHead.ACCEPT_CHARSET,
new BasicHeader(HttpReqHead.ACCEPT_CHARSET, acceptCharset));
return this;
}
/**
* 指定浏览器可以支持的web服务器返回内容压缩编码类型
* 例如:Accept-Encoding: compress, gzip
*
* @param acceptEncoding accept-encoding
* @return 返回当前对象
*/
public HttpHeaderBuilder acceptEncoding(String acceptEncoding) {
headerMaps.put(HttpReqHead.ACCEPT_ENCODING,
new BasicHeader(HttpReqHead.ACCEPT_ENCODING, acceptEncoding));
return this;
}
/**
* 浏览器可接受的语言
* 例如:Accept-Language: en,zh
*
* @param acceptLanguage accept-language
* @return 返回当前对象
*/
public HttpHeaderBuilder acceptLanguage(String acceptLanguage) {
headerMaps.put(HttpReqHead.ACCEPT_LANGUAGE,
new BasicHeader(HttpReqHead.ACCEPT_LANGUAGE, acceptLanguage));
return this;
}
/**
* 可以请求网页实体的一个或者多个子范围字段
* 例如:Accept-Ranges: bytes
*
* @param acceptRanges accept-ranges
* @return 返回当前对象
*/
public HttpHeaderBuilder acceptRanges(String acceptRanges) {
headerMaps.put(HttpReqHead.ACCEPT_RANGES,
new BasicHeader(HttpReqHead.ACCEPT_RANGES, acceptRanges));
return this;
}
/**
* HTTP授权的授权证书
* 例如:Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
*
* @param authorization authorization
* @return 返回当前对象
*/
public HttpHeaderBuilder authorization(String authorization) {
headerMaps.put(HttpReqHead.AUTHORIZATION,
new BasicHeader(HttpReqHead.AUTHORIZATION, authorization));
return this;
}
public HttpHeaderBuilder ac_property_id(String ac_property_id) {
headerMaps.put(HttpReqHead.AC_PROPERTY_ID,
new BasicHeader(HttpReqHead.AC_PROPERTY_ID, ac_property_id));
return this;
}
/**
* 指定请求和响应遵循的缓存机制
* 例如:Cache-Control: no-cache
*
* @param cacheControl cache-control
* @return 返回当前对象
*/
public HttpHeaderBuilder cacheControl(String cacheControl) {
headerMaps.put(HttpReqHead.CACHE_CONTROL,
new BasicHeader(HttpReqHead.CACHE_CONTROL, cacheControl));
return this;
}
/**
* 表示是否需要持久连接(HTTP 1.1默认进行持久连接)
* 例如:Connection: close 短链接; Connection: keep-alive 长连接
*
* @param connection connection
* @return 返回当前对象
*/
public HttpHeaderBuilder connection(String connection) {
headerMaps.put(HttpReqHead.CONNECTION,
new BasicHeader(HttpReqHead.CONNECTION, connection));
return this;
}
/**
* HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器
* 例如:Cookie: $Version=1; Skin=new;
*
* @param cookie cookie
* @return 返回当前对象
*/
public HttpHeaderBuilder cookie(String cookie) {
headerMaps.put(HttpReqHead.COOKIE,
new BasicHeader(HttpReqHead.COOKIE, cookie));
return this;
}
/**
* 请求内容长度
* 例如:Content-Length: 348
*
* @param contentLength content-length
* @return 返回当前对象
*/
public HttpHeaderBuilder contentLength(String contentLength) {
headerMaps.put(HttpReqHead.CONTENT_LENGTH,
new BasicHeader(HttpReqHead.CONTENT_LENGTH, contentLength));
return this;
}
/**
* 请求的与实体对应的MIME信息
* 例如:Content-Type: application/x-www-form-urlencoded
*
* @param contentType content-type
* @return 返回当前对象
*/
public HttpHeaderBuilder contentType(String contentType) {
headerMaps.put(HttpReqHead.CONTENT_TYPE,
new BasicHeader(HttpReqHead.CONTENT_TYPE, contentType));
return this;
}
/**
* 请求发送的日期和时间
* 例如:Date: Tue, 15 Nov 2010 08:12:31 GMT
*
* @param date date
* @return 返回当前对象
*/
public HttpHeaderBuilder date(String date) {
headerMaps.put(HttpReqHead.DATE,
new BasicHeader(HttpReqHead.DATE, date));
return this;
}
/**
* 请求的特定的服务器行为
* 例如:Expect: 100-continue
*
* @param expect expect
* @return 返回当前对象
*/
public HttpHeaderBuilder expect(String expect) {
headerMaps.put(HttpReqHead.EXPECT,
new BasicHeader(HttpReqHead.EXPECT, expect));
return this;
}
/**
* 发出请求的用户的Email
* 例如:From: user@email.com
*
* @param from from
* @return 返回当前对象
*/
public HttpHeaderBuilder from(String from) {
headerMaps.put(HttpReqHead.FROM,
new BasicHeader(HttpReqHead.FROM, from));
return this;
}
/**
* 指定请求的服务器的域名和端口号
* 例如:Host: blog.csdn.net
*
* @param host host
* @return 返回当前对象
*/
public HttpHeaderBuilder host(String host) {
headerMaps.put(HttpReqHead.HOST,
new BasicHeader(HttpReqHead.HOST, host));
return this;
}
/**
* 只有请求内容与实体相匹配才有效
* 例如:If-Match: "737060cd8c284d8af7ad3082f209582d"
*
* @param ifMatch if-match
* @return 返回当前对象
*/
public HttpHeaderBuilder ifMatch(String ifMatch) {
headerMaps.put(HttpReqHead.IF_MATCH,
new BasicHeader(HttpReqHead.IF_MATCH, ifMatch));
return this;
}
/**
* 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码
* 例如:If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
*
* @param ifModifiedSince if-modified-Since
* @return 返回当前对象
*/
public HttpHeaderBuilder ifModifiedSince(String ifModifiedSince) {
headerMaps.put(HttpReqHead.IF_MODIFIED_SINCE,
new BasicHeader(HttpReqHead.IF_MODIFIED_SINCE, ifModifiedSince));
return this;
}
/**
* 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变
* 例如:If-None-Match: "737060cd8c284d8af7ad3082f209582d"
*
* @param ifNoneMatch if-none-match
* @return 返回当前对象
*/
public HttpHeaderBuilder ifNoneMatch(String ifNoneMatch) {
headerMaps.put(HttpReqHead.IF_NONE_MATCH,
new BasicHeader(HttpReqHead.IF_NONE_MATCH, ifNoneMatch));
return this;
}
/**
* 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag
* 例如:If-Range: "737060cd8c284d8af7ad3082f209582d"
*
* @param ifRange if-range
* @return 返回当前对象
*/
public HttpHeaderBuilder ifRange(String ifRange) {
headerMaps.put(HttpReqHead.IF_RANGE,
new BasicHeader(HttpReqHead.IF_RANGE, ifRange));
return this;
}
/**
* 只在实体在指定时间之后未被修改才请求成功
* 例如:If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
*
* @param ifUnmodifiedSince if-unmodified-since
* @return 返回当前对象
*/
public HttpHeaderBuilder ifUnmodifiedSince(String ifUnmodifiedSince) {
headerMaps.put(HttpReqHead.IF_UNMODIFIED_SINCE,
new BasicHeader(HttpReqHead.IF_UNMODIFIED_SINCE, ifUnmodifiedSince));
return this;
}
/**
* 限制信息通过代理和网关传送的时间
* 例如:Max-Forwards: 10
*
* @param maxForwards max-forwards
* @return 返回当前对象
*/
public HttpHeaderBuilder maxForwards(String maxForwards) {
headerMaps.put(HttpReqHead.MAX_FORWARDS,
new BasicHeader(HttpReqHead.MAX_FORWARDS, maxForwards));
return this;
}
/**
* 用来包含实现特定的指令
* 例如:Pragma: no-cache
*
* @param pragma pragma
* @return 返回当前对象
*/
public HttpHeaderBuilder pragma(String pragma) {
headerMaps.put(HttpReqHead.PRAGMA,
new BasicHeader(HttpReqHead.PRAGMA, pragma));
return this;
}
/**
* 连接到代理的授权证书
* 例如:Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
*
* @param proxyAuthorization proxy-authorization
* @return 返回当前对象
*/
public HttpHeaderBuilder proxyAuthorization(String proxyAuthorization) {
headerMaps.put(HttpReqHead.PROXY_AUTHORIZATION,
new BasicHeader(HttpReqHead.PROXY_AUTHORIZATION, proxyAuthorization));
return this;
}
/**
* 只请求实体的一部分,指定范围
* 例如:Range: bytes=500-999
*
* @param range range
* @return 返回当前对象
*/
public HttpHeaderBuilder range(String range) {
headerMaps.put(HttpReqHead.RANGE,
new BasicHeader(HttpReqHead.RANGE, range));
return this;
}
/**
* 先前网页的地址,当前请求网页紧随其后,即来路
* 例如:Referer: http://www.zcmhi.com/archives/71.html
*
* @param referer referer
* @return 返回当前对象
*/
public HttpHeaderBuilder referer(String referer) {
headerMaps.put(HttpReqHead.REFERER,
new BasicHeader(HttpReqHead.REFERER, referer));
return this;
}
/**
* 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息
* 例如:TE: trailers,deflate;q=0.5
*
* @param te te
* @return 返回当前对象
*/
public HttpHeaderBuilder te(String te) {
headerMaps.put(HttpReqHead.TE,
new BasicHeader(HttpReqHead.TE, te));
return this;
}
/**
* 向服务器指定某种传输协议以便服务器进行转换(如果支持)
* 例如:Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
*
* @param upgrade upgrade
* @return 返回当前对象
*/
public HttpHeaderBuilder upgrade(String upgrade) {
headerMaps.put(HttpReqHead.UPGRADE,
new BasicHeader(HttpReqHead.UPGRADE, upgrade));
return this;
}
/**
* User-Agent的内容包含发出请求的用户信息
*
* @param userAgent user-agent
* @return 返回当前对象
*/
public HttpHeaderBuilder userAgent(String userAgent) {
headerMaps.put(HttpReqHead.USER_AGENT,
new BasicHeader(HttpReqHead.USER_AGENT, userAgent));
return this;
}
/**
* 关于消息实体的警告信息
* 例如:Warn: 199 Miscellaneous warning
*
* @param warning warning
* @return 返回当前对象
*/
public HttpHeaderBuilder warning(String warning) {
headerMaps.put(HttpReqHead.WARNING,
new BasicHeader(HttpReqHead.WARNING, warning));
return this;
}
/**
* 通知中间网关或代理服务器地址,通信协议
* 例如:Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
*
* @param via via
* @return 返回当前对象
*/
public HttpHeaderBuilder via(String via) {
headerMaps.put(HttpReqHead.VIA,
new BasicHeader(HttpReqHead.VIA, via));
return this;
}
/**
* 设置此HTTP连接的持续时间(超时时间)
* 例如:Keep-Alive: 300
*
* @param keepAlive keep-alive
* @return 返回当前对象
*
*/
public HttpHeaderBuilder keepAlive(String keepAlive) {
headerMaps.put(HttpReqHead.KEEP_ALIVE,
new BasicHeader(HttpReqHead.KEEP_ALIVE, keepAlive));
return this;
}
public String accept() {
return get(HttpReqHead.ACCEPT);
}
public String acceptCharset() {
return get(HttpReqHead.ACCEPT_CHARSET);
}
public String acceptEncoding() {
return get(HttpReqHead.ACCEPT_ENCODING);
}
public String acceptLanguage() {
return get(HttpReqHead.ACCEPT_LANGUAGE);
}
public String acceptRanges() {
return get(HttpReqHead.ACCEPT_RANGES);
}
public String authorization() {
return get(HttpReqHead.AUTHORIZATION);
}
public String cacheControl() {
return get(HttpReqHead.CACHE_CONTROL);
}
public String connection() {
return get(HttpReqHead.CONNECTION);
}
public String cookie() {
return get(HttpReqHead.COOKIE);
}
public String contentLength() {
return get(HttpReqHead.CONTENT_LENGTH);
}
public String contentType() {
return get(HttpReqHead.CONTENT_TYPE);
}
public String date() {
return get(HttpReqHead.DATE);
}
public String expect() {
return get(HttpReqHead.EXPECT);
}
public String from() {
return get(HttpReqHead.FROM);
}
public String host() {
return get(HttpReqHead.HOST);
}
public String ifMatch() {
return get(HttpReqHead.IF_MATCH);
}
public String ifModifiedSince() {
return get(HttpReqHead.IF_MODIFIED_SINCE);
}
public String ifNoneMatch() {
return get(HttpReqHead.IF_NONE_MATCH);
}
public String ifRange() {
return get(HttpReqHead.IF_RANGE);
}
public String ifUnmodifiedSince() {
return get(HttpReqHead.IF_UNMODIFIED_SINCE);
}
public String maxForwards() {
return get(HttpReqHead.MAX_FORWARDS);
}
public String pragma() {
return get(HttpReqHead.PRAGMA);
}
public String proxyAuthorization() {
return get(HttpReqHead.PROXY_AUTHORIZATION);
}
public String referer() {
return get(HttpReqHead.REFERER);
}
public String te() {
return get(HttpReqHead.TE);
}
public String upgrade() {
return get(HttpReqHead.UPGRADE);
}
public String userAgent() {
return get(HttpReqHead.USER_AGENT);
}
public String via() {
return get(HttpReqHead.VIA);
}
public String warning() {
return get(HttpReqHead.WARNING);
}
public String keepAlive() {
return get(HttpReqHead.KEEP_ALIVE);
}
/**
* 获取head信息
*
* @return
*/
private String get(String headName) {
if (headerMaps.containsKey(headName)) {
return headerMaps.get(headName).getValue();
}
return null;
}
/**
* 返回header头信息
*
* @return 返回构建的header头信息数组
*/
public Header[] build() {
Header[] headers = new Header[headerMaps.size()];
int i = 0;
for (Header header : headerMaps.values()) {
headers[i] = header;
i++;
}
headerMaps.clear();
headerMaps = null;
return headers;
}
/**
* Http头信息
*
* @author arron
* @date 2015年11月9日 上午11:29:04
* @version 1.0
*/
private static class HttpReqHead {
public static final String ACCEPT = "Accept";
public static final String ACCEPT_CHARSET = "Accept-Charset";
public static final String ACCEPT_ENCODING = "Accept-Encoding";
public static final String ACCEPT_LANGUAGE = "Accept-Language";
public static final String ACCEPT_RANGES = "Accept-Ranges";
public static final String AUTHORIZATION = "Authorization";
public static final String AC_PROPERTY_ID = "AC-Property-Id";
public static final String CACHE_CONTROL = "Cache-Control";
public static final String CONNECTION = "Connection";
public static final String COOKIE = "Cookie";
public static final String CONTENT_LENGTH = "Content-Length";
public static final String CONTENT_TYPE = "Content-Type";
public static final String DATE= "Date";
public static final String EXPECT = "Expect";
public static final String FROM = "From";
public static final String HOST = "Host";
public static final String IF_MATCH = "If-Match ";
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
public static final String IF_NONE_MATCH = "If-None-Match";
public static final String IF_RANGE = "If-Range";
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
public static final String KEEP_ALIVE = "Keep-Alive";
public static final String MAX_FORWARDS = "Max-Forwards";
public static final String PRAGMA = "Pragma";
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
public static final String RANGE = "Range";
public static final String REFERER = "Referer";
public static final String TE = "TE";
public static final String UPGRADE = "Upgrade";
public static final String USER_AGENT = "User-Agent";
public static final String VIA = "Via";
public static final String WARNING = "Warning";
}
/**
* 常用头信息配置
*
* @author arron
* @version 1.0
*/
public static class Headers{
public static final String APP_FORM_URLENCODED="application/x-www-form-urlencoded";
public static final String TEXT_PLAIN="text/plain";
public static final String TEXT_HTML="text/html";
public static final String TEXT_XML="text/xml";
public static final String TEXT_JSON="text/json";
public static final String CONTENT_CHARSET_ISO_8859_1 = StandardCharsets.ISO_8859_1.name();
public static final String CONTENT_CHARSET_UTF8 = StandardCharsets.UTF_8.name();
public static final String DEF_PROTOCOL_CHARSET = StandardCharsets.US_ASCII.name();
public static final String CONN_CLOSE = "close";
public static final String KEEP_ALIVE = "keep-alive";
public static final String EXPECT_CONTINUE = "100-continue";
}
public static Map<String, String> getDefaultHeaders(URLConnection connection) {
Map<String, String> defaultHeaders = new HashMap<>();
defaultHeaders.put("Accept", "*/*");
defaultHeaders.put("Connection", "Keep-Alive");
defaultHeaders.put("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
defaultHeaders.put("Accept-Charset", "utf-8");
return defaultHeaders;
}
/**
* 設置請求頭到連接對象
*
* @param conn URL連接對象
* @param contentType 內容類型
* @param headers HTTP 請求頭 Map(可選,如果為 null 或空則使用默認值)
*/
public static void setRequestHeaders(URLConnection conn, String contentType, Map<String, String> headers) {
Map<String, String> defaultHeaders = getDefaultHeaders(conn);
// 如果傳入了自定義 headers,則合併(自定義的優先)
if (headers != null && !headers.isEmpty()) {
defaultHeaders.putAll(headers);
}
// 設置所有請求頭
for (Map.Entry<String, String> entry : defaultHeaders.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
// Content-Type 優先級最高,如果指定了則覆蓋
if (contentType != null) {
conn.setRequestProperty("Content-Type", contentType);
}
}
}
@@ -0,0 +1,72 @@
package com.mgm.mcrm.common.httpclient;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;
/**
* HTTP 参数构建器
*
* 用于构建 HTTP 请求参数(查询字符串、请求体等)。
*
* @author Aaka
*/
public class HttpParameterBuilder {
private static final Logger log = LoggerFactory.getLogger(HttpParameterBuilder.class);
/**
* 將參數 Map 轉換為查詢字符串(用於 GET 請求)
*
* @param params 參數 Map
* @return 查詢字符串,格式為 name1=value1&name2=value2
*/
public static String buildQueryString(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return "";
}
return params.entrySet().stream()
.filter(entry -> entry.getKey() != null && entry.getValue() != null)
.map(entry -> {
try {
String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
String value = URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8);
return key + "=" + value;
} catch (Exception e) {
log.warn("Failed to encode parameter: {}={}", entry.getKey(), entry.getValue(), e);
return null;
}
})
.filter(java.util.Objects::nonNull)
.collect(Collectors.joining("&"));
}
/**
* 將參數 Map 轉換為請求體字符串
*
* @param params 參數 Map
* @param contentType 內容類型
* @return 請求體字符串
*/
public static String buildRequestBody(Map<String, Object> params, String contentType) {
if (params == null || params.isEmpty()) {
return "";
}
// 如果是 JSON 格式
if (contentType != null && contentType.contains("application/json")) {
// 使用 FastJSON 將 Map 轉換為 JSON 字符串
return JSON.toJSONString(params);
} else {
// 默認使用 form-urlencoded 格式
return buildQueryString(params);
}
}
}
@@ -0,0 +1,366 @@
package com.mgm.mcrm.common.httpclient;
import com.mgm.mcrm.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;
import com.mgm.mcrm.common.httpclient.HttpEntityBuilder;
/**
* HTTP 请求选项类
*
* 用于构建 HTTP 请求的选项对象,支持链式调用。
* 注意:此类不是 Spring 配置类,而是 HTTP 请求参数的构建器。
*
* @author Aaka
*/
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.GET;
/**
* 请求方法名称
*/
private String methodName;
/**
* 用于cookie操作
*/
private HttpContext context;
/**
* 以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<OutputStream> outs = new ThreadLocal<OutputStream>();
/**
* 解决多线程处理时,url被覆盖问题
*/
private static final ThreadLocal<String> urls = new ThreadLocal<String>();
/**
* 解决多线程处理时,url被覆盖问题
*/
private static final ThreadLocal<Map<String,Object>> maps = new ThreadLocal<Map<String,Object>>();
/**
* @param client HttpClient对象
* @return 返回当前对象
*/
public HttpRequestOptions client(HttpClient client) {
this.client = client;
return this;
}
/**
* @param url 资源url
* @return 返回当前对象
*/
public HttpRequestOptions url(String url) {
urls.set(url);
return this;
}
/**
* @param headers Header头信息
* @return 返回当前对象
*/
public HttpRequestOptions headers(Header[] headers) {
this.headers = headers;
return this;
}
/**
* Header头信息(是否返回response中的headers)
*
* @param headers Header头信息
* @param isReturnRespHeaders 是否返回response中的headers
* @return 返回当前对象
*/
public HttpRequestOptions headers(Header[] headers, boolean isReturnRespHeaders) {
this.headers = headers;
this.isReturnRespHeaders=isReturnRespHeaders;
return this;
}
/**
* @param method 请求方法
* @return 返回当前对象
*/
public HttpRequestOptions method(HttpMethods method) {
this.method = method;
return this;
}
/**
* @param methodName 请求方法
* @return 返回当前对象
*/
public HttpRequestOptions methodName(String methodName) {
this.methodName = methodName;
return this;
}
/**
* @param context cookie操作相关
* @return 返回当前对象
*/
public HttpRequestOptions context(HttpContext context) {
this.context = context;
return this;
}
/**
* @param map 传递参数
* @return 返回当前对象
*/
public HttpRequestOptions map(Map<String, Object> map) {
Map<String, Object> m = maps.get();
if(m == null || map.isEmpty()){
m = map;
}else {
m.putAll(map);
}
maps.set(m);
return this;
}
/**
* @param json 以json格式字符串作为参数
* @return 返回当前对象
*/
public HttpRequestOptions json(String json) {
this.json = json;
Map<String, Object> map = new HashMap<String, Object>();
map.put(HttpEntityBuilder.ENTITY_JSON, json);
maps.set(map);
return this;
}
/**
* @param filePaths 待上传文件所在路径
* @return 返回当前对象
*/
public HttpRequestOptions files(String[] filePaths) {
return files(filePaths, "file");
}
/**
* 上传文件时用到
* @param filePaths 待上传文件所在路径
* @param inputName 即file input 标签的name值,默认为file
* @return 返回当前对象
*/
public HttpRequestOptions files(String[] filePaths, String inputName) {
return files(filePaths, inputName, false);
}
/**
* 上传文件时用到
* @param filePaths 待上传文件所在路径
* @param inputName 即file input 标签的name值,默认为file
* @param forceRemoveContentTypeChraset 是否强制一处content-type中设置的编码类型
* @return 返回当前对象
*/
public HttpRequestOptions files(String[] filePaths, String inputName, boolean forceRemoveContentTypeChraset) {
Map<String, Object> m = maps.get();
if(m == null || m.isEmpty()){
m = new HashMap<String, Object>();
}
m.put(HttpEntityBuilder.ENTITY_MULTIPART, filePaths);
m.put(HttpEntityBuilder.ENTITY_MULTIPART+".name", inputName);
m.put(HttpEntityBuilder.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset);
maps.set(m);
return this;
}
/**
* @param encoding 输入输出编码
* @return 返回当前对象
*/
public HttpRequestOptions encoding(String encoding) {
//设置输入输出
inenc(encoding);
outenc(encoding);
this.encoding = encoding;
return this;
}
/**
* @param inenc 输入编码
*/
public void inenc(String inenc) {
this.inenc = inenc;
}
/**
* @param outenc 输出编码
*/
public void outenc(String outenc) {
this.outenc = outenc;
}
/**
* @param out 输出流对象
* @return 返回当前对象
*/
public HttpRequestOptions out(OutputStream out) {
outs.set(out);
return this;
}
/**
* 设置超时时间
*
* @param timeout 超时时间,单位-毫秒
* @return 返回当前对象
*/
public HttpRequestOptions timeout(int timeout){
return timeout(timeout, true);
}
/**
* 设置超时时间以及是否允许网页重定向(自动跳转 302)
*
* @param timeout 超时时间,单位-毫秒
* @param redirectEnable 自动跳转
* @return 返回当前对象
*/
public HttpRequestOptions timeout(int timeout, boolean redirectEnable){
// 配置请求的超时设置
// 注意:HttpClient 5 中需要使用 Timeout 对象,而不是直接的 int 值
Timeout timeoutValue = Timeout.ofMilliseconds(timeout);
RequestConfig config = RequestConfig.custom()
.setConnectionRequestTimeout(timeoutValue)
.setResponseTimeout(timeoutValue)
.setRedirectsEnabled(redirectEnable)
.build();
return timeout(config);
}
/**
* 设置代理、超时时间、允许网页重定向等
*
* @param requestConfig 超时时间,单位-毫秒
* @return 返回当前对象
*/
public HttpRequestOptions timeout(RequestConfig requestConfig){
this.requestConfig = requestConfig;
return this;
}
public HttpClient client() {
return client;
}
public Header[] headers() {
return headers;
}
public boolean isReturnRespHeaders() {
return isReturnRespHeaders;
}
public String url() {
return urls.get();
}
public HttpMethods method() {
return method;
}
public String methodName() {
return methodName;
}
public HttpContext context() {
return context;
}
public Map<String, Object> map() {
return maps.get();
}
public String json() {
return json;
}
public String encoding() {
return encoding;
}
public String inenc() {
return inenc == null ? encoding : inenc;
}
public String outenc() {
return outenc == null ? encoding : outenc;
}
public OutputStream out() {
return outs.get();
}
public RequestConfig requestConfig() {
return requestConfig;
}
}
@@ -0,0 +1,203 @@
package com.mgm.mcrm.common.httpclient;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.ssl.SSLContexts;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
/**
* SSL 支持类(基于 HttpClient 5)
*
* 提供:
* - 信任所有证书的 SSLSocketFactory
* - 信任所有证书的 SSLConnectionSocketFactory
*
* 注意:默认实现信任所有证书,仅适用于内部系统或测试环境。
*
* @author Anka
* @version 2.0 (client5)
*/
public class HttpSslSupport {
private static final SSLHandler SIMPLE_VERIFIER = new SSLHandler();
private static SSLSocketFactory sslFactory;
// 注意:SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated,
// 但这是目前唯一可用的方式,新的 API 尚未稳定
@SuppressWarnings("deprecation")
private static SSLConnectionSocketFactory sslConnFactory;
private static final HttpSslSupport INSTANCE = new HttpSslSupport();
private SSLContext sc;
public static HttpSslSupport getInstance() {
return INSTANCE;
}
public static HttpSslSupport create() {
return new HttpSslSupport();
}
/**
* 重写 X509TrustManager 和 HostnameVerifier,信任所有证书与主机名。
* 仅推荐在内部环境或调试场景使用。
*/
private static class SSLHandler implements X509TrustManager, HostnameVerifier {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain,
String authType) throws java.security.cert.CertificateException {
// 信任所有服务器证书
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain,
String authType) throws java.security.cert.CertificateException {
// 信任所有客户端证书
}
@Override
public boolean verify(String paramString, SSLSession paramSSLSession) {
// 信任所有主机名
return true;
}
}
/**
* 获取默认 HostnameVerifier(信任所有主机名)
*/
public static HostnameVerifier getVerifier() {
return SIMPLE_VERIFIER;
}
/**
* 获取信任所有证书的 {@link SSLSocketFactory}
*/
public synchronized SSLSocketFactory getSSLSF(SSLProtocolVersion sslpv) {
if (sslFactory != null) {
return sslFactory;
}
try {
SSLContext context = getSSLContext(sslpv);
context.init(null, new TrustManager[]{SIMPLE_VERIFIER}, null);
sslFactory = context.getSocketFactory();
return sslFactory;
} catch (KeyManagementException e) {
throw new RuntimeException("初始化 SSLSocketFactory 失败", e);
}
}
/**
* 获取信任所有证书的 {@link SSLConnectionSocketFactory}(HttpClient5)
*
* 注意:SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated,
* 但这是目前唯一可用的方式,新的 API 尚未稳定
*/
@SuppressWarnings("deprecation")
public synchronized SSLConnectionSocketFactory getSSLCONNSF(SSLProtocolVersion sslpv) {
if (sslConnFactory != null) {
return sslConnFactory;
}
try {
SSLContext context = getSSLContext(sslpv);
context.init(null, new TrustManager[]{SIMPLE_VERIFIER}, new java.security.SecureRandom());
sslConnFactory = new SSLConnectionSocketFactory(context, SIMPLE_VERIFIER);
return sslConnFactory;
} catch (KeyManagementException e) {
throw new RuntimeException("初始化 SSLConnectionSocketFactory 失败", e);
}
}
/**
* 使用自定义证书(trustStore)初始化 SSLContext
*/
public HttpSslSupport customSSL(String keyStorePath, String keyStorepass) {
FileInputStream instream = null;
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
instream = new FileInputStream(new File(keyStorePath));
trustStore.load(instream, keyStorepass.toCharArray());
// 相信自己的 CA 和所有自签名证书
this.sc = SSLContexts.custom()
.loadTrustMaterial(trustStore, (chain, authType) -> true)
.build();
return this;
} catch (KeyManagementException | KeyStoreException |
NoSuchAlgorithmException | CertificateException | IOException e) {
throw new RuntimeException("初始化自定义 SSLContext 失败", e);
} finally {
if (instream != null) {
try {
instream.close();
} catch (IOException ignore) {
// ignore
}
}
}
}
/**
* 获取(或创建)指定协议版本的 {@link SSLContext}
*/
public SSLContext getSSLContext(SSLProtocolVersion sslpv) {
try {
if (this.sc == null) {
this.sc = SSLContext.getInstance(sslpv.getName());
}
return this.sc;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("不支持的 SSL 协议版本: " + sslpv.getName(), e);
}
}
/**
* 支持的 SSL/TLS 协议版本枚举
*/
public enum SSLProtocolVersion {
SSL("SSL"),
SSLv3("SSLv3"),
TLSv1("TLSv1"),
TLSv1_1("TLSv1.1"),
TLSv1_2("TLSv1.2"),
TLSv1_3("TLSv1.3");
private final String name;
SSLProtocolVersion(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public static SSLProtocolVersion find(String name) {
for (SSLProtocolVersion pv : SSLProtocolVersion.values()) {
if (pv.getName().equalsIgnoreCase(name)) {
return pv;
}
}
throw new IllegalArgumentException("未支持当前 SSL 版本号:" + name);
}
}
}