news 2026/6/16 2:10:18

基于大模型的个人消费分析和理财助手:开发日志 6

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于大模型的个人消费分析和理财助手:开发日志 6

基于大模型的个人消费分析和理财助手:开发日志 6

Token 刷新拦截器重构:从复杂到简洁

背景与问题

用户认证体系的 Token 通常有较短的过期时间(如 15 分钟),配合较长的 Refresh Token(如 7 天)。当 Token 过期时,拦截器需要自动捕获 401 错误、刷新 Token、然后重试原始请求。

最初的实现方案采用了一种基于 UUID 的请求追踪机制:每个请求分配唯一 UUID 放入请求头,拦截器内部维护一个Map<String, _PendingRequest>映射表,跟踪哪些请求在等待重试,哪些已经重试过。这个方案在复杂度上走了弯路——一个 176 行的拦截器,代码相当难维护。

旧方案的问题

旧方案的核心流程:

1. 每个请求附加一个 UUID(x-dio-uuid 请求头) 2. onRequest 将请求信息存入 _pendingRequests 映射 3. onError 时,从错误中提取 UUID,查找对应的 pending request 4. 检查 hasTried 标记,决定是否重试 5. 刷新 Token 后,遍历 _pendingRequests 中的所有请求,逐个重试

问题在于:

  • 状态管理过于分散:UUID 生成、提取、映射表查找分散在多个方法中
  • 竞态处理复杂:多个请求同时过期时,需要等待刷新 Token 完成,然后批量重试——Completer+ 映射表组合逻辑难以推理
  • 调试困难:UUID 不携带业务含义,无法从日志中追踪具体请求
  • 代码量膨胀_PendingRequest数据类、_genUUID()_extractUUID()_retry()四个辅助函数/实体

新方案:retryTimes + 直接重试

classAuthInterceptorextendsInterceptor{staticconstMAX_RETRY_TIMES=1;Completer<bool>?_refreshCompleter;// 注意:不再需要 _pendingRequests 映射表!}

核心变更:

  1. retryTimes替代 UUID 映射表
// onRequest —— 初始化重试次数finalretryTimes=options.extra['retryTimes']asint?;if(retryTimes==null){options.extra['retryTimes']=0;}// onError —— 检查并递增finalretryTimes=err.requestOptions.extra['retryTimes']asint;if(retryTimes>=MAX_RETRY_TIMES){returnhandler.next(err);// 超过上限,直接返回错误}err.requestOptions.extra['retryTimes']=retryTimes+1;

options.extra是 Dio 提供的请求附加数据容器,本质上是一个Map<String, dynamic>。利用它来存储重试次数,将请求状态附着在请求对象本身,彻底消除了外部映射表。

  1. 直接重试而非通过 handler 转发
// 旧方案:通过 handler 转发// _pendingRequests[uuid] = (...);// 然后在刷新完成后逐个 pending.value.handler.resolve(resp)// 新方案:直接重试finalresp=await_dio.fetch(err.requestOptions);returnhandler.resolve(resp);

直接调用_dio.fetch()重新发送请求——dio.fetch是一个底层方法,它会重新走拦截器链,包括onRequest。这意味着:

  • 重试时会自动附加最新的 Token(因为onRequest中的_attachToken会读取最新的 token)
  • 重试次数 +1 后不会再次尝试刷新 Token(因为retryTimes >= MAX_RETRY_TIMES会短路)
  1. 区分 Token 过期和提前刷新
// Token 即将过期 —— 异步刷新,不阻塞请求if(!JwtUtils.isExpired(userController.token.value)&&JwtUtils.isExpired(userController.token.value,leadSeconds:60)){_doRefresh(logoutWhenFailed:false);// 静默刷新returnhandler.next(options);}// Token 已过期 —— 必须等待刷新后再重试if(JwtUtils.isExpired(userController.token.value)){// ...refreshSuccess=await_refreshCompleter?.future??false;// 确认刷新成功后,再走 _dio.fetch 重试}

leadSeconds: 60参数是关键设计——在 Token 实际过期前 60 秒就触发刷新。这样并发请求中某个慢请求执行到服务端时,Token 可能已经被刷新了,避免了一次不必要的 401。

logoutWhenFailed参数的引入也很有讲究:

  • "提前刷新"场景下刷新失败 → 不退出登录,因为当前 Token 还有效
  • "过期刷新"场景下刷新失败 → 退出登录,因为连 Refresh Token 也可能过期了

附带的 Bug 修复:FormData 不可复用

在重写拦截器的过程中,发现一个隐蔽的坑:

// 重试请求if(err.requestOptions.dataisFormData){// FormData 不可复用,需要克隆err.requestOptions.data=(err.requestOptions.dataasFormData).clone();}finalresp=await_dio.fetch(err.requestOptions);

Dio 的FormData设计为一次性消费——当它被dio.post(data: formData)发送后,内部的字节流已经被读取,不可回放。重试时如果直接复用原始的RequestOptions,FormData 会是一个空流。这里使用.clone()创建一个新的 FormData 副本,解决重试时的数据丢失问题。

同时需要import 'package:get/get.dart' hide FormData隐藏 GetX 包中的FormData类型,避免与 Dio 的FormData命名冲突。

总结

方面旧方案(UUID 映射)新方案(retryTimes)
代码行数176 行69 行(-61%)
状态管理外部映射表options.extra内嵌
重试逻辑遍历映射表批量重试单个请求直接重试
辅助类型_PendingRequest数据类无需额外类型
可调试性UUID 无意义retryTimes直接可读

这个重构的收益远不止代码量的减少——消除了全局状态的复杂度是最大的胜利。旧的映射表方案在并发场景下容易出现竞态条件(如刷新完成前就有新请求加入映射表),而新方案中每个请求独立管理自己的重试,互不干扰,显著降低了心智负担。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 12:40:23

钢结构工程高强度螺栓安装工艺流程图

钢结构工程高强度螺栓安装工艺流程图 高强螺栓安装前,构件将采用临时安装螺栓进行临时固定,待高强螺栓完成部分安装时,拆除临时安装螺栓,以高强螺栓代替。高强螺栓分两次拧紧,第一次初拧到标准予拉力的60-80%,第二次终拧到标准予拉力的100%。 工艺流程 第一步:临时螺…

作者头像 李华
网站建设 2026/6/14 5:48:41

深度解析Wan2.2-TI2V-5B:混合专家架构如何重塑720P视频生成新范式

深度解析Wan2.2-TI2V-5B&#xff1a;混合专家架构如何重塑720P视频生成新范式 【免费下载链接】Wan2.2-TI2V-5B Wan2.2-TI2V-5B是一款开源的先进视频生成模型&#xff0c;基于创新的混合专家架构&#xff08;MoE&#xff09;设计&#xff0c;显著提升了视频生成的质量与效率。该…

作者头像 李华
网站建设 2026/6/13 17:15:45

Shopify Python API:官方 Shopify Admin SDK

文章目录Shopify Python API&#xff1a;官方 Shopify Admin SDK1、认证流程2、API 调用方式3、应用计费4、需要注意的事5、安装Shopify Python API&#xff1a;官方 Shopify Admin SDK Shopify Python API 在 GitHub 上已经拿到 1,427 Star。 这是 Shopify 官方维护的 Pytho…

作者头像 李华