鉴于最近业务需求的新增,作为一个微信网上商城,微信支付等功能不能单单靠内网穿透和测试公众号进行调试,也没有一个相对类似的环境可以更好的测试这类功能,自己便着手搭建了测试用的一套服务(最主要的还是实用,搭建成功跟生产环境支付没有任何区别,测试更加理想 (●ˇ∀ˇ●)),妈妈再也不用担心我怎么线上测试支付啦。
首先列举一下测试环境需要什么东西:
**必须的东西: **
为了重用之前公众号的域名,之前的想法就是页面:
www.aaa.com/view/index.html 转化成
www.aaa.com/test/view/index.html;
并且其中的请求:
www.aaa.com/web名/controller名/接口 转化成
www.aaa.com/test_ web名/controller名/接口
其中之所以页面没有web名是因为用nginx做了静态页面分离。好处就百度吧=-= 我暂时也不大懂。注意上面url的test!然后这样做既可以满足使用同一个微信的appid、安全域名等一系列东西,又不会影响到线上的程序。岂不美哉?
想法有了就可以一步一步做了,我这边使用的是zookeeper代理服务,为了不重复service一个是换一台服务器装,一个是再装一个。目前因为服务器负载比较大,配置又不高,我就拿自己个人的服务器安装了jdk,zookeeper,redis,nginx,tomcat。这边的nginx通过生产环境的nginx又多做了一层代理。又可以重新搭建一下环境,熟悉一下。
其实最主要的环节就是这块nginx的配置。因为这边分了两个服务器的nginx,所以有两套,看懂的话用一个服务器的nginx和多个代理转发也能实现。
整体重要的配置:
#微信服务(生产环境服务)
upstream weixin_server{
#ip_hash;
server 127.0.0.1:8080 max_fails=2 fail_timeout=5s;
}
#微信web测试服务
upstream test_weixin_server{
server 120.xx.xx.247:80 max_fails=2 fail_timeout=5s;
}
server {
listen 80;
server_name www.aaa.com;
#这边使用正则拦截对应的请求,代理到我的服务器nginx处理,也可以直接代理到服务器下的测试程序中(可以代理到不同的端口),这个是微信后台
location ~ ^/test_gsswe/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://test_weixin_server;
}
#生产环境后台
location ~ ^/gsswe/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://weixin_server;
}
#测试用页面,转发到测试服务器/转发到本地后台对应端口
location ~ ^/test/wefruitmall/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://test_weixin_server_web;
}
}
配置IP白名单(我这里用的另外一台服务器处理微信页面,所以得配置白名单)
配置商户支付授权目录(重要,支付用的测试的显然需要增加一个)
支付安全路径
商户平台,找到交易中心,下载cert证书,下载的时候会提示你密码之类的,密码就是你的商户平台密码哦。
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyConfig extends WXPayConfig {
private static final Logger log = LoggerFactory.getLogger(MyConfig.class);
private byte[] certData;
public MyConfig() {
//这边就是读取证书文件,记得下载好的证书要跟这里对应,最好重命名以下证书
try {
String certPath = "/path/to/apiclient_cert.p12";
File file = new File(certPath);
InputStream certStream;
certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
} catch (FileNotFoundException e) {
log.error("cert file no found", e);
} catch (IOException e) {
log.error("read cert file err", e);
}
}
// #微信公众号AppId
@Value("${wx.appid}")
private String wxAppId;
// #微信商户号
@Value("${wx.mchid}")
private String wxMchId;
// #微信支付API密钥
@Value("${wx.apikey}")
private String wxPayApiKey;
@Override
public String getAppID() {
return wxAppId;
}
@Override
String getMchID() {
return wxMchId;
}
@Override
String getKey() {
return wxPayApiKey;
}
@Override
InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
IWXPayDomain getWXPayDomain() {
return WXPayDomainSimpleImpl.instance();
}
}
(这个是百度上“借鉴”的 ε=ε=ε=( ̄▽ ̄) )
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.httpclient.ConnectTimeoutException;
/**
* Created by blaketang on 2017/6/16.
*/
public class WXPayDomainSimpleImpl implements IWXPayDomain {
private WXPayDomainSimpleImpl() {
}
private static class WxpayDomainHolder {
private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
}
public static IWXPayDomain instance() {
return WxpayDomainHolder.holder;
}
public synchronized void report(final String domain, long elapsedTimeMillis, final Exception ex) {
DomainStatics info = domainData.get(domain);
if (info == null) {
info = new DomainStatics(domain);
domainData.put(domain, info);
}
if (ex == null) { // success
if (info.succCount >= 2) { // continue succ, clear error count
info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;
} else {
++info.succCount;
}
} else if (ex instanceof ConnectTimeoutException) {
info.succCount = info.dnsErrorCount = 0;
++info.connectTimeoutCount;
} else if (ex instanceof UnknownHostException) {
info.succCount = 0;
++info.dnsErrorCount;
} else {
info.succCount = 0;
++info.otherErrorCount;
}
}
public synchronized DomainInfo getDomain(final WXPayConfig config) {
DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);
if (primaryDomain == null || primaryDomain.isGood()) {
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
long now = System.currentTimeMillis();
if (switchToAlternateDomainTime == 0) { // first switch
switchToAlternateDomainTime = now;
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
} else if (now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC) {
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if (alternateDomain == null || alternateDomain.isGood()
|| alternateDomain.badCount() < primaryDomain.badCount()) {
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
} else {
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
} else { // force switch back
switchToAlternateDomainTime = 0;
primaryDomain.resetCount();
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if (alternateDomain != null)
alternateDomain.resetCount();
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
}
static class DomainStatics {
final String domain;
int succCount = 0;
int connectTimeoutCount = 0;
int dnsErrorCount = 0;
int otherErrorCount = 0;
DomainStatics(String domain) {
this.domain = domain;
}
void resetCount() {
succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;
}
boolean isGood() {
return connectTimeoutCount <= 2 && dnsErrorCount <= 2;
}
int badCount() {
return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;
}
}
private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000; // 3 minutes
private long switchToAlternateDomainTime = 0;
private Map domainData = new HashMap();
}
使用的时候只要对照着api放入必要的参数即可,我这边是前后端默认都是md5,所以稍稍把sdk的编码统一md5了。
/**
* 微信预订单生成
*
* @param wxOrder
* 初始化基本数据
* @param wxNotifyUrl
* 如:/order/xxx.do 微信回调接口
* @return
* @throws Exception
*/
public String wechatPrepay(WxPayOrder wxOrder, String wxNotifyUrl) throws Exception {
String settleId = wxOrder.getOutTradeNo();
String ipAddr = wxOrder.getClientIp();
String openId = wxOrder.getOpenid();
String totalFee = wxOrder.getTotalFee();
String body = wxOrder.getBody();
WXPay pay = new WXPay(config, autoReport, useSandbox);
SortedMap reqData = new TreeMap();
reqData.put("attach", settleId);
reqData.put("body", body);
reqData.put("openid", openId);
reqData.put("out_trade_no", settleId);
reqData.put("spbill_create_ip", ipAddr);
reqData.put("total_fee", totalFee);
reqData.put("trade_type", "JSAPI");
reqData.put("notify_url", concatChatServerUrl() + wxNotifyUrl);
Map unifiedOrder = pay.unifiedOrder(reqData);
String prepayId = unifiedOrder.get("prepay_id");
if (StringUtils.isEmpty(prepayId)) {
throw new Exception("微信生成预支付订单失败");
}
return prepayId;
}
/**
* 退款
*
* @param settleId 支付id
* @param refundId 退款id
* @param totalMoney 订单总价
* @param refundMoney 退款价格
* @return
* @throws Exception
*/
public Map refund(String settleId, String refundId, double totalMoney, double refundMoney)
throws Exception {
return this.refund(settleId, refundId, totalMoney, refundMoney, null);
}
rewrite只能放在 server{}, location{}, if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用。
这个如果不想改war包名的另一种处理方式:
www.aaa.com/web名/controller名/接口 转化成
www.aaa.com/test/web名/controller名/接口
具体配置:在location中加入重写地址,自己试过加在server中的,location没有尝试
#重写url将/test/去除,
rewrite ^(.*)/test/(.*) $1/$2 last;
#效果:将www.aaa.com/test/项目名/接口名?数据
#替换为www.aaa.com/项目名/接口名?数据
#如果应该能实现通过它转发到其他代理端口并保持代码、项目名不需要更改
下载地址:https://github.com/apache/incubator-dubbo
最新版我不知道怎么装( ̄▽ ̄)" 用的2.5x版本。只要能用就好,哈哈。我这个直接放的windows下(为什么不装linux服务器?2G运存伤不起啊 =-=,maven都没法熟悉安装了,装了一半内存不够打不开dubbo-admin,尴尬。),进入dubbo-admin目录,用的maven命令打包
mvn package -Dmaven.skip.test=true
一切正常将在该文件夹的target内有对应的war包
生成的war包可以直接放到tomcat中启动(这边修改了对应启动的端口8888)免得冲突。
可以把war包改成ROOT.war这样就不用输入项目名。
默认配置文件在WEB-INF的dubbo.properties里面。
如果有三台不同网段的服务器ABC:A服务器在B中注册提供服务,C服务器消费。阿里云会出现一点问题。A提供出的服务会默认是内网ip时C获取不到要消费的服务。这时候只需要进入host文件 vim /etc/hosts 将内网ip改成公网ip重新注册服务即可。
测试环境中,如果需要开启redis远程连接,需要配置以下:
#注释掉连接地址,才能开启远程访问
#//小知识:vim快速搜索文本(在命令模式输入 / 搜索内容,n下一个,shift+n上一个)
#bind 127.0.0.1
#保护模式关闭
protected-mode no
#配置密码(重要,如果没有加密码,很容易被黑客装上挖矿程序,我已中奖,希望小伙伴注意配置自己的密码 (ノ`Д)ノ )
requirepass zhangxxxx