From 5dc5205d9cab502e322f44dfbc55f582a8f8f655 Mon Sep 17 00:00:00 2001 From: "felord.cn" Date: Sat, 28 Nov 2020 21:21:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89messageConverter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wechat/v3/UploadHttpMessageConverter.java | 248 ++++++++++++++++++ .../payment/wechat/v3/WechatPayClient.java | 20 +- 2 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/UploadHttpMessageConverter.java diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/UploadHttpMessageConverter.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/UploadHttpMessageConverter.java new file mode 100644 index 0000000..9a535dc --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/UploadHttpMessageConverter.java @@ -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> 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 map, @Nullable MediaType contentType, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + + if (!isMultipart(map, contentType)) { + writeForm((MultiValueMap) map, contentType, outputMessage); + } else { + writeMultipart((MultiValueMap) map, outputMessage); + } + } + + private boolean isMultipart(MultiValueMap 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 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 parts, HttpOutputMessage outputMessage) + throws IOException { + + + Map 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 parts, byte[] boundary) throws IOException { + for (Map.Entry> 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) 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> 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); + } + } + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayClient.java b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayClient.java index 86c4b2e..175b46a 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayClient.java +++ b/payment-spring-boot-autoconfigure/src/main/java/com/enongm/dianji/payment/wechat/v3/WechatPayClient.java @@ -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> messageConverters = restTemplate.getMessageConverters(); + // upload + messageConverters.add(new UploadHttpMessageConverter()); + restTemplate.setMessageConverters(messageConverters); + this.restOperations = restTemplate; + } /** * 构造私钥签名.