340 lines
9.4 KiB
Dart
340 lines
9.4 KiB
Dart
import 'package:cloudreve4_flutter/presentation/widgets/toast_helper.dart';
|
||
import 'package:cloudreve4_flutter/presentation/widgets/user_avatar.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import '../../data/models/server_model.dart';
|
||
import '../../data/models/user_model.dart';
|
||
import '../../services/auth_service.dart';
|
||
import '../../services/server_service.dart';
|
||
import '../../services/storage_service.dart';
|
||
import '../../services/api_service.dart';
|
||
import '../../core/exceptions/app_exception.dart';
|
||
import '../../core/utils/app_logger.dart';
|
||
|
||
/// 认证状态
|
||
enum AuthState { loading, authenticated, unauthenticated, error }
|
||
|
||
/// 认证Provider
|
||
class AuthProvider extends ChangeNotifier {
|
||
AuthState _state = AuthState.unauthenticated;
|
||
UserModel? _user;
|
||
String? _errorMessage;
|
||
bool _hasRefreshTokenExpired = false;
|
||
|
||
AuthState get state => _state;
|
||
UserModel? get user => _user;
|
||
String? get errorMessage => _errorMessage;
|
||
bool get isAuthenticated => _state == AuthState.authenticated;
|
||
bool get isLoading => _state == AuthState.loading;
|
||
bool get hasRefreshTokenExpired => _hasRefreshTokenExpired;
|
||
bool get isAdmin {
|
||
final name = _user?.group?.name.toLowerCase();
|
||
return name == 'admin' || name == '管理员';
|
||
}
|
||
|
||
/// 当前选中的服务器
|
||
ServerModel? get currentServer => ServerService.instance.currentServer;
|
||
|
||
/// 获取当前用户的 token
|
||
TokenModel? get token => _user?.token;
|
||
|
||
/// 初始化
|
||
Future<void> init() async {
|
||
try {
|
||
setState(AuthState.loading);
|
||
|
||
// 初始化服务器服务
|
||
await ServerService.instance.init();
|
||
|
||
// 获取当前服务器
|
||
final server = ServerService.instance.currentServer;
|
||
if (server == null) {
|
||
setState(AuthState.unauthenticated);
|
||
return;
|
||
}
|
||
|
||
// 设置 API 的 baseUrl
|
||
await _setApiBaseUrl(server.baseUrl);
|
||
|
||
// 设置 ApiService 的认证回调
|
||
_setupApiCallbacks();
|
||
|
||
// 检查是否有保存的登录信息
|
||
if (server.user != null && server.user!.token != null) {
|
||
// 有保存的用户信息,检查 token 是否过期
|
||
if (!server.user!.token!.isRefreshTokenExpired) {
|
||
// Refresh token 未过期,设置用户信息
|
||
setUser(server.user);
|
||
setState(AuthState.authenticated);
|
||
return;
|
||
} else {
|
||
// Refresh token 已过期,清除登录信息
|
||
await ServerService.instance.clearCurrentServerLogin();
|
||
}
|
||
}
|
||
|
||
_user = null;
|
||
setState(AuthState.unauthenticated);
|
||
} catch (e) {
|
||
AppLogger.d('AuthProvider 初始化失败: $e');
|
||
_user = null;
|
||
setState(AuthState.unauthenticated);
|
||
}
|
||
}
|
||
|
||
/// 设置 ApiService 的认证回调
|
||
void _setupApiCallbacks() {
|
||
ApiService.setAuthCallbacks(
|
||
getToken: () async {
|
||
// 返回当前的 access token
|
||
return _user?.token?.accessToken;
|
||
},
|
||
refreshToken: () async {
|
||
// 刷新 token
|
||
try {
|
||
await refreshToken();
|
||
} catch (e) {
|
||
// 刷新失败,设置过期标志
|
||
if (e is RefreshTokenExpiredException) {
|
||
setRefreshTokenExpired();
|
||
}
|
||
rethrow;
|
||
}
|
||
},
|
||
clearAuth: () async {
|
||
// 清除认证数据
|
||
await ServerService.instance.clearCurrentServerLogin();
|
||
setRefreshTokenExpired();
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 设置 API baseUrl
|
||
Future<void> _setApiBaseUrl(String baseUrl) async {
|
||
// 同时更新存储和 ApiService 的 baseUrl
|
||
final storageService = StorageService.instance;
|
||
await storageService.setCustomBaseUrl(baseUrl);
|
||
await ApiService.instance.setBaseUrl(baseUrl);
|
||
}
|
||
|
||
/// 密码登录
|
||
Future<bool> passwordLogin({
|
||
required String email,
|
||
required String password,
|
||
bool rememberMe = false,
|
||
String? captcha,
|
||
String? ticket,
|
||
}) async {
|
||
try {
|
||
setState(AuthState.loading);
|
||
|
||
// 获取当前服务器
|
||
final server = ServerService.instance.currentServer;
|
||
if (server == null) {
|
||
_errorMessage = '服务器初始化失败';
|
||
_user = null;
|
||
setState(AuthState.error);
|
||
return false;
|
||
}
|
||
|
||
// 每次登录时都重新设置 API 的 baseUrl,确保使用最新的服务器地址
|
||
await _setApiBaseUrl(server.baseUrl);
|
||
|
||
// 执行登录
|
||
final response = await AuthService.instance.passwordLogin(
|
||
email: email,
|
||
password: password,
|
||
captcha: captcha,
|
||
ticket: ticket,
|
||
);
|
||
AppLogger.d('AuthProvider 登录成功: $response');
|
||
// 保存登录信息到当前服务器(包含完整 user 和 token)
|
||
await ServerService.instance.updateCurrentServerLogin(
|
||
email: rememberMe ? email : null,
|
||
password: rememberMe ? password : null,
|
||
user: response.user,
|
||
rememberMe: rememberMe,
|
||
);
|
||
|
||
setUser(response.user);
|
||
setState(AuthState.authenticated);
|
||
|
||
return true;
|
||
} on TwoFactorRequiredException {
|
||
// 两步验证需要,不设置 error 状态,重新抛出让调用方处理
|
||
setState(AuthState.unauthenticated);
|
||
rethrow;
|
||
} catch (e) {
|
||
_errorMessage = e.toString();
|
||
_user = null;
|
||
setState(AuthState.error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 两步验证登录
|
||
Future<bool> twoFactorLogin({
|
||
required String otp,
|
||
required String sessionId,
|
||
required String email,
|
||
required String password,
|
||
bool rememberMe = false,
|
||
}) async {
|
||
try {
|
||
setState(AuthState.loading);
|
||
|
||
final response = await AuthService.instance.twoFactorLogin(
|
||
otp: otp,
|
||
sessionId: sessionId,
|
||
);
|
||
|
||
await ServerService.instance.updateCurrentServerLogin(
|
||
email: rememberMe ? email : null,
|
||
password: rememberMe ? password : null,
|
||
user: response.user,
|
||
rememberMe: rememberMe,
|
||
);
|
||
|
||
setUser(response.user);
|
||
setState(AuthState.authenticated);
|
||
return true;
|
||
} catch (e) {
|
||
_errorMessage = e.toString();
|
||
_user = null;
|
||
setState(AuthState.error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 登出
|
||
Future<void> logout() async {
|
||
try {
|
||
// 调用登出 API(需要 token)
|
||
if (token?.refreshToken != null) {
|
||
try {
|
||
await AuthService.instance.logout();
|
||
} catch (e) {
|
||
// 登出 API 调用失败不影响本地清理
|
||
AppLogger.d('登出 API 调用失败: $e');
|
||
}
|
||
}
|
||
|
||
// 清除当前服务器的登录信息
|
||
await ServerService.instance.clearCurrentServerLogin();
|
||
|
||
_clearUserData();
|
||
// 清除头像缓存
|
||
await UserAvatar.clearAllCache();
|
||
setState(AuthState.unauthenticated);
|
||
} catch (e) {
|
||
// 即使出错也要清除本地状态
|
||
_clearUserData();
|
||
setState(AuthState.unauthenticated);
|
||
_errorMessage = e.toString();
|
||
}
|
||
}
|
||
|
||
/// 刷新用户信息
|
||
Future<void> refreshUser() async {
|
||
try {
|
||
final user = await AuthService.instance.getCurrentUser();
|
||
setUser(user);
|
||
|
||
// 更新服务器中的用户信息(保留 token)
|
||
final server = ServerService.instance.currentServer;
|
||
if (server != null && token != null) {
|
||
await ServerService.instance.updateCurrentServerLogin(
|
||
user: user.copyWith(token: token),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
_errorMessage = e.toString();
|
||
}
|
||
}
|
||
|
||
/// 刷新 token
|
||
Future<void> refreshToken() async {
|
||
try {
|
||
final currentToken = token;
|
||
if (currentToken == null || currentToken.isRefreshTokenExpired) {
|
||
ToastHelper.failure('已注销或Refresh token 已过期,需要重新登录');
|
||
throw Exception('Refresh token 已过期,需要重新登录');
|
||
}
|
||
|
||
// 调用刷新 token 接口
|
||
final newToken = await AuthService.instance.refreshToken(
|
||
currentToken.refreshToken,
|
||
);
|
||
|
||
// 更新用户信息中的 token
|
||
final updatedUser = _user!.copyWith(token: newToken);
|
||
|
||
// 保存到服务器
|
||
await ServerService.instance.updateCurrentServerLogin(user: updatedUser);
|
||
|
||
setUser(updatedUser);
|
||
} catch (e) {
|
||
ToastHelper.error('刷新 token 失败: $e');
|
||
AppLogger.d('刷新 token 失败: $e');
|
||
_user = null;
|
||
_errorMessage = e.toString();
|
||
notifyListeners();
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 设置用户
|
||
void setUser(UserModel? user) {
|
||
_user = user;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// 设置状态
|
||
void setState(AuthState state) {
|
||
_state = state;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// 清除错误
|
||
void clearError() {
|
||
_errorMessage = null;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// 清除用户数据
|
||
void _clearUserData() {
|
||
_user = null;
|
||
_errorMessage = null;
|
||
_hasRefreshTokenExpired = false;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// 处理 RefreshTokenExpiredException
|
||
void setRefreshTokenExpired() {
|
||
_hasRefreshTokenExpired = true;
|
||
notifyListeners();
|
||
}
|
||
|
||
void clearRefreshTokenExpired() {
|
||
_hasRefreshTokenExpired = false;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// 获取上次登录的邮箱(用于填充输入框)
|
||
String? get rememberedEmail {
|
||
final server = ServerService.instance.currentServer;
|
||
return server?.email;
|
||
}
|
||
|
||
/// 获取上次登录的密码(用于填充输入框)
|
||
String? get rememberedPassword {
|
||
final server = ServerService.instance.currentServer;
|
||
return server?.password;
|
||
}
|
||
|
||
/// 是否记住密码
|
||
bool get rememberMe {
|
||
final server = ServerService.instance.currentServer;
|
||
return server?.rememberMe ?? false;
|
||
}
|
||
}
|