自定义messageConverter

This commit is contained in:
felord.cn
2020-11-28 21:21:10 +08:00
parent 5e1ea67bf5
commit 5dc5205d9c
2 changed files with 263 additions and 5 deletions

View File

@@ -0,0 +1,248 @@
package com.enongm.dianji.payment.wechat.v3;
import org.springframework.http.*;
import org.springframework.http.converter.*;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* The type Upload http message converter.
*/
final class UploadHttpMessageConverter extends FormHttpMessageConverter {
private static final String BOUNDARY = "boundary";
private final List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
/**
* Instantiates a new Upload http message converter.
*/
public UploadHttpMessageConverter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
applyDefaultCharset();
}
/**
* Apply the configured charset as a default to registered part converters.
*/
private void applyDefaultCharset() {
for (HttpMessageConverter<?> candidate : this.partConverters) {
if (candidate instanceof AbstractHttpMessageConverter) {
AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
// Only override default charset if the converter operates with a charset to begin with...
if (converter.getDefaultCharset() != null) {
converter.setDefaultCharset(DEFAULT_CHARSET);
}
}
}
}
@Override
@SuppressWarnings("unchecked")
public void write(MultiValueMap<String, ?> map, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (!isMultipart(map, contentType)) {
writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);
} else {
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
}
}
private boolean isMultipart(MultiValueMap<String, ?> map, @Nullable MediaType contentType) {
if (contentType != null) {
return MediaType.MULTIPART_FORM_DATA.includes(contentType);
}
for (List<?> values : map.values()) {
for (Object value : values) {
if (value != null && !(value instanceof String)) {
return true;
}
}
}
return false;
}
private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException {
contentType = getMediaType(contentType);
outputMessage.getHeaders().setContentType(contentType);
Charset charset = contentType.getCharset();
Assert.notNull(charset, "No charset"); // should never occur
final byte[] bytes = serializeForm(formData, charset).getBytes(charset);
outputMessage.getHeaders().setContentLength(bytes.length);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(bytes, outputStream));
} else {
StreamUtils.copy(bytes, outputMessage.getBody());
}
}
private MediaType getMediaType(@Nullable MediaType mediaType) {
if (mediaType == null) {
return new MediaType(MediaType.APPLICATION_FORM_URLENCODED, DEFAULT_CHARSET);
} else if (mediaType.getCharset() == null) {
return new MediaType(mediaType, DEFAULT_CHARSET);
} else {
return mediaType;
}
}
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
throws IOException {
Map<String, String> parameters = new LinkedHashMap<>(1);
parameters.put(BOUNDARY, BOUNDARY);
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders();
headers.setContentType(contentType);
byte[] boundaryBytes = BOUNDARY.getBytes();
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> {
writeParts(outputStream, parts, boundaryBytes);
writeEnd(outputStream, boundaryBytes);
});
} else {
writeParts(outputMessage.getBody(), parts, boundaryBytes);
writeEnd(outputMessage.getBody(), boundaryBytes);
}
}
private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException {
for (Map.Entry<String, List<Object>> entry : parts.entrySet()) {
String name = entry.getKey();
for (Object part : entry.getValue()) {
if (part != null) {
writeBoundary(os, boundary);
writePart(name, getHttpEntity(part), os);
writeNewLine(os);
}
}
}
}
@SuppressWarnings("unchecked")
private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
Object partBody = partEntity.getBody();
if (partBody == null) {
throw new IllegalStateException("Empty body for part '" + name + "': " + partEntity);
}
Class<?> partType = partBody.getClass();
HttpHeaders partHeaders = partEntity.getHeaders();
MediaType partContentType = partHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : this.partConverters) {
if (messageConverter.canWrite(partType, partContentType)) {
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os, DEFAULT_CHARSET);
multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
if (!partHeaders.isEmpty()) {
multipartMessage.getHeaders().putAll(partHeaders);
}
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
return;
}
}
throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
"found for request type [" + partType.getName() + "]");
}
private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
writeNewLine(os);
}
private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
os.write('-');
os.write('-');
writeNewLine(os);
}
private static void writeNewLine(OutputStream os) throws IOException {
os.write('\r');
os.write('\n');
}
private static class MultipartHttpOutputMessage implements HttpOutputMessage {
private final OutputStream outputStream;
private final Charset charset;
private final HttpHeaders headers = new HttpHeaders();
private boolean headersWritten = false;
/**
* Instantiates a new Multipart http output message.
*
* @param outputStream the output stream
* @param charset the charset
*/
public MultipartHttpOutputMessage(OutputStream outputStream, Charset charset) {
this.outputStream = outputStream;
this.charset = charset;
}
@Override
public HttpHeaders getHeaders() {
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
public OutputStream getBody() throws IOException {
writeHeaders();
return this.outputStream;
}
private void writeHeaders() throws IOException {
if (!this.headersWritten) {
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
byte[] headerName = getBytes(entry.getKey());
for (String headerValueString : entry.getValue()) {
byte[] headerValue = getBytes(headerValueString);
this.outputStream.write(headerName);
this.outputStream.write(':');
this.outputStream.write(' ');
this.outputStream.write(headerValue);
writeNewLine(this.outputStream);
}
}
writeNewLine(this.outputStream);
this.headersWritten = true;
}
}
private byte[] getBytes(String name) {
return name.getBytes(this.charset);
}
}
}

View File

@@ -8,6 +8,7 @@ import com.enongm.dianji.payment.wechat.v3.model.ResponseSignVerifyParams;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
import org.springframework.http.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestOperations;
@@ -16,6 +17,7 @@ import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -62,7 +64,7 @@ public class WechatPayClient {
* The V 3 pay type.
*/
private final WechatPayV3Type wechatPayV3Type;
private final RestOperations restOperations;
private RestOperations restOperations;
private final SignatureProvider signatureProvider;
private final M model;
@@ -89,10 +91,8 @@ public class WechatPayClient {
this.wechatPayV3Type = wechatPayV3Type;
this.model = model;
this.signatureProvider = signatureProvider;
RestTemplate restTemplate = new RestTemplate();
DefaultResponseErrorHandler errorHandler = new WechatPayResponseErrorHandler();
restTemplate.setErrorHandler(errorHandler);
this.restOperations = restTemplate;
applyDefaultRestTemplate();
}
/**
@@ -128,6 +128,16 @@ public class WechatPayClient {
this.doExecute(this.header(wechatRequestEntity));
}
private void applyDefaultRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
DefaultResponseErrorHandler errorHandler = new WechatPayResponseErrorHandler();
restTemplate.setErrorHandler(errorHandler);
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
// upload
messageConverters.add(new UploadHttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
this.restOperations = restTemplate;
}
/**
* 构造私钥签名.