前言
首先,我们看一下微信开放文档中的一张图:
上面的一幅图中清楚地介绍了微信登录整个过程,下面对图上所示进行总结:
一、二维码的获得
- 用户打开登录网页后,登录网页后台根据微信
OAuth2.0
协议向微信开发平台请求授权登录,并传递事先在微信开发平台中审核通过的AppID
和AppSecrect
等参数; - 微信开发平台对
AppID
等参数进行验证,并向登录网页后台返回二维码; - 登录网页后台将二维码传送至前台进行显示;
二、微信客户端授权登录
- 用户使用微信客户端扫描二维码并授权登录;
- 微信客户端将二维码特定的
uid
与微信账号绑定,传送至微信开发平台; - 微信开发平台验证绑定数据,调用登录网页后台的回调接口,发送授权临时票据
code
;
三、网页后台请求数据
- 登录网页后台接收到
code
,表明微信开发平台同意数据请求; - 登录网页后台根据
code
参数,再加上AppID
和AppSecret
请求微信开发平台换取access_token
; - 微信开发平台验证参数,并返回
access_token
; - 登录网页后台收到
access_token
后即可进行参数分析获得用户账号数据。
实现
了解了大致原理之后,我们就开始简单实现这个逻辑。因为没有直接调用微信开发平台,所以这里只是演示效果。你也可以通过访问
https://www.maomin.club/qrcodelogin/
这个我的线上网址体验一下。以下代码是主要逻辑,结合线上网址体验更容易理解。
let http = require("http"); let express = require("express"); let qrcode = require("qr-image"); let app = express(); let path = require("path"); let server = http.createServer(app); let url = require("url"); let fs = require("fs"); let UUID = require("uuid-js"); let generateHTML = null; app.use(express.static("./public")); /* * Description: 读取网页文件,用于替换关键字,相当于简易模板 * Params: * sessionID - 生成的uid * req - 网页请求 * res - 网页应答 * fileName - 网页文件所在路径 */ generateHTML = function (sessionID, req, res, fileName) { fs.readFile(fileName, "UTF-8", function (err, data) { if (!err) { data = data.replace(/SESSION_UID/g, sessionID); res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8", }); res.end(data); } else { console.log(err); res.writeHead(404, { "Content-Type": "text/html; charset=UTF-8", }); res.end(); } }); }; /* * Description: 写入JSON文件 * Params: * fileName - JSON文件所在路径 * uid - 生成的uid * writeData - 需要写入的JSON格式数据 * */ let setJSONValue = function (fileName, uid, writeData) { let data = fs.readFileSync(fileName); let users = JSON.parse(data.toString()); let addFlag = true; let delFlag = writeData === null; for (let i = 0; i < users.data.length; i++) { if (users.data[i].uid === uid) { addFlag = false; if (delFlag) { users.data.splice(i, 1); } else { users.data[i].status = writeData.status; console.log( "writeJSON: " + JSON.stringify(users.data[i]) + " modified." ); } } } if (addFlag) { users.data.push(writeData); console.log("writeJSON: " + JSON.stringify(writeData) + " inserted."); } // 同步写入文件 let writeJSON = JSON.stringify(users); fs.writeFileSync(fileName, writeJSON); }; /* * Description: 读取JSON文件(要返回数据,选择同步读取) * Params: * fileName - JSON文件所在路径 * uid - 生成的uid * */ getJSONValue = function (fileName, uid) { let readData = null; // 同步读取文件 let data = fs.readFileSync(fileName); let users = JSON.parse(data.toString()); for (let i = 0; i < users.data.length; i++) { if (users.data[i].uid === uid) { readData = JSON.stringify(users.data[i]); break; } } return readData; }; // 显示网站首页 app.get("/", function (req, res) { // 生成唯一的ID let uid = UUID.create(); console.log("uid: '" + uid + "' generated."); // 替换网页模板内的UID关键字 generateHTML(uid, req, res, path.join(__dirname, "/views/main.html")); }); // 生成二维码图片并显示 app.get("/qrcode", function (req, res, next) { let uid = url.parse(req.url, true).query.uid; try { if (typeof uid !== "undefined") { // 写入二维码内的网址,微信扫描后自动跳转。下面的网址是我的网址,https://www.maomin.club/qrcodelogin ,你可以换成自己的线上网址或者本地服务器。加上后面的"/scanned?uid=" let jumpURL = "https://www.maomin.club/qrcodelogin/scanned?uid=" + uid; // 生成二维码(size:图片大小, margin: 边框留白) let img = qrcode.image(jumpURL, { size: 6, margin: 2 }); res.writeHead(200, { "Content-Type": "image/png" }); img.pipe(res); } else { res.writeHead(414, { "Content-Type": "text/html" }); res.end("<h1>414 Request-URI Too Large</h1>"); } } catch (e) { res.writeHead(414, { "Content-Type": "text/html" }); res.end("<h1>414 Request-URI Too Large</h1>"); } }); // 显示手机扫描后的确认界面 app.get("/scanned", function (req, res) { let uid = url.parse(req.url, true).query.uid; if (typeof uid !== "undefined") { generateHTML(uid, req, res, path.join(__dirname, "/views/confirm.html")); console.log("uid: '" + uid + "' scanned."); // 获取JSON文件内对应uid的数据,更改其数据状态 let jsonData = getJSONValue(path.join(__dirname, "/bin/data.json"), uid); if (jsonData === null) { jsonData = { uid: uid, status: "scanned", name: "USER", }; } else { jsonData = JSON.parse(jsonData); jsonData.status = "scanned"; } // 写入JSON文件 setJSONValue(path.join(__dirname, "/bin/data.json"), uid, jsonData); } else { res.writeHead(414, { "Content-Type": "text/html" }); res.end("<h1>414 Request-URI Too Large</h1>"); } }); // 在确认界面操作的响应 app.get("/confirmed", function (req, res) { let uid = url.parse(req.url, true).query.uid; let operate = url.parse(req.url, true).query.operate; if (typeof uid !== "undefined") { console.log("uid: '" + uid + "' " + operate); let jsonData = getJSONValue(path.join(__dirname, "/bin/data.json"), uid); let status = operate === "confirm" ? "verified" : "canceled"; if (jsonData === null) { jsonData = { uid: uid, status: status, name: "USER", }; } else { jsonData = JSON.parse(jsonData); jsonData.status = status; } setJSONValue(path.join(__dirname, "/bin/data.json"), uid, jsonData); if (status === "verified") { res.writeHead(200, { "Content-Type": "text/html" }); res.end("<h1 style='textAlign:center;'>登录成功!</h1>"); } else { res.writeHead(200, { "Content-Type": "text/html" }); res.end("<h1 style='textAlign:center;'>Canceled!</h1>"); } } else { res.writeHead(414, { "Content-Type": "text/html" }); res.end("<h1 style='textAlign:center;'>414 Request-URI Too Large</h1>"); } }); // 响应主页不断的AJAX请求 app.get("/verified", function (req, res) { let uid = url.parse(req.url, true).query.uid; // normal - 没有任何触发 // scanned - 已扫描 // canceled - 已取消 // verified - 已验证 let dataStatus = { cmd: "normal", user: "", }; console.log("uid: '" + uid + "' query ..."); if (typeof uid !== "undefined") { let userData = getJSONValue(path.join(__dirname, "/bin/data.json"), uid); // 返回JSON数据用于首页AJAX操作 if (userData !== null) { userData = JSON.parse(userData); dataStatus.cmd = userData.status; dataStatus.user = userData.name; } } res.end(JSON.stringify(dataStatus)); }); server.listen(4000); console.log( "Express server listening on port %d in %s mode", server.address().port, app.settings.env );
看到这里,你是不是觉得代码不够全,咋就给了一个主要逻辑代码,别着急,代码满汉全席马上奉上,代码解释可以看注释哦! 以下是github网址,如果觉得对自己有用,欢迎star~
https://github.com/maomincoding/qrcodelogin.git
结语
看到这里了,你可能直接拉取代码,发现项目咋运行不了呢?效果也不跟线上网址那样。是这样的,如果你有线上服务器,可以把它部署到云端。如果没有线上服务器,你可以自己搭建一个本地局域网服务器。一定要保证手机跟电脑网页在一个IP网段上。