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>
注意
掃描二維碼時請使用微信中的掃一掃(微信掃一掃相當於一個掃碼器),如果使用了其他掃一掃工具,會提示請使用微信客戶端打開等信息。
歡迎提出更好的解決辦法。