/// ============================================================ /// 闲言APP — 认证状态管理 /// 创建时间: 2026-04-28 /// 更新时间: 2026-05-15 /// 作用: 管理用户登录状态、Token、用户信息 /// 上次更新: v10.1.0 register增加密保参数; changePassword支持多验证方式 /// ============================================================ import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/network/api_exception.dart'; import '../../../core/services/device/device_info_service.dart'; import '../../../core/storage/app_kv_store.dart'; import '../../../core/storage/secure_storage.dart'; import '../../../core/utils/logger.dart'; import '../models/user_model.dart'; import '../services/auth_service.dart'; class AuthState { const AuthState({ this.user, this.isLoading = false, this.isLoggedIn = false, this.isInitialized = false, this.error, }); final UserModel? user; final bool isLoading; final bool isLoggedIn; final bool isInitialized; final String? error; AuthState copyWith({ UserModel? user, bool clearUser = false, bool? isLoading, bool? isLoggedIn, bool? isInitialized, String? error, bool clearError = false, }) { return AuthState( user: clearUser ? null : (user ?? this.user), isLoading: isLoading ?? this.isLoading, isLoggedIn: isLoggedIn ?? this.isLoggedIn, isInitialized: isInitialized ?? this.isInitialized, error: clearError ? null : (error ?? this.error), ); } } class AuthNotifier extends Notifier { @override AuthState build() => _loadInitialState(); static const String _userCacheKey = 'cached_user_info'; AuthNotifier() { _init(); } static AuthState _loadInitialState() { if (!AppKVStore.isReady) return const AuthState(); final cached = AppKVStore.getString(_userCacheKey); if (cached != null) { try { final user = UserModel.fromJson( jsonDecode(cached) as Map, ); return AuthState(user: user, isLoggedIn: true); } catch (_) {} } return const AuthState(); } Future _init() async { final hasToken = await SecureStorage.isLoggedIn; if (state.user != null && hasToken) { state = state.copyWith(isInitialized: true); _validateTokenInBackground(); } else if (state.user != null && !hasToken) { await AuthService.clearCachedUser(); state = const AuthState(isInitialized: true); } else if (hasToken) { state = state.copyWith(isLoading: true); final success = await AuthService.tryAutoLogin(); if (success) { try { final data = await AuthService.getUserInfo(); final user = UserModel.fromJson(data); await _saveUserCache(user); state = state.copyWith( user: user, isLoggedIn: true, isLoading: false, isInitialized: true, clearError: true, ); } catch (e) { Log.w('自动登录后获取用户信息失败: $e'); state = state.copyWith(isLoading: false, isInitialized: true); } } else { state = state.copyWith( isLoading: false, isInitialized: true, clearUser: true, ); } } else { state = state.copyWith(isLoading: false, isInitialized: true); } } Future _validateTokenInBackground() async { try { final valid = await AuthService.validateLocalToken(); if (!valid) { final refreshed = await AuthService.refreshToken(); if (!refreshed) { await AuthService.clearCachedUser(); state = state.copyWith(isLoggedIn: false, clearUser: true); Log.i('Token已失效且刷新失败,已清除登录状态'); } else { try { final data = await AuthService.getUserInfo(); final user = UserModel.fromJson(data); await _saveUserCache(user); state = state.copyWith(user: user); } catch (_) {} } } else { try { final data = await AuthService.getUserInfo(); final user = UserModel.fromJson(data); await _saveUserCache(user); state = state.copyWith(user: user); } catch (_) {} } } catch (e) { Log.w('后台Token验证失败: $e'); } } Future _saveUserCache(UserModel user) async { try { await AppKVStore.setString(_userCacheKey, jsonEncode(user.toJson())); } catch (e) { Log.w('保存用户缓存失败: $e'); } } Future login({ required String account, required String password, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final user = await AuthService.login( account: account, password: password, ); await _saveUserCache(user); state = state.copyWith(user: user, isLoggedIn: true, isLoading: false); DeviceInfoService.registerDeviceIfNeeded(); return true; } on ApiException catch (e) { state = state.copyWith(isLoading: false, error: e.message); return false; } catch (e) { state = state.copyWith(isLoading: false, error: '登录失败: $e'); return false; } } Future register({ required String username, required String password, required String email, String? mobile, String? mobileCode, int? secQuestion, String? secAnswer, }) async { state = state.copyWith(isLoading: true, clearError: true); try { final user = await AuthService.register( username: username, password: password, email: email, mobile: mobile, mobileCode: mobileCode, secQuestion: secQuestion, secAnswer: secAnswer, ); await _saveUserCache(user); state = state.copyWith(user: user, isLoggedIn: true, isLoading: false); DeviceInfoService.registerDeviceIfNeeded(); return true; } on ApiException catch (e) { state = state.copyWith(isLoading: false, error: e.message); return false; } catch (e) { state = state.copyWith(isLoading: false, error: '注册失败: $e'); return false; } } Future logout() async { await DeviceInfoService.resetRegistration(); await AuthService.logout(); await AuthService.clearCachedUser(); state = const AuthState(isInitialized: true); } Future refreshUser() async { try { final data = await AuthService.getUserInfo(); final user = UserModel.fromJson(data); await _saveUserCache(user); state = state.copyWith(user: user); } catch (e) { Log.e('刷新用户信息失败', e); } } Future updateProfile({ String? nickname, String? bio, String? avatarUrl, }) async { try { await AuthService.updateProfile( nickname: nickname, bio: bio, avatarUrl: avatarUrl, ); await refreshUser(); return true; } on ApiException catch (e) { state = state.copyWith(error: e.message); return false; } } Future changePassword({ required String newPassword, String? oldPassword, String? secAnswer, String? userId, String verifyMethod = 'password', }) async { try { await AuthService.changePassword( newPassword: newPassword, oldPassword: oldPassword, secAnswer: secAnswer, userId: userId, verifyMethod: verifyMethod, ); return true; } on ApiException catch (e) { state = state.copyWith(error: e.message); return false; } } } final authProvider = NotifierProvider(AuthNotifier.new); final isLoggedInProvider = Provider((ref) { return ref.watch(authProvider).isLoggedIn; }); final currentUserProvider = Provider((ref) { return ref.watch(authProvider).user; });