Skip to content

Commit 5e326b0

Browse files
authored
feat: 图片上传 (#18)
* feat: 图片上传支持 BREAKING CHANGE: Credentials.getToken(request),参数改为使用HttpRequestWrapper refactor: SignatureExec.convertToRepeatableXXXEntity 使用BufferedHttpEntity * update version to 0.2.0
1 parent 9a96c73 commit 5e326b0

File tree

7 files changed

+193
-68
lines changed

7 files changed

+193
-68
lines changed

README.md

+42-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
## 项目状态
1010

11-
当前版本`0.1.6`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
11+
当前版本`0.2.0`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
1212

1313
## 环境要求
1414

15-
+ Java 1.8
15+
+ Java 1.8+
1616

1717
## 安装
1818

@@ -51,7 +51,7 @@ dependencies {
5151
<dependency>
5252
<groupId>com.github.wechatpay-apiv3</groupId>
5353
<artifactId>wechatpay-apache-httpclient</artifactId>
54-
<version>0.1.6</version>
54+
<version>0.2.0</version>
5555
</dependency>
5656
```
5757

@@ -154,6 +154,33 @@ try {
154154
}
155155
```
156156

157+
## 图片/视频上传
158+
159+
我们对上传的参数组装和签名逻辑进行了一定的封装,只需要以下几步:
160+
161+
1. 使用`WechatPayUploadHttpPost`构造一个上传的`HttpPost`,需设置待上传文件的文件名,SHA256摘要,文件的输入流。
162+
2. 通过`WechatPayHttpClientBuilder`得到的`HttpClient`发送请求。
163+
164+
示例请参考下列代码。
165+
166+
```java
167+
String filePath = "/your/home/hellokitty.png";
168+
URI uri = new URI("https://api.mch.weixin.qq.com/v3/merchant/media/upload");
169+
File file = new File(filePath);
170+
171+
try (FileInputStream ins1 = new FileInputStream(file)) {
172+
String sha256 = DigestUtils.sha256Hex(ins1);
173+
try (InputStream ins2 = new FileInputStream(file)) {
174+
HttpPost request = new WechatPayUploadHttpPost.Builder(uri)
175+
.withImage(file.getName(), sha256, ins2)
176+
.build();
177+
CloseableHttpResponse response1 = httpClient.execute(request);
178+
}
179+
}
180+
```
181+
182+
[AutoUpdateVerifierTest.uploadImageTest](/src/test/java/com/wechat/pay/contrib/apache/httpclient/AutoUpdateVerifierTest.java#L86)是一个更完整的示例。
183+
157184
## 常见问题
158185

159186
### 如何下载平台证书?
@@ -173,6 +200,18 @@ CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
173200

174201
请参考[AesUtil.Java](https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/blob/master/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/AesUtil.java)
175202

203+
### 我想使用以前的版本,要怎么办
204+
205+
可以在gradle中指定版本号。例如希望使用0.1.6版本,可以使用以下的方式。
206+
207+
```xml
208+
<dependency>
209+
<groupId>com.github.wechatpay-apiv3</groupId>
210+
<artifactId>wechatpay-apache-httpclient</artifactId>
211+
<version>0.1.6</version>
212+
</dependency>
213+
```
214+
176215
## 联系我们
177216

178217
如果你发现了**BUG**或者有任何疑问、建议,请通过issue进行反馈。

build.gradle

+3-16
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group 'com.github.wechatpay-apiv3'
7-
version '0.1.6'
7+
version '0.2.0'
88

99
sourceCompatibility = 1.8
1010
targetCompatibility = 1.8
@@ -22,6 +22,7 @@ ext {
2222

2323
dependencies {
2424
api "org.apache.httpcomponents:httpclient:$httpclient_version"
25+
implementation "org.apache.httpcomponents:httpmime:$httpclient_version"
2526
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
2627
implementation "org.slf4j:slf4j-api:$slf4j_version"
2728
testImplementation "org.slf4j:slf4j-simple:$slf4j_version"
@@ -39,27 +40,13 @@ jar {
3940

4041
task sourcesJar(type: Jar) {
4142
from sourceSets.main.allJava
42-
archiveClassifier = 'sources'
43+
archiveClassifier.set("sources")
4344
}
4445

4546
artifacts {
4647
archives sourcesJar
4748
}
4849

49-
install {
50-
repositories.mavenInstaller {
51-
pom.project {
52-
licenses {
53-
license {
54-
name 'The Apache Software License, Version 2.0'
55-
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
56-
distribution 'repo'
57-
}
58-
}
59-
}
60-
}
61-
}
62-
6350
wrapper {
6451
gradleVersion = "5.4.1"
6552
distributionType = Wrapper.DistributionType.ALL
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.wechat.pay.contrib.apache.httpclient;
22

33
import java.io.IOException;
4-
import org.apache.http.client.methods.HttpUriRequest;
4+
import org.apache.http.client.methods.HttpRequestWrapper;
55

66
public interface Credentials {
77

88
String getSchema();
99

10-
String getToken(HttpUriRequest request) throws IOException;
10+
String getToken(HttpRequestWrapper request) throws IOException;
1111
}
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
package com.wechat.pay.contrib.apache.httpclient;
22

3-
import java.io.IOException;
43
import org.apache.http.HttpEntity;
4+
import org.apache.http.HttpEntityEnclosingRequest;
55
import org.apache.http.HttpException;
66
import org.apache.http.StatusLine;
77
import org.apache.http.client.methods.CloseableHttpResponse;
8-
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
98
import org.apache.http.client.methods.HttpExecutionAware;
109
import org.apache.http.client.methods.HttpRequestWrapper;
11-
import org.apache.http.client.methods.HttpUriRequest;
12-
import org.apache.http.client.methods.RequestBuilder;
1310
import org.apache.http.client.protocol.HttpClientContext;
1411
import org.apache.http.conn.routing.HttpRoute;
15-
import org.apache.http.entity.ByteArrayEntity;
12+
import org.apache.http.entity.BufferedHttpEntity;
1613
import org.apache.http.impl.execchain.ClientExecChain;
17-
import org.apache.http.util.EntityUtils;
14+
15+
import java.io.IOException;
1816

1917
public class SignatureExec implements ClientExecChain {
2018
final ClientExecChain mainExec;
@@ -27,27 +25,19 @@ public class SignatureExec implements ClientExecChain {
2725
this.mainExec = mainExec;
2826
}
2927

30-
protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException {
31-
byte[] content = EntityUtils.toByteArray(entity);
32-
ByteArrayEntity newEntity = new ByteArrayEntity(content);
33-
newEntity.setContentEncoding(entity.getContentEncoding());
34-
newEntity.setContentType(entity.getContentType());
35-
36-
return newEntity;
37-
}
38-
39-
protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException {
28+
protected void convertToRepeatableResponseEntity(CloseableHttpResponse response)
29+
throws IOException {
4030
HttpEntity entity = response.getEntity();
41-
if (entity != null && !entity.isRepeatable()) {
42-
response.setEntity(newRepeatableEntity(entity));
31+
if (entity != null) {
32+
response.setEntity(new BufferedHttpEntity(entity));
4333
}
4434
}
4535

46-
protected void convertToRepeatableRequestEntity(HttpUriRequest request) throws IOException {
47-
if (request instanceof HttpEntityEnclosingRequestBase) {
48-
HttpEntity entity = ((HttpEntityEnclosingRequestBase) request).getEntity();
49-
if (entity != null && !entity.isRepeatable()) {
50-
((HttpEntityEnclosingRequestBase) request).setEntity(newRepeatableEntity(entity));
36+
protected void convertToRepeatableRequestEntity(HttpRequestWrapper request) throws IOException {
37+
if (request instanceof HttpEntityEnclosingRequest) {
38+
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
39+
if (entity != null) {
40+
((HttpEntityEnclosingRequest) request).setEntity(new BufferedHttpEntity(entity));
5141
}
5242
}
5343
}
@@ -64,15 +54,16 @@ public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request
6454

6555
private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request,
6656
HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException {
67-
HttpUriRequest newRequest = RequestBuilder.copy(request.getOriginal()).build();
68-
convertToRepeatableRequestEntity(newRequest);
57+
// 上传类不需要消耗两次故不做转换
58+
if (!(request.getOriginal() instanceof WechatPayUploadHttpPost)) {
59+
convertToRepeatableRequestEntity(request);
60+
}
6961
// 添加认证信息
70-
newRequest.addHeader("Authorization",
71-
credentials.getSchema() + " " + credentials.getToken(newRequest));
62+
request.addHeader("Authorization",
63+
credentials.getSchema() + " " + credentials.getToken(request));
7264

7365
// 执行
74-
CloseableHttpResponse response = mainExec.execute(
75-
route, HttpRequestWrapper.wrap(newRequest), context, execAware);
66+
CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);
7667

7768
// 对成功应答验签
7869
StatusLine statusLine = response.getStatusLine();
@@ -84,5 +75,4 @@ private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestW
8475
}
8576
return response;
8677
}
87-
8878
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.wechat.pay.contrib.apache.httpclient;
2+
3+
import java.io.InputStream;
4+
import java.net.URI;
5+
import java.net.URLConnection;
6+
import org.apache.http.client.methods.HttpPost;
7+
import org.apache.http.entity.ContentType;
8+
import org.apache.http.entity.mime.HttpMultipartMode;
9+
import org.apache.http.entity.mime.MultipartEntityBuilder;
10+
11+
public class WechatPayUploadHttpPost extends HttpPost {
12+
13+
private String meta;
14+
15+
private WechatPayUploadHttpPost(URI uri, String meta) {
16+
super(uri);
17+
18+
this.meta = meta;
19+
}
20+
21+
public String getMeta() {
22+
return meta;
23+
}
24+
25+
public static class Builder {
26+
27+
private String fileName;
28+
private String fileSha256;
29+
private InputStream fileInputStream;
30+
private org.apache.http.entity.ContentType fileContentType;
31+
private URI uri;
32+
33+
public Builder(URI uri) {
34+
this.uri = uri;
35+
}
36+
37+
public Builder withImage(String fileName, String fileSha256, InputStream inputStream) {
38+
this.fileName = fileName;
39+
this.fileSha256 = fileSha256;
40+
this.fileInputStream = inputStream;
41+
42+
String mimeType = URLConnection.guessContentTypeFromName(fileName);
43+
if (mimeType == null) {
44+
// guess this is a video uploading
45+
this.fileContentType = ContentType.APPLICATION_OCTET_STREAM;
46+
} else {
47+
this.fileContentType = ContentType.create(mimeType);
48+
}
49+
return this;
50+
}
51+
52+
public WechatPayUploadHttpPost build() {
53+
if (fileName == null || fileSha256 == null || fileInputStream == null) {
54+
throw new IllegalArgumentException("缺少待上传图片文件信息");
55+
}
56+
57+
if (uri == null) {
58+
throw new IllegalArgumentException("缺少上传图片接口URL");
59+
}
60+
61+
String meta = String.format("{\"filename\":\"%s\",\"sha256\":\"%s\"}", fileName, fileSha256);
62+
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost(uri, meta);
63+
64+
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
65+
entityBuilder.setMode(HttpMultipartMode.RFC6532)
66+
.addBinaryBody("file", fileInputStream, fileContentType, fileName)
67+
.addTextBody("meta", meta, org.apache.http.entity.ContentType.APPLICATION_JSON);
68+
69+
request.setEntity(entityBuilder.build());
70+
request.addHeader("Accept", org.apache.http.entity.ContentType.APPLICATION_JSON.toString());
71+
72+
return request;
73+
}
74+
}
75+
}

src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/WechatPay2Credentials.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.wechat.pay.contrib.apache.httpclient.auth;
22

33
import com.wechat.pay.contrib.apache.httpclient.Credentials;
4+
import com.wechat.pay.contrib.apache.httpclient.WechatPayUploadHttpPost;
45
import java.io.IOException;
56
import java.net.URI;
7+
import java.nio.charset.StandardCharsets;
68
import java.security.SecureRandom;
7-
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
8-
import org.apache.http.client.methods.HttpUriRequest;
9+
10+
import org.apache.http.HttpEntityEnclosingRequest;
11+
import org.apache.http.client.methods.HttpRequestWrapper;
912
import org.apache.http.util.EntityUtils;
1013
import org.slf4j.Logger;
1114
import org.slf4j.LoggerFactory;
@@ -46,14 +49,14 @@ public final String getSchema() {
4649
}
4750

4851
@Override
49-
public final String getToken(HttpUriRequest request) throws IOException {
52+
public final String getToken(HttpRequestWrapper request) throws IOException {
5053
String nonceStr = generateNonceStr();
5154
long timestamp = generateTimestamp();
5255

5356
String message = buildMessage(nonceStr, timestamp, request);
5457
log.debug("authorization message=[{}]", message);
5558

56-
Signer.SignatureResult signature = signer.sign(message.getBytes("utf-8"));
59+
Signer.SignatureResult signature = signer.sign(message.getBytes(StandardCharsets.UTF_8));
5760

5861
String token = "mchid=\"" + getMerchantId() + "\","
5962
+ "nonce_str=\"" + nonceStr + "\","
@@ -65,7 +68,7 @@ public final String getToken(HttpUriRequest request) throws IOException {
6568
return token;
6669
}
6770

68-
protected final String buildMessage(String nonce, long timestamp, HttpUriRequest request)
71+
protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request)
6972
throws IOException {
7073
URI uri = request.getURI();
7174
String canonicalUrl = uri.getRawPath();
@@ -75,8 +78,10 @@ protected final String buildMessage(String nonce, long timestamp, HttpUriRequest
7578

7679
String body = "";
7780
// PATCH,POST,PUT
78-
if (request instanceof HttpEntityEnclosingRequestBase) {
79-
body = EntityUtils.toString(((HttpEntityEnclosingRequestBase) request).getEntity());
81+
if (request.getOriginal() instanceof WechatPayUploadHttpPost) {
82+
body = ((WechatPayUploadHttpPost) request.getOriginal()).getMeta();
83+
} else if (request instanceof HttpEntityEnclosingRequest) {
84+
body = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity());
8085
}
8186

8287
return request.getRequestLine().getMethod() + "\n"

0 commit comments

Comments
 (0)