Skip to content

Commit cbf18e8

Browse files
committed
binarywang#321 微信支付下载对账单接口增加对GZIP格式的支持
1 parent 2e85dfd commit cbf18e8

7 files changed

Lines changed: 254 additions & 74 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,13 @@
225225
<version>${jetty.version}</version>
226226
<scope>test</scope>
227227
</dependency>
228+
<dependency>
229+
<groupId>org.assertj</groupId>
230+
<artifactId>assertj-guava</artifactId>
231+
<version>3.0.0</version>
232+
<scope>test</scope>
233+
</dependency>
234+
228235
<dependency>
229236
<groupId>redis.clients</groupId>
230237
<artifactId>jedis</artifactId>

weixin-java-pay/pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
<dependency>
2828
<groupId>org.jodd</groupId>
2929
<artifactId>jodd-http</artifactId>
30-
<scope>provided</scope>
3130
</dependency>
3231

3332
<dependency>
@@ -45,6 +44,11 @@
4544
<artifactId>testng</artifactId>
4645
<scope>test</scope>
4746
</dependency>
47+
<dependency>
48+
<groupId>org.assertj</groupId>
49+
<artifactId>assertj-guava</artifactId>
50+
<scope>test</scope>
51+
</dependency>
4852
<dependency>
4953
<groupId>com.google.inject</groupId>
5054
<artifactId>guice</artifactId>

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,17 @@ public static class CheckNameOption {
3636
}
3737

3838
/**
39-
* 订单类型
39+
* 压缩账单的类型
40+
*/
41+
public static class TarType {
42+
/**
43+
* 固定值:GZIP,返回格式为.gzip的压缩包账单
44+
*/
45+
public static final String GZIP = "GZIP";
46+
}
47+
48+
/**
49+
* 账单类型
4050
*/
4151
public static class BillType {
4252
/**

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceAbstractImpl.java

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,47 @@
1111
import com.github.binarywang.wxpay.bean.request.*;
1212
import com.github.binarywang.wxpay.bean.result.*;
1313
import com.github.binarywang.wxpay.config.WxPayConfig;
14+
import com.github.binarywang.wxpay.constant.WxPayConstants;
1415
import com.github.binarywang.wxpay.constant.WxPayConstants.BillType;
1516
import com.github.binarywang.wxpay.constant.WxPayConstants.SignType;
1617
import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType;
1718
import com.github.binarywang.wxpay.exception.WxPayException;
1819
import com.github.binarywang.wxpay.service.WxPayService;
1920
import com.github.binarywang.wxpay.util.SignUtils;
21+
import com.google.common.base.Joiner;
2022
import com.google.common.collect.Maps;
23+
import jodd.io.ZipUtil;
24+
import jodd.util.Base64;
2125
import org.apache.commons.lang3.StringUtils;
26+
import org.apache.http.auth.AuthScope;
27+
import org.apache.http.auth.UsernamePasswordCredentials;
28+
import org.apache.http.client.CredentialsProvider;
29+
import org.apache.http.client.config.RequestConfig;
30+
import org.apache.http.client.methods.CloseableHttpResponse;
31+
import org.apache.http.client.methods.HttpPost;
32+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
33+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
34+
import org.apache.http.entity.StringEntity;
35+
import org.apache.http.impl.client.BasicCredentialsProvider;
36+
import org.apache.http.impl.client.CloseableHttpClient;
37+
import org.apache.http.impl.client.HttpClientBuilder;
38+
import org.apache.http.impl.client.HttpClients;
39+
import org.apache.http.util.EntityUtils;
2240
import org.slf4j.Logger;
2341
import org.slf4j.LoggerFactory;
2442

43+
import javax.net.ssl.SSLContext;
2544
import java.io.File;
45+
import java.io.IOException;
46+
import java.nio.charset.StandardCharsets;
47+
import java.nio.file.Files;
48+
import java.nio.file.Path;
49+
import java.nio.file.Paths;
2650
import java.util.*;
51+
import java.util.zip.ZipException;
2752

2853
import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT;
54+
import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType;
2955

3056
/**
3157
* <pre>
@@ -61,7 +87,17 @@ private String getPayBaseUrl() {
6187
}
6288

6389
/**
64-
* 发送post请求
90+
* 发送post请求,得到响应字节数组
91+
*
92+
* @param url 请求地址
93+
* @param requestStr 请求信息
94+
* @param useKey 是否使用证书
95+
* @return 返回请求结果字节数组
96+
*/
97+
protected abstract byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException;
98+
99+
/**
100+
* 发送post请求,得到响应字符串
65101
*
66102
* @param url 请求地址
67103
* @param requestStr 请求信息
@@ -410,6 +446,10 @@ public void report(WxPayReportRequest request) throws WxPayException {
410446

411447
@Override
412448
public WxPayBillResult downloadBill(String billDate, String billType, String tarType, String deviceInfo) throws WxPayException {
449+
if (!BillType.ALL.equals(billType)) {
450+
throw new WxPayException("目前仅支持ALL类型的对账单下载");
451+
}
452+
413453
WxPayDownloadBillRequest request = new WxPayDownloadBillRequest();
414454
request.setBillType(billType);
415455
request.setBillDate(billDate);
@@ -419,15 +459,52 @@ public WxPayBillResult downloadBill(String billDate, String billType, String tar
419459
request.checkAndSign(this.getConfig(), false);
420460

421461
String url = this.getPayBaseUrl() + "/pay/downloadbill";
422-
String responseContent = this.post(url, request.toXML(), false);
423-
if (responseContent.startsWith("<")) {
424-
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class));
462+
463+
String responseContent;
464+
if (TarType.GZIP.equals(tarType)) {
465+
responseContent = this.handleGzipBill(url, request.toXML());
466+
} else {
467+
responseContent = this.post(url, request.toXML(), false);
468+
if (responseContent.startsWith("<")) {
469+
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class));
470+
}
471+
}
472+
473+
return this.handleBill(billType, responseContent);
474+
}
475+
476+
private WxPayBillResult handleBill(String billType, String responseContent) {
477+
if (!BillType.ALL.equals(billType)) {
478+
return null;
425479
}
426480

427-
return this.handleBillInformation(responseContent);
481+
return this.handleAllBill(responseContent);
482+
}
483+
484+
private String handleGzipBill(String url, String requestStr) throws WxPayException {
485+
try {
486+
byte[] responseBytes = this.postForBytes(url, requestStr, false);
487+
Path tempDirectory = Files.createTempDirectory("bill");
488+
Path path = Paths.get(tempDirectory.toString(), System.currentTimeMillis() + ".gzip");
489+
Files.write(path, responseBytes);
490+
try {
491+
List<String> allLines = Files.readAllLines(ZipUtil.ungzip(path.toFile()).toPath(), StandardCharsets.UTF_8);
492+
return Joiner.on("\n").join(allLines);
493+
} catch (ZipException e) {
494+
if (e.getMessage().contains("Not in GZIP format")) {
495+
throw WxPayException.from(WxPayBaseResult.fromXML(new String(responseBytes, StandardCharsets.UTF_8),
496+
WxPayCommonResult.class));
497+
} else {
498+
this.log.error("解压zip文件出错", e);
499+
}
500+
}
501+
} catch (IOException e) {
502+
e.printStackTrace();
503+
}
504+
return null;
428505
}
429506

430-
private WxPayBillResult handleBillInformation(String responseContent) {
507+
private WxPayBillResult handleAllBill(String responseContent) {
431508
WxPayBillResult wxPayBillResult = new WxPayBillResult();
432509

433510
String listStr = "";
@@ -444,11 +521,16 @@ private WxPayBillResult handleBillInformation(String responseContent) {
444521
* 参考以上格式进行取值
445522
*/
446523
List<WxPayBillBaseResult> wxPayBillBaseResultLst = new LinkedList<>();
447-
String newStr = listStr.replaceAll(",", " "); // 去空格
448-
String[] tempStr = newStr.split("`"); // 数据分组
449-
String[] t = tempStr[0].split(" ");// 分组标题
450-
int j = tempStr.length / t.length; // 计算循环次数
451-
int k = 1; // 纪录数组下标
524+
// 去空格
525+
String newStr = listStr.replaceAll(",", " ");
526+
// 数据分组
527+
String[] tempStr = newStr.split("`");
528+
// 分组标题
529+
String[] t = tempStr[0].split(" ");
530+
// 计算循环次数
531+
int j = tempStr.length / t.length;
532+
// 纪录数组下标
533+
int k = 1;
452534
for (int i = 0; i < j; i++) {
453535
WxPayBillBaseResult wxPayBillBaseResult = new WxPayBillBaseResult();
454536

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.binarywang.wxpay.bean.WxPayApiData;
44
import com.github.binarywang.wxpay.exception.WxPayException;
5+
import jodd.util.Base64;
56
import org.apache.commons.lang3.StringUtils;
67
import org.apache.http.auth.AuthScope;
78
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -19,11 +20,12 @@
1920
import org.apache.http.util.EntityUtils;
2021

2122
import javax.net.ssl.SSLContext;
23+
import java.io.UnsupportedEncodingException;
2224
import java.nio.charset.StandardCharsets;
2325

2426
/**
2527
* <pre>
26-
* 微信支付请求实现类,apache httpclient实现
28+
* 微信支付请求实现类,apache httpclient实现.
2729
* Created by Binary Wang on 2016/7/28.
2830
* </pre>
2931
*
@@ -32,40 +34,34 @@
3234
public class WxPayServiceApacheHttpImpl extends WxPayServiceAbstractImpl {
3335

3436
@Override
35-
protected String post(String url, String requestStr, boolean useKey) throws WxPayException {
37+
protected byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
3638
try {
37-
HttpClientBuilder httpClientBuilder = HttpClients.custom();
38-
if (useKey) {
39-
SSLContext sslContext = this.getConfig().getSslContext();
40-
if (null == sslContext) {
41-
sslContext = this.getConfig().initSSLContext();
39+
HttpClientBuilder httpClientBuilder = createHttpClientBuilder(useKey);
40+
HttpPost httpPost = this.createHttpPost(url, requestStr);
41+
try (CloseableHttpClient httpclient = httpClientBuilder.build()) {
42+
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
43+
final byte[] bytes = EntityUtils.toByteArray(response.getEntity());
44+
final String responseData = Base64.encodeToString(bytes);
45+
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseData);
46+
wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
47+
return bytes;
4248
}
43-
44-
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
45-
new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
46-
httpClientBuilder.setSSLSocketFactory(sslsf);
47-
}
48-
49-
HttpPost httpPost = new HttpPost(url);
50-
51-
httpPost.setConfig(RequestConfig.custom()
52-
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
53-
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
54-
.setSocketTimeout(this.getConfig().getHttpTimeout())
55-
.build());
56-
57-
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost())
58-
&& this.getConfig().getHttpProxyPort() > 0) {
59-
// 使用代理服务器 需要用户认证的代理服务器
60-
CredentialsProvider provider = new BasicCredentialsProvider();
61-
provider.setCredentials(
62-
new AuthScope(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()),
63-
new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()));
64-
httpClientBuilder.setDefaultCredentialsProvider(provider);
49+
} finally {
50+
httpPost.releaseConnection();
6551
}
52+
} catch (Exception e) {
53+
this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
54+
wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
55+
throw new WxPayException(e.getMessage(), e);
56+
}
57+
}
6658

59+
@Override
60+
protected String post(String url, String requestStr, boolean useKey) throws WxPayException {
61+
try {
62+
HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey);
63+
HttpPost httpPost = this.createHttpPost(url, requestStr);
6764
try (CloseableHttpClient httpclient = httpClientBuilder.build()) {
68-
httpPost.setEntity(new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)));
6965
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
7066
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
7167
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
@@ -82,4 +78,56 @@ protected String post(String url, String requestStr, boolean useKey) throws WxPa
8278
}
8379
}
8480

81+
private StringEntity createEntry(String requestStr) {
82+
try {
83+
return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
84+
} catch (UnsupportedEncodingException e) {
85+
//cannot happen
86+
this.log.error(e.getMessage(),e);
87+
return null;
88+
}
89+
}
90+
91+
private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException {
92+
HttpClientBuilder httpClientBuilder = HttpClients.custom();
93+
if (useKey) {
94+
this.setKey(httpClientBuilder);
95+
}
96+
97+
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost())
98+
&& this.getConfig().getHttpProxyPort() > 0) {
99+
// 使用代理服务器 需要用户认证的代理服务器
100+
CredentialsProvider provider = new BasicCredentialsProvider();
101+
provider.setCredentials(
102+
new AuthScope(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()),
103+
new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()));
104+
httpClientBuilder.setDefaultCredentialsProvider(provider);
105+
}
106+
return httpClientBuilder;
107+
}
108+
109+
private HttpPost createHttpPost(String url, String requestStr) {
110+
HttpPost httpPost = new HttpPost(url);
111+
httpPost.setEntity(this.createEntry(requestStr));
112+
113+
httpPost.setConfig(RequestConfig.custom()
114+
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
115+
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
116+
.setSocketTimeout(this.getConfig().getHttpTimeout())
117+
.build());
118+
119+
return httpPost;
120+
}
121+
122+
private void setKey(HttpClientBuilder httpClientBuilder) throws WxPayException {
123+
SSLContext sslContext = this.getConfig().getSslContext();
124+
if (null == sslContext) {
125+
sslContext = this.getConfig().initSSLContext();
126+
}
127+
128+
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
129+
new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
130+
httpClientBuilder.setSSLSocketFactory(sslsf);
131+
}
132+
85133
}

0 commit comments

Comments
 (0)