import 'package:dio/dio.dart'; import 'package:sunny_mochi/core/config/api_config.dart'; import 'package:sunny_mochi/core/config/env.dart'; import 'package:sunny_mochi/core/network/interceptors/auth_interceptor.dart'; import 'package:sunny_mochi/core/network/interceptors/auth_logout_interceptor.dart'; import 'package:sunny_mochi/core/network/interceptors/cert_pinning_interceptor.dart'; import 'package:sunny_mochi/core/network/interceptors/error_interceptor.dart'; import 'package:sunny_mochi/core/network/interceptors/log_interceptor.dart'; import 'package:sunny_mochi/core/network/interceptors/retry_interceptor.dart'; import 'package:sunny_mochi/core/network/interceptors/token_refresh_interceptor.dart'; import 'package:sunny_mochi/core/network/mock/dio_mock_adapter.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'dio_client.g.dart'; /// 完整 7 拦截器栈: /// CertPinning → Auth → TokenRefresh → Retry → Error → AuthLogout → Log /// /// 顺序敏感: /// - CertPinning 最早(在请求出去前校验) /// - Auth 注 Token,TokenRefresh 在 401 时拦截再发 /// - Retry 在网络/5xx 错误时退避重试 /// - Error 把所有 DioException 映射为应用层 Failure /// - **AuthLogout 必须在 Error 之后**:依赖 e.error 已被映射为 AuthFailure /// - Log 最后,记录最终结果(Release 包 noop) @Riverpod(keepAlive: true) Dio dioClient(Ref ref) { final dio = Dio( BaseOptions( baseUrl: ApiConfig.baseUrl, connectTimeout: ApiConfig.connectTimeout, receiveTimeout: ApiConfig.receiveTimeout, sendTimeout: ApiConfig.sendTimeout, headers: const { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ), ); dio.interceptors.addAll([ CertPinningInterceptor(), AuthInterceptor(ref), TokenRefreshInterceptor(ref, dio), RetryInterceptor(dio), ErrorInterceptor(), AuthLogoutInterceptor(ref), buildNetworkLogInterceptor(), ]); if (Env.useMock) { dio.httpClientAdapter = buildDefaultMockAdapter(); } return dio; }