feat(client): P1 真实数据接入地基(#6 6A)
- api_config.dart:API 基址单源(消除各处重复 _kApiUrl) - api_client.dart:统一鉴权 HTTP 客户端,401→AuthNotifier.refresh→重试一次, 错误统一 AuthApiException;+ 单测覆盖 401 刷新重试 - 模型 me/device/plan/usage_point(对齐后端 snake_case 契约) - account_api.dart:/v1/me、/v1/plans、/v1/me/devices(列表+删)、/v1/usage、 /v1/redeem、/v1/ads/unlock 封装 - auth_provider:注入 AuthApi + refresh();account_providers 装配 ApiClient/AccountApi - 顺带修复 onboarding 提交遗留的测试 stub(_NullTokenStore 缺 isOnboarded/markOnboarded) flutter analyze 0 error;114 tests passed。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
// account_providers.dart — 账户域 Riverpod 装配。
|
||||
//
|
||||
// 此处只做依赖装配:把 authProvider 的令牌/刷新接到统一 ApiClient,
|
||||
// 再产出 AccountApi。具体数据 provider(meProvider/devicesProvider/usage…)
|
||||
// 在后续阶段加入本文件。
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../services/account_api.dart';
|
||||
import '../services/api_client.dart';
|
||||
import '../services/api_config.dart';
|
||||
import 'auth_provider.dart';
|
||||
|
||||
/// 统一鉴权 HTTP 客户端:令牌取自 authProvider,401 时触发 AuthNotifier.refresh。
|
||||
final apiClientProvider = Provider<ApiClient>((ref) {
|
||||
return ApiClient(
|
||||
baseUrl: kApiBaseUrl,
|
||||
getToken: () => ref.read(authProvider).accessToken,
|
||||
refresh: () => ref.read(authProvider.notifier).refresh(),
|
||||
);
|
||||
});
|
||||
|
||||
/// 账户域端点封装。
|
||||
final accountApiProvider = Provider<AccountApi>(
|
||||
(ref) => AccountApi(ref.watch(apiClientProvider)),
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
// auth_provider.dart — 认证状态(JWT 令牌生命周期)
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../services/api_config.dart';
|
||||
import '../services/auth_api.dart';
|
||||
import '../services/token_store.dart';
|
||||
|
||||
@@ -23,11 +24,14 @@ class AuthState {
|
||||
// ── 状态机 ───────────────────────────────────────────────────────────
|
||||
|
||||
class AuthNotifier extends StateNotifier<AuthState> {
|
||||
AuthNotifier(this._store) : super(const AuthState(isLoading: true)) {
|
||||
AuthNotifier(this._store, {AuthApi? api})
|
||||
: _api = api ?? AuthApi(baseUrl: kApiBaseUrl),
|
||||
super(const AuthState(isLoading: true)) {
|
||||
_loadFromStore();
|
||||
}
|
||||
|
||||
final TokenStore _store;
|
||||
final AuthApi _api;
|
||||
|
||||
Future<void> _loadFromStore() async {
|
||||
try {
|
||||
@@ -48,6 +52,19 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
||||
state = AuthState(accessToken: tokens.accessToken);
|
||||
}
|
||||
|
||||
/// 用存储的 refresh token 刷新令牌(供 ApiClient 在 401 时调用)。
|
||||
/// 成功写入新令牌并更新状态返回 true;无 refresh token / 刷新失败返回 false。
|
||||
Future<bool> refresh() async {
|
||||
final rt = await _store.loadRefreshToken();
|
||||
if (rt == null || rt.isEmpty) return false;
|
||||
try {
|
||||
await saveTokens(await _api.refresh(rt));
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 退出登录:清除本地令牌。
|
||||
Future<void> logout() async {
|
||||
await _store.clear();
|
||||
|
||||
Reference in New Issue
Block a user