如今的网站开发,都绕不开微信登陆(毕竟微信已经成为国民工具)。虽然文档已经写得很详细,可是对于没有经验的开发者仍是容易踩坑。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}`) // 微信回调请求地址 };
按照上面的配置,我描述一下前端、用户端、微信服务器和后端交互的逻辑:
前端收到微信服务器传来消息,根据wxOption的redirect_uri参数,跳转到此url地址。注意:
/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渐进式教程》