如今的網站開發,都繞不開微信登陸(畢竟微信已經成爲國民工具)。雖然文檔已經寫得很詳細,可是對於沒有經驗的開發者仍是容易踩坑。javascript
因此,專門記錄一下微信網頁認證的交互邏輯,也方便本身往後回查:css
在多人團隊協做中,加載資源的代碼須要格外當心。由於可能會有多個開發者在同一業務邏輯下調用,這會形成資源的重複加載。前端
處理方法有兩種,第一種是對外暴露多餘接口,專門check是否重複加載。可是考慮到調用者每次在加載前,都須要顯式調用check()
方法進行檢查,不免會有遺漏。java
因此採用第二種方法--設計模式中的緩存模式,代碼以下:webpack
// 備忘錄模式: 防止重複加載
export const loadWeChatJs = (() => {
let exists = false; // 打點
const src = '//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'; // 微信sdk網址
return () => new Promise((resolve, reject) => {
// 防止重複加載
if(exists) return resolve(window.WxLogin);
let script = document.createElement('script');
script.src = src;
script.type = 'text/javascript';
script.onerror = reject; // TODO: 失敗時候, 能夠移除script標籤
script.onload = () => {
exists = true;
resolve(window.WxLogin);
};
document.body.appendChild(script);
});
})();
複製代碼
根據《微信登錄開發指南》,將參數傳遞給window.WxLogin()
便可。css3
// 微信默認配置
const baseOption = {
self_redirect: true, // true: 頁內iframe跳轉; false: 新標籤頁打開
id: 'wechat-container',
appid: 'wechat-appid',
scope: 'snsapi_login',
redirect_uri: encodeURIComponent('//1.1.1.1/'),
state: '',
};
export const loadQRCode = (option, intl = false, width, height) => {
const _option = {...baseOption, ...option};
return new Promise((resolve, reject) => {
try {
window.WxLogin(_option);
const ele = document.getElementById(_option['id']);
const iframe = ele.querySelector('iframe');
iframe.width = width? width : '300';
iframe.height = height? height : '420';
// 處理國際化
intl && (iframe.src = iframe.src + '&lang=en');
resolve(true);
} catch(error) {
reject(error);
}
});
};
複製代碼
在須要使用的業務組件中,能夠在周期函數componentDidMount
調用,下面是demo代碼:git
componentDidMount() {
const wxOption = {
// ...
};
loadWeChatJs()
.then(WxLogin => loadQRCode(wxOption))
.catch(error => console.log(`Error: ${error.message}`));
}
複製代碼
這一塊我以爲是微信登錄交互中最複雜和難以理解的一段邏輯。開頭有講過,微信二維碼渲染有2中方式,一種是打開新的標籤頁,另外一種是在指定id的容器中插入iframe。es6
毫無疑問,第二種交互方式更友好,由於要涉及不一樣級層的頁面通訊,代碼處理也更具挑戰。github
爲了方便說明,請先看模擬的數據配置:
// redirect 地址會被後端拿到, 後端重定向到此地址, 前端會訪問此頁面
// redirect 地址中的參數, 是前端人員留給本身使用的; 後端會根據業務須要, 添加更多的字段, 而後一塊兒返回前端
const querystr = '?' + stringify({
redirect: encodeURIComponent(`${window.location.origin}/account/redirect?` + stringify({
to: encodeURIComponent(window.location.origin),
origin: encodeURIComponent(window.location.origin),
state: 'login'
})),
type: 'login'
});
const wxOption = {
id: 'wechat-container',
self_redirect: true,
redirect_uri: encodeURIComponent(`//1.1.1.1/api/socials/weixin/authorizations${querystr}`) // 微信回調請求地址
};
複製代碼
按照上面的配置,我描述一下前端、用戶端、微信服務器和後端交互的邏輯:
/api/socials/weixin/authorizations${querystr}
的請求,decode解碼querystr中的信息。而後向微信服務端請求用戶公衆密鑰。根絕先後端的約定(demo中用的是redirect字段),重定向到前端指定的redirect字段,而且拼接用戶公衆密鑰等更多信息。前面流程走完了,如今的狀況是頁面中iframe的二維碼區域,已經被替換成了/account/redirect?...
的內容。
爲了實現通訊,須要在頁面的週期中監聽message
事件,並在組件卸載時,卸載此事件:
componentDidMount() {
// ... ...
window.addEventListener('message', this.msgReceive, false);
}
componentWillUnmount() {
window.removeEventListener('message', this.msgReceive);
}
msgReceive(event) {
// 監測是不是安全iframe
if(!event.isTrusted) {
return;
}
console.log(event.data); // 獲取iframe中傳來的數據, 進一步進行邏輯處理
}
複製代碼
而在/account/redirect?...
路由對應的組件中,咱們須要解析路由中的params參數,按照業務邏輯檢查後,將結果傳遞給前面的頁面:
componentDidMount() {
// step1: 獲取url中params參數
const querys = getQueryVariable(this.props.location.search);
// step2: 檢查querys中的數據是否符合要求 ...
// step3: 向頂級頁面傳遞消息
return window.parent && window.parent.postMessage('data', '*');
}
複製代碼
至此,微信網頁認證的流程完成。
更多:關於iframe通訊的更多細節,請查看MDN的文檔
《前端知識體系》
《設計模式手冊》
《Webpack4漸進式教程》