Skip to content

【工作】RestTemplate实战

请求通用类封装

java
@Slf4j
@Component
public class RestTemplateHelper {

    private static final Pattern FORM_FEED_PATTERN = Pattern.compile("\f");
    private static final Pattern COLON_PATTERN = Pattern.compile(":");

    @Autowired
    private ObjectMapper objectMapper;

    @Resource(name = "domainFunctionRestTemplate")
    private RestTemplate restTemplate;

    /**
     * http请求
     *
     * @param uriString           请求路径
     * @param httpMethod          请求方法类型
     * @param headerMultiValueMap 请求头
     * @param paramMultiValueMap  请求参数
     * @param contentLength       HTTP消息实体的传输长度(请求头Content-Length)
     * @param contentType         数据类型(请求头Content-Type)
     * @param body                请求体
     * @param <T>                 请求体类型
     * @return 响应结果的封装类
     */
    public <T> ResponseEntity<byte[]> request(
            String uriString, HttpMethod httpMethod, MultiValueMap<String, String> headerMultiValueMap,
            MultiValueMap<String, String> paramMultiValueMap, Long contentLength, MediaType contentType, T body) {
        // 拼接请求参数到url上
        URI uri = buildUriWithQueryParam(uriString, paramMultiValueMap);

        RequestEntity.BodyBuilder requestEntityBuilder = RequestEntity.method(httpMethod, uri);

        // 将用户传的请求头进行透传
        if (!CollectionUtils.isEmpty(headerMultiValueMap)) {
            for (Map.Entry<String, List<String>> entry : headerMultiValueMap.entrySet()) {
                requestEntityBuilder.header(entry.getKey(), entry.getValue().toArray(new String[0]));
            }
        }

        RequestEntity<?> requestEntity = requestEntityBuilder.build();
        // 如果请求是POST、PUT、DELETE、PATCH支持请求体的,则将Content-Length、Content-Type、请求体进行透传
        if (HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod) ||
                HttpMethod.DELETE.equals(httpMethod) || HttpMethod.PATCH.equals(httpMethod)) {
            if (contentLength != null) {
                requestEntityBuilder.contentLength(contentLength);
            }
            if (contentType != null) {
                requestEntityBuilder.contentType(contentType);
            }
            if (body != null) {
                requestEntity = requestEntityBuilder.body(body);
            }
        }

        // 记录请求日志
        logRequestInfo(requestEntity);

        // http请求
        ResponseEntity<byte[]> responseEntity = restTemplate
                .exchange(requestEntity, new ParameterizedTypeReference<byte[]>() {
        });

        // 记录响应日志
        logResponseInfo(responseEntity);

        return responseEntity;
    }

    /**
     * 参考org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#getQueryString
     */
    private URI buildUriWithQueryParam(String uri, MultiValueMap<String, String> paramsMultiValueMap) {
        if (CollectionUtils.isEmpty(paramsMultiValueMap)) {
            return UriComponentsBuilder.fromUriString(uri).build().toUri();
        }

        // 创建字符串拼接器,字符串以&进行拼接,拼接后字符串前缀为?,无后缀
        StringJoiner paramPlaceholder = new StringJoiner("&", "?", "");
        Map<String, Object> paramsMap = new HashMap<>(paramsMultiValueMap.size());

        for (Map.Entry<String, List<String>> entry : paramsMultiValueMap.entrySet()) {
            String paramName = entry.getKey();
            int i = 0;
            for (String paramValue : entry.getValue()) {
                StringBuilder keyPair = new StringBuilder(paramName);
                if (!Strings.isNullOrEmpty(paramValue)) {
                    String key = paramName;
                    if (key.contains("\f")) {
                        key = (FORM_FEED_PATTERN.matcher(key).replaceAll("\f\f"));
                    }
                    if (key.contains(":")) {
                        key = COLON_PATTERN.matcher(key).replaceAll("\f");
                    }
                    key = key + i;
                    paramsMap.put(key, paramValue);
                    keyPair.append("={");
                    keyPair.append(key);
                    keyPair.append("}");
                }
                paramPlaceholder.add(keyPair);
                i++;
            }
        }
        return UriComponentsBuilder.fromUriString(uri + paramPlaceholder).build(paramsMap);
    }

    private void logRequestInfo(RequestEntity<?> requestEntity) {
        log.info("请求方法:{}", requestEntity.getMethod());
        log.info("请求url:{}", requestEntity.getUrl());
        log.info("请求头:{}", requestEntity.getHeaders());

        if (requestEntity.hasBody()) {
            byte[] body = new byte[]{};
            if (requestEntity.getBody() instanceof byte[]) {
                body = (byte[]) requestEntity.getBody();
            } else {
                try {
                    body = objectMapper.writeValueAsBytes(requestEntity.getBody());
                } catch (JsonProcessingException e) {
                    log.error("数据转换异常: {}", requestEntity.getBody(), e);
                }
            }
            log.info("请求体:{}", new String(body, StandardCharsets.UTF_8));
        }
    }

    private void logResponseInfo(ResponseEntity<byte[]> responseEntity) {
        log.info("响应码:{}", responseEntity.getStatusCodeValue());
        log.info("响应头:{}", responseEntity.getHeaders());
        if (responseEntity.getBody() != null) {
            log.info("响应体:{}", new String(responseEntity.getBody(), StandardCharsets.UTF_8));
        }
    }

}
java
ResponseEntity<byte[]> responseEntity = restTemplateHelper.request(
        请求路径, 请求方法, 请求头, 请求参数, 
        HTTP消息实体的传输长度, 数据类型, parseMultipartRequest(request)
);

文件上传

从请求头获取文件数据

java
private MultiValueMap<String, Object> parseMultipartRequest(HttpServletRequest request) {
    MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
        if (request instanceof MultipartRequest) {
            MultipartRequest multipartRequest = (MultipartRequest) request;
            for (Map.Entry<String, List<MultipartFile>> entry : multipartRequest.getMultiFileMap().entrySet()) {
                for (MultipartFile file : entry.getValue()) {
                    multiValueMap.add(entry.getKey(), file.getResource());
                }
            }
        }
    return CollectionUtils.isEmpty(multiValueMap) ? null : multiValueMap;
}

文件下载获取数据

响应数据类型使用字节数组进行接收byte[]
详情见:https://stackoverflow.com/questions/70632754/download-file-of-content-type-octet-stream-using-resttemplate


自定义异常处理

处理HTTP状态码为400、500等错误码时,如何获取到其响应结果内容

java
/**
 * 设置自定义异常处理器,RestTemplate默认对于4**或5**的状态码会认为异常
 */
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    // 自定义ResponseErrorHandler
    ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {
        @Override
        public boolean hasError(@NonNull ClientHttpResponse clientHttpResponse) {
            // 返回false,没有错误,不抛异常
            return false;
        }

        @Override
        public void handleError(@NonNull ClientHttpResponse clientHttpResponse) {
            // hasError方法返回false,即没有异常,所以无需处理
        }
    };

    return builder.errorHandler(responseErrorHandler).build();
}