公众号支付多人协同开发,请求分发怎么做
1.为什么需要将流量分发
在做微信公众号支付的时候,所有的支付操作都必须在设置的支付授权目录下进行,而且支付授权目录只能设置一个。如果多个开发人员一起做开发的话,不可能每个人都去开通一个公众号去绑定支付授权目录去调试,只能是多个开发人员共用一个公众号,共用一个支付授权目录。但是如果多个人同时开发的话,为了不互相影响,自己调试自己的代码,需要将请求进行分发,根据一些策略分发到对应的开发人员那里。
2.具体的解决思路
把每次的请求做一个处理,添加一个标识,然后到了分发层,根据这个标识去做分发。
3.实现
(1)对每次请求拦截并处理,添加Header
首先,安装Charles进行http抓包,修改http请求,添加Header,Charles的安装与使用说明(参考这篇文章):
https://blog.csdn.net/kaluman/article/details/78320321
安装好以后需要添加一个固定的Header:
【我们可以添加一个channel:127.0.0.1:808的header,到时候可以直接用channel作为代理的路径】
(2)安装OpenResty,写lua脚本,通过获取Header中的channel来进行流量分发。
安装OpenReaty,参考文章:https://www.cnblogs.com/zdz8207/p/Nginx-Lua-OpenResty.html
安装完后,需要添加http的库:
#添加http的库:
cd /usr/servers/lualib/resty/
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua
#进入lua的配置文件:
vim /www/server/nginx/conf/lua1.conf
修改成下面的代码:
server
{
listen 80;
server_name 微信公众号支付的域名;
access_log /home/lua/access.hongsou.log;
error_log /home/lua/error.hongsou.log;
#lua测试
location /lua {
default_type 'text/html';
content_by_lua 'ngx.say("hlo world")';
}
location / {
#lua_code_cache off;
resolver 8.8.8.8;
set $backend '';
rewrite_by_lua_file /usr/servers/lua/hongsou.lua;
proxy_pass http://$backend;
}
}
【不加resolver的话可能会报错, 无法解析,加一个8.8.8.8就可以搞定了。
lua_code_cache 是开发环境的配置, 不缓存lua代码, 修改完lua直接生效, 不然每次要重启nginx, 上生产环境要关掉, 严重影响性能。】
#修改lua的文件
mkdir /usr/servers/lua
vim /usr/servers/lua/hongsou.lua
--线上测试服务器的一套代码(云服务器上一套默认的代码,可以供测试人员测试)
--ngx.var.backend = 'ip:port'
--分发到不同开发人员本地的代码
--ngx.var.backend = '127.0.0.1:808'
local basePath='';
local redirectPath=nil;
local headers_tab = ngx.req.get_headers()
for k, v in pairs(headers_tab) do
print(k..":"..v)
if("channel"==k)
then
redirectPath = v
end
end
if redirectPath ~=nil
then
ngx.var.backend = redirectPath
else
ngx.var.backend = 'ip:port'
end
现在,在云服务器上可以做转发了,但是为了方便测试人员,在本地的电脑上做项目的调试,还需要将云服务器上的请求内网穿透到本地的电脑上,本地的电脑再运行支付的项目。这样就实现了我们的需求。
(3)将云服务器上的请求内网穿透到自己本地的电脑上
我们的添加了Header请求头(channel:127.0.0.1:808),在上面的lua脚本中判断如果有channel的请求头,就会将这个channel请求头的value值(也就是127.0.0.1:808)作为转发的路径,这样请求就会转发到127.0.0.1:808,
这时候可以监听这个端口,再做内网穿透处理。
在码云上找到一个java写的内网穿透项目,地址:
https://gitee.com/wmao/javanet
微信公众支付开发,我们公司是一个页面,但是用这个开源项目始终无法显示页面,发现是在这里:
package net.bcxuexi.server.stream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import net.bcxuexi.config.Config;
import net.bcxuexi.server.model.SocketModel;
import net.bcxuexi.tools.MyLog;
/**
* socket输出流
*/
public class WriteStream extends Thread {
private SocketModel socketModel;
private boolean stopWrite = false;
// 数据阻塞队列,使用LinkedBlockingQueue 大小默认为Integer.MAX_VALUE
private BlockingQueue<StreamData> blockingQueue;
public WriteStream(SocketModel socketModel) {
this.socketModel = socketModel;
blockingQueue = new LinkedBlockingQueue<StreamData>();
}
@Override
public void run() {
try {
Socket socket = socketModel.getSocket();
while (!stopWrite) {
if (socket == null || socket.isClosed()) {
stopWrite = true;
}
String socketType = socketModel.getType();
StreamData streamData = null;// 待发送数据
try {
streamData = take();// 从阻塞队列中取数据,可能会被阻塞
} catch (InterruptedException e) {
// 没有取到数据
}
if (streamData != null) {
// 数据发送
DataOutputStream out = new DataOutputStream(
socket.getOutputStream());
if(Config.debug){
String msg = new String(streamData.data, 0, streamData.total);
MyLog.info("向客户端发送数据connId="+socketModel.getConnId()+";数据:"+msg);
}
out.write(streamData.data, 0, streamData.total);
}
try {
sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭socket
socketModel.closeSocket("writestream结束,关闭socket服务."+socketModel.toString());
}
}
/**
* 添加数据。可能发生阻断。 如果队列已满则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续
*
* @param data
* @throws InterruptedException
* 中断异常
*/
public void addData(StreamData data) throws InterruptedException {
blockingQueue.put(data);
}
/**
* 取走BlockingQueue里排在首位的对象。
* 若BlockingQueue为空,阻断调用此方法的线程,直到BlockingQueue有新的数据被加入
*
* @return
* @throws InterruptedException
* 中断异常
*/
protected StreamData take() throws InterruptedException {
return blockingQueue.take();
}
public void removeData(StreamData data) {
blockingQueue.remove(data);
}
/**
* 是否在处理proxyConnId的数据
* @param proxyConnId
*/
public boolean hasProxyConnId(String proxyConnId){
for (Iterator iterator = blockingQueue.iterator(); iterator.hasNext();) {
StreamData streamData = (StreamData) iterator.next();
String pConnId = streamData.getProxyConnId();
if(proxyConnId.equals(pConnId)){
return true;
}
}
return false;
}
public int getSize(){
return blockingQueue.size();
}
public void stopWrite() {
this.stopWrite = true;
}
}
问题就在这个类里,我要想显示页面,这个socket必须关闭,不然数据写不出去。
所以,我就改造了一下,因为数据都是http请求数据,数据的开头肯定是“HTTP” ,可以根据这个来跳出while循环,然后关闭socket。然后页面就神奇的显示了。下面是修改的代码:
package net.bcxuexi.server.stream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import net.bcxuexi.config.Config;
import net.bcxuexi.server.model.SocketModel;
import net.bcxuexi.tools.MyLog;
/**
* socket输出流
*/
public class WriteStream extends Thread {
private SocketModel socketModel;
private boolean stopWrite = false;
// 数据阻塞队列,使用LinkedBlockingQueue 大小默认为Integer.MAX_VALUE
private BlockingQueue<StreamData> blockingQueue;
public WriteStream(SocketModel socketModel) {
this.socketModel = socketModel;
blockingQueue = new LinkedBlockingQueue<StreamData>();
}
@Override
public void run() {
Socket socket=null;
DataOutputStream out=null;
try {
socket = socketModel.getSocket();
while (!stopWrite) {
if (socket == null || socket.isClosed()) {
stopWrite = true;
}
String socketType = socketModel.getType();
//数据类型,是控制型数据还是通讯型数据 control data
System.out.println("数据类型:"+socketType);
StreamData streamData = null;// 待发送数据
try {
streamData = take();// 从阻塞队列中取数据,可能会被阻塞
} catch (InterruptedException e) {
// 没有取到数据
}
if (streamData != null) {
// 数据发送
out = new DataOutputStream(
socket.getOutputStream());
if(Config.debug){
String msg = new String(streamData.data, 0, streamData.total);
MyLog.info("向客户端发送数据connId="+socketModel.getConnId()+";数据:"+msg);
}
//==============================梁臣 begin=================================================
String liangChenData=null;
try {
liangChenData=new String(streamData.data,"UTF-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
System.out.println("------将要发送给浏览器的数据-------:"+liangChenData);
out.write(streamData.data, 0, streamData.total);
if(liangChenData.startsWith("HTTP")){
stopWrite=true;
}
//==============================梁臣 end==================================================
}
try {
sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out = new DataOutputStream(
socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
// 关闭socket
socketModel.closeSocket1("writestream结束,关闭socket服务."+socketModel.toString(),out,socket);
}
}
/**
* 添加数据。可能发生阻断。 如果队列已满则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续
*
* @param data
* @throws InterruptedException
* 中断异常
*/
public void addData(StreamData data) throws InterruptedException {
blockingQueue.put(data);
}
/**
* 取走BlockingQueue里排在首位的对象。
* 若BlockingQueue为空,阻断调用此方法的线程,直到BlockingQueue有新的数据被加入
*
* @return
* @throws InterruptedException
* 中断异常
*/
protected StreamData take() throws InterruptedException {
return blockingQueue.take();
}
public void removeData(StreamData data) {
blockingQueue.remove(data);
}
/**
* 是否在处理proxyConnId的数据
* @param proxyConnId
*/
public boolean hasProxyConnId(String proxyConnId){
for (Iterator iterator = blockingQueue.iterator(); iterator.hasNext();) {
StreamData streamData = (StreamData) iterator.next();
String pConnId = streamData.getProxyConnId();
if(proxyConnId.equals(pConnId)){
return true;
}
}
return false;
}
public int getSize(){
return blockingQueue.size();
}
public void stopWrite() {
this.stopWrite = true;
}
}
这个也算是这个开源项目的bug。修复了这个bug,这个内网穿透的项目就能用了。
然后,监听808端口,把请求转发到本地电脑里运行的电脑里了。
这个内网穿透的工具,我已经把它改造成springBoot版本的了,百度网盘地址:
链接:https://pan.baidu.com/s/1wHQ-szg-mZgWnYcMSaBckQ
提取码:mn2g
至此,微信公众号支付的多人协同开发就可以实现了,程序员A可以在抓包工具里添加Header(channel:127.0.0.1:808) ,lua的流量分发脚本会将程序员A的发起的请求转发到支付域名绑定的云服务器的808端口,内网穿透监听808端口,然后把请求转发到程序员A的本地代码里。同理,程序员B发起的请求会访问到程序员B的本地代码里。也就是他们自己开发自己的代码,自己调试自己的代码,但是是用的同一个支付域名。这也等同于灰度发布系统的理念吧。