Browse Source

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

master
Binary Wang 7 years ago
parent
commit
cbf18e8ca4
7 changed files with 254 additions and 74 deletions
  1. +7
    -0
      pom.xml
  2. +5
    -1
      weixin-java-pay/pom.xml
  3. +11
    -1
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java
  4. +93
    -11
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceAbstractImpl.java
  5. +78
    -30
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
  6. +48
    -27
      weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
  7. +12
    -4
      weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceAbstractImplTest.java

+ 7
- 0
pom.xml 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>


+ 5
- 1
weixin-java-pay/pom.xml 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>


+ 11
- 1
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/constant/WxPayConstants.java 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 {
/**


+ 93
- 11
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceAbstractImpl.java 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.handleBill(billType, responseContent);
}

private WxPayBillResult handleBill(String billType, String responseContent) {
if (!BillType.ALL.equals(billType)) {
return null;
}

return this.handleBillInformation(responseContent);
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 handleBillInformation(String responseContent) {
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();



+ 78
- 30
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java 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>
*
@@ -32,40 +34,34 @@ import java.nio.charset.StandardCharsets;
public class WxPayServiceApacheHttpImpl extends WxPayServiceAbstractImpl {

@Override
protected String post(String url, String requestStr, boolean useKey) throws WxPayException {
protected byte[] postForBytes(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();
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;
}

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);
} 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 = 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);
}

}

+ 48
- 27
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java View File

@@ -9,13 +9,14 @@ 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>
@@ -23,34 +24,25 @@ import java.nio.charset.StandardCharsets;
public class WxPayServiceJoddHttpImpl extends WxPayServiceAbstractImpl {

@Override
protected String post(String url, String requestStr, boolean useKey) throws WxPayException {
protected byte[] postForBytes(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);
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 = 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));


+ 12
- 4
weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceAbstractImplTest.java 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();



Loading…
Cancel
Save