二次封装系列(持续更新)
【二次封装】之树级结构排序. 拿来即用. 根据 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
// 调用