Flutter中tryAutoLogin函数优化及类Facebook自动登录实现咨询
Flutter 自动登录(类似Facebook)的tryAutoLogin函数优化方案
现有代码的核心问题
你当前的tryAutoLogin和相关函数存在几个逻辑漏洞和错误,直接运行会导致自动登录失效或接口请求失败:
- 混淆refreshToken和accessToken:把refreshToken直接赋值给
_token,但refreshToken只能用来换accessToken,不能作为接口请求的凭证 - 刷新token后未持久化新数据:调用
_refreshNewToken后没有更新SharedPreferences里的用户数据,下次启动还是用旧的过期token - 未处理刷新失败场景:如果refreshToken过期或接口报错,当前逻辑依然返回
true,导致后续请求全部失败 - 时间戳处理错误:API返回的
tokenExpireAt是毫秒级时间戳,你用Duration(seconds: ...)计算过期时间,会导致过期时间严重错误 - 空安全与异常处理缺失:解析本地存储数据时没有处理null或格式错误,容易引发崩溃
- 返回值逻辑错误:无论token是否有效、刷新是否成功,都无条件返回
true,无法正确告知上层自动登录是否成功
优化后的完整代码
1. 修正SignIn函数(时间戳处理+空安全)
Future<void> signIn(String userName, String pass) async { final url = Uri.parse('MyAPI_login'); debugPrint("$userName / $pass"); try { var response = await http.post(url, body: { 'user_name': userName, 'password': pass, 'platform': 'mobile', 'device_token': '', }); final responseData = jsonDecode(response.body); // 校验API返回状态 if (responseData['status'] != 200 || responseData['data'] == null) { throw Exception(responseData['message'] ?? '登录失败'); } final data = responseData['data']; _userName = userName; _token = data['accessToken']; // 修正:毫秒级时间戳转DateTime final tokenExpireMs = int.parse(data['tokenExpireAt'].toString()); _expiryDate = DateTime.fromMillisecondsSinceEpoch(tokenExpireMs); _refreshToken = data['refreshToken']; final refreshExpireMs = int.parse(data['refreshTokenExpireAt'].toString()); _timerRefreshToken = refreshExpireMs; notifyListeners(); // 持久化用户数据 final prefs = await SharedPreferences.getInstance(); final userData = json.encode({ '_userId': _userName, 'token': _token, 'expiryDate': _expiryDate!.toIso8601String(), 'refreshToken': _refreshToken, 'refreshTokenExpireAt': _timerRefreshToken.toString(), }); await prefs.setString('userData', userData); } catch (error) { throw Exception(error.toString()); } }
2. 重构tryAutoLogin函数(完善逻辑+异常处理)
Future<bool> tryAutoLogin() async { final prefs = await SharedPreferences.getInstance(); // 检查本地是否有用户数据 final userDataStr = prefs.getString('userData'); if (userDataStr == null) { return false; } try { final extractedUserData = json.decode(userDataStr) as Map<String, dynamic>; // 解析并校验accessToken过期时间 final expiryDate = DateTime.parse(extractedUserData['expiryDate']); final now = DateTime.now(); if (expiryDate.isAfter(now)) { // accessToken未过期,直接加载本地数据 _userName = extractedUserData['_userId']; _token = extractedUserData['token']; _refreshToken = extractedUserData['refreshToken']; _timerRefreshToken = int.parse(extractedUserData['refreshTokenExpireAt']); notifyListeners(); return true; } // accessToken过期,检查refreshToken是否有效 final refreshExpireMs = int.parse(extractedUserData['refreshTokenExpireAt']); final refreshExpiryDate = DateTime.fromMillisecondsSinceEpoch(refreshExpireMs); if (refreshExpiryDate.isBefore(now)) { // refreshToken也过期,清除本地数据,返回自动登录失败 await prefs.remove('userData'); return false; } // 调用刷新token接口,获取新凭证 final refreshSuccess = await _refreshNewToken(extractedUserData['refreshToken']); if (refreshSuccess) { notifyListeners(); return true; } else { // 刷新失败,清除本地数据 await prefs.remove('userData'); return false; } } catch (e) { // 解析数据出错,清除本地数据 await prefs.remove('userData'); debugPrint('自动登录解析数据失败: $e'); return false; } }
3. 完善_refreshNewToken函数(处理返回结果+更新本地存储)
Future<bool> _refreshNewToken(String oldRefreshToken) async { final url = Uri.parse('MyAPI_refreshtoken'); try { var response = await http.post(url, body: {'refreshToken': oldRefreshToken}); final responseData = jsonDecode(response.body); // 校验接口返回状态 if (responseData['status'] != 200 || responseData['data'] == null) { debugPrint('刷新token失败: ${responseData['message']}'); return false; } final data = responseData['data']; // 更新内存中的凭证 _token = data['accessToken']; final tokenExpireMs = int.parse(data['tokenExpireAt'].toString()); _expiryDate = DateTime.fromMillisecondsSinceEpoch(tokenExpireMs); _refreshToken = data['refreshToken']; final refreshExpireMs = int.parse(data['refreshTokenExpireAt'].toString()); _timerRefreshToken = refreshExpireMs; // 更新本地存储 final prefs = await SharedPreferences.getInstance(); final userData = json.encode({ '_userId': _userName, 'token': _token, 'expiryDate': _expiryDate!.toIso8601String(), 'refreshToken': _refreshToken, 'refreshTokenExpireAt': _timerRefreshToken.toString(), }); await prefs.setString('userData', userData); return true; } catch (e) { debugPrint('刷新token请求异常: $e'); return false; } }
关键优化点说明
- 区分accessToken和refreshToken的作用:refreshToken只用于换取新的accessToken,绝不直接作为接口凭证
- 正确处理时间戳:API返回的是毫秒级时间戳,用
DateTime.fromMillisecondsSinceEpoch转换,避免过期时间计算错误 - 完善异常与错误处理:
- 解析本地数据或接口返回出错时,自动清除本地无效数据
- 检查refreshToken是否过期,过期则要求用户重新登录
- 刷新token后持久化新数据:确保新的accessToken、refreshToken和过期时间能保存到本地,下次启动直接使用
- 准确返回自动登录结果:根据token状态、刷新结果返回
true或false,上层可以根据结果跳转登录页或首页 - 空安全增强:避免因null值引发的崩溃,比如先判断
userDataStr是否为null再解析
内容的提问来源于stack exchange,提问作者SomeOrdinaryDev
相关产品推荐
相关产品推荐

