今天我們來實現一個使用個人訂閱號實現網站的功能,後端使用的是 express
。其它框架原理基本一致,只是定義路由或返回響應資料部分程式碼跟 express
有所出入。先來一波效果圖:
1. 前言
20 年 3 月在掘金寫過一篇文章,介紹了使用 express
開發微信公眾號的案例: 原文地址。當時使用的 nodejs
版本還是 v8.x
,現如今,nodejs
的最新長期穩定版已經來到了 v18.16.0
, 新特性嚐鮮版更是已經到了 v20.1.0
。
不得不感慨時間之飛逝,nodejs 的版本升級之路見證了技術的飛速進步,也見證了我髮際線的飛速退後。
......
2. 為何要用訂閱號去登入
看了看掘金,發現掘金的登入註冊分為兩種:
- 手機號驗證碼註冊
- 第三方登入(支援微信掃碼、微博、Github)
企業站來說,這種方式固然好,給了使用者提供了更多的選擇。
但是對於個人站點,這種方式就不太友好了,因為:
手機號接收驗證碼註冊:1 條簡訊 1 毛錢,10 條就是 1 塊,100 條就能吃個豪華早餐了
接入微信開放平臺是需要企業資質的。我一個打工人,上哪兒去搞企業資質?這就是一道坎,更別說後續接入的繁瑣流程了。
Github 倒是不需要,不過接入也麻煩,棄之!
微博?使用者沒有微博賬號是不是還得去註冊個微博賬號?棄!
那就普通的註冊吧?填寫使用者名稱、密碼、確認密碼、輸入郵箱,郵箱驗證碼確認?棄!
那麼,它來了!訂閱號註冊免費,花極少的時間去接入
使用者只需關注公眾號,反手輸入個登入,回車!Ctrl + CV。欸,很快啊,登入成功!
相對於傳統的註冊填一大堆資料 + 確認密碼 + 驗證碼,孰好孰壞,熟快熟慢,一試便知。
而且,且看下圖,它對於保護使用者隱私來說,是極好的
,因為開發者只能拿到使用者的 OpenID
,頭像和暱稱都不給你!
不過, 有 OpenID
就足夠了,畢竟它對於當前公眾號來說,是 唯一的
3. 公眾號後臺配置
完整的接入指南詳見: https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
進入微信公眾平臺
設定與開發 -> 基本配置 -> 公眾號開發資訊 -> IP 白名單中配置你的伺服器 IP
設定與開發 -> 基本配置 -> 伺服器配置(未開啟的需要先開啟)
-
URL
是你的後端服務對應驗證授權
的介面地址,也就是你後端服務部署後繫結了域名的介面地址。舉例:https://www.xxx.com/wechat
-
Token
這塊的Token
按照平臺提示的規則自定義即可,注意,需要和上面程式碼中的Token
相對應,不然會驗證不成功。 -
EncodingAESKey
是訊息加解密金鑰,這個可以隨機生成,也可以自定義 -
訊息加解密方式
選擇明文模式即可
填寫好上面這些內容之後,我們先不必急著點選提交
,因為這個時候服務還沒部署,伺服器是無法正常響應微信伺服器的驗證請求,必然會提交失敗。
且看下文
4. 登入流程梳理
我們簡單的梳理一下登入流程:
-
首先是需要在網站需要登入的地方,放置一個公眾號的二維碼,並用醒目的文字給使用者提示:關注公眾號後傳送 "登入" ,獲取登入所需要的驗證碼
-
後臺接收到使用者請求,判斷使用者傳送的內容的確是 "登入" 關鍵字,返回一個驗證碼給使用者,並將當前驗證碼存起來
-
使用者輸入驗證碼,後臺根據存起來的驗證碼進行驗證,驗證成功,返回登入成功,否則,登入失敗
5. 定義服務端路由,處理驗證邏輯及服務端部署
5.1 定義授權驗證介面
主要是驗證訊息的確來自微信伺服器
import express from "express";
import jsSHA from "jssha";
const router = express.Router();
router.get("/wechat_mp", (req, res, next) => {
const token = "這裡是自定義Token,可自定義,內容規則詳見下文";
//1.獲取微信伺服器Get請求的引數 signature、timestamp、nonce、echostr
const { signature, timestamp, nonce, echostr } = req.query;
//2.將token、timestamp、nonce三個引數進行字典序排序
const array = [token, timestamp, nonce].sort();
//3.將三個引數字串拼接成一個字串進行sha1加密
const tempStr = array.join("");
const shaObj = new jsSHA("SHA-1", "TEXT");
shaObj.update(tempStr);
const scyptoString = shaObj.getHash("HEX");
//4.開發者獲得加密後的字串可與signature對比,標識該請求來源於微信
if (signature === scyptoString) {
console.log("驗證成功");
res.send(echostr);
} else {
console.log("驗證失敗");
res.send("驗證失敗");
}
});
5.2 處理使用者 "登入" 訊息
接下來我們定義使用者傳送訊息的邏輯:
當使用者傳送文字訊息給公眾號的時候,微信伺服器會將使用者傳送的訊息以 XML格式
的引數去請求這個介面,只不過這個時候,我們需要透過 POST
請求來接收引數。
先上程式碼:
定義POST介面
import { parseString } from "xml2js";
import myCache from "../store";
/**
* 隨機6位驗證碼
*/
function randomCode() {
return Math.random().toString().slice(-6);
}
/**
* 回覆文字訊息封裝
*/
function sendTextMsg(toUser, fromUser, content) {
let resultXml = "<xml><ToUserName><![CDATA[" + fromUser + "]]></ToUserName>";
resultXml += "<FromUserName><![CDATA[" + toUser + "]]></FromUserName>";
resultXml += "<CreateTime>" + new Date().getTime() + "</CreateTime>";
resultXml += "<MsgType><![CDATA[text]]></MsgType>";
resultXml += "<Content><![CDATA[" + content + "]]></Content></xml>";
return resultXml;
}
router.post("/wechat_mp", function (req, res) {
var buffer = [];
req.on("data", function (data) {
buffer.push(data);
});
// 內容接收完畢
req.on("end", function () {
var msgXml = Buffer.concat(buffer).toString("utf-8");
parseString(msgXml, { explicitArray: false }, function (err, result) {
if (err) throw err;
result = result.xml;
const { ToUserName, FromUserName, MsgType, Content } = result;
if (MsgType === "text" && Content === "登入") {
const code = randomCode();
// 這裡的FromUserName就是使用者的OpenID
myCache.set(code, FromUserName, 1 * 60 * 5);
const sendXml = sendTextMsg(
ToUserName,
FromUserName,
`您的登入驗證碼是:${code} , 有效期為5分鐘`
);
res.send(sendXml);
}
});
});
});
store/index.js
import NodeCache from "node-cache";
const myCache = new NodeCache();
export default myCache;
程式碼解釋:
-
XML 解析:可以透過安裝
xml2js
這個庫來解析XML
格式的引數 -
接化發:檢測到使用者傳送
訊息型別
為text
且內容為登入
關鍵字的時候,我們去生成一個6位隨機驗證碼
,再將驗證碼和使用者的OpenID
以key(code)、value(OpenId)
的格式存入 存入node-cache
中,並設定有效期為5分鐘
,同時將隨機驗證碼傳送給使用者。
5.3 處理網站登入邏輯
上一步,使用者已經在微信公眾號上獲取到了隨機驗證碼,現在只需要在網站需要登入的地方輸入驗證碼,呼叫驗證碼校驗介面即可進行校驗。
定義驗證碼校驗介面
import myCache from "../store";
router.get("/verify", async function (req, res) {
const { code } = req.query;
const OpenID = myCache.get(code);
if (OpenID) {
const token = "使用OpenID進行jwt鑑權頒發Token";
res.json({
code: 200,
data: { token },
});
} else {
res.json({
code: 400,
msg: "您輸入的驗證碼有誤或已過期,請重新輸入!-_-",
});
}
});
程式碼解釋:
根據使用者輸入的驗證碼,去 node-cache
中獲取 OpenID
,如果存在,則說明驗證碼正確,jwt
鑑權頒發 Token
。反之,校驗失敗。
驗證碼校驗成功後,透過唯一的 OpenID
和自定義的 secret
給使用者頒發 Token
,使用者再次訪問網站的時候,只需要攜帶 Token
即可進行鑑權。
關於 jwt
鑑權概念、流程及使用,大家可以參考這條 AI 問答分享:
我在ChatGPTer(https://ai.iiter.cn)網站上建立了一個AI對話分享,快來看看吧!
「jwt鑑權概念、流程」
連結:https://ai.iiter.cn/#/share/6465c5a2e82a696c417bbfa9
同時,也歡迎大家自己進行 AI 問答: https://ai.iiter.cn
5.4 部署服務
服務部署這塊大家可以參考我之前寫的一篇傻瓜式部署文章:寶塔皮膚結合 pm2 程式管理工具部署前端專案
當然,您也可以使用自己順手的部署方式
5.5 提交公眾號配置
服務部署成功後,回到我們的 公眾號後臺配置
部分,點選提交即可
結語
至此,我們的個人訂閱號登入功能就已經完成了,相信基於以上,大家都可以很快的去做出一個網站登入功能出來
而且在給使用者提供服務的同時,可以直接將使用者引導至自己的公眾號上。不管是後面對網站的更新記錄,還是一些重要的通知,都可以透過公眾號進行訊息推送,一舉兩得
好了,今天的文章分享就到這裡了,如果對大家有所幫助的話,希望您不要吝嗇手中的贊呦~
正式給大家介紹一下:
基於 OpenAI
的 API
開發的一款 ChatGPT
網站,模型是gpt-3.5-turbo
,使用的是本篇文章的同款登入方式,歡迎大家體驗
免費!免費!免費!https://ai.iiter.cn