// 认证仓库 — 登录/注册/令牌管理的统一入口 // // 职责: // - 封装后端认证 API 调用(登录/注册/刷新令牌/登出) // - 通过 SecureTokenStore 安全持久化 JWT 令牌(PIPL 合规) // - 为 AuthBloc 提供干净的认证数据访问接口 import 'dart:convert'; import 'package:logger/logger.dart'; import '../local/secure_token_store.dart'; import '../models/auth_token.dart'; import '../models/user.dart'; import '../remote/api_client.dart'; /// 安全存储键名 const _keyAccessToken = 'auth_access_token'; const _keyRefreshToken = 'auth_refresh_token'; const _keyExpiresAt = 'auth_expires_at'; const _keyUserJson = 'auth_user_json'; /// 认证异常 — 认证流程中出现的错误 class AuthException implements Exception { final String message; final int? statusCode; const AuthException(this.message, {this.statusCode}); @override String toString() => 'AuthException: $message'; } /// 认证仓库 — 管理用户登录状态和令牌 /// /// 使用 [ApiClient] 与后端通信,使用 [SecureTokenStore] 持久化令牌。 /// 原生平台使用加密存储,Web 平台使用 shared_preferences。 class AuthRepository { final ApiClient _apiClient; final SecureTokenStore _tokenStore; final Logger _logger = Logger(printer: PrettyPrinter(methodCount: 0)); AuthToken? _currentToken; User? _currentUser; AuthRepository({ required ApiClient apiClient, required SecureTokenStore tokenStore, }) : _apiClient = apiClient, _tokenStore = tokenStore; /// 当前用户(可能为 null) User? get currentUser => _currentUser; /// 当前令牌(可能为 null) AuthToken? get currentToken => _currentToken; /// 是否已登录 bool get isAuthenticated => _currentToken != null && !_currentToken!.isExpired; // ===== 登录 ===== /// 用户名密码登录 /// /// 调用后端 `POST /auth/login`,成功后保存令牌和用户信息。 Future login({ required String username, required String password, }) async { _logger.i('登录请求: $username'); final response = await _apiClient.post('/auth/login', data: { 'username': username, 'password': password, 'client_type': 'mobile', }); final data = _extractData(response.data); final token = AuthToken.fromJson(data); final user = User.fromJson(data['user'] as Map); await _saveAuth(token, user); _apiClient.setToken(token.accessToken); _logger.i('登录成功: ${user.displayLabel}'); return user; } // ===== 注册 ===== /// 注册新用户 /// /// 调用后端 `POST /users`,成功后自动登录。 Future register({ required String username, required String password, String? displayName, }) async { _logger.i('注册请求: $username'); await _apiClient.post('/users', data: { 'username': username, 'password': password, if (displayName != null) 'display_name': displayName, }); // 注册成功后自动登录 return await login(username: username, password: password); } // ===== 刷新令牌 ===== /// 刷新访问令牌 /// /// 调用后端 `POST /auth/refresh`,使用 refresh_token 获取新的 access_token。 Future refreshToken() async { if (_currentToken == null) { throw const AuthException('无可用令牌,请重新登录'); } _logger.d('刷新令牌'); final response = await _apiClient.post('/auth/refresh', data: { 'refresh_token': _currentToken!.refreshToken, }); final data = _extractData(response.data); final token = AuthToken.fromJson(data); await _saveToken(token); _apiClient.setToken(token.accessToken); return token; } // ===== 登出 ===== /// 登出 /// /// 调用后端 `POST /auth/logout` 并清除本地存储。 Future logout() async { _logger.i('登出'); try { if (isAuthenticated) { await _apiClient.post('/auth/logout'); } } catch (e) { _logger.w('登出 API 调用失败(忽略): $e'); } await _clearAuth(); _apiClient.clearToken(); } // ===== 本地恢复 ===== /// 从安全存储恢复认证状态 /// /// App 启动时调用,检查是否有有效的持久化令牌。 /// 如果令牌即将过期,自动刷新。 Future restoreAuth() async { _logger.d('恢复认证状态'); try { final accessToken = await _tokenStore.read(_keyAccessToken); final refreshTokenStr = await _tokenStore.read(_keyRefreshToken); final expiresAtStr = await _tokenStore.read(_keyExpiresAt); final userJsonStr = await _tokenStore.read(_keyUserJson); if (accessToken == null || refreshTokenStr == null || userJsonStr == null) { _logger.d('无存储的认证信息'); return null; } final expiresAt = DateTime.parse(expiresAtStr ?? DateTime.now().subtract(const Duration(hours: 1)).toIso8601String()); _currentToken = AuthToken( accessToken: accessToken, refreshToken: refreshTokenStr, expiresIn: 0, expiresAt: expiresAt, ); _currentUser = User.fromJson( jsonDecode(userJsonStr) as Map, ); // 令牌已过期 → 尝试刷新 if (_currentToken!.isExpired) { _logger.d('令牌已过期,尝试刷新'); try { await refreshToken(); } catch (e) { _logger.w('令牌刷新失败,需要重新登录: $e'); await _clearAuth(); return null; } } else if (_currentToken!.isExpiringSoon) { // 即将过期 → 后台刷新(不阻塞) _logger.d('令牌即将过期,后台刷新'); refreshToken().catchError((e) { _logger.w('后台刷新失败: $e'); return _currentToken!; // 返回当前令牌作为 fallback }); } _apiClient.setToken(_currentToken!.accessToken); _logger.i('认证恢复成功: ${_currentUser!.displayLabel}'); return _currentUser; } catch (e) { _logger.e('认证恢复失败: $e'); await _clearAuth(); return null; } } // ===== 私有方法 ===== /// 从 API 响应中提取 data 字段 Map _extractData(dynamic responseData) { if (responseData is Map) { // 后端 ApiResponse 格式: { success: bool, data: T, message: String? } if (responseData.containsKey('data')) { return responseData['data'] as Map; } return responseData; } throw const AuthException('服务器响应格式异常'); } /// 保存令牌和用户到安全存储 Future _saveAuth(AuthToken token, User user) async { _currentToken = token; _currentUser = user; await _saveToken(token); await _tokenStore.write( _keyUserJson, jsonEncode(user.toJson()), ); } /// 仅保存令牌到安全存储 Future _saveToken(AuthToken token) async { _currentToken = token; await _tokenStore.write(_keyAccessToken, token.accessToken); await _tokenStore.write(_keyRefreshToken, token.refreshToken); await _tokenStore.write(_keyExpiresAt, token.expiresAt.toIso8601String()); } /// 清除所有认证数据 Future _clearAuth() async { _currentToken = null; _currentUser = null; await _tokenStore.delete(_keyAccessToken); await _tokenStore.delete(_keyRefreshToken); await _tokenStore.delete(_keyExpiresAt); await _tokenStore.delete(_keyUserJson); } }