#321 微信支付下载对账单接口增加对GZIP格式的支持

This commit is contained in:
Binary Wang 2017-12-16 17:07:16 +08:00
parent 2e85dfdf7a
commit cbf18e8ca4
7 changed files with 256 additions and 76 deletions

View File

@ -225,6 +225,13 @@
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-guava</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>

View File

@ -27,7 +27,6 @@
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-http</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@ -45,6 +44,11 @@
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-guava</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>

View File

@ -36,7 +36,17 @@ public class WxPayConstants {
}
/**
* 订单类型
* 压缩账单的类型
*/
public static class TarType {
/**
* 固定值GZIP返回格式为.gzip的压缩包账单
*/
public static final String GZIP = "GZIP";
}
/**
* 账单类型
*/
public static class BillType {
/**

View File

@ -11,21 +11,47 @@ import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.constant.WxPayConstants.BillType;
import com.github.binarywang.wxpay.constant.WxPayConstants.SignType;
import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import jodd.io.ZipUtil;
import jodd.util.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipException;
import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT;
import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType;
/**
* <pre>
@ -61,7 +87,17 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
}
/**
* 发送post请求
* 发送post请求得到响应字节数组
*
* @param url 请求地址
* @param requestStr 请求信息
* @param useKey 是否使用证书
* @return 返回请求结果字节数组
*/
protected abstract byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException;
/**
* 发送post请求得到响应字符串
*
* @param url 请求地址
* @param requestStr 请求信息
@ -410,6 +446,10 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
@Override
public WxPayBillResult downloadBill(String billDate, String billType, String tarType, String deviceInfo) throws WxPayException {
if (!BillType.ALL.equals(billType)) {
throw new WxPayException("目前仅支持ALL类型的对账单下载");
}
WxPayDownloadBillRequest request = new WxPayDownloadBillRequest();
request.setBillType(billType);
request.setBillDate(billDate);
@ -419,15 +459,52 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
request.checkAndSign(this.getConfig(), false);
String url = this.getPayBaseUrl() + "/pay/downloadbill";
String responseContent = this.post(url, request.toXML(), false);
if (responseContent.startsWith("<")) {
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class));
String responseContent;
if (TarType.GZIP.equals(tarType)) {
responseContent = this.handleGzipBill(url, request.toXML());
} else {
responseContent = this.post(url, request.toXML(), false);
if (responseContent.startsWith("<")) {
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class));
}
}
return this.handleBillInformation(responseContent);
return this.handleBill(billType, responseContent);
}
private WxPayBillResult handleBillInformation(String responseContent) {
private WxPayBillResult handleBill(String billType, String responseContent) {
if (!BillType.ALL.equals(billType)) {
return null;
}
return this.handleAllBill(responseContent);
}
private String handleGzipBill(String url, String requestStr) throws WxPayException {
try {
byte[] responseBytes = this.postForBytes(url, requestStr, false);
Path tempDirectory = Files.createTempDirectory("bill");
Path path = Paths.get(tempDirectory.toString(), System.currentTimeMillis() + ".gzip");
Files.write(path, responseBytes);
try {
List<String> allLines = Files.readAllLines(ZipUtil.ungzip(path.toFile()).toPath(), StandardCharsets.UTF_8);
return Joiner.on("\n").join(allLines);
} catch (ZipException e) {
if (e.getMessage().contains("Not in GZIP format")) {
throw WxPayException.from(WxPayBaseResult.fromXML(new String(responseBytes, StandardCharsets.UTF_8),
WxPayCommonResult.class));
} else {
this.log.error("解压zip文件出错", e);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private WxPayBillResult handleAllBill(String responseContent) {
WxPayBillResult wxPayBillResult = new WxPayBillResult();
String listStr = "";
@ -444,11 +521,16 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
* 参考以上格式进行取值
*/
List<WxPayBillBaseResult> wxPayBillBaseResultLst = new LinkedList<>();
String newStr = listStr.replaceAll(",", " "); // 去空格
String[] tempStr = newStr.split("`"); // 数据分组
String[] t = tempStr[0].split(" ");// 分组标题
int j = tempStr.length / t.length; // 计算循环次数
int k = 1; // 纪录数组下标
// 去空格
String newStr = listStr.replaceAll(",", " ");
// 数据分组
String[] tempStr = newStr.split("`");
// 分组标题
String[] t = tempStr[0].split(" ");
// 计算循环次数
int j = tempStr.length / t.length;
// 纪录数组下标
int k = 1;
for (int i = 0; i < j; i++) {
WxPayBillBaseResult wxPayBillBaseResult = new WxPayBillBaseResult();

View File

@ -2,6 +2,7 @@ package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.WxPayApiData;
import com.github.binarywang.wxpay.exception.WxPayException;
import jodd.util.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
@ -19,11 +20,12 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
/**
* <pre>
* 微信支付请求实现类apache httpclient实现
* 微信支付请求实现类apache httpclient实现.
* Created by Binary Wang on 2016/7/28.
* </pre>
*
@ -31,41 +33,35 @@ import java.nio.charset.StandardCharsets;
*/
public class WxPayServiceApacheHttpImpl extends WxPayServiceAbstractImpl {
@Override
protected byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
try {
HttpClientBuilder httpClientBuilder = createHttpClientBuilder(useKey);
HttpPost httpPost = this.createHttpPost(url, requestStr);
try (CloseableHttpClient httpclient = httpClientBuilder.build()) {
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
final byte[] bytes = EntityUtils.toByteArray(response.getEntity());
final String responseData = Base64.encodeToString(bytes);
this.log.info("\n【请求地址】{}\n【请求数据】{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseData);
wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
return bytes;
}
} finally {
httpPost.releaseConnection();
}
} catch (Exception e) {
this.log.error("\n【请求地址】{}\n【请求数据】{}\n【异常信息】{}", url, requestStr, e.getMessage());
wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
throw new WxPayException(e.getMessage(), e);
}
}
@Override
protected String post(String url, String requestStr, boolean useKey) throws WxPayException {
try {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (useKey) {
SSLContext sslContext = this.getConfig().getSslContext();
if (null == sslContext) {
sslContext = this.getConfig().initSSLContext();
}
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
httpClientBuilder.setSSLSocketFactory(sslsf);
}
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(RequestConfig.custom()
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
.setSocketTimeout(this.getConfig().getHttpTimeout())
.build());
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost())
&& this.getConfig().getHttpProxyPort() > 0) {
// 使用代理服务器 需要用户认证的代理服务器
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(
new AuthScope(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()),
new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()));
httpClientBuilder.setDefaultCredentialsProvider(provider);
}
HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey);
HttpPost httpPost = this.createHttpPost(url, requestStr);
try (CloseableHttpClient httpclient = httpClientBuilder.build()) {
httpPost.setEntity(new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)));
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
this.log.info("\n【请求地址】{}\n【请求数据】{}\n【响应数据】{}", url, requestStr, responseString);
@ -82,4 +78,56 @@ public class WxPayServiceApacheHttpImpl extends WxPayServiceAbstractImpl {
}
}
private StringEntity createEntry(String requestStr) {
try {
return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
} catch (UnsupportedEncodingException e) {
//cannot happen
this.log.error(e.getMessage(),e);
return null;
}
}
private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (useKey) {
this.setKey(httpClientBuilder);
}
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost())
&& this.getConfig().getHttpProxyPort() > 0) {
// 使用代理服务器 需要用户认证的代理服务器
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(
new AuthScope(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()),
new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()));
httpClientBuilder.setDefaultCredentialsProvider(provider);
}
return httpClientBuilder;
}
private HttpPost createHttpPost(String url, String requestStr) {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(this.createEntry(requestStr));
httpPost.setConfig(RequestConfig.custom()
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
.setSocketTimeout(this.getConfig().getHttpTimeout())
.build());
return httpPost;
}
private void setKey(HttpClientBuilder httpClientBuilder) throws WxPayException {
SSLContext sslContext = this.getConfig().getSslContext();
if (null == sslContext) {
sslContext = this.getConfig().initSSLContext();
}
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
httpClientBuilder.setSSLSocketFactory(sslsf);
}
}

View File

@ -9,48 +9,40 @@ import jodd.http.ProxyInfo;
import jodd.http.ProxyInfo.ProxyType;
import jodd.http.net.SSLSocketHttpConnectionProvider;
import jodd.http.net.SocketHttpConnectionProvider;
import jodd.util.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
/**
* 微信支付请求实现类jodd-http实现
* 微信支付请求实现类jodd-http实现.
* Created by Binary Wang on 2016/7/28.
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxPayServiceJoddHttpImpl extends WxPayServiceAbstractImpl {
@Override
protected byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
try {
HttpRequest request = this.buildHttpRequest(url, requestStr, useKey);
byte[] responseBytes = request.send().bodyBytes();
final String responseString = Base64.encodeToString(responseBytes);
this.log.info("\n【请求地址】{}\n【请求数据】{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseString);
wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
return responseBytes;
} catch (Exception e) {
this.log.error("\n【请求地址】{}\n【请求数据】{}\n【异常信息】{}", url, requestStr, e.getMessage());
wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
throw new WxPayException(e.getMessage(), e);
}
}
@Override
protected String post(String url, String requestStr, boolean useKey) throws WxPayException {
try {
HttpRequest request = HttpRequest
.post(url)
.timeout(this.getConfig().getHttpTimeout())
.connectionTimeout(this.getConfig().getHttpConnectionTimeout())
.bodyText(requestStr);
if (useKey) {
SSLContext sslContext = this.getConfig().getSslContext();
if (null == sslContext) {
sslContext = this.getConfig().initSSLContext();
}
final SSLSocketHttpConnectionProvider provider = new SSLSocketHttpConnectionProvider(sslContext);
request.withConnectionProvider(provider);
}
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) {
ProxyInfo httpProxy = new ProxyInfo(ProxyType.HTTP, this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort(),
this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword());
HttpConnectionProvider provider = request.connectionProvider();
if (null == provider) {
provider = new SocketHttpConnectionProvider();
}
provider.useProxy(httpProxy);
request.withConnectionProvider(provider);
}
HttpRequest request = this.buildHttpRequest(url, requestStr, useKey);
String responseString = this.getResponseString(request.send());
this.log.info("\n【请求地址】{}\n【请求数据】{}\n【响应数据】{}", url, requestStr, responseString);
@ -63,6 +55,35 @@ public class WxPayServiceJoddHttpImpl extends WxPayServiceAbstractImpl {
}
}
private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey) throws WxPayException {
HttpRequest request = HttpRequest
.post(url)
.timeout(this.getConfig().getHttpTimeout())
.connectionTimeout(this.getConfig().getHttpConnectionTimeout())
.bodyText(requestStr);
if (useKey) {
SSLContext sslContext = this.getConfig().getSslContext();
if (null == sslContext) {
sslContext = this.getConfig().initSSLContext();
}
final SSLSocketHttpConnectionProvider provider = new SSLSocketHttpConnectionProvider(sslContext);
request.withConnectionProvider(provider);
}
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) {
ProxyInfo httpProxy = new ProxyInfo(ProxyType.HTTP, this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort(),
this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword());
HttpConnectionProvider provider = request.connectionProvider();
if (null == provider) {
provider = new SocketHttpConnectionProvider();
}
provider.useProxy(httpProxy);
request.withConnectionProvider(provider);
}
return request;
}
private String getResponseString(HttpResponse response) throws WxPayException {
try {
this.log.debug("【微信服务器响应头信息】:\n{}", response.toString(false));

View File

@ -26,6 +26,8 @@ import java.nio.file.Path;
import java.util.Calendar;
import java.util.Date;
import static com.github.binarywang.wxpay.constant.WxPayConstants.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.*;
/**
@ -139,7 +141,13 @@ public class WxPayServiceAbstractImplTest {
@DataProvider
public Object[][] billingData() {
return new Object[][]{
// {"20170831", BillType.ALL, null, "deviceInfo"},
{"20170831", BillType.ALL, TarType.GZIP, "deviceInfo"},
{"20170831", BillType.RECHARGE_REFUND, TarType.GZIP, "deviceInfo"},
{"20170831", BillType.REFUND, TarType.GZIP, "deviceInfo"},
{"20170831", BillType.SUCCESS, TarType.GZIP, "deviceInfo"},
{"20170831", BillType.ALL, null, "deviceInfo"},
{"20170831", BillType.RECHARGE_REFUND, null, "deviceInfo"},
{"20170831", BillType.REFUND, null, "deviceInfo"},
{"20170831", BillType.SUCCESS, null, "deviceInfo"}
};
}
@ -148,11 +156,11 @@ public class WxPayServiceAbstractImplTest {
public void testDownloadBill(String billDate, String billType,
String tarType, String deviceInfo) throws Exception {
WxPayBillResult billResult = this.payService.downloadBill(billDate, billType, tarType, deviceInfo);
assertNotNull(billResult);
assertThat(billResult).isNotNull();
this.logger.info(billResult.toString());
}
@Test
@Test(expectedExceptions = WxPayException.class)
public void testDownloadBill_withNoParams() throws Exception {
//必填字段为空时抛出异常
this.payService.downloadBill("", "", "", null);
@ -247,7 +255,7 @@ public class WxPayServiceAbstractImplTest {
.openid("ojOQA0y9o-Eb6Aep7uVTdbkJqrP4")
.amount(1)
.spbillCreateIp("10.10.10.10")
.checkName(WxPayConstants.CheckNameOption.NO_CHECK)
.checkName(CheckNameOption.NO_CHECK)
.description("描述信息")
.build();