自定义Resttemplate,支持添加认证,打印请求,参数和响应,支持响应内容为xml的解析成对象

发布于:2024-07-06 ⋅ 阅读:(45) ⋅ 点赞:(0)

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());
        }
    }