You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中tryAutoLogin函数优化及类Facebook自动登录实现咨询

Flutter 自动登录(类似Facebook)的tryAutoLogin函数优化方案

现有代码的核心问题

你当前的tryAutoLogin和相关函数存在几个逻辑漏洞和错误,直接运行会导致自动登录失效或接口请求失败:

  1. 混淆refreshToken和accessToken:把refreshToken直接赋值给_token,但refreshToken只能用来换accessToken,不能作为接口请求的凭证
  2. 刷新token后未持久化新数据:调用_refreshNewToken后没有更新SharedPreferences里的用户数据,下次启动还是用旧的过期token
  3. 未处理刷新失败场景:如果refreshToken过期或接口报错,当前逻辑依然返回true,导致后续请求全部失败
  4. 时间戳处理错误:API返回的tokenExpireAt是毫秒级时间戳,你用Duration(seconds: ...)计算过期时间,会导致过期时间严重错误
  5. 空安全与异常处理缺失:解析本地存储数据时没有处理null或格式错误,容易引发崩溃
  6. 返回值逻辑错误:无论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;
  }
}

关键优化点说明

  1. 区分accessToken和refreshToken的作用:refreshToken只用于换取新的accessToken,绝不直接作为接口凭证
  2. 正确处理时间戳:API返回的是毫秒级时间戳,用DateTime.fromMillisecondsSinceEpoch转换,避免过期时间计算错误
  3. 完善异常与错误处理:
    • 解析本地数据或接口返回出错时,自动清除本地无效数据
    • 检查refreshToken是否过期,过期则要求用户重新登录
  4. 刷新token后持久化新数据:确保新的accessToken、refreshToken和过期时间能保存到本地,下次启动直接使用
  5. 准确返回自动登录结果:根据token状态、刷新结果返回true或false,上层可以根据结果跳转登录页或首页
  6. 空安全增强:避免因null值引发的崩溃,比如先判断userDataStr是否为null再解析

内容的提问来源于stack exchange,提问作者SomeOrdinaryDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 07:45:51