로그인/등록을 위한 세심한 디자인의 목적은 물론 이를 사이트 전체 차단을 피할 수 있을 만큼 강력하게 애플리케이션의 기본 기능으로 만드는 것입니다.
동시에 새로운 소규모 프로그램을 개발할 때 재사용 기능을 신속하게 제거하고 반복되는 함정을 피할 수 있는 방법을 완전히 분리하고 캡슐화해야 합니다.
로그인 및 등록 모듈은 단지 "계정과 비밀번호를 입력하면 로그인이 완료된다"는 의미라고 생각합니다. 하지만 실제로는 고려해야 할 다양한 문제가 있습니다.
여기서 저는 최근 소규모 프로그램 로그인/등록 모듈을 완료한 후 축적한 디자인 경험과 아이디어를 여기 있는 모든 사람들과 공유하고 싶습니다.
사용자가 미니 프로그램을 탐색하는 과정에서 비즈니스 요구로 인해 사용자의 몇 가지 기본 정보를 얻어야 하는 경우가 많습니다.
제품은 사용자에 대한 정보 요구 사항이 다르며 승인 프로세스도 다릅니다.
첫 번째 유형은 전자상거래 시스템에서 일반적입니다. 사용자가 상품을 구매할 때 사용자의 멀티 플랫폼 계정을 식별하기 위해 휴대폰 번호를 사용하여 연락하는 경우가 많습니다. 휴대폰 번호.
둘째, 사용자 정보의 기본 초기화를 위해서는 WeChat 닉네임, unionId
등과 같은 사용자 정보를 추가로 획득해야 하는 경우가 많으며 사용자 승인을 요청해야 합니다.
세 번째 유형에는 첫 번째 유형과 두 번째 유형이 있습니다.
유니버설 미니 프로그램 로그인 솔루션 및 서비스의 선점을 목표로 사업을 분석하고 변수를 도출해보자.
기술적 디자인을 하기 전에 꼭 필요한 말도 안 되는 이야기를 나누고 몇 가지 컨셉에 대한 기본 튜닝을 진행하세요.
로그인은 영어로 "login"이고, 이에 해당하는 것은 "logout"입니다. 로그인하기 전에 계정이 있어야 하고 "등록"(또는 가입)해야 합니다.
초기 제품에는 로그인/등록 기능이 없었는데, 사용하시는 분들이 늘어나면서 점차 가능해 졌다고 합니다. 상품 자체의 필요상 "이용자"의 식별이 필요합니다.
실제 사회에서 우리 각자는 신분증, 즉 신분증을 가지고 있습니다. 저는 16세가 되었을 때 처음으로 신분증을 받으러 공안국에 갔을 때 '등록'을 완료했습니다. 그런 다음 인터넷 카페에 가서 인터넷 서핑을 하고, 신분증을 긁고 '로그인' 동작을 완료했습니다.
그래서 가상세계의 인터넷에서는 이 신분증이 "계정 + 비밀번호" 입니다.
일반적인 로그인/등록 방법은 다음과 같습니다.
계정 및 비밀번호 등록
인터넷 초기에는 개인 이메일과 휴대폰 범위가 작았습니다. 따라서 사용자는 계정 이름을 생각해야 합니다. 이러한 형태로 QQ 계정을 등록합니다.
이메일 주소 등록
밀레니엄 이후 PC 인터넷 시대가 급속히 대중화되면서 우리는 모두 각자의 개인 이메일 주소를 만들게 되었습니다. 또한 QQ에는 이메일 계정도 함께 제공됩니다. 이메일은 비공개이고 정보를 전달할 수 있기 때문에 대부분의 웹사이트는 등록을 위한 사용자 이름으로 이메일 계정을 사용하기 시작했으며, 등록 과정에서 활성화 이메일을 확인하기 위해 해당 이메일에 로그인하여 당사에 소유권이 있는지 확인해야 합니다. 등록된 이메일 주소입니다.
휴대폰번호등록
인터넷이 대중화되면서 스마트폰과 모바일 인터넷이 급속도로 발전했습니다. 휴대전화는 모든 사람에게 없어서는 안 될 모바일 기기가 되었고, 모바일 인터넷은 모든 사람의 현대 생활에 깊숙이 들어왔습니다. 따라서 현재는 이메일에 비해 휴대전화번호가 개인과 더욱 밀접하게 연관되어 있으며, 모바일 애플리케이션이 점점 더 많이 등장하고 있으며, 휴대전화번호를 사용자명으로 활용하는 등록 방식도 널리 사용되고 있다.
2020년까지 WeChat 사용자 수는 12억 명에 도달했습니다. 글쎄요, 적어도 중국에서는 위챗 계정이 신세대 인터넷 세계의 '신원 표시'가 되었습니다.
WeChat 애플릿의 경우 현재 사용자의 WeChat 계정 ID를 아는 것은 당연합니다. WeChat을 사용하면 미니 프로그램 애플리케이션이 사용자가 인지하지 못하는 사이에 미니 프로그램 애플리케이션에 조용히 "로그인"할 수 있습니다. 이를 우리는 종종 "자동 로그인"이라고 부릅니다.
사실 WeChat 애플릿의 로그인은 기본적으로 기존 웹 애플리케이션의 "Single Sign-On"과 동일한 개념입니다.
Http는 원래 무상태이므로 로그인 상태에 대한 업계의 기본적인 일반 접근 방식은 다음과 같습니다.
위챗 애플릿에서 "JS 로직 레이어"는 브라우저 환경이 아니며 당연히 쿠키
도 없기 때문에 액세스 토큰
방식을 주로 사용합니다. Cookie
,那么通常会使用 access token
的方式。
对于需要更进一步获取用的用户昵称、用户手机号等信息的产品来说。微信出于用户隐私的考虑,需要用户主动同意授权。小程序应用才能获取到这部分信息,这就有了目前流行的小程序「WeChat 미니 프로그램 로그인의 프런트 엔드 디자인 및 구현 이해」、「WeChat 미니 프로그램 로그인의 프런트 엔드 디자인 및 구현 이해」的交互了。
出于不同的用户信息敏感度不同的考虑,微信小程序对于不同的用户信息提供「授权」的方式不尽相同:
wx.getLocation()
的时候,如果用户未授权,则会弹出地址授权界面。wx.getLocation()
直接返回失败。<button open-type="xxx"></button>
方式。wx.authorize()
,提前询问授权,之后需要获取相关信息的时候不用再次弹出授权。梳理清楚了概念之后,我们模块的划分上,可以拆分为两大块:
Session
Auth
微信官方提供的登录方案,总结为三步:
wx.login()
获取一次性加密凭证 code,交给后端。openId
和授权凭证 session_key
。(用于后续服务器端和微信服务器的特殊 API 调用,具体看:微信官方文档-服务端获取开放数据)。如果只是实现这个流程的话,挺简单的。
但要实现一个健壮的登录过程,还需要注意更多的边界情况:
收拢 wx.login()
的调用:
由于 wx.login()
会产生不可预测的副作用,例如会可能导致session_key
失效,从而导致后续的授权解密场景中的失败。我们这里可以提供一个像 session.login()
的方法,掌握 wx.login()
控制权,对其做一系列的封装和容错处理。
调用的时机:
通常我们会在应用启动的时候( app.onLaunch()
wx.getLocation()
을 호출할 때 사용자가 인증되지 않은 경우 주소 인증 인터페이스가 팝업됩니다. 🎜🎜거부되면 해당 창이 다시 팝업되지 않으며, wx.getLocation()
에서 바로 실패를 반환합니다. 🎜🎜🎜🎜<button open-type="xxx"></button>
방법. 🎜🎜지원되는 항목: 사용자 민감한 정보 및 사용자 휴대폰 번호 데이터를 얻으려면 대칭 암호화 및 암호 해독을 위해 백엔드와 협력해야 합니다. 🎜🎜사용자가 거부한 경우에도 버튼을 다시 클릭하면 팝업 창이 나타납니다. 🎜🎜🎜🎜wx.authorize()
를 사용하여 사전에 승인을 요청하세요. 나중에 관련 정보를 얻어야 할 때 승인을 다시 팝업할 필요가 없습니다. 🎜🎜세션
🎜🎜인증: 사용자와의 상호작용을 담당합니다. , 정보 획득 및 업데이트, 권한 제어 등. 모듈 이름은 Auth
🎜🎜openId
및 인증 인증서 session_key
와 교환하여 이 코드를 WeChat 서버로 전송합니다. (후속 서버 측 및 WeChat 서버에 사용되는 특수 API 호출, 자세한 내용은 WeChat 공식 문서 - 서버 측 공개 데이터 획득을 참조하세요.) 🎜🎜백엔드는 WeChat 서버에서 얻은 사용자 자격 증명과 자체 생성된 로그인 자격 증명(토큰)을 프런트엔드로 전송합니다. 프런트엔드는 이를 저장했다가 다음에 요청할 때 백엔드로 가져오므로 어떤 사용자인지 식별할 수 있습니다. 🎜🎜🎜이 프로세스만 구현하면 꽤 간단합니다. 🎜🎜그러나 강력한 로그인 프로세스를 구현하려면 더 많은 극단적인 경우에 주의를 기울여야 합니다. 🎜🎜🎜🎜wx.login()
호출 축소: 🎜🎜 wx.login()
으로 인해 예측할 수 없는 부작용이 발생합니다. 예를 들어 session_key
가 유효하지 않게 되어 후속 인증 암호 해독 시나리오에서 실패할 수 있습니다. 여기서 session.login()
과 같은 메서드를 제공하여 wx.login()
을 제어하고 이에 대한 일련의 캡슐화 및 내결함성 처리를 수행할 수 있습니다. 🎜🎜🎜🎜호출 타이밍: 🎜🎜일반적으로 애플리케이션이 시작될 때 자동 로그인을 시작합니다( app.onLaunch()
). 그러나 애플릿 수명주기 설계 문제로 인해 비동기 문제가 발생합니다. 페이지를 로드할 때 로그인 상태가 필요한 백엔드 API를 호출할 때 이전 비동기 정적 로그인 프로세스가 완료되지 않아 요청이 실패할 수 있습니다. . 🎜물론 로그인 상태가 필요한 첫 번째 인터페이스가 호출될 때 비동기 차단 방식으로 로그인 호출을 시작할 수도 있습니다. 이를 위해서는 잘 설계된 인터페이스 레이어가 필요합니다.
위에 언급된 두 가지 시나리오에 대한 자세한 디자인 아이디어는 아래에서 논의됩니다.
동시 호출 문제:
비즈니스 시나리오에서는 로그인을 실행하는 데 필요한 여러 코드가 있을 수밖에 없습니다. 극단적인 경우 이러한 여러 코드가 동시에 호출을 시작하게 됩니다. 이로 인해 이전 요청이 완료되지 않았더라도 로그인 프로세스가 짧은 시간 내에 여러 번 시작됩니다. 이러한 상황에서는 정자와 난자를 결합하는 과정과 마찬가지로 첫 번째 호출을 차단하고 후속 호출의 결과를 기다릴 수 있습니다.
만료되지 않은 통화 문제:
로그인 상태가 만료되지 않았고 정상적으로 사용할 수 있는 경우 기본적으로 로그인 프로세스를 시작할 필요가 없습니다. 이때 먼저 로그인 상태가 기본적으로 사용 가능한지 확인할 수 있습니다. 그렇지 않은 경우 요청을 시작할 수 있습니다. 그런 다음 강제 로그인을 위해 session.login({ force: true })
와 유사한 매개변수를 제공할 수도 있습니다. session.login({ force: true })
的参数去强行发起登录。
1. 应用启动的时候调用
因为大部分情况都需要依赖登录态,我们会很自然而然的想到把这个调用的时机放到应用启动的时候( app.onLaunch()
)来调用。
但是由于原生的小程序启动流程中, App
,Page
,Component
的生命周期钩子函数,都不支持异步阻塞。
那么我们很容易会遇到 app.onLaunch
发起的「登录过程」在 page.onLoad
的时候还没有完成,我们就无法正确去做一些依赖登录态的操作。
针对这种情况,我们设计了一个WeChat 미니 프로그램 로그인의 프런트 엔드 디자인 및 구현 이해的工具:status
基于WeChat 미니 프로그램 로그인의 프런트 엔드 디자인 및 구현 이해,我们就可以编写这样的代码:
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」的时候再去发起「静默登录」,对于并发的场景,让其他请求等待一下就好了。
以 fly.js 作为 wx.request()
封装的「网络请求层」,做一个简单的例子:
// 发起请求,并表明该请求是需要登录态的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
等。
前端可以在「网络请求层」去监听所有请求的这个状态码,然后发起刷新登录态,再去重放失败的请求:
// 添加响应拦截器fly.interceptors.response.use( (response) => { const code = res.data; // 登录态过期或失效 if ( ['AUTH_EXPIRED', 'AUTH_INVALID'].includes(code) ) { // 刷新登录态 await session.refreshLogin(); // 然后重新发起请求 return fly.request(request); } } )复制代码
那么如果并发的发起多个请求,都返回了登录态失效的状态码,上述代码就会被执行多次。
我们需要对 session.refreshLogin()
做一些特殊的容错处理:
示例代码:
class Session { // .... // 刷新登录保险丝,最多重复 3 次,然后熔断,5s 后恢复 refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT; refreshLoginFuseLocked = false; refreshLoginFuseRestoreTime = 5000; // 熔断控制 refreshLoginFuse(): Promise<void> { if (this.refreshLoginFuseLocked) { return Promise.reject('刷新登录-保险丝已熔断,请稍后'); } if (this.refreshLoginFuseLine > 0) { this.refreshLoginFuseLine = this.refreshLoginFuseLine - 1; return Promise.resolve(); } else { this.refreshLoginFuseLocked = true; setTimeout(() => { this.refreshLoginFuseLocked = false; this.refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT; logger.info('刷新登录-保险丝熔断解除'); }, this.refreshLoginFuseRestoreTime); return Promise.reject('刷新登录-保险丝熔断!!'); } } // 并发回调队列 refreshLoginQueueMaxLength = 100; refreshLoginQueue: any[] = []; refreshLoginLocked = false; // 刷新登录态 refreshLogin(): Promise<void> { return Promise.resolve() // 回调队列 + 熔断 控制 .then(() => this.refreshLoginFuse()) .then(() => { if (this.refreshLoginLocked) { const maxLength = this.refreshLoginQueueMaxLength; if (this.refreshLoginQueue.length >= maxLength) { return Promise.reject(`refreshLoginQueue 超出容量:${maxLength}`); } return new Promise((resolve, reject) => { this.refreshLoginQueue.push([resolve, reject]); }); } this.refreshLoginLocked = true; }) // 通过前置控制之后,发起登录过程 .then(() => { this.clearSession(); wx.showLoading({ title: '刷新登录态中', mask: true }); return this.login() .then(() => { wx.hideLoading(); wx.showToast({ icon: 'none', title: '登录成功' }); this.refreshLoginQueue.forEach(([resolve]) => resolve()); this.refreshLoginLocked = false; }) .catch(err => { wx.hideLoading(); wx.showToast({ icon: 'none', title: '登录失败' }); this.refreshLoginQueue.forEach(([, reject]) => reject()); this.refreshLoginLocked = false; throw err; }); }); // ...}复制代码</void></void>
我们从上面的「静默登录」之后,微信服务器端会下发一个 session_key
给后端,而这个会在需要获取WeChat 미니 프로그램 로그인의 프런트 엔드 디자인 및 구현 이해的时候会用到。
而 session_key
1 대부분의 상황에서는 종속성이 필요하므로 애플리케이션이 시작될 때 호출됩니다. 우리는 자연스럽게 애플리케이션이 시작될 때 이 호출을 호출하는 것을 생각할 것입니다(
app.onLaunch()
).그러나 기본 애플릿 시작 프로세스로 인해
🎜 그러면App
,Page
및Component
의 라이프 사이클 후크 기능은 비동기 차단을 지원하지 않습니다. .app.onLaunch
에 의해 시작된 "로그인 프로세스"가page.onLoad
시점에 완료되지 않았음을 쉽게 접할 수 있으며, 로그인할 수 없습니다. 일부 종속성을 올바르게 만들기 위해. 🎜🎜이 상황을 위해 우리는 상태 머신 툴을 설계했습니다: status🎜🎜🎜🎜상태 머신을 기반으로 다음과 같은 코드를 작성할 수 있습니다:🎜🎜🎜2. 로그인 상태가 필요한 인터페이스가 호출되면 로그인이 시작됩니다🎜🎜🎜또한 로그인 상태가 필요한 더 깊은 노드는 "로그인 상태가 필요한 백엔드 API"가 시작될 때임을 알 수 있습니다. 🎜🎜 그런 다음 "로그인 상태가 필요한 백엔드 API"를 호출할 때 "자동 로그인"을 시작할 수 있습니다. 동시 시나리오의 경우 다른 요청을 기다리면 됩니다. 🎜🎜fly.js를// 定义检查 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' }); } } )复制代码로그인 후 복사로그인 후 복사wx.request()
로 캡슐화된 "네트워크 요청 레이어"로 사용하여 간단한 예를 만드세요: 🎜// 用户登录的阶段export enum AuthStep { // 阶段一:只有登录态,没有用户信息,没有手机号 ONE = 1, // 阶段二:有用户信息,没有手机号 TWO = 2, // 阶段三:有用户信息,有手机号 THREE = 3, }复制代码로그인 후 복사로그인 후 복사3.1.3 사용자 정의 로그인 상태 만료의 내결함성 처리
🎜사용자 정의 로그인 상태가 만료되면 백엔드는AUTH_EXPIRED
,AUTH_INVALID
와 같은 특정 상태 코드를 반환해야 합니다. > 기다려요. 🎜🎜프런트 엔드는 "네트워크 요청 계층"에서 모든 요청의 상태 코드를 모니터링한 다음 로그인 상태 새로 고침을 시작한 다음 실패한 요청을 재생할 수 있습니다. 🎜🎜 그런 다음 여러 요청이 동시에 시작되면 로그인 상태가 반환됩니다. 잘못된 상태 코드입니다. 위 코드는 여러 번 실행됩니다. 🎜🎜// 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 }); }, // ...});复制代码로그인 후 복사로그인 후 복사session.refreshLogin()
에 대해 특별한 내결함성 처리를 수행해야 합니다. 🎜🎜🎜요청 잠금🎜: 동시에 하나의 진행 중인 네트워크 요청만 허용됩니다. 🎜🎜🎜대기 대기열🎜: 요청이 잠긴 후 이 메서드에 대한 모든 호출이 대기열로 푸시되어 반환 결과를 공유하기 위해 네트워크 요청이 완료될 때까지 기다립니다. 🎜🎜🎜회로 차단기 메커니즘🎜: 짧은 시간 내에 여러 번 호출되면 TCP 느린 시작과 유사하게 일정 시간 동안 응답이 중지됩니다. 🎜
🎜샘플 코드: 🎜<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>复制代码로그인 후 복사로그인 후 복사3.1.4 WeChat session_key 만료의 내결함성 처리
🎜위의 "자동 로그인"을 시작한 후, WeChat 서버 Asession_key
는 백엔드로 전송되며, 이는 WeChat 공개 데이터를 획득해야 할 때 사용됩니다. 🎜🎜🎜🎜그리고session_key
는 시간에 민감합니다. 다음은 WeChat의 공식 설명에서 가져온 것입니다: 🎜🎜🎜세션 키 session_key 유효성🎜🎜개발자가 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!