
03. Flutter 鸿蒙实战 03登录注册与本地会话持久化用 AuthController、AuthScope 和 shared_preferences_ohos 管理登录态。登录态入口登录态管理在lib/features/auth目录下核心文件是auth_controller.dart、auth_repository.dart、session_store.dart和auth_scope.dart。项目启动时TalkingAlbumApp创建AuthController并调用initialize()。这个过程决定首屏显示登录页还是应用主页面。_authControllerAuthController(apiClient:_apiClient,sessionStore:SharedPreferencesSessionStore(),);_authController.initialize();MaterialApp的 home 是_AuthGate它根据状态切页面returnswitch(auth.status){AuthStatus.initializingconstScaffold(body:Center(child:CircularProgressIndicator()),),AuthStatus.signedOutconstAuthPage(),AuthStatus.signedInconstAppShell(initialRoute:AppRoutes.home),};这个结构比在登录页里手动判断更干净。登录态是应用级状态不应该散落在各个页面里。SessionStore 抽象项目没有直接在AuthController里写SharedPreferences而是抽象了一个SessionStoreabstractinterfaceclassSessionStore{FutureStoredSession?read();Futurevoidwrite(Stringtoken,AuthUseruser);Futurevoidclear();}当前实现是SharedPreferencesSessionStore底层使用shared_preferences_ohos适配鸿蒙classSharedPreferencesSessionStoreimplementsSessionStore{staticconst_tokenKeytalking_album_token;staticconst_userKeytalking_album_user;overrideFutureStoredSession?read()async{finalpreferencesawaitSharedPreferences.getInstance();finaltokenpreferences.getString(_tokenKey);finalrawUserpreferences.getString(_userKey);if(tokennull||rawUsernull)returnnull;try{finaljsonjsonDecode(rawUser);if(jsonis!MapString,dynamic)returnnull;returnStoredSession(token:token,user:AuthUser.fromJson(json));}onFormatException{awaitclear();returnnull;}}}这里有一个细节本地用户 JSON 解析失败时会clear()不会继续使用损坏会话。移动端本地存储并不总是可靠升级版本、字段变化、异常写入都可能导致旧数据不能解析。登录流程AuthRepository.login只负责请求接口和解析返回AuthController.login负责状态变更、token 写入和 UI 通知Futureboollogin(Stringaccount,Stringpassword)async{errornull;notifyListeners();try{finalresultawait_repository.login(account.trim(),password);StoriesRepository.clearCache();_apiClient.accessTokenresult.token;userresult.user;await_sessionStore.write(result.token,result.user);statusAuthStatus.signedIn;notifyListeners();returntrue;}catch(exception){error_message(exception);notifyListeners();returnfalse;}}登录成功后清理故事缓存是合理的。不同用户看到的故事、照片、家人关系都不一样如果不清缓存切账号后可能看到上个账号的数据。启动恢复登录initialize()会先读本地 session如果存在 token就请求/api/auth/me校验Futurevoidinitialize()async{finalsessionawait_sessionStore.read();if(sessionnull){statusAuthStatus.signedOut;notifyListeners();return;}try{userawait_repository.me(session.token);await_sessionStore.write(session.token,user!);statusAuthStatus.signedIn;}catch(_){await_sessionStore.clear();_apiClient.accessTokennull;StoriesRepository.clearCache();statusAuthStatus.signedOut;}notifyListeners();}这个设计避免了一个常见问题本地有 token 但服务端已经失效App 还误以为登录成功。这里通过/me做一次真实校验失败就清理本地会话。退出登录退出登录不只是回到登录页还要清理 token、用户、错误状态和业务缓存Futurevoidlogout()async{await_sessionStore.clear();_apiClient.accessTokennull;StoriesRepository.clearCache();usernull;errornull;statusAuthStatus.signedOut;notifyListeners();}在多用户应用里退出时清业务缓存很关键。照片故事类数据带有强烈用户属性不能跨账号残留。扩展拆解登录态和业务缓存的关系登录成功后项目会调用StoriesRepository.clearCache()。这一步很重要因为故事、照片、地图、家人关系都和当前账号有关。如果切换账号时不清缓存可能出现新账号看到旧账号故事的严重问题。finalresultawait_repository.login(account.trim(),password);StoriesRepository.clearCache();_apiClient.accessTokenresult.token;userresult.user;await_sessionStore.write(result.token,result.user);statusAuthStatus.signedIn;退出登录同样要清理await_sessionStore.clear();_apiClient.accessTokennull;StoriesRepository.clearCache();usernull;statusAuthStatus.signedOut;这说明登录模块不是孤立的表单功能它会影响整个应用的数据边界。移动端项目里 token、用户信息、业务缓存必须同步管理不能只保存 token。注册和资料更新注册只负责创建账号不直接登录。资料更新、头像上传成功后会更新当前用户并写回本地 session。这样应用重启后昵称和头像不会回到旧状态。dOut;这说明登录模块不是孤立的表单功能它会影响整个应用的数据边界。移动端项目里 token、用户信息、业务缓存必须同步管理不能只保存 token。 ## 注册和资料更新 注册只负责创建账号不直接登录。资料更新、头像上传成功后会更新当前用户并写回本地 session。这样应用重启后昵称和头像不会回到旧状态。