常见的微信扫码登录逻辑有两种
1、微信开放平台
2、微信服务号
这两种都需要企业资料认证,对个人开发者是没有权限的,无意间看到腾讯文档扫码登录的时候打开的是小程序,开始还以为只有腾讯有权限(滑稽的笑了😄 ),然后就一顿搜索,发现这个逻辑其实就是我们日常开发小程序中的小程序码功能,外加一个websocket功能就可以搞定
文章首发于我的博客,欢迎大家前来围观🚀️ 🚀️ 🚀️
实现原理
- 博客点击微信登录弹窗,建立websocket连接,连接建立成功之后,发送消息从服务端获取带有时间戳的随机id,接受到id之后携带id请求小程序码(id用于网页端和小程序的通信绑定),
- 用户通过微信扫码点击登录之后,把用户信息、code、和id提交到服务端,
- 服务端根据code获取openid,然后根据openid判断是创建还是更新用户信息,然后生成token通过websocket广播给唯一id的前端网页
- 前端接受到token,存储token,页面重新加载获取用户信息,断开websocket连接
前端代码实现
前端我使用的vue
//初始化weosocket
initWebSocket() {
if (this.websock) {
this.websock.close();
}
// websocket 消息处理
const messageHandler = {
// 获得 codeId 接下来我们要生成二维码
CODE_ID: async (res) => {
let $res = await wxcodeUrl({ scene: res.data.uuid });
this.wxCodeSrc = "http://localhost:3000/" + $res.data.url;
this.codeLoading = false;
this.isFailure = false;
this.desccontent = "请使用微信扫一扫";
},
// 二维码被扫描,等待客户端确认
SCANNED: () => {
this.isLoadingScan = true;
this.desccontent = "请在微信中点击确认登录";
},
// 登录成功,保存获得的 token
SUCCESS: (res) => {
this.$store.commit("user/SET_TOKEN", res.data.token);
setToken(res.data.token);
location.reload();
this.$notify({
title: "成功",
message: "登录成功~",
type: "success",
});
this.codeLoading = false;
this.websock.close();
},
};
//初始化weosocket
this.websock = new WebSocket("ws://localhost:3000/webSocket");
this.websock.onmessage = (e) => {
const res = JSON.parse(e.data);
const handlerFn = messageHandler[res.data.type];
// 匹配到相应的函数,执行之
if (typeof handlerFn === "function") {
handlerFn(res);
}
};
// 连接建立成功
this.websock.onopen = () => {
// 发送消息,请求随机 id
this.websock.send("GET_CODE");
};
this.websock.onerror = this.websocketonerror;
this.websock.onclose = this.websocketclose;
},
//连接建立失败重连
websocketonerror() {
this.initWebSocket();
},
//关闭连接
websocketclose() {
this.isFailure = true;
this.isLoadingScan = false;
this.desccontent = "";
},
服务端代码
服务端我使用的是koa,webSocket连接使用的是ws插件,只贴一部分代码,像那些生成小程序码都是接口调用 由于我koa和webSocket使用的是同一个端口号,所以在koa启动的时候就初始化webSocket
app.listen(3000);
app.on('error', onError);
app.on('listening', onListening);
// websocket 初始化
const wss = createWss(server);
initWebsocket(wss);
WebSocket Server 对象
const WebSocket = require('ws');
// WebSocket Server 对象
let wss = null;
// 创建 wss 对象,我们可以把 Koa 的 Server 的对象传入,来共享端口,
// 第一次创建时,我们会结果赋值给将上面的 wss,
// 这样的好处是方便在接口层中获取 wss 对象,匹配正确的客户端
const createWss = (server) => {
if (!wss) {
wss = new WebSocket.Server({
server: server
})
return wss;
} else {
return wss;
}
}
// 获取 wss 对象
const getWss = () => {
return wss;
}
module.exports = {
createWss: createWss,
getWss: getWss
}
初始化后的事件
// 发送消息
const sendData = (client, status, data) => {
if (client && client.send) {
client.send(JSON.stringify({
status: status,
data: data
}));
}
}
// 服务初始化
const initWebsocket = (wss) => {
wss.on("connection", (ws) => {
console.log(`[SERVER] connection`);
// 接收到数据
ws.on('message', (msg) => {
console.log(`[SERVER] Received: ${msg}`);
// 发送数据
const fn = loginMessageHandler[msg];
if (fn) {
fn(ws);
} else {
ws.send("bad command!");
}
});
});
}
// 处理登录消息,根据客户端发来的消息匹配相应的业务逻辑操作函数
const loginMessageHandler = {
"GET_CODE": (ws) => {
const uid = new Date().getTime();
console.log("获取二维码----" + uid);
ws.loginCondition = {
uuid: uid,
status: 0
}
sendData(ws, "ok", {
uuid: uid,
type: "CODE_ID"
});
}
}
module.exports = {
initWebsocket: initWebsocket,
sendData: sendData
}
微信扫码登录
/**
* 微信扫码登录
* @param {*} ctx
*/
static async wxscanLogin(ctx) {
const { scene: uuid } = ctx.request.body;
let payload = ctx.state.user;
try {
const token = tools.generatorToken({ id: payload.id });
const wss = getWss();
if (wss && wss.clients && token) {
// 可以在 wss.clients 中找到相应的客户端
const clients = wss.clients;
// 找到对应的 ws 客户端
let targetClient = [...clients].find((client) => client.loginCondition.uuid == uuid);
if (targetClient) {
if (targetClient.loginCondition.status == 0) {
sendData(targetClient, "ok", {
uuid: uuid,
type: "SUCCESS",
token: token
});
}
}
ctx.success();
}
} catch (err) {
ctx.fail();
}
}
演示地址 : youxiubiji.com