159 lines
4.7 KiB
Dart
159 lines
4.7 KiB
Dart
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,
|
||
);
|