03-Flutter 鸿蒙实战 03:登录注册与本地会话持久化

03. Flutter 鸿蒙实战 03:登录注册与本地会话持久化

用 AuthController、AuthScope 和 shared_preferences_ohos 管理登录态。

登录态入口

登录态管理在 lib/features/auth 目录下,核心文件是 auth_controller.dartauth_repository.dartsession_store.dartauth_scope.dart。项目启动时,TalkingAlbumApp 创建 AuthController 并调用 initialize()。这个过程决定首屏显示登录页还是应用主页面。

dart 复制代码
_authController = AuthController(
  apiClient: _apiClient,
  sessionStore: SharedPreferencesSessionStore(),
);
_authController.initialize();

MaterialApp 的 home 是 _AuthGate,它根据状态切页面:

dart 复制代码
return switch (auth.status) {
  AuthStatus.initializing => const Scaffold(
      body: Center(child: CircularProgressIndicator()),
    ),
  AuthStatus.signedOut => const AuthPage(),
  AuthStatus.signedIn => const AppShell(initialRoute: AppRoutes.home),
};

这个结构比在登录页里手动判断更干净。登录态是应用级状态,不应该散落在各个页面里。

SessionStore 抽象

项目没有直接在 AuthController 里写 SharedPreferences,而是抽象了一个 SessionStore

dart 复制代码
abstract interface class SessionStore {
  Future<StoredSession?> read();
  Future<void> write(String token, AuthUser user);
  Future<void> clear();
}

当前实现是 SharedPreferencesSessionStore,底层使用 shared_preferences_ohos 适配鸿蒙:

dart 复制代码
class SharedPreferencesSessionStore implements SessionStore {
  static const _tokenKey = 'talking_album_token';
  static const _userKey = 'talking_album_user';

  @override
  Future<StoredSession?> read() async {
    final preferences = await SharedPreferences.getInstance();
    final token = preferences.getString(_tokenKey);
    final rawUser = preferences.getString(_userKey);
    if (token == null || rawUser == null) return null;
    try {
      final json = jsonDecode(rawUser);
      if (json is! Map<String, dynamic>) return null;
      return StoredSession(token: token, user: AuthUser.fromJson(json));
    } on FormatException {
      await clear();
      return null;
    }
  }
}

这里有一个细节:本地用户 JSON 解析失败时会 clear(),不会继续使用损坏会话。移动端本地存储并不总是可靠,升级版本、字段变化、异常写入都可能导致旧数据不能解析。

登录流程

AuthRepository.login 只负责请求接口和解析返回,AuthController.login 负责状态变更、token 写入和 UI 通知:

dart 复制代码
Future<bool> login(String account, String password) async {
  error = null;
  notifyListeners();
  try {
    final result = await _repository.login(account.trim(), password);
    StoriesRepository.clearCache();
    _apiClient.accessToken = result.token;
    user = result.user;
    await _sessionStore.write(result.token, result.user);
    status = AuthStatus.signedIn;
    notifyListeners();
    return true;
  } catch (exception) {
    error = _message(exception);
    notifyListeners();
    return false;
  }
}

登录成功后清理故事缓存是合理的。不同用户看到的故事、照片、家人关系都不一样,如果不清缓存,切账号后可能看到上个账号的数据。

启动恢复登录

initialize() 会先读本地 session,如果存在 token,就请求 /api/auth/me 校验:

dart 复制代码
Future<void> initialize() async {
  final session = await _sessionStore.read();
  if (session == null) {
    status = AuthStatus.signedOut;
    notifyListeners();
    return;
  }
  try {
    user = await _repository.me(session.token);
    await _sessionStore.write(session.token, user!);
    status = AuthStatus.signedIn;
  } catch (_) {
    await _sessionStore.clear();
    _apiClient.accessToken = null;
    StoriesRepository.clearCache();
    status = AuthStatus.signedOut;
  }
  notifyListeners();
}

这个设计避免了一个常见问题:本地有 token 但服务端已经失效,App 还误以为登录成功。这里通过 /me 做一次真实校验,失败就清理本地会话。

退出登录

退出登录不只是回到登录页,还要清理 token、用户、错误状态和业务缓存:

dart 复制代码
Future<void> logout() async {
  await _sessionStore.clear();
  _apiClient.accessToken = null;
  StoriesRepository.clearCache();
  user = null;
  error = null;
  status = AuthStatus.signedOut;
  notifyListeners();
}

在多用户应用里,退出时清业务缓存很关键。照片故事类数据带有强烈用户属性,不能跨账号残留。

扩展拆解

登录态和业务缓存的关系

登录成功后,项目会调用 StoriesRepository.clearCache()。这一步很重要,因为故事、照片、地图、家人关系都和当前账号有关。如果切换账号时不清缓存,可能出现新账号看到旧账号故事的严重问题。

dart 复制代码
final result = await _repository.login(account.trim(), password);
StoriesRepository.clearCache();
_apiClient.accessToken = result.token;
user = result.user;
await _sessionStore.write(result.token, result.user);
status = AuthStatus.signedIn;

退出登录同样要清理:

dart 复制代码
await _sessionStore.clear();
_apiClient.accessToken = null;
StoriesRepository.clearCache();
user = null;
status = AuthStatus.signedOut;

这说明登录模块不是孤立的表单功能,它会影响整个应用的数据边界。移动端项目里 token、用户信息、业务缓存必须同步管理,不能只保存 token。

注册和资料更新

注册只负责创建账号,不直接登录。资料更新、头像上传成功后,会更新当前用户并写回本地 session。这样应用重启后,昵称和头像不会回到旧状态。

dOut;

复制代码
这说明登录模块不是孤立的表单功能,它会影响整个应用的数据边界。移动端项目里 token、用户信息、业务缓存必须同步管理,不能只保存 token。

## 注册和资料更新

注册只负责创建账号,不直接登录。资料更新、头像上传成功后,会更新当前用户并写回本地 session。这样应用重启后,昵称和头像不会回到旧状态。
相关推荐
MC皮蛋侠客4 小时前
DNP3 系列(十二):测试与工具链——模拟器、Wireshark 与一致性测试
网络·wireshark·dnp3
观测云6 小时前
Fastjson 高危 RCE(CVE-2026-16723):如何用观测云快速发现并完成修复
网络·安全·可观测性·观测云
楷哥爱开发7 小时前
TikTok Shop新加坡本土店入驻:如何选择新加坡本土住宅IP?
网络·网络协议·tcp/ip
网安蟹佬霸8 小时前
# 网络流量分析实战:从Wireshark到入侵检测系统构建(2026最新版,附完整流量分析链与IDS规则集)
网络·测试工具·安全·开源·wireshark·密码学·哈希算法
Magic-ZYJ8 小时前
HarmonyOS Release 构建安全:ArkGuard 混淆、签名配置与秘密管理
安全·华为·harmonyos·移动应用开发·心晴手记
见山是山-见水是水10 小时前
拆解HarmonyOS网络请求适配:原生鸿蒙页面的实现路径与调试方法
华为·harmonyos
m0_7496902310 小时前
【寻迹校园 HarmonyOS NEXT 实战 36】深色模式不是反色:用语义 Token 管理品牌色与状态色
harmonyos·arkts·深色模式·ui设计·主题设计
昌原的儿子LEO10 小时前
进程和线程(2)
linux·服务器·网络
Zzzzmo_11 小时前
【网络原理】TCP/IP协议02
网络·tcp/ip
DianSan_ERP11 小时前
WMS接入电商平台自动化履约实战:一张订单从平台到出库的接口时序设计
java·前端·网络·数据库·安全·架构·自动化