假期让你的博客集成微信小程序扫码登录

1,220 阅读3分钟

常见的微信扫码登录逻辑有两种

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