前言
首先,我们看一下微信开放文档中的一张图:
上面的一幅图中清楚地介绍了微信登录整个过程,下面对图上所示进行总结:
一、二维码的获得
-
用户打开登录网页后,登录网页后台根据微信
OAuth2.0协议向微信开发平台请求授权登录,并传递事先在微信开发平台中审核通过的AppID和AppSecrect等参数; -
微信开发平台对
AppID等参数进行验证,并向登录网页后台返回二维码; -
登录网页后台将二维码传送至前台进行显示;
二、微信客户端授权登录
-
用户使用微信客户端扫描二维码并授权登录;
-
微信客户端将二维码特定的
uid与微信账号绑定,传送至微信开发平台; -
微信开发平台验证绑定数据,调用登录网页后台的回调接口,发送授权临时票据
code;
三、网页后台请求数据
-
登录网页后台接收到
code,表明微信开发平台同意数据请求; -
登录网页后台根据
code参数,再加上AppID和AppSecret请求微信开发平台换取access_token; -
微信开发平台验证参数,并返回
access_token; -
登录网页后台收到
access_token后即可进行参数分析获得用户账号数据。
实现
了解了大致原理之后,我们就开始简单实现这个逻辑。因为没有直接调用微信开发平台,所以这里只是演示效果。你也可以通过访问:
1`https://www.maomin.club/qrcodelogin/ 2`
这个我的线上网址体验一下。以下代码是主要逻辑,结合线上网址体验更容易理解。
1`let http = require("http"); 2let express = require("express"); 3let qrcode = require("qr-image"); 4let app = express(); 5let path = require("path"); 6let server = http.createServer(app); 7let url = require("url"); 8let fs = require("fs"); 9let UUID = require("uuid-js"); 10let generateHTML = null; 11 12app.use(express.static("./public")); 13 14/* 15 * Description: 读取网页文件,用于替换关键字,相当于简易模板 16 * Params: 17 * sessionID - 生成的uid 18 * req - 网页请求 19 * res - 网页应答 20 * fileName - 网页文件所在路径 21 */ 22generateHTML = function (sessionID, req, res, fileName) { 23 fs.readFile(fileName, "UTF-8", function (err, data) { 24 if (!err) { 25 data = data.replace(/SESSION_UID/g, sessionID); 26 res.writeHead(200, { 27 "Content-Type": "text/html; charset=UTF-8", 28 }); 29 res.end(data); 30 } else { 31 console.log(err); 32 33 res.writeHead(404, { 34 "Content-Type": "text/html; charset=UTF-8", 35 }); 36 res.end(); 37 } 38 }); 39}; 40 41/* 42 * Description: 写入JSON文件 43 * Params: 44 * fileName - JSON文件所在路径 45 * uid - 生成的uid 46 * writeData - 需要写入的JSON格式数据 47 * 48 */ 49let setJSONValue = function (fileName, uid, writeData) { 50 let data = fs.readFileSync(fileName); 51 52 let users = JSON.parse(data.toString()); 53 let addFlag = true; 54 let delFlag = writeData === null; 55 56 for (let i = 0; i < users.data.length; i++) { 57 if (users.data[i].uid === uid) { 58 addFlag = false; 59 60 if (delFlag) { 61 users.data.splice(i, 1); 62 } else { 63 users.data[i].status = writeData.status; 64 65 console.log( 66 "writeJSON: " + JSON.stringify(users.data[i]) + " modified." 67 ); 68 } 69 } 70 } 71 72 if (addFlag) { 73 users.data.push(writeData); 74 console.log("writeJSON: " + JSON.stringify(writeData) + " inserted."); 75 } 76 77 // 同步写入文件 78 let writeJSON = JSON.stringify(users); 79 fs.writeFileSync(fileName, writeJSON); 80}; 81 82/* 83 * Description: 读取JSON文件(要返回数据,选择同步读取) 84 * Params: 85 * fileName - JSON文件所在路径 86 * uid - 生成的uid 87 * 88 */ 89getJSONValue = function (fileName, uid) { 90 let readData = null; 91 92 // 同步读取文件 93 let data = fs.readFileSync(fileName); 94 95 let users = JSON.parse(data.toString()); 96 97 for (let i = 0; i < users.data.length; i++) { 98 if (users.data[i].uid === uid) { 99 readData = JSON.stringify(users.data[i]); 100 break; 101 } 102 } 103 104 return readData; 105}; 106 107// 显示网站首页 108app.get("/", function (req, res) { 109 // 生成唯一的ID 110 let uid = UUID.create(); 111 console.log("uid: '" + uid + "' generated."); 112 // 替换网页模板内的UID关键字 113 generateHTML(uid, req, res, path.join(__dirname, "/views/main.html")); 114}); 115 116// 生成二维码图片并显示 117app.get("/qrcode", function (req, res, next) { 118 let uid = url.parse(req.url, true).query.uid; 119 120 try { 121 if (typeof uid !== "undefined") { 122 // 写入二维码内的网址,微信扫描后自动跳转。下面的网址是我的网址,https://www.maomin.club/qrcodelogin ,你可以换成自己的线上网址或者本地服务器。加上后面的"/scanned?uid=" 123 let jumpURL = "https://www.maomin.club/qrcodelogin/scanned?uid=" + uid; 124 // 生成二维码(size:图片大小, margin: 边框留白) 125 let img = qrcode.image(jumpURL, { size: 6, margin: 2 }); 126 res.writeHead(200, { "Content-Type": "image/png" }); 127 img.pipe(res); 128 } else { 129 res.writeHead(414, { "Content-Type": "text/html" }); 130 res.end("<h1>414 Request-URI Too Large</h1>"); 131 } 132 } catch (e) { 133 res.writeHead(414, { "Content-Type": "text/html" }); 134 res.end("<h1>414 Request-URI Too Large</h1>"); 135 } 136}); 137 138// 显示手机扫描后的确认界面 139app.get("/scanned", function (req, res) { 140 let uid = url.parse(req.url, true).query.uid; 141 142 if (typeof uid !== "undefined") { 143 generateHTML(uid, req, res, path.join(__dirname, "/views/confirm.html")); 144 145 console.log("uid: '" + uid + "' scanned."); 146 147 // 获取JSON文件内对应uid的数据,更改其数据状态 148 let jsonData = getJSONValue(path.join(__dirname, "/bin/data.json"), uid); 149 150 if (jsonData === null) { 151 jsonData = { 152 uid: uid, 153 status: "scanned", 154 name: "USER", 155 }; 156 } else { 157 jsonData = JSON.parse(jsonData); 158 jsonData.status = "scanned"; 159 } 160 161 // 写入JSON文件 162 setJSONValue(path.join(__dirname, "/bin/data.json"), uid, jsonData); 163 } else { 164 res.writeHead(414, { "Content-Type": "text/html" }); 165 res.end("<h1>414 Request-URI Too Large</h1>"); 166 } 167}); 168 169// 在确认界面操作的响应 170app.get("/confirmed", function (req, res) { 171 let uid = url.parse(req.url, true).query.uid; 172 let operate = url.parse(req.url, true).query.operate; 173 174 if (typeof uid !== "undefined") { 175 console.log("uid: '" + uid + "' " + operate); 176 177 let jsonData = getJSONValue(path.join(__dirname, "/bin/data.json"), uid); 178 let status = operate === "confirm" ? "verified" : "canceled"; 179 180 if (jsonData === null) { 181 jsonData = { 182 uid: uid, 183 status: status, 184 name: "USER", 185 }; 186 } else { 187 jsonData = JSON.parse(jsonData); 188 jsonData.status = status; 189 } 190 191 setJSONValue(path.join(__dirname, "/bin/data.json"), uid, jsonData); 192 193 if (status === "verified") { 194 res.writeHead(200, { "Content-Type": "text/html" }); 195 res.end("<h1 style='textAlign:center;'>登录成功!</h1>"); 196 } else { 197 res.writeHead(200, { "Content-Type": "text/html" }); 198 res.end("<h1 style='textAlign:center;'>Canceled!</h1>"); 199 } 200 } else { 201 res.writeHead(414, { "Content-Type": "text/html" }); 202 res.end("<h1 style='textAlign:center;'>414 Request-URI Too Large</h1>"); 203 } 204}); 205 206// 响应主页不断的AJAX请求 207app.get("/verified", function (req, res) { 208 let uid = url.parse(req.url, true).query.uid; 209 210 // normal - 没有任何触发 211 // scanned - 已扫描 212 // canceled - 已取消 213 // verified - 已验证 214 let dataStatus = { 215 cmd: "normal", 216 user: "", 217 }; 218 219 console.log("uid: '" + uid + "' query ..."); 220 221 if (typeof uid !== "undefined") { 222 let userData = getJSONValue(path.join(__dirname, "/bin/data.json"), uid); 223 224 // 返回JSON数据用于首页AJAX操作 225 if (userData !== null) { 226 userData = JSON.parse(userData); 227 dataStatus.cmd = userData.status; 228 dataStatus.user = userData.name; 229 } 230 } 231 232 res.end(JSON.stringify(dataStatus)); 233}); 234 235server.listen(4000); 236console.log( 237 "Express server listening on port %d in %s mode", 238 server.address().port, 239 app.settings.env 240); 241`
看到这里,你是不是觉得代码不够全,咋就给了一个主要逻辑代码,别着急,代码满汉全席马上奉上,代码解释可以看注释哦!以下是github网址,如果觉得对自己有用,欢迎star~
1`https://github.com/maomincoding/qrcodelogin.git 2`
结语
看到这里了,你可能直接拉取代码,发现项目咋运行不了呢?效果也不跟线上网址那样。是这样的,如果你有线上服务器,可以把它部署到云端。如果没有线上服务器,你可以自己搭建一个本地局域网服务器。一定要保证手机跟电脑网页在一个IP网段上。
效果图如下:
- 登录网页

- 登录授权页

欢迎关注我的公众号 前端历劫之路
回复关键词 电子书 ,即可获取12本前端热门电子书
回复关键词 红宝书第4版 ,即可获取最新《JavaScript高级程序设计》(第四版)电子书
关注公众号后,点击下方菜单即可加我微信,我拉拢了很多IT大佬,创建了一个技术交流、文章分享群,期待你的加入
- END -

本文转转自微信公众号前端历劫之路原创https://mp.weixin.qq.com/s/w4cBaLQoRAAUlOHRpcmf_A,如有侵权,请联系删除。

