Files
CloudHome-Ops/lib/providers/auth_provider.dart

159 lines
4.7 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/user.dart';
import '../services/api_client.dart';
import '../services/auth_service.dart';
import '../utils/credential_storage.dart';
import '../utils/session_org_scope.dart';
/// AuthService 提供者。
final authServiceProvider = Provider<AuthService>((ref) => AuthService());
/// 登录状态。
class AuthState {
const AuthState({
this.user,
this.isLoading = false,
this.errorMessage,
});
/// 当前登录用户;为 null 表示未登录。
final User? user;
/// 是否正在执行登录请求。
final bool isLoading;
/// 最近一次登录失败的错误信息。
final String? errorMessage;
/// 是否已登录。
bool get isLoggedIn => user != null;
AuthState copyWith({
User? user,
bool? isLoading,
String? errorMessage,
bool clearUser = false,
bool clearError = false,
}) {
return AuthState(
user: clearUser ? null : (user ?? this.user),
isLoading: isLoading ?? this.isLoading,
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
);
}
}
/// 登录状态管理。
class AuthNotifier extends Notifier<AuthState> {
@override
AuthState build() {
// 注册会话失效回调:401 刷新失败 → 清空登录态(含本地持久化),
// 路由 redirect 监听本状态,自动跳转登录页。
ApiClient.instance.onUnauthorized = () {
SessionOrgScope.instance.clear();
unawaited(_clearPersistedSession());
state = const AuthState();
};
// 尝试从本地存储恢复登录态(免登录)。
return _tryRestoreSession();
}
/// 从本地存储恢复登录态。
AuthState _tryRestoreSession() {
final cs = CredentialStorage.instance;
final token = cs.token;
final userMap = cs.savedUserMap;
if (token != null && token.isNotEmpty && userMap != null) {
// 恢复 TokenStore(供 ApiInterceptor 使用)。
TokenStore.instance.save(
token: token,
refreshToken: cs.refreshToken,
);
try {
final user = User.fromJson(userMap);
// 恢复数据权限范围:仅当登录用户 orgId 非空时收敛。
SessionOrgScope.instance.setCurrentOrg(user.orgId);
return AuthState(user: user);
} catch (_) {
// JSON 解析失败,清除残留数据。
_clearPersistedSession();
}
}
return const AuthState();
}
/// 执行登录;成功返回 true,失败返回 false 并写入错误信息。
///
/// [captcha]/[uuid] 仅在后端要求验证码时传值;当前各环境已关闭登录验证码,
/// 调用方(登录页)可不传,由 [AuthService.login] 决定是否写入请求体。
Future<bool> login({
required String account,
required String password,
String captcha = '',
String uuid = '',
bool rememberPassword = false,
}) async {
state = state.copyWith(isLoading: true, clearError: true);
try {
final user = await ref.read(authServiceProvider).login(
account: account,
password: password,
captcha: captcha,
uuid: uuid,
);
// 持久化登录态(token + 用户信息),用于免登录恢复。
await CredentialStorage.instance.saveSession(
token: user.token,
refreshToken: TokenStore.instance.refreshToken,
userJson: jsonEncode(user.toJson()),
);
// 记住密码。
if (rememberPassword) {
await CredentialStorage.instance.saveCredentials(
account: account,
password: password,
);
} else {
await CredentialStorage.instance.clearCredentials();
}
state = AuthState(user: user, isLoading: false);
// 登录成功后写入数据权限范围,供 OrgService 客户端收敛组织/门店数据。
SessionOrgScope.instance.setCurrentOrg(user.orgId);
return true;
} on AuthException catch (e) {
state = state.copyWith(isLoading: false, errorMessage: e.message);
return false;
} catch (_) {
state = state.copyWith(isLoading: false, errorMessage: '登录失败,请稍后重试');
return false;
}
}
/// 登出并清空登录态。
Future<void> logout() async {
await ref.read(authServiceProvider).logout();
SessionOrgScope.instance.clear();
await _clearPersistedSession();
state = const AuthState();
}
/// 清除本地持久化的登录态。
Future<void> _clearPersistedSession() async {
await CredentialStorage.instance.clearSession();
}
}
/// 登录状态提供者。
final authProvider = NotifierProvider<AuthNotifier, AuthState>(
AuthNotifier.new,
);