fix:参考金值,研听,研值工程通用通用工程公共能力,重点对比网络请求、登录认证、状态管理、本地存储、WebView、推送、版本更新、一键登录、主题/UI 等模块,确定后续统一整改方案

This commit is contained in:
jingyun committed 2026-09-10 09:52:36 +08:00
1 parent 6997a65c96
commit d364c6d908
46 files changed
+2060 -395

No files matched your search

@@ -1,13 +1,13 @@
import 'package:dio/dio.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../common/network/api_exception.dart';
import '../../../common/network/dio_provider.dart';
import '../../../common/storage/secure_identity_storage.dart';
import '../../../common/storage/preferences_storage.dart';
import '../../../common/storage/storage_keys.dart';
import '../../../common/storage/storage_service.dart';
import '../data/auth_api.dart';
import '../data/auth_models.dart';
import 'auth_session_coordinator.dart';
const _legacyMockLoggedInKey = 'jinzhi_mock_logged_in';
@@ -15,26 +15,27 @@ final authProvider =
StateNotifierProvider<AuthController, AsyncValue<AuthState>>((ref) {
return AuthController(
ref.read(dioProvider),
identityStorage: ref.read(secureIdentityStorageProvider),
preferences: StorageService.to.preferences,
preferences: ref.read(preferencesStorageProvider),
coordinator: ref.read(authSessionCoordinatorProvider),
);
});
class AuthController extends StateNotifier<AsyncValue<AuthState>> {
AuthController(
this._dio, {
required SecureIdentityStorage identityStorage,
required SharedPreferences preferences,
required PreferencesStorage preferences,
required AuthSessionCoordinator coordinator,
bool autoBootstrap = true,
}) : _identityStorage = identityStorage,
_preferences = preferences,
}) : _preferences = preferences,
_coordinator = coordinator,
super(const AsyncLoading()) {
_coordinator.addListener(_onSessionEnded);
if (autoBootstrap) _bootstrap();
}
final Dio _dio;
final SecureIdentityStorage _identityStorage;
final SharedPreferences _preferences;
final PreferencesStorage _preferences;
final AuthSessionCoordinator _coordinator;
AuthApi get _api => AuthApi(_dio);
@@ -42,28 +43,40 @@ class AuthController extends StateNotifier<AsyncValue<AuthState>> {
Future<void> _bootstrap() async {
state = const AsyncLoading();
final token = await _identityStorage.readJwt();
final token = await _coordinator.restore();
if (token == null || token.isEmpty) {
state = const AsyncData(GuestAuthState());
return;
}
final cached = _readCachedProfile();
if (cached != null) {
state = AsyncData(
LoggedInAuthState(user: cached, token: token, isVerified: false),
);
await _persistProfile(cached);
}
await _verifyProfile(token, _coordinator.generation);
}
Future<void> _verifyProfile(String token, int generation) async {
try {
final user = await _api.getProfile();
if (generation != _coordinator.generation) return;
await _persistProfile(user);
state = AsyncData(LoggedInAuthState(user: user, token: token));
state = AsyncData(
LoggedInAuthState(user: user, token: token, isVerified: true),
);
} catch (error, stackTrace) {
if (_isUnauthorized(error)) {
await _clearLocalSession();
state = const AsyncData(GuestAuthState());
if (generation != _coordinator.generation) return;
final exception = ApiException.from(error, stackTrace: stackTrace);
if (await _coordinator.expireIfNeeded(exception)) {
return;
}
final cached = _canUseCachedProfile(error) ? _readCachedProfile() : null;
if (cached != null) {
await _persistProfile(cached);
state = AsyncData(LoggedInAuthState(user: cached, token: token));
} else {
state = AsyncError(error, stackTrace);
}
// Any non-session error (transport, 403, 429, 5xx, decode) preserves a
// stale cached session. With no profile there is nothing safe to render,
// but the credential still remains available for retry.
if (state.valueOrNull is LoggedInAuthState) return;
state = AsyncError(exception, stackTrace);
}
}
@@ -80,7 +93,7 @@ class AuthController extends StateNotifier<AsyncValue<AuthState>> {
}
Future<void> _persistLogin(AuthLoginResult reply) async {
await _identityStorage.saveJwt(reply.accessToken);
await _coordinator.startSession(reply.accessToken);
await _persistProfile(reply.user);
state = AsyncData(
LoggedInAuthState(user: reply.user, token: reply.accessToken),
@@ -88,8 +101,7 @@ class AuthController extends StateNotifier<AsyncValue<AuthState>> {
}
Future<void> logout() async {
await _clearLocalSession();
state = const AsyncData(GuestAuthState());
await _coordinator.logout();
}
Future<void> deleteAccount(String code) async {
@@ -98,8 +110,7 @@ class AuthController extends StateNotifier<AsyncValue<AuthState>> {
throw ArgumentError.value(code, 'code', 'must not be empty');
}
await _api.deleteAccount(normalized);
await _clearLocalSession();
state = const AsyncData(GuestAuthState());
await _coordinator.accountDeleted();
}
Future<void> _persistProfile(JinzhiUser user) async {
@@ -122,22 +133,19 @@ class AuthController extends StateNotifier<AsyncValue<AuthState>> {
return JinzhiUser(id: id, phone: phone, createdAt: createdAt);
}
Future<void> _clearLocalSession() async {
await _identityStorage.clearJwt();
await _preferences.remove(storageUserId);
await _preferences.remove(storageUserPhone);
await _preferences.remove(storageUserCreatedAt);
await _preferences.remove(_legacyMockLoggedInKey);
void _onSessionEnded(SessionEndReason reason) {
state = AsyncData(
reason == SessionEndReason.expired
? const SessionExpiredAuthState()
: const GuestAuthState(),
);
}
bool get isLoggedIn => state.valueOrNull is LoggedInAuthState;
}
bool _isUnauthorized(Object error) =>
error is DioException && error.response?.statusCode == 401;
bool _canUseCachedProfile(Object error) {
if (error is! DioException) return false;
final status = error.response?.statusCode;
return status == null || status == 408 || status == 429 || status >= 500;
@override
void dispose() {
_coordinator.removeListener(_onSessionEnded);
super.dispose();
}
}