ログイン/登録設計の目的は、もちろん、これをアプリケーションの基本機能にし、サイト全体での発生を回避するのに十分な堅牢性を持たせることです。 。
同時に、分離とカプセル化の方法を十分に考慮する必要があり、新しい小さなプログラムを開発する場合、再利用機能をすぐに削除して、繰り返される落とし穴を回避できます。
ログインと登録モジュールは氷山のようなもので、「アカウントとパスワードを入力すればログインできる」だけだと思っていますが、実際には考慮すべきさまざまな問題があります。
ここでは、最近小さなプログラムのログイン/登録モジュールを完了した後、私が蓄積したデザインの経験とアイデアをすべて共有したいと思います。
ユーザーがミニ プログラムを閲覧する過程で、ビジネス ニーズにより、多くの場合、ユーザーの基本情報を取得する必要があります。一般的なものには次のようなものがあります:
製品が異なれば、ユーザーに対する情報要件も異なり、認証プロセスも異なります。
最初のタイプは、電子商取引システムで一般的です。ユーザーが商品を購入するとき、ユーザーのマルチプラットフォーム アカウントを識別するために、連絡先を作成するために携帯電話番号を使用することがよくあります。ユーザーは携帯電話番号を認証する必要があります。
第 2 に、ユーザー情報の基本的な初期化を行うために、多くの場合、WeChat ニックネーム、unionId
、などの追加のユーザー情報を取得する必要があります。など、ユーザー権限が必要です。
第 3 のタイプには、第 1 のタイプと第 2 のタイプが含まれます。
ユニバーサル ミニ プログラム ログイン ソリューションとサービスのセットを作成することを目標に、ビジネスを分析して変数を導き出しましょう。
技術設計を行う前に、必要なナンセンスについて話し、いくつかの概念について基本的な調整を行ってください。
ログインは英語では「login」、それに相当するのは「logout」です。ログインする前に、アカウントを取得して「登録」(またはサインアップ)する必要があります。
初期製品にはログイン・登録機能がなかったそうですが、利用者が増えるにつれて徐々に使えるようになりました。製品自体のニーズにより、「ユーザー」を識別する必要があります。
現実社会では、私たち一人ひとりがアイデンティティ ID、つまり ID カードを持っています。 16歳になったとき、初めて身分証明書を取りに公安局に行ったときに「登録」をしました。次に、インターネット カフェに行ってインターネットを閲覧し、ID カードをかざして「ログイン」動作を完了しました。
したがって、仮想世界のインターネットの場合、この ID 証明書は「アカウントのパスワード」になります。
一般的なログイン/登録方法は次のとおりです:
アカウントとパスワードの登録
インターネットの初期には、個人用の電子メールと携帯電話の範囲は狭いです。そのため、アカウント名はユーザーが考える必要があり、QQアカウントを登録するのがこのような形になります。
メールアドレス登録
ミレニアムを経て、PCインターネット時代が急速に普及し、私たちは皆、自分自身の個人メールを作成しました。さらに、QQ には電子メール アカウントも付属しています。電子メールは個人的でプライベートなものであり、情報を伝達できるため、ほとんどの Web サイトでは登録用のユーザー名として電子メール アカウントを使用し始めています。また、登録プロセス中に、対応する電子メールにログインしてアクティベーション電子メールをチェックして、登録されたメールアドレスの所有権を持っています。
#携帯電話番号の登録
インターネットの普及後、スマートフォンやモバイルインターネットが急速に発展しました。携帯電話は誰にとっても欠かせないモバイルデバイスとなり、モバイルインターネットはすべての人の現代生活に深く組み込まれています。そのため、現在では電子メールに比べて携帯電話番号はより個人と密接な関係にあり、モバイルアプリケーションも登場しており、携帯電話番号をユーザー名として利用する登録方法も広く普及しています。# 2020 年までに、WeChat ユーザーの数は 12 億人に達すると予想されます。少なくとも中国では、WeChat アカウントは新世代のインターネット世界の「アイデンティティ マーク」となっています。
WeChat アプレットの場合、現在のユーザーの WeChat アカウント ID を知るのは当然です。 WeChat を使用すると、ミニ プログラム アプリケーションがユーザーに気づかれずにミニ プログラム アプリケーションに静かに「ログイン」できます。これは、私たちがよく「サイレント ログイン」と呼ぶものです。
実際、WeChat アプレットのログインは、従来の Web アプリケーションの「シングル サインオン」と本質的に同じ概念です。
HTTP は元々ステートレスであるため、ログイン状態に対する業界の基本的な一般的なアプローチは次のとおりです。
Cookie## はありません# の場合、通常は アクセス トークン
が使用されます。 2.2「認証」について
異なるユーザー情報の機密性が異なるため、WeChat ミニ プログラムは、異なるユーザー情報に対して異なる「承認」メソッドを提供します。
特定の API メソッドであるポップアップ承認を呼び出します。
拒否された場合、ウィンドウは再度ポップアップせず、
サポートされるのは、ユーザーの機密情報とユーザーの携帯電話番号のみです。データを取得するには、対称暗号化および復号化のためにバックエンドと連携する必要があります。
##Authorization##3.1 ログイン実装
フロントエンドは
wx.login() ## を渡します。 # ワンタイム暗号化証明書コードを取得し、バックエンドに渡します。 バックエンドは、ユーザーの一意の識別子session_key
と引き換えに、このコードを WeChat サーバーに送信します。 (後続のサーバー側および WeChat サーバーに使用される特別な API 呼び出し。詳細については、WeChat 公式ドキュメント - サーバー側でのオープン データの取得を参照してください)。
この処理を実装するだけなら非常に簡単です。
wx.login()
wx.login() は予期しない副作用を引き起こすため、たとえば、
session_key が無効になり、その後の認証 復号化シナリオでの失敗。ここで
のようなメソッドを提供して、wx.login()
を制御し、それに対して一連のカプセル化とフォールト トレランスの処理を実行できます。 呼び出しのタイミング
:
) に移動します。サイレントログインを開始します。ただし、アプレットのライフサイクル設計の問題によって引き起こされる非同期の問題があります。ページをロードするとき、ログイン状態を必要とするバックエンド API を呼び出すときに、前の非同期静的ログイン プロセスが完了せず、リクエストが失敗する可能性があります。 もちろん、ログイン ステータスを必要とする最初のインターフェイスが呼び出されたときに、非同期ブロッキング方式でログイン呼び出しを開始することもできます。これには、適切に設計されたインターフェイス層が必要です。 上記の 2 つのシナリオの詳細な設計アイデアについては、以下で説明します。
同時呼び出しに関する問題:
ビジネス シナリオでは、ログインをトリガーする必要があるコードが複数存在することは避けられません。極端な状況に遭遇すると、これらの複数のコードが同時に通話を開始します。そのため、前のリクエストが完了していない場合でも、ログイン プロセスが短期間に複数回開始されることになります。この状況では、精子と卵子を結合するプロセスと同様に、最初の呼び出しをブロックし、後続の呼び出しの結果を待つことができます。
有効期限が切れていない通話に関する問題:
ログイン ステータスの有効期限が切れていない場合は、通常どおり使用できます。デフォルトでは、その必要はありません。再度電話をかけるには、ログイン プロセスを開始してください。この時点で、まずデフォルトでログイン状態が利用可能かどうかを確認し、利用できない場合はリクエストを開始できます。その後、session.login({force: true })
のようなパラメータを指定してログインを強制することもできます。
1. アプリケーションの起動時に呼び出されます
理由は、ほとんどの場合、これらはすべてログイン状態に依存する必要があるため、アプリケーションの起動時にこの呼び出しを呼び出すことを自然に考えることになります (app.onLaunch()
)。
ただし、ネイティブ アプレットの起動プロセスのため、App
、Page
、Component
のライフサイクル フック関数は非同期ブロックをサポートしていません。 . .
この場合、app.onLaunch
によって開始された「ログイン プロセス」が page.onLoad
で完了していないことが簡単にわかり、ログインできなくなります。一部の操作はログイン ステータスに依存します。
この状況に対応して、ステート マシン ツールを設計しました: status
ステート マシンに基づいて、次のようなコードを記述できます:
import { Status } from '@beautywe/plugin-status';// on app.jsApp({ status: { login: new Status('login'); }, onLaunch() { session // 发起静默登录调用 .login() // 把WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する设置为 success .then(() => this.status.login.success()) // 把WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する设置为 fail .catch(() => this.status.login.fail()); }, });// on page.jsPage({ onLoad() { const loginStatus = getApp().status.login; // must 里面会进行状态的判断,例如登录中就等待,登录成功就直接返回,登录失败抛出等。 loginStatus().status.login.must(() => { // 进行一些需要登录态的操作... }); }, });复制代码
2. 「ログイン状態を必要とする最初のインターフェース」が呼び出されたときにログインを開始します
さらに、より深いレベルのログイン状態が必要であることがわかります。 「ログインステータスを必要とするバックエンド API」を起動します。
その後、「ログイン ステータスを必要とするバックエンド API」を呼び出すときに「サイレント ログイン」を開始できます。同時シナリオの場合は、他のリクエストを待機させるだけです。
wx.request()
によってカプセル化された「ネットワーク リクエスト レイヤー」として fly.js を使用して、簡単な例を作成します。
// 发起请求,并表明该请求是需要登录态的fly.post('https://...', params, { needLogin: true });// 在 fly 拦截器中处理逻辑fly.interceptors.request.use(async (req)=>{ // 在请求需要登录态的时候 if (req.needLogin !== false) { // ensureLogin 核心逻辑是:判断是否已登录,如否发起登录调用,如果正在登录,则进入队列等待回调。 await session.ensureLogin(); // 登录成功后,获取 token,通过 headers 传递给后端。 const token = await session.getToken(); Object.assign(req.headers, { [AUTH_KEY_NAME]: token }); } return req; });复制代码
カスタム ログイン状態の有効期限が切れると、バックエンドは AUTH_EXPIRED
、AUTH_INVALID
などの特定のステータス コードを返す必要があります。
フロントエンドは、「ネットワーク リクエスト レイヤー」ですべてのリクエストのステータス コードを監視し、ログイン ステータスの更新を開始して、失敗したリクエストを再生します。同時リクエストが多数ある 各リクエストは、ログイン ステータスが無効であることを示すステータス コードを返し、上記のコードは複数回実行されます。
session.refreshLogin():
// 添加响应拦截器fly.interceptors.response.use( (response) => { const code = res.data; // 登录态过期或失效 if ( ['AUTH_EXPIRED', 'AUTH_INVALID'].includes(code) ) { // 刷新登录态 await session.refreshLogin(); // 然后重新发起请求 return fly.request(request); } } )复制代码
3.1.4 WeChat session_key の有効期限のフォールトトレラントな処理
がバックエンドに与えられ、これは WeChat オープン データを取得する必要がある場合に使用されます。
And
session_key is time-sensitive. 以下は WeChat の公式説明からの引用です:
開発者が session_key が正しくないために署名検証の失敗または復号化の失敗が発生した場合は、session_key に関連する次の注意事項に注意してください。
- wx.login 调用时,用户的 session_key 可能会被更新而致使旧 session_key 失效(刷新机制存在最短周期,如果同一个用户短时间内多次调用 wx.login,并非每次调用都导致 session_key 刷新)。开发者应该在明确需要重新登录时才调用 wx.login,及时通过 auth.code2Session 接口更新服务器存储的 session_key。
- 微信不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序,session_key 有效期越长。
- 开发者在 session_key 失效时,可以通过重新执行登录流程获取有效的 session_key。使用接口 wx.checkSession可以校验 session_key 是否有效,从而避免小程序反复执行登录流程。
- 当开发者在实现自定义登录态时,可以考虑以 session_key 有效期作为自身登录态有效期,也可以实现自定义的时效性策略。
翻译成简单的两句话:
session_key
时效性由微信控制,开发者不可预测。wx.login
可能会导致 session_key
过期,可以在使用接口之前用 wx.checkSession
检查一下。而对于第二点,我们通过实验发现,偶发性的在 session_key
已过期的情况下,wx.checkSession
会概率性返回 true
社区也有相关的反馈未得到解决:
所以结论是:wx.checkSession
可靠性是不达 100% 的。
基于以上,我们需要对 session_key
的过期做一些容错处理:
session_key
的请求前,做一次 wx.checkSession
操作,如果失败了刷新登录态。session_key
解密开放数据失败之后,返回特定错误码(如:DECRYPT_WX_OPEN_DATA_FAIL
),前端刷新登录态。示例代码:
// 定义检查 session_key 有效性的操作const ensureSessionKey = async () => { const hasSession = await new Promise(resolve => { wx.checkSession({ success: () => resolve(true), fail: () => resolve(false), }); }); if (!hasSession) { logger.info('sessionKey 已过期,刷新登录态'); // 接上面提到的刷新登录逻辑 return session.refreshLogin(); } return Promise.resolve(); }// 在发起请求的时候,先做一次确保 session_key 最新的操作(以 fly.js 作为网络请求层为例)const updatePhone = async (params) => { await ensureSessionKey(); const res = await fly.post('https://xxx', params); }// 添加响应拦截器, 监听网络请求返回fly.interceptors.response.use( (response) => { const code = res.data; // 登录态过期或失效 if ( ['DECRYPT_WX_OPEN_DATA_FAIL'].includes(code)) { // 刷新登录态 await session.refreshLogin(); // 由于加密场景的加密数据由用户点击产生,session_key 可能已经更改,需要用户重新点击一遍。 wx.showToast({ title: '网络出小差了,请稍后重试', icon: 'none' }); } } )复制代码
在用户信息和手机号获取的方式上,微信是以 <button open-type="'xxx'"></button>
的方式,让用户主动点击授权的。
那么为了让代码更解耦,我们设计这样三个组件:
<user-contaienr getuserinfo="onUserInfoAuth"></user-contaienr>
: 包装点击交互,通过 <slot></slot>
支持点击区域的自定义UI。<phone-container getphonennmber="onPhoneAuth"></phone-container>
: 与 <user-container></user-container>
同理。<auth-flow></auth-flow>
: 根据业务需要,组合 <user-container></user-container>
、<phone-container></phone-container>
组合来定义不同的授权流程。以开头的业务场景的流程为例,它有这样的要求:
那么授权的阶段可以分三层:
// 用户登录的阶段export enum AuthStep { // 阶段一:只有登录态,没有用户信息,没有手机号 ONE = 1, // 阶段二:有用户信息,没有手机号 TWO = 2, // 阶段三:有用户信息,有手机号 THREE = 3, }复制代码
AuthStep
的推进过程是不可逆的,我们可以定义一个 nextStep
函数来封装 AuthStep 更新的逻辑。外部使用的话,只要无脑调用 nextStep
方法,等待回调结果就行。
示例伪代码:
// auth-flow componentComponent({ // ... data: { // 默认情况下,只需要到达阶段二。 mustAuthStep: AuthStep.TWO }, // 允许临时更改组件的需要达到的阶段。 setMustAuthStep(mustAuthStep: AuthStep) { this.setData({ mustAuthStep }); }, // 根据用户当前的信息,计算用户处在授权的阶段 getAuthStep() { let currAuthStep; // 没有用户信息,尚在第一步 if (!session.hasUser() || !session.hasUnionId()) { currAuthStep = AuthStepType.ONE; } // 没有手机号,尚在第二步 if (!session.hasPhone()) { currAuthStep = AuthStepType.TWO; } // 都有,尚在第三步 currAuthStep = AuthStepType.THREE; return currAuthStep; } // 发起下一步授权,如果都已经完成,就直接返回成功。 nextStep(e) { const { mustAuthStep } = this.data; const currAuthStep = this.updateAuthStep(); // 已完成授权 if (currAuthStep >= mustAuthStep || currAuthStep === AuthStepType.THREE) { // 更新全局的授权WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する,广播消息给订阅者。 return getApp().status.auth.success(); } // 第一步:更新用户信息 if (currAuthStep === AuthStepType.ONE) { // 已有密文信息,更新用户信息 if (e) session.updateUser(e); // 更新到视图层,展示对应UI,等待获取用户信息 else this.setData({ currAuthStep }); return; } // 第二步:更新手机信息 if (currAuthStep === AuthStepType.TWO) { // 已有密文信息,更新手机号 if (e) this.bindPhone(e); // 未有密文信息,弹出获取窗口 else this.setData({ currAuthStep }); return; } console.warn('auth.nextStep 错误', { currAuthStep, mustAuthStep }); }, // ...});复制代码
那么我们的 <auth-flow></auth-flow>
中就可以根据 currAuthStep
和 mustAuthStep
来去做不同的 UI 展示。需要注意的是使用 <user-container></user-container>
、<phone-container></phone-container>
的时候连接上 nextStep(e)
函数。
示例伪代码:
<view> <!-- 已完成授权 --> <block> <view>已完成授权</view> </block> <!-- 未完成授权,第一步:WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する --> <block> <user-container> <view>WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する</view> </user-container> </block> <!-- 未完成授权,第二步:WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する --> <block> <phone-container> <view>WeChat ミニ プログラム ログインのフロントエンド設計と実装を理解する</view> </phone-container> </block> </view>复制代码
到这里,我们制作好了用来承载授权流程的组件 <auth-flow></auth-flow>
,那么接下来就是决定要使用它的时机了。
我们梳理需要授权的场景:
点击某个按钮,例如:购买某个商品。
对于这种场景,常见的是通过弹窗完成授权,用户可以选择关闭。
浏览某个页面,例如:访问个人中心。
对于这种场景,我们可以在点击跳转某个页面的时候,进行拦截,弹窗处理。但这样的缺点是,跳转到目标页面的地方可能会很多,每个都拦截,难免会错漏。而且当目标页面作为「小程序落地页面」的时候,就避免不了。
这时候,我们可以通过重定向到授权页面来完成授权流程,完成之后,再回来。
那么我们定义一个枚举变量:
// 授权的展示形式export enum AuthDisplayMode { // 以弹窗形式 POPUP = 'button', // 以页面形式 PAGE = 'page', }复制代码
我们可以设计一个 mustAuth
方法,在点击某个按钮,或者页面加载的时候,进行授权控制。
伪代码示例:
class Session { // ... mustAuth({ mustAuthStep = AuthStepType.TWO, // 需要授权的LEVEL,默认需要获取用户资料 popupCompName = 'auth-popup', // 授权弹窗组件的 id mode = AuthDisplayMode.POPUP, // 默认以弹窗模式 } = {}): Promise<void> { // 如果当前的授权步骤已经达标,则返回成功 if (this.currentAuthStep() >= mustAuthStep) return Promise.resolve(); // 尝试获取当前页面的 <auth-popup></auth-popup> 组件实例 const pages = getCurrentPages(); const curPage = pages[pages.length - 1]; const popupComp = curPage.selectComponent(`#${popupCompName}`); // 组件不存在或者显示指定页面,跳转到授权页面 if (!popupComp || mode === AuthDisplayMode.PAGE) { const curRoute = curPage.route; // 跳转到授权页面,带上当前页面路由,授权完成之后,回到当前页面。 wx.redirectTo({ url: `authPage?backTo=${encodeURIComponent(curRoute)}` }); return Promise.resolve(); } // 设置授权 LEVEL,然后调用 <auth-popup> 的 nextStep 方法,进行进一步的授权。 popupComp.setMustAuthStep(mustAuthStep); popupComp.nextStep(); // 等待成功回调或者失败回调 return new Promise((resolve, reject) => { const authStatus = getApp().status.auth; authStatus.onceSuccess(resolve); authStatus.onceFail(reject); }); } // ...}复制代码</auth-popup></void>
那么我们就能在按钮点击,或者页面加载的时候进行授权拦截:
Page({ onLoad() { session.mustAuth().then(() => { // 开始初始化页面... }); } onClick(e) { session.mustAuth().then(() => { // 开始处理回调逻辑... }); } })复制代码
当然,如果项目使用了 TS 的话,或者支持 ES7 Decorator 特性的话,我们可以为 mustAuth
提供一个装饰器版本:
export function mustAuth(option = {}) { return function( _target, _propertyName, descriptor, ) { // 劫持目标方法 const method = descriptor.value; // 重写目标方法 descriptor.value = function(...args: any[]) { return session.mustAuth(option).then(() => { // 登录完成之后,重放原来方法 if (method) return method.apply(this, args); }); }; }; }复制代码
那么使用方式就简单一些了:
Page({ @mustAuth(); onLoad() { // 开始初始化页面... } @mustAuth(); onClick(e) { // 开始处理回调逻辑... } });复制代码
作为一套可复用的小程序登录方案,当然需要去定义好前后端的交互协议。
那么整套登录流程下来,需要的接口有这么几个:
静默登录 silentLogin
token
给前端token
前端会存起来,每个请求都会带上nickname
和phone
字段,前端用于计算当前用户的授权阶段。当然这个状态的记录可以放在后端,但是我们认为放在前端,会更加灵活。更新用户信息 updateUser
iv
, encryptedData
unionId
等nickname
等用户基本信息。session
中,用于计算授权阶段。更新用户手机号 updatePhone
iv
, encryptedData
session
中,用于计算授权阶段。解绑手机号 unbindPhone
登录 logout
入参:-
出参:-
说明:后端主动过期登录态,成功与否,走业务定义的前后端协议。
最后我们来梳理一下整体的「登录服务」的架构图:
由「登录服务」和「底层建设」组合提供的通用服务,业务层只需要去根据产品需求,定制授权的流程 <auth-flow></auth-flow>
,就能满足大部分场景了。
本篇文章通过一些常见的登录授权场景来展开来描述细节点。
整理了「登录」、「授权」的概念。
然后分别针对「登录」介绍了一些关键的技术实现:
session_key
期限切れフォールトトレラントな処理「認可」に関しては、UI部分の設計ロジックがあり、コンポーネントの分割も必要となります:
関連する無料学習の推奨事項:WeChat ミニ プログラム開発##
以上がWeChat ミニ プログラム ログインのフロントエンド設計と実装を理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。