公眾號支付多人協同開發,請求分發怎麼做
1.為什麼需要將流量分發
在做微信公眾號支付的時候,所有的支付操作都必須在設定的支付授權目錄下進行,而且支付授權目錄只能設定一個。如果多個開發人員一起做開發的話,不可能每個人都去開通一個公眾號去繫結支付授權目錄去除錯,只能是多個開發人員共用一個公眾號,共用一個支付授權目錄。但是如果多個人同時開發的話,為了不互相影響,自己除錯自己的程式碼,需要將請求進行分發,根據一些策略分發到對應的開發人員那裡。
2.具體的解決思路
把每次的請求做一個處理,新增一個標識,然後到了分發層,根據這個標識去做分發。
3.實現
(1)對每次請求攔截並處理,新增Header
首先,安裝Charles進行http抓包,修改http請求,新增Header,Charles的安裝與使用說明(參考這篇文章):
(2)安裝OpenResty,寫lua指令碼,通過獲取Header中的channel來進行流量分發。
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的原生代碼裡。也就是他們自己開發自己的程式碼,自己除錯自己的程式碼,但是是用的同一個支付域名。這也等同於灰度釋出系統的理念吧。