nodejs之二維碼掃描-實現兌換碼自動核銷
nodejs二維碼掃描—實現兌換碼自動核銷
背景
該文章主要是為了實現在沒有掃碼器的情況下,怎麼識別分辨是哪個使用者用掃一掃掃描的二維碼。設計由來:主要是公司專案有個功能是一個關於將類似於優惠券的兌換碼生成二維碼後,使用者需要向商家展示二維碼,由商家掃描二維碼進行核銷。這樣就涉及的如何判斷是商家掃描的而不是普通使用者掃描的?
實現概述
使用者點選優惠券->後臺會返回相應的二維碼(帶有相應產品的資訊)->使用者/商戶掃描二維碼->進行微信網頁授權->進入後臺進行微信openid驗證,判斷是否為商戶使用者(提前將商戶的微信openid儲存下來,供本次核對商戶資訊)->為商戶(進行核銷)/普通使用者(只顯示兌換碼資訊等普通訊息)
開發環境
1.nodejs
相關依賴
1.qr-image(其實還有個使用者量很多的node-qrcode,但是實現本功能比較繁瑣,所以選擇了小眾的qr-image):主要是為了動態顯示二維碼,無需後臺儲存二維碼圖片。
2.微信網頁授權:用於識別是哪個使用者掃描的二維碼。
微信網頁授權
第一步:使用者同意授權,獲取code
在確保微信公眾賬號擁有授權作用域(scope引數)的許可權的前提下(服務號獲得高階介面後,預設擁有scope引數中的snsapi_base和snsapi_userinfo),引導關注者開啟如下頁面:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“該連結無法訪問”,請檢查引數是否填寫錯誤,是否擁有scope引數對應的授權作用域許可權。(如果想讓微信回撥自己的路由時同時帶有自己的引數,可以在回撥路由裡新增自己的引數,再進行url編碼,這樣即可帶有自己的引數回調回來,具體實現可看下方實現)
第二步:通過code換取網頁授權access_token
首先請注意,這裡通過code換取的是一個特殊的網頁授權access_token,與基礎支援中的access_token(該access_token用於呼叫其他介面)不同。公眾號可通過下述介面來獲取網頁授權access_token。如果網頁授權的作用域為snsapi_base,則本步驟中獲取到網頁授權access_token的同時,也獲取到了openid,snsapi_base式的網頁授權流程即到此為止。
尤其注意:由於公眾號的secret和獲取到的access_token安全級別都非常高,必須只儲存在伺服器,不允許傳給客戶端。後續重新整理access_token、通過access_token獲取使用者資訊等步驟,也必須從伺服器發起。
請求方法
獲取code後,請求以下連結獲取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
第三步:拉取使用者資訊(需scope為 snsapi_userinfo)
如果網頁授權作用域為snsapi_userinfo,則此時開發者可以通過access_token和openid拉取使用者資訊了。
請求方法
http:GET(請使用https協議) https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
該呼叫,主要是為了獲取使用者的詳細資訊用於後臺的核對,分辨是自己後臺中的商戶還是普通使用者。
乾貨程式碼
第一步客戶端請求
//bms.js
router.get("/QRcode",BMSServer.qrcode);
//bmsserver.js
//獲取使用者傳過來的獲取兌換碼請求
qrcode:function(req,res){
let pid=req.body.pid;//使用者告知是哪個商品(即優惠券ID)
res.render(renderPath+'qrcode',{pid:pid});
},
//qrcode.ejs
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='/stylesheets/style.css'/>
</head>
<body>
<h1 style="position:absolute;left:43%;top:5%">優惠券二維碼</h1>
<img src="/Data/create_qrcode?url=https://xxxx.xxxxx.xxxx/xxx/qrcodeInfo&pid=<%=pid%>" style="width: 100%;height:100%;margin-top:10%"/>
</body>
</html>
第二步後臺接收到第一步img標籤的src請求
//1.由img標籤的src請求create_qrcode請求返回二維碼
//data.js
router.get("/create_qrcode",DataServer.create_qrcode);
//dataserver.js
create_qrcode:function(req,res){
var url = req.query.url;//img標籤src請求中帶有的引數
var pid=req.query.pid;
let text=`${url}?pid=${pid}`;
try {
//text="哈哈";
var img = qr.image(text,{size :10});//將引數內容寫入的二維碼中,即商品資訊引數和qrcodeInfo請求
res.writeHead(200, {'Content-Type': 'image/png'});
img.pipe(res);
} catch (e) {
res.writeHead(414, {'Content-Type': 'text/html'});
res.end('<h1>414 Request-URI Too Large</h1>');
}
},
//2.使用者掃描二維碼
//bms.js
//使用者掃描二維碼後會自動請求該路由
router.get("/qrcodeInfo",BMSServer.qrcodeInfo);
//bmsserver.js
qrcodeInfo:function(req,res){
let pid=req.query['pid'];
res.render(renderPath+'qrcodeInfo',{pid:pid,title:"優惠券"});
},
//qrcodeInfo.ejs
//自動微信網頁授權
<html>
<head>
<title><%=title%></title>
<link rel='stylesheet' href='/stylesheets/style.css'/>
<script src="/js/jquery.js"></script>
<script src="/js/jquery.mCustomScrollbar.concat.min.js"></script>
<link href="http://fonts.googleapis.com/css?family=Oxygen|Marck+Script" rel="stylesheet" type="text/css">
<link href="/css/bootstrap.css" rel="stylesheet">
<link href="/css/font-awesome.css" rel="stylesheet">
<link href="/css/admin.css" rel="stylesheet">
<script src="/js/jquery.min.js"></script>
<script src="/js/bootstrap.js"></script>
<script src="/js/excanvas.min.js"></script>
<script src="/js/jquery.flot.min.js"></script>
<script src="/js/jquery.flot.resize.js"></script>
</head>
<script>
(function($){
$(window).load(function(){//載入該網頁自動進行網頁授權,不用手動點選
let pid=$("#pid").val();
let newUrl=escape(`https://xxxx.xxxxx.xxx/xxx/webLogin?pid=${pid}`);//進行url編碼
location.href="https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd53d8ea8fc7e67cb&redirect_uri="+newUrl+"&response_type=code&scope=snsapi_base&state=123#wechat_redirect";//微信網頁授權的第一步
});
})(jQuery);
</script>
<body>
<input type="text" value="<%=pid%>" id="pid" style="display: none">
</body>
</html>
第三步後臺接收到微信網頁授權的回撥
//bms.js
router.get("/webLogin",BMSServer.weblogin);//微信網頁授權
//bmsserver.js
weblogin:function(req,res){
let body=isAvailableData(req.query).data;
let code=body.code;//微信回撥返回的code用於獲取access_token
let state=body.state;
let pid=body.pid;//自己的引數用於查詢商品的資訊即兌換碼或有效期等
request('https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code='+code+'&grant_type=authorization_code ', async function(error,respone,body){
if(!error&&respone.statusCode==200){
var wxData = JSON.parse(body);
console.log(wxData)
let access_token=wxData.access_token;//微信返回的access_token
let openid=wxData.openid;//微信返回的openid,此請求唯一一個可分辨使用者的標識,如果想獲取詳細資訊,就需要進行第三步:
request("https://api.weixin.qq.com/sns/userinfo?access_token="+access_token+"&openid="+openid+"&lang=zh_CN ",async function(error,respone,body){
if(!error&&respone.statusCode==200){
let userInfo = JSON.parse(body);//微信返回的使用者詳細資訊
console.log(userInfo)
let qrcodeUser=new QrCodeUser();//封裝好的商戶資訊model
let qrcodeuser=await qrcodeUser.findQrCodeUserByOpenid(userInfo.openid);//查詢該openid使用者是否在商戶表中
let title="";
if(qrcodeuser==null){
title="您不是商鋪使用者";
res.render(renderPath+'webLoginPage',{title:title,userInfo:null,msg:cdkey});
}else{
//可以隨意做你需要的操作
title="您為商鋪使用者";
res.render(renderPath+'webLoginPage',{title:title,userInfo:"hahah",msg:"核銷完成"});
}
}
});
}
});
}
//webLoginPage.ejs
//weui:為微信提供的一套網頁版ui。具體參考:https://www.w3cschool.cn/weixinkaifawendang/k72f1qe4.html
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
<title>核銷</title>
<link rel="stylesheet" href="/weui/dist/style/weui.css"/>
<link rel="stylesheet" href="/weui/dist/example/example.css"/>
</head>
<body ontouchstart>
<%if(userInfo!=null){%>
<%=title%>:核銷兌換碼結果為<%=msg%>
<%}else{%>
<%=title%>:只能檢視兌換碼<%=msg%>
<%}%>
</body>
</html>
注意
掃描二維碼時請使用微信中的掃一掃(微信掃一掃相當於一個掃碼器),如果使用了其他掃一掃工具,會提示請使用微信客戶端開啟等資訊。
歡迎提出更好的解決辦法。