背景
移动app中展示的数据多数都是通过服务器接口获取的,当接口数据与用户相关时,服务端接口会要求客户端把用户信息通过接口发送到服务器。普遍的做法是把用户登录后的token数据发送给服务器的接口。考虑到安全问题,token都有过期时间,token过期后服务端就不能通过这个token查询用户的具体信息了。为了刷新过期token,服务端会提供一个刷新token的接口给客户端使用。
问题分析
- 由于要求上传token的服务端接口会有很多,所以这些接口的调用都需要考虑token过期失效问题。
- 这些接口调用的异常处理中需要增加token过期处理,在token过期的情况下触发token刷新处理。
- token刷新后触发接口的重试请求。
如何使用flow实现
首先我们实现一个刷新token的flow。
private val refreshTokenFlow = flow {
if (expiredToken) {
cachedToken = serverApi.refreshToken("token-0")
expiredToken = false
}
emit(cachedToken)
}
expiredToken变量代表token是否过期,实际开发过程中这个变量的值应该根据过期时间计算得出的。cachedToken变量保存着最新的token值,请求用户相关信息的接口时可以把这个token传递给服务端用于查询。我们可以看到cachedToken的值是通过调用服务端提供的刷新接口获取的。这个flow最终发射的是最新的token值,同时我们也看到这个flow调用刷新接口的逻辑只有token过期时才会被调用。
第二步定义请求用户相关数据的flow
private fun getDataFlow(token: String) = flow {
emit(serverApi.getDataViaToken(token))
}
由于请求用户数据的接口依赖token的值,所以这个flow是通过方法生成的。flow的生成也比较简单,它直接调用服务端的接口并将数据发射出去。
第三步将刷新token的flow和请求用户数据的flow展开
private val userDataFlow = refreshTokenFlow.flatMapConcat{token->
getDataFlow(token)
}
flatMapConcat方法将前面定义的两个流拼接在一起,这时我们要是收集拼接后的userDataFlow,refreshTokenFlow会被收集,flatMapConcat方法接收到 refreshTokenFlow发射的token后开始收集getDataFlow方法返回的flow。这样连个flow的依赖关系通过flatMapConcat完美实现了。
第四步实现token过期重试机制
private val userDataFlow = refreshTokenFlow.flatMapConcat{token->
getDataFlow(token)
}.retryWhen { cause, attempt ->
if (attempt > 1) {
false
} else if(cause is InvalidTokenException) {
expiredToken = true
true
}else{
false
}
}.catch {
msgView.text = it.message
}
基于第三步的flow拼接我们添加了retryWhen和cach两个块。retryWhen块用于实现重试机制,参数cause是前面流程发生的异常,参数attempt代表重试的次数,返回值true代表进行重试,返回值false代表不进行重试。在retryWhen块中我们可以通过cause的类型来判断是否要重试,当cause为InvalidTokenException时代表token过期,所以进行重试并且重置了token过期expiredToken。为了避免无限地进行重试,这里限制重试次数为一次。catch块处理不进行重试时的逻辑,一般会将出错的信息显示到界面上。
总结
通过flow的方式实现token刷新机制相较于传统的方式有如下优势:
- 逻辑简单明了,刷新流程简单清晰直观。flow操作符语义简单明了,链式的方式声明处理过程更容易理解,没有复杂的嵌套。
- 通过声明的方式定义了刷新流程,同时也支持通过声明的方式扩展已有流程。比如这里拼接了刷新token的flow和获取用户数据的flow。
- 重试机制可以根据不同的依赖token接口进行定制处理,接口的重试机制更加灵活方便。这里没有针对重试流程定义新的flow,但在实际应用中我们可以为重试流程定义flow,然后将flow拼接在已有flow后。这样我们可以在不同的地方重用重试流程的flow。