【二次封装】之 RestTemplate. 统一调用方式, 方便记忆以及使用.

二次封装系列(持续更新)

【二次封装】之树级结构排序. 拿来即用. 根据 id, parentId 对对象进行树级结构排序. 就是在前人的模板基础上, 进行了泛型化.


目的

主要是因为 RestTemplate 的方法太多记不住, 但是 RestTemplate 的底层调用都是同一个方法, 因此想通过封装一个统一的传参方式, 来方便调用

config 配置类

CustomHttpInterceptor 拦截器

/**
 * @author: lsx
 * @create: 2022-02-23
 * @description: 拦截器 可以自定义对请求进行拦截, 建议针对文件上传接口取消拦截
 **/
@Slf4j
public class CustomHttpInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        long timeStamp = System.currentTimeMillis();
        trackRequest(request, body, timeStamp);
        ClientHttpResponse httpResponse = execution.execute(request, body);
        trackResponse(httpResponse, timeStamp);
        return httpResponse;
    }

    private void trackResponse(ClientHttpResponse httpResponse, Long timeStamp) throws IOException {
        log.info("============================response begin==========================================");
        log.info("Time Stamp   : {}", timeStamp);
        log.info("Status code  : {}", httpResponse.getStatusCode());
        log.info("Status text  : {}", httpResponse.getStatusText());
        log.info("Headers      : {}", httpResponse.getHeaders());
        log.info("=======================response end=================================================");
    }

    private void trackRequest(HttpRequest request, byte[] body, Long timeStamp) throws UnsupportedEncodingException {
        log.info("============================request begin==========================================");
        log.info("Time Stamp   : {}", timeStamp);
        log.info("uri : {}", request.getURI());
        log.info("method : {}", request.getMethod());
        log.info("headers : {}", request.getHeaders());
        log.info("request body : {}", new String(body, StandardCharsets.UTF_8));
        log.info("=======================request end=================================================");
    }
}

CustomMappingMessageConverter 消息转换器

/**
 * @author: lsx
 * @create: 2022-02-24
 * @description: 主要是添加对接口响应结果不同类型的支持
 **/
public class CustomMappingMessageConverter extends MappingJackson2HttpMessageConverter {
    public CustomMappingMessageConverter() {
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(new MediaType("text", "plain", StandardCharsets.UTF_8));
        mediaTypes.add(MediaType.TEXT_PLAIN);
        //加入text/html类型的支持
        mediaTypes.add(MediaType.TEXT_HTML);
        mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        mediaTypes.add(MediaType.APPLICATION_XML);
        mediaTypes.add(new MediaType("application", "xml", StandardCharsets.UTF_8));
        // tag6
        setSupportedMediaTypes(mediaTypes);

        //设置允许出现特殊字符的字符集
        getObjectMapper().enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
    }
}

CustomRestErrorHandler 自定义的异常处理

/**
 * @author: lsx
 * @create: 2022-05-30
 * @description: 自定义的异常处理这里只是摘抄了resttemplate 默认的异常处理, 这里都是可以根据自己需要来自定义的
 **/
@Slf4j
public class CustomRestErrorHandler implements ResponseErrorHandler {
    /**
     * 判断返回结果response是否是异常结果
     * 主要是去检查response 的HTTP Status
     * 仿造DefaultResponseErrorHandler实现即可
     */
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        int rawStatusCode = response.getRawStatusCode();
        HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);

        if (HttpStatus.UNAUTHORIZED == statusCode) {
            log.error("授权异常, 重新获取 token, 不抛出异常(等待重新调用).");
            return false;
        }

        return (statusCode != null ? statusCode.isError() : hasError(rawStatusCode));
    }

    protected boolean hasError(int unknownStatusCode) {
        HttpStatus.Series series = HttpStatus.Series.resolve(unknownStatusCode);
        return (series == HttpStatus.Series.CLIENT_ERROR || series == HttpStatus.Series.SERVER_ERROR);
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        // 里面可以实现你自己遇到了Error进行合理的处理
        HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
        if (statusCode == null) {
            byte[] body = getResponseBody(response);
            String message = getErrorMessage(response.getRawStatusCode(),
                    response.getStatusText(), body, getCharset(response));
            throw new UnknownHttpStatusCodeException(message,
                    response.getRawStatusCode(), response.getStatusText(),
                    response.getHeaders(), body, getCharset(response));
        }
        handleError(response, statusCode);
    }

    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = getResponseBody(response);
        Charset charset = getCharset(response);
        String message = getErrorMessage(statusCode.value(), statusText, body, charset);

        switch (statusCode.series()) {
            case CLIENT_ERROR:
                throw HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
            case SERVER_ERROR:
                throw HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
            default:
                throw new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
        }
    }

    protected byte[] getResponseBody(ClientHttpResponse response) {
        try {
            return FileCopyUtils.copyToByteArray(response.getBody());
        } catch (IOException ex) {
            // ignore
        }
        return new byte[0];
    }

    private String getErrorMessage(
            int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) {

        String preface = rawStatusCode + " " + statusText + ": ";
        if (ObjectUtils.isEmpty(responseBody)) {
            return preface + "[no body]";
        }

        if (charset == null) {
            charset = StandardCharsets.UTF_8;
        }
        int maxChars = 200;

        if (responseBody.length < maxChars * 2) {
            return preface + "[" + new String(responseBody, charset) + "]";
        }

        try {
            Reader reader = new InputStreamReader(new ByteArrayInputStream(responseBody), charset);
            CharBuffer buffer = CharBuffer.allocate(maxChars);
            reader.read(buffer);
            reader.close();
            buffer.flip();
            return preface + "[" + buffer + "... (" + responseBody.length + " bytes)]";
        } catch (IOException ex) {
            // should never happen
            throw new IllegalStateException(ex);
        }
    }

    @Nullable
    protected Charset getCharset(ClientHttpResponse response) {
        HttpHeaders headers = response.getHeaders();
        MediaType contentType = headers.getContentType();
        return (contentType != null ? contentType.getCharset() : null);
    }
}

HttpsClientHttpRequestFactory 绕过 Https 认证请求

/**
 * @author: lsx
 * @create: 2022-05-09
 * @description: Https 绕过 认证请求
 **/
@Slf4j
public class HttpsClientHttpRequestFactory extends SimpleClientHttpRequestFactory {

    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
        if (connection instanceof HttpsURLConnection) {
            prepareHttpsConnection((HttpsURLConnection) connection);
        }
        super.prepareConnection(connection, httpMethod);
    }

    private void prepareHttpsConnection(HttpsURLConnection connection) {
        connection.setHostnameVerifier(new SkipHostnameVerifier());
        try {
            connection.setSSLSocketFactory(createSslSocketFactory());
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
    }

    private SSLSocketFactory createSslSocketFactory() throws Exception {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new TrustManager[]{new SkipX509TrustManager()}, new SecureRandom());
        return context.getSocketFactory();
    }

    private static class SkipX509TrustManager implements X509TrustManager {

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

    }

    private static class SkipHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }

    }
}

RestTemplateConfig Bean创建

/**
 * @author: lsx
 * @create: 2022-02-23
 * @description: RestTemplate
 **/
@Configuration
public class RestTemplateConfig {

    private static final int CONNECT_TIMEOUT = 100000;
    private static final int TIMEOUT = 150000;

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        //先获取到converter列表
        RestTemplate restTemplate = new RestTemplate(factory);

        //消息转换器
        restTemplate.getMessageConverters().add(new CustomMappingMessageConverter());

        //添加拦截器
        //restTemplate.getInterceptors().add(new CustomHttpInterceptor());

        //异常消息自定义处理
        //restTemplate.setErrorHandler(new CustomRestErrorHandler());

        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        //绕过 https 接口认证
        HttpsClientHttpRequestFactory factory = new HttpsClientHttpRequestFactory();
        factory.setReadTimeout(TIMEOUT);
        factory.setConnectTimeout(CONNECT_TIMEOUT);
        return factory;
    }
}

util 配置

RestTemplateBuild 主要的参数封装对象

import lombok.Getter;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Lsx
 * @create: 2022-01-07 18:58
 * @description: RestTemplateBuild
 **/
@Getter
public class RestTemplateBuild<T> {
    private final String url;
    private final HttpMethod method;
    private final Map<String, String> headers;
    private final Map<String, Object> params;
    private final MultiValueMap<String, Object> bodyMap;
    private final Object body;
    private final MediaType contentType;
    @JsonIgnore
    private final Class<T> responseBody;
    @JsonIgnore
    private final ParameterizedTypeReference<T> parameterizedTypeReference;

    private RestTemplateBuild(String url, HttpMethod method, Map<String, String> headers, Map<String, Object> params, MultiValueMap<String, Object> bodyMap, Object body, MediaType contentType, Class<T> responseBody, ParameterizedTypeReference<T> parameterizedTypeReference) {
        this.url = url;
        this.method = method;
        this.headers = headers;
        this.params = params;
        this.bodyMap = bodyMap;
        this.body = body;
        this.contentType = contentType;
        this.responseBody = responseBody;
        this.parameterizedTypeReference = parameterizedTypeReference;
    }

    /**
     * 基础响应
     *
     * @param url          url
     * @param method       请求方式
     * @return builder
     */
    public static RestTemplateBuilder builder(String url, HttpMethod method) {
        return new RestTemplateBuilder(url, method);
    }


    public static final String APPLICATION_XML_UTF8_VALUES = "application/xml;charset=utf-8";
    public static final MediaType APPLICATION_XML_UTF8;

    static {
        APPLICATION_XML_UTF8 = new MediaType("application", "xml", StandardCharsets.UTF_8);
    }


    public static class RestTemplateBuilder {
        private final String url;
        private final HttpMethod method;
        private Map<String, String> headers;
        private Map<String, Object> params;
        private MultiValueMap<String, Object> bodyMap;
        private Object body;
        private MediaType contentType;

        private RestTemplateBuilder(String url, HttpMethod method) {
            this.url = url;
            this.method = method;
            //就算没有 也要传个空对象进去, 否则会报 'uriVariables' must not be null
            this.params = new HashMap<>();
            this.headers = new HashMap<>();
        }

        public RestTemplateBuilder headers(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }

        public RestTemplateBuilder header(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public RestTemplateBuilder params(Map<String, Object> params) {
            this.params = params;
            return this;
        }

        public RestTemplateBuilder param(String key, Object value) {
            this.params.put(key, value);
            return this;
        }

        public RestTemplateBuilder paramObj(Object paramObj) {
            this.params = objTMap(paramObj);
            return this;
        }

        public RestTemplateBuilder bodyMap(MultiValueMap<String, Object> bodyMap) {
            this.bodyMap = bodyMap;
            return this;
        }

        public RestTemplateBuilder body(Object body) {
            this.body = body;
            return this;
        }


        public RestTemplateBuilder contentType(MediaType contentType) {
            this.contentType = contentType;
            return this;
        }

        public RestTemplateBuilder uploadFileParams(FileSystemResource fileSystemResource) {
            if (this.bodyMap == null) {
                this.bodyMap = new LinkedMultiValueMap<>();
            }
            bodyMap.add("file", fileSystemResource);

            if (this.contentType == null) {
                this.contentType = MediaType.MULTIPART_FORM_DATA;
            }

            return this;
        }

        /**
         * 不会生成临时文件,节省了内存.代码实现也不复杂,推荐使用. .
         */
        public RestTemplateBuilder uploadFileParams(MultipartFile multipartFile) throws IOException {
            CommonInputStreamResource commonInputStreamResource = new CommonInputStreamResource(multipartFile.getInputStream(), multipartFile.getSize(), multipartFile.getOriginalFilename());

            if (this.bodyMap == null) {
                this.bodyMap = new LinkedMultiValueMap<>();
            }

            bodyMap.add("file", commonInputStreamResource);

            if (this.contentType == null) {
                this.contentType = MediaType.MULTIPART_FORM_DATA;
            }

            return this;
        }

        public <T> RestTemplateBuild<T> build(Class<T> tClass) {
            return new RestTemplateBuild<>(url, method, headers, params, bodyMap, body, contentType, tClass, null);
        }

        public <T> RestTemplateBuild<T> build(ParameterizedTypeReference<T> parameterizedTypeReference) {
            return new RestTemplateBuild<>(url, method, headers, params, bodyMap, body, contentType, null, parameterizedTypeReference);
        }
    }

    public static Map<String, Object> objTMap(Object object) {
        return Arrays.stream(BeanUtils.getPropertyDescriptors(object.getClass()))
                .filter(pd -> !"class".equals(pd.getName()))
                .collect(HashMap::new,
                        (map, pd) -> map.put(pd.getName(), ReflectionUtils.invokeMethod(pd.getReadMethod(), object)),
                        HashMap::putAll);
    }

    private static class CommonInputStreamResource extends InputStreamResource {

        private long length;
        private String fileName;

        public CommonInputStreamResource(InputStream inputStream, long length, String fileName) {
            super(inputStream);
            this.length = length;
            this.fileName = fileName;
        }

        /**
         * 覆写父类方法
         * 如果不重写这个方法,并且文件有一定大小,那么服务端会出现异常
         * {@code The multi-part request contained parameter data (excluding uploaded files) that exceeded}
         */
        @Override
        public String getFilename() {
            return fileName;
        }

        /**
         * 覆写父类 contentLength 方法
         * 因为 {@link org.springframework.core.io.AbstractResource#contentLength()}方法会重新读取一遍文件,
         * 而上传文件时,restTemplate 会通过这个方法获取大小。然后当真正需要读取内容的时候,发现已经读完,会报如下错误。
         * <code>
         * java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
         * at org.springframework.core.io.InputStreamResource.getInputStream(InputStreamResource.java:96)
         * </code>
         * <p>
         * ref:com.amazonaws.services.s3.model.S3ObjectInputStream#available()
         */
        @Override
        public long contentLength() {
            long estimate = length;
            return estimate == 0 ? 1 : estimate;
        }

        public void setLength(long length) {
            this.length = length;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
    }
}

RestTemplatService 主要的封装对象


import com.cloud.cli.resttemplate.build.RestTemplateBuild;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;

/**
 * @author: Lsx
 * @create: 2022-01-07 19:01
 * @description: RestTemplateUtil
 **/
@Service
public record RestTemplateService(RestTemplate restTemplate) {

    public RestTemplate getRestTemplate() {
        return restTemplate;
    }

    public <T> T exchange(RestTemplateBuild<T> build) {
        Class<T> responseBody = build.getResponseBody();
        if (responseBody != null) {
            return restTemplate.exchange(
                    build.getUrl(),
                    build.getMethod(),
                    headers(build),
                    responseBody,
                    build.getParams()
            ).getBody();
        }

        return restTemplate.exchange(
                build.getUrl(),
                build.getMethod(),
                headers(build),
                build.getParameterizedTypeReference(),
                build.getParams()
        ).getBody();
    }

    public void downloadBigFile(String url, HttpMethod method, String fileName) {
        RequestCallback requestCallback = request -> request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
        restTemplate.execute(url, method, requestCallback, clientHttpResponse -> {
            // Files.copy在clientHttpResponse的回调函数中,当获取到响应体的数据,就会向文件进行写入
            Files.copy(clientHttpResponse.getBody(), Paths.get(fileName));
            return null;
        });
    }

    public <T> void downloadFile(RestTemplateBuild<T> build, String fileName) throws IOException {
        byte[] body = restTemplate.exchange(build.getUrl(), build.getMethod(), headers(build), byte[].class, build.getParams()).getBody();
        Files.write(Paths.get(fileName), Objects.requireNonNull(body, "未获取到下载文件"));
    }

    private <T> HttpEntity<?> headers(RestTemplateBuild<T> build) {
        HttpHeaders header = new HttpHeaders();
        header.setAll(build.getHeaders());

        if (build.getContentType() != null) {
            header.setContentType(build.getContentType());
        }

        Object request = null != build.getBody() ? build.getBody() : build.getBodyMap();

        return new HttpEntity<>(request, header);
    }
}

调用方式

@Resource 
private RestTemplateService restTemplateService;

// 构建 build

// 调用
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值