Files
gongyun 1d9e0a831f
Build Android Release / Build Android (push) Failing after 7s
Android Feature Optimization and Updates
2026-05-21 10:31:43 +08:00

340 lines
9.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}