import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
@Configuration
public class RestTemplateConfig {
private static int connectTimeout = 60 * 1000;
private static int readTimeout = 60 * 1000;
@Resource
private RestTemplateLog restTemplateLog;
//如果没有此类认证的请求,可以把参数去掉String account,String password
@Bean(name = "myRestAuthTemplate")
public RestTemplate restTemplate(@Value("${test.account}") String account,
@Value("${test.password}") String password) throws Exception {
RestTemplate restTemplate = createRestTemplate(account,password);
//重新设置StringHttpMessageConverter字符集为UTF-8,解决中文乱码问题
modifyDefaultCharset(restTemplate);
// 添加日志拦截器
restTemplate.getInterceptors().add(restTemplateLog);
// 请求失败异常处理,如果不重写hasError方法,抛出异常,无法执行后续代码
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) {
return false;
}
});
return restTemplate;
}
private void modifyDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = StandardCharsets.UTF_8;
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
//xml响应类型解析
//MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
//restTemplate.getMessageConverters().add(converter);
}
public static RestTemplate createRestTemplate( String account, String password)
throws KeyManagementException, NoSuchAlgorithmException {
// 创建一个信任所有证书的TrustManager
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
// 安装所有信任的trust manager
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// 创建一个SSL连接套接字工厂,它允许我们绕过证书验证
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(connectTimeout)
.setSocketTimeout(readTimeout)
.setConnectionRequestTimeout(connectTimeout)
.build();
//如果要添加认证
//BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
//basicCredentialsProvider.setCredentials(
//new AuthScope("10.10.10.10", 8080, "auth realm"),
//new UsernamePasswordCredentials(account,password)
);
// 使用这个套接字工厂创建一个CloseableHttpClient
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(requestConfig)
//.setDefaultCredentialsProvider(basicCredentialsProvider)
.build();
// 将此HttpClient设置为RestTemplate的请求工厂
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
// 创建并返回RestTemplate实例
return new RestTemplate(requestFactory);
}
}
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Component
@Slf4j
public class RestTemplateLog implements ClientHttpRequestInterceptor {
//不打印密码的路径
private static final String pathWhite = "api/test";
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
ClientHttpResponse response = execution.execute(request, body);
//get打印参数或delete请求
if (HttpMethod.GET.equals(request.getMethod()) || HttpMethod.DELETE.equals(request.getMethod())) {
traceGetRequest(request);
} else {
//其他请求打印参数
traceRequest(request, body);
}
return traceResponse(response);
}
/**
* 打印get请求信息
* @date 2024/6/3 9:19
*/
private void traceGetRequest(HttpRequest request) {
log.info("requestUri:{}",request.getURI().toString());
log.info("headers:{}",request.getHeaders());
// 使用UriComponentsBuilder解析请求的URI并获取查询参数
UriComponents uriComponents = UriComponentsBuilder.fromUri(request.getURI()).build();
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
//get请求打印效果是list,但不影响查看参数,实际应该是k,v结构的形式
log.info("queryParameters:{}", queryParams);
}
/**
* 在白名单不打印参数
*/
private void traceRequest(HttpRequest request, byte[] body) {
log.info("requestUri:{}",request.getURI().getPath());
log.info("headers:{}",request.getHeaders());
String bodyStr = new String(body, StandardCharsets.UTF_8);
//密码脱敏
boolean shouldLogParams = true;
//在白名单不打印参数
if(request.getURI().getPath().contains(pathWhite )){
shouldLogParams = false;
}
if (shouldLogParams) {
if(StringUtils.isNotBlank(bodyStr)){
log.info("requestBody:{}",bodyStr);
}
}
}
private BufferingClientHttpResponseWrapper traceResponse(ClientHttpResponse response) throws IOException {
//为了多次读响应流
BufferingClientHttpResponseWrapper responseWrapper = new BufferingClientHttpResponseWrapper(response);
byte[] responseBodyBytes = StreamUtils.copyToByteArray(responseWrapper.getBody());
LogUtil.info(log,"traceResponse","\nResponse Status:{}",responseWrapper.getStatusCode().value());
//LogUtil.info(log,"traceResponse","\nresponseBody:{}", JSONUtil.toJsonPrettyStr(JsonUtil.parseObject(responseBodyBytes,Object.class)));
LogUtil.info(log,"traceResponse","\nresponseBody:{}", new String(responseBodyBytes, StandardCharsets.UTF_8));
return responseWrapper;
}
}
package com.midea.mops.client.http.imc;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 缓冲客户端HTTP响应包装器类。
* <p>
* 此类设计用于封装{@link ClientHttpResponse}实例,并在首次访问响应体时将其内容缓冲至内存中,
* 以便后续多次读取无需重新获取流数据,提高效率和可重复读取能力。
*
* @date: 2024/5/17 17:00
* @version: 1.0
*/
public final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response; // 原始客户端HTTP响应对象
@Nullable
private byte[] body; // 缓存的响应体内容
/**
* 构造方法,创建一个缓冲响应包装器实例,包裹给定的原始响应对象。
*
* @param response 原始的{@link ClientHttpResponse}实例
*/
public BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
this.response = response;
}
/**
* 获取HTTP响应状态码。
*
* @return 响应的{@link HttpStatus}对象
* @throws IOException 如果读取过程中发生I/O错误
*/
public HttpStatus getStatusCode() throws IOException {
return this.response.getStatusCode();
}
/**
* 获取原始的HTTP响应状态码(整数值)。
*
* @return 响应的原始状态码
* @throws IOException 如果读取过程中发生I/O错误
*/
public int getRawStatusCode() throws IOException {
return this.response.getRawStatusCode();
}
/**
* 获取HTTP状态文本描述。
*
* @return 状态文本
* @throws IOException 如果读取过程中发生I/O错误
*/
public String getStatusText() throws IOException {
return this.response.getStatusText();
}
/**
* 获取响应头信息。
*
* @return 响应的{@link HttpHeaders}实例
*/
public HttpHeaders getHeaders() {
return this.response.getHeaders();
}
/**
* 获取响应体的输入流。
* <p>
* 首次调用时,会将响应体内容读取至内存并缓存,后续调用直接返回缓存内容的流。
*
* @return 输入流,读取响应体内容
* @throws IOException 如果读取过程中发生I/O错误
*/
public InputStream getBody() throws IOException {
if (this.body == null) {
this.body = StreamUtils.copyToByteArray(this.response.getBody());
}
return new ByteArrayInputStream(this.body);
}
/**
* 关闭响应,释放资源。
* 实际上是关闭底层的原始响应对象。
*/
public void close() {
this.response.close();
}
}
使用的时候注入就行
@Autowired
@Qualifier(value = "myRestAuthTemplate")
RestTemplate restTemplate;
如果实体类要解析成xml还需要
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Data;
@Data
@JacksonXmlRootElement(localName = "user")
public class User {
@JacksonXmlProperty(isAttribute = false)
private int id;
@JacksonXmlProperty(localName = "userName")
private String userName;
}
方法封装
public <T> T doPatch(String url,Object param, Class<T> prefixesResClass) {
ResponseEntity<T> responseEntity;
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Authorization", authenticate);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(JsonUtil.toJsonString(param), httpHeaders);
responseEntity = restTemplate.exchange(url, HttpMethod.PATCH, entity, prefixesResClass);
if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
return responseEntity.getBody();
}
throw new BusinessException(ERROR_LOG + JsonUtil.toJsonString(responseEntity.getBody()));
} catch (Exception e) {
log.error("失败了,失败信息:{}", e.getMessage());
throw new BusinessException(ERROR_LOG + e.getMessage());
}
}
public void doDelete(String url, Class<Void> prefixesResClass) {
ResponseEntity<Void> responseEntity;
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Authorization", authenticate);
HttpEntity<String> entity = new HttpEntity<>(null, httpHeaders);
responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, entity, prefixesResClass);
if (!responseEntity.getStatusCode().equals(HttpStatus.NO_CONTENT)) {
throw new BusinessException(ERROR_LOG + JsonUtil.toJsonString(responseEntity.getBody()));
}
} catch (Exception e) {
log.error("失败了,失败信息:{}", e.getMessage());
throw new BusinessException(ERROR_LOG + e.getMessage());
}
}
public <T> T doGet(String url ,ParameterizedTypeReference<T> responseType,HashMap<String,Object> param) {
ResponseEntity<T> responseEntity = null;
try {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authenticate);
// 创建HttpEntity实例,将请求头放入
HttpEntity<String> entity = new HttpEntity<>(headers);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
param.forEach(builder::queryParam);
// 发起GET请求
responseEntity = restTemplate.exchange(builder.build().encode(StandardCharsets.UTF_8).toUri(), HttpMethod.GET, entity, responseType);
if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
return responseEntity.getBody();
}
throw new BusinessException("失败信息" + JsonUtil.toJsonString(responseEntity.getBody()));
} catch (Exception e) {
log.error("失败了,失败信息:{}", e.getMessage());
throw new BusinessException(ERROR_LOG + e.getMessage());
}
}
/**
* post请求请求示例 201
*/
public <T> T doPost(String uriStr, String payload, ParameterizedTypeReference<T> responseTypeRef) {
ResponseEntity<T> responseEntity = null;
try {
HttpHeaders httpHeaders = new HttpHeaders();
//httpHeaders.set(entrySet.getKey(), entrySet.getValue());
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(payload, httpHeaders);
responseEntity = restTemplate.exchange(baseurl + uriStr, HttpMethod.POST, entity, responseTypeRef);
if (responseEntity.getStatusCode().equals(HttpStatus.CREATED)) {
return responseEntity.getBody();
}
throw new BusinessException("失败信息为:" + JsonUtil.toJsonString(responseEntity.getBody()));
} catch (Exception e) {
log.error("失败信息为:{}", e.getMessage());
throw new BusinessException("失败信息为:" + e.getMessage());
}
}
/**
* post请求请求示例 200
*/
public <T> T doPost2(String uriStr, String payload, ParameterizedTypeReference<T> responseTypeRef) {
ResponseEntity<T> responseEntity = null;
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(payload, httpHeaders);
responseEntity = restTemplate.exchange(baseurl + uriStr, HttpMethod.POST, entity, responseTypeRef);
if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
return responseEntity.getBody();
}
throw new BusinessException("失败信息为:" + JsonUtil.toJsonString(responseEntity.getBody()));
} catch (Exception e) {
log.error("doPost2失败:{}", e.getMessage());
throw new BusinessException("失败信息为:" + e.getMessage());
}
}
/**
* put请求请求示例 204
*/
public void doPut(String uriStr, String payload) {
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(payload, httpHeaders);
restTemplate.put(uriStr, entity, String.class);
} catch (Exception e) {
log.error("doPut失败:{}", e.getMessage());
throw new BusinessException("失败信息为:" + e.getMessage());
}
}