业务前端-移动端接口请求的登录封装

740 阅读6分钟

1、登录系统设计

我们要有 1个认知,登录系统设计是一个很大的问题。

从业务上来讲,涉及几个方面

  • 用户的账户类型,如,手机用户,证件用户(身份证,军官证等),其他社交帐号登录,用户有分vip等级
  • 自家app登录交互,如免登,强登录,密码登录,otp登录,微信帐号登录,支付宝帐号登录,验证后,说不定还有指纹、人脸……
  • 第3方app登录,如开发页面投放到微信,我们是否可以打通微信免登
  • ……

是不是很多,我还没说技术方面,从技术架构上来说,稍微大或者成体系的公司,登录系统有专门的平台团队研发和维护,涉及很多工种,前端、后端、ios、安卓,RN……

作为业务前端开发,面对这1个庞大体系,我们可能从技术上来说,没有亲身亲历过,掌握得肯定是不那么透彻。但是从业务场景上,我们是实实在在的使用者(接入方),需要掌握,如怎么在微信免登、怎么接入人脸登录等,了解每个场景下的登录负责人,遇到登录问题,及时联系对应负责人,协助帮忙看问题。

有兴趣,也可以看看知乎上这个问题大型网站的用户登录系统是如何设计的?

在技术上来讲,也不是一点技术含量没有?业务方前端开发,也是需要进行二次封装的。

2、业务方登录

我目前接触移动端的项目有 csr,ssr,RN,三种类型项目,但这三种类型的项目接口请求和登录方法设计思路是一样。

按照场景来说,业务登录大多数更接口挂钩,接口调用分为2种:单个请求和多个请求并发。实际上我们的项目一定是按照多个请求来设计,没有哪个团队会说每个页面调用1个接口。

2.1 请求登录设计

假设有4个接口请求,3个请求要登录。这种情况登录交互设计如下:

image.png

首先,这4接口请求并行,其中3个请求,只要其中1个跳蹬路,另外2个响应不再处理登录。登录成功后,这个时候怎么处理了,即登录成功后回调,或者登录成后的钩子怎么执行。我想到的有3种处理方案

2.2 登录成功后处理

方案1,登录成功后,直接刷新

优点:逻辑处理简单。

缺点:体验不好,性能是大问题,很浪费流量(cdn费用有可能浪费百万元以上,不要觉得这个流量不起眼)。

适用场景:流量极少、不想花太大人力的项目

方案2,登录成功后,重新调用接口

优点:处理逻辑比方案1稍微复杂一些,但逻辑处理还是简单。

缺点:相对方案1流量少很多,但是用户体验不是最优。如request4不需要登录,登录成功后是否还需调用了。

适用场景:流量大,但不想登录处理逻辑过于复杂,后续接入新开发,能快速上手。我们的大多数项目可能就这类。

方案3,登录成功后,只调用需要登录接口

这个方案实际就是方案1,只不过做了更精准的处理。

优点:如果做到极致,那么这个方案的性能和用户体验,是最佳的。

缺点:

  • 处理逻辑非常复杂,要对业务非常熟悉,哪些接口需要登录,哪些接口不需要登录,哪些接口虽然不需要登录,但是在有登录态的时候,返回出参是不一样的。
  • 不需要重新调用接口,那么这些接口的数据该怎么缓存。
  • 怎么保证登录成功后每个接口响应依赖处理逻辑,和没有跳登录是一样的。

适用场景:这个方案,常见于RN项目。但h5 app混合开发,虽然不多。如: 我们在电商买了一个东西,加入到购物车后,准备点击结算,但没有登录,这个时候就会让你登录,登录成功回来后,显然是不会让用户重复点击结算按钮,而是登录成功后执行点击结算按钮的逻辑。

总结一下

这3个方案的,不是孰优孰劣,一定要看情况使用。

3、请求登录代码封装

请求登录封装思路见下图

image.png

登录代码封装思路

const globalLoginInfo = {
	// 判断是否正在登录,true,不跳登录;false,跳登录
	isLogining: false,
	handleAfterloginSuccess: () => {},
}

export function setGlobalLoginInfo (otps) {
	// 这么处理,防止 globalLoginInfo 被污染 
	Object.assign(globalLoginInfo)
}

export function getGlobalLoginInfo () {
	// 这么处理,防止 globalLoginInfo 被污染 
	return cloneDeep(globalLoginInfo)
}

// 判断是否登录
export function getIsLogin() {
	// ...
}

// 登出
export function loginOut() {
	// ...
}


// 登录
export function login () {
	const {
		isLogining,
		handleAfterloginSuccess
	} = getGlobalLogin()
	// 正在登录中,跳出函数
	if (isLogining) {
		return
	}
	
	// 跳登录,设置正在登录中
	setGlobalLoginInfo({ isLogining: true })
	// 登出,清除之前登录
	loginOut()
	// 封装一个事件,用来执行登录成功后的回调
	event.on('loginSuccess', () => {
		setGlobalLoginInfo({ isLogining: false })
		// 在自家app内
		if (isInApp) {
			handleAfterloginSuccess 
				? handleAfterloginSuccess()
				: window.location.reload()
			return
		}
		window.location.reload()			
	})
	// 跳转登录页面
	sdk.login()
}


接口请求的登录封装

// 接口请求
async function request ({ params, url, method, handleAfterLoginSuccess, isNeedLogin }) {
	// ...
	setGlobalLoginInfo({ handleAfterLoginSuccess })
	// 如果使用axios,那就使用 beforeReuqest 和 afterResponse
	const res = await fetch(url, method, params)
	// ...
	const isLogin = getIsLogin(res)
	if (isNeedLogin && !isLogin ) {
		login()
	}
	// ...
}

业务页面调用

// 页面初始化
function pageInit () {
	// 设置当前页面的登录成功后的回调
	setGlobalLoginInfo({ handleAfterLoginSuccess: () => pageInit()})
	Promise.all([
		request1(),
		request2(),
		request3(),
		request4()
	])
}

4、补充:ios 的 wkView 的 cookie 问题

现在移动端项目,大多数是在自己公司的app webView运行,涉及到native层。直白的说, 调用接口请求方法,不再是xmlHttpRequest 或者w3c的fetch,有可能是自己公司native封装。我们简单取名叫 native 的 http request。

那这有什么问题?问题在于ios的 wkWebView(ios app 的主流webView) native 层 cookie 和 web 层 cookie 是独立、不共享的。导致即使拥web层请求又使用native请求,那就存在一个cookie同步过程。常见的项目是ssr项目。

ssr 项目有服务端请求、客户端请求,客户端请求是使用 native 的 http request,能够拿到native 层cookie,服务端请求,使用的web层的请求,只能拿到 web层 cookie。那如何使服务端拿到cookie呢?显然是将native层 cookie 同步到 web 层 cookie就可以了,也就是下图,黄色方块内容,native 单独封装了一个page reload,执行页面刷新,就会同步cookie

image.png

ps: 严格来讲, ssr项目不应该通过执行native page reload 来同步cookie,这样会导致静态资源重复加载。但是在我的团队项目实践中,ssr 项目的服务接口请求,比客户端请求响应过,原因是我司服务端接口请求网络是内部网络,不需要执行https速度快,当然速度快还有其他原因。刷新页面比客户端重新调用接口后,页面渲染更快。所以,为了体验好,团队ssr项目登录成功使用上面方案1,即登录成功后刷新页面。 所以,很多时候,问题的解决方案,往往根据实践情况变更的,方案不是永层不变得。

(完)