// 功能描述:装配用户端依赖,并统一管理登录、退出和失效会话。 // 版本:1.1.0 import 'package:flutter/foundation.dart'; import '../data/repositories/client_repository.dart'; import '../data/services/api_client.dart'; import '../data/services/secure_session_store.dart'; class AppDependencies { AppDependencies({ required this.session, required this.repository, }); final UserSession session; final ClientRepository repository; static Future create() async { final store = SecureSessionStore(); final session = UserSession(store); await session.restore(); final api = ApiClient( () => session.token, onUnauthorized: session.invalidate, ); return AppDependencies(session: session, repository: ClientRepository(api)); } } /// 管理内存令牌、持久化令牌以及会话失效通知。 class UserSession extends ChangeNotifier { UserSession(this._store); static const _root = '/heqi/client/v1/user'; final SessionStore _store; String _token = ''; bool _expired = false; Future _pendingClear = Future.value(); String get token => _token; bool get isAuthenticated => _token.isNotEmpty; bool get hasExpired => _expired; /// 从安全存储恢复令牌;是否有效由后续受保护接口响应确认。 Future restore() async { _token = await _store.readToken() ?? ''; _expired = false; } Future login({ required String phone, required String password, String? verificationCode, String? requestIdentity, }) async { final api = ApiClient(() => ''); final verification = verificationCode != null && verificationCode.isNotEmpty; final details = jsonMap( await api.post( '$_root/auth/login', authenticated: false, body: { 'phone': phone, 'mode': verification ? 'verification_code' : 'password', 'password': verification ? '' : password, 'code': verificationCode ?? '', 'request_identity': requestIdentity ?? '', }, ), ); final token = details['access_token'] as String? ?? ''; if (token.isEmpty) throw const ApiException(500, '登录令牌缺失'); // 等待旧令牌清理结束,避免迟到的删除任务误删刚写入的新令牌。 await _pendingClear; await _store.writeToken(token); _token = token; _expired = false; notifyListeners(); } Future sendCode(String phone, String purpose) async { final api = ApiClient(() => ''); final details = jsonMap( await api.post( '$_root/auth/verification-code', authenticated: false, body: {'phone': phone, 'purpose': purpose}, ), ); return details['request_identity'] as String? ?? ''; } Future logout() async { _token = ''; _expired = false; notifyListeners(); _pendingClear = _clearStoredToken(); await _pendingClear; } /// 仅当服务端拒绝的仍是当前令牌时,使会话失效并通知路由。 void invalidate(String rejectedToken) { if (rejectedToken.isEmpty || rejectedToken != _token || _expired) return; _token = ''; _expired = true; notifyListeners(); _pendingClear = _clearStoredToken(); } /// 尽力删除持久化令牌;失败不得阻塞用户返回登录页。 Future _clearStoredToken() async { try { await _store.clear(); } catch (error, stackTrace) { debugPrint('清理失效登录令牌失败:$error'); debugPrintStack(stackTrace: stackTrace); } } }