Flutter+HarmonyOS跨端实战—第13篇:网络请求封装与错误处理
·
构建健壮的网络层架构
前言
网络请求是移动应用的核心基础设施。一个设计良好的网络层能够:
- 统一管理:Token 刷新、请求日志、错误处理
- 提升体验:自动重试、超时控制、离线缓存
- 便于维护:集中配置、类型安全、易于测试
CleanMark AI 使用 Dio 作为网络库,通过拦截器实现统一的请求处理。这篇文章我会带你构建一个生产级的网络层。
一、网络层架构设计
1.1 分层结构
ApiClient(Dio 封装)
↓
Interceptors(拦截器链)
├── TokenInterceptor(Token 注入)
├── LogInterceptor(请求日志)
├── ErrorInterceptor(错误处理)
└── RetryInterceptor(自动重试)
↓
DataSource(数据源)
↓
Repository(仓库)
1.2 核心组件
// lib/core/network/api_client.dart
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../constants/api_constants.dart';
import 'interceptors/token_interceptor.dart';
import 'interceptors/log_interceptor.dart';
import 'interceptors/error_interceptor.dart';
import 'interceptors/retry_interceptor.dart';
/// API 客户端封装
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(_baseOptions);
_setupInterceptors();
}
/// 基础配置
BaseOptions get _baseOptions => BaseOptions(
baseUrl: ApiConstants.baseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
/// 配置拦截器
void _setupInterceptors() {
_dio.interceptors.addAll([
TokenInterceptor(), // Token 注入
LogInterceptor(), // 请求日志
ErrorInterceptor(), // 错误处理
RetryInterceptor(_dio), // 自动重试
]);
}
/// GET 请求
Future<Response> get(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
return await _dio.get(
path,
queryParameters: queryParameters,
options: options,
);
}
/// POST 请求
Future<Response> post(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
ProgressCallback? onSendProgress,
}) async {
return await _dio.post(
path,
data: data,
queryParameters: queryParameters,
options: options,
onSendProgress: onSendProgress,
);
}
/// PUT 请求
Future<Response> put(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
return await _dio.put(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
/// DELETE 请求
Future<Response> delete(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
return await _dio.delete(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
/// 下载文件
Future<Response> download(
String urlPath,
String savePath, {
ProgressCallback? onReceiveProgress,
CancelToken? cancelToken,
}) async {
return await _dio.download(
urlPath,
savePath,
onReceiveProgress: onReceiveProgress,
cancelToken: cancelToken,
);
}
}
/// Provider 定义
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient();
});
本篇小结(第一部分)
这部分我们完成了:
- ✅ 网络层架构设计(分层结构)
- ✅ ApiClient 封装(Dio 配置)
- ✅ 基础请求方法(GET、POST、PUT、DELETE、下载)
下一部分我会继续讲解拦截器的实现。
二、拦截器实现
2.1 Token 拦截器
// lib/core/network/interceptors/token_interceptor.dart
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Token 拦截器,自动注入 JWT Token
class TokenInterceptor extends Interceptor {
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
// 从本地存储获取 Token
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
if (token != null && token.isNotEmpty) {
// 注入 Authorization 头
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
void onError(DioException err, ErrorInterceptorHandler handler) async {
// Token 过期,尝试刷新
if (err.response?.statusCode == 401) {
try {
final newToken = await _refreshToken();
if (newToken != null) {
// 保存新 Token
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', newToken);
// 重试原请求
final options = err.requestOptions;
options.headers['Authorization'] = 'Bearer $newToken';
final response = await Dio().fetch(options);
return handler.resolve(response);
}
} catch (e) {
// Token 刷新失败,清除本地 Token,跳转登录页
final prefs = await SharedPreferences.getInstance();
await prefs.remove('auth_token');
}
}
handler.next(err);
}
/// 刷新 Token
Future<String?> _refreshToken() async {
try {
final prefs = await SharedPreferences.getInstance();
final refreshToken = prefs.getString('refresh_token');
if (refreshToken == null) return null;
final response = await Dio().post(
'/api/auth/refresh',
data: {'refreshToken': refreshToken},
);
return response.data['token'] as String?;
} catch (e) {
return null;
}
}
}
2.2 日志拦截器
// lib/core/network/interceptors/log_interceptor.dart
import 'package:dio/dio.dart';
import 'package:logger/logger.dart';
/// 日志拦截器,记录请求和响应
class LogInterceptor extends Interceptor {
final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 0,
errorMethodCount: 5,
lineLength: 80,
colors: true,
printEmojis: true,
),
);
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
_logger.d(
'┌─────────────────────────────────────────────────────\n'
'│ 请求: ${options.method} ${options.uri}\n'
'│ Headers: ${options.headers}\n'
'│ Data: ${options.data}\n'
'└─────────────────────────────────────────────────────',
);
handler.next(options);
}
void onResponse(Response response, ResponseInterceptorHandler handler) {
_logger.i(
'┌─────────────────────────────────────────────────────\n'
'│ 响应: ${response.statusCode} ${response.requestOptions.uri}\n'
'│ Data: ${response.data}\n'
'└─────────────────────────────────────────────────────',
);
handler.next(response);
}
void onError(DioException err, ErrorInterceptorHandler handler) {
_logger.e(
'┌─────────────────────────────────────────────────────\n'
'│ 错误: ${err.requestOptions.method} ${err.requestOptions.uri}\n'
'│ 状态码: ${err.response?.statusCode}\n'
'│ 错误信息: ${err.message}\n'
'│ 响应数据: ${err.response?.data}\n'
'└─────────────────────────────────────────────────────',
);
handler.next(err);
}
}
2.3 错误拦截器
// lib/core/network/interceptors/error_interceptor.dart
import 'package:dio/dio.dart';
/// 错误拦截器,统一处理错误
class ErrorInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) {
String errorMessage;
switch (err.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
errorMessage = '网络连接超时,请检查网络设置';
break;
case DioExceptionType.badResponse:
errorMessage = _handleStatusCode(err.response?.statusCode);
break;
case DioExceptionType.cancel:
errorMessage = '请求已取消';
break;
case DioExceptionType.connectionError:
errorMessage = '网络连接失败,请检查网络设置';
break;
case DioExceptionType.unknown:
errorMessage = '未知错误,请重试';
break;
default:
errorMessage = '网络请求失败: ${err.message}';
}
// 将错误信息附加到异常中
handler.next(
DioException(
requestOptions: err.requestOptions,
response: err.response,
type: err.type,
error: errorMessage,
message: errorMessage,
),
);
}
/// 处理 HTTP 状态码
String _handleStatusCode(int? statusCode) {
switch (statusCode) {
case 400:
return '请求参数错误';
case 401:
return '未授权,请重新登录';
case 402:
return '积分不足,请先充值';
case 403:
return '没有权限访问';
case 404:
return '请求的资源不存在';
case 500:
return '服务器内部错误';
case 502:
return '网关错误';
case 503:
return '服务暂时不可用';
default:
return '服务器错误 ($statusCode)';
}
}
}
2.4 重试拦截器
// lib/core/network/interceptors/retry_interceptor.dart
import 'package:dio/dio.dart';
/// 重试拦截器,自动重试失败的请求
class RetryInterceptor extends Interceptor {
final Dio _dio;
final int maxRetries;
final Duration retryDelay;
RetryInterceptor(
this._dio, {
this.maxRetries = 3,
this.retryDelay = const Duration(seconds: 1),
});
void onError(DioException err, ErrorInterceptorHandler handler) async {
// 只重试特定类型的错误
if (!_shouldRetry(err)) {
return handler.next(err);
}
// 获取重试次数
final retryCount = err.requestOptions.extra['retryCount'] as int? ?? 0;
if (retryCount >= maxRetries) {
return handler.next(err);
}
// 延迟后重试
await Future.delayed(retryDelay * (retryCount + 1));
// 更新重试次数
err.requestOptions.extra['retryCount'] = retryCount + 1;
try {
final response = await _dio.fetch(err.requestOptions);
return handler.resolve(response);
} catch (e) {
return handler.next(err);
}
}
/// 判断是否应该重试
bool _shouldRetry(DioException err) {
// 超时错误重试
if (err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.receiveTimeout) {
return true;
}
// 网络错误重试
if (err.type == DioExceptionType.connectionError) {
return true;
}
// 5xx 服务器错误重试
if (err.response?.statusCode != null &&
err.response!.statusCode! >= 500) {
return true;
}
return false;
}
}
本篇小结(第二部分)
这部分我们完成了:
- ✅ TokenInterceptor(Token 注入 + 自动刷新)
- ✅ LogInterceptor(请求/响应日志)
- ✅ ErrorInterceptor(统一错误处理)
- ✅ RetryInterceptor(自动重试机制)
下一部分我会继续讲解 API 常量管理和错误类型定义。
三、API 常量与错误定义
3.1 API 常量管理
// lib/core/constants/api_constants.dart
/// API 常量配置
class ApiConstants {
/// 基础 URL(根据环境切换)
static const String baseUrl = _getBaseUrl();
/// 开发环境
static const String devBaseUrl = 'http://localhost:3000';
/// 生产环境
static const String prodBaseUrl = 'https://api.cleanmark.ai';
/// 根据环境获取 Base URL
static String _getBaseUrl() {
const env = String.fromEnvironment('ENV', defaultValue: 'dev');
return env == 'prod' ? prodBaseUrl : devBaseUrl;
}
// ========== 认证相关 ==========
static const String register = '/api/auth/register';
static const String login = '/api/auth/login';
static const String refreshToken = '/api/auth/refresh';
static const String logout = '/api/auth/logout';
// ========== 用户相关 ==========
static const String userInfo = '/api/user/me';
static const String updateProfile = '/api/user/profile';
// ========== 图片去水印 ==========
static const String imageInpaint = '/api/inpaint/remove';
static const String imageTasks = '/api/inpaint/tasks';
static String imageTask(String id) => '/api/inpaint/tasks/$id';
// ========== 视频去水印 ==========
static const String videoInpaint = '/api/video-inpaint/remove';
static const String videoTasks = '/api/video-inpaint/tasks';
static String videoTask(String id) => '/api/video-inpaint/tasks/$id';
// ========== 积分相关 ==========
static const String creditHistory = '/api/credits/history';
static const String rewardAdCredit = '/api/credits/reward-ad';
static const String purchaseCredits = '/api/credits/purchase';
// ========== 历史记录 ==========
static const String allTasks = '/api/tasks';
static String deleteTask(String id) => '/api/tasks/$id';
// ========== 文件访问 ==========
static String fileUrl(String filename) => '/files/output/$filename';
}
3.2 错误类型定义
// lib/core/errors/failures.dart
/// 业务失败基类
abstract class Failure {
final String message;
const Failure(this.message);
String toString() => message;
}
/// 网络连接失败
class NetworkFailure extends Failure {
const NetworkFailure([String message = '网络连接失败,请检查网络设置'])
: super(message);
}
/// 服务器返回错误
class ServerFailure extends Failure {
const ServerFailure(String message) : super(message);
}
/// 验证失败(参数错误)
class ValidationFailure extends Failure {
const ValidationFailure(String message) : super(message);
}
/// 认证失败(未登录或 Token 过期)
class AuthFailure extends Failure {
const AuthFailure([String message = '未授权,请重新登录']) : super(message);
}
/// 积分不足
class InsufficientCreditsFailure extends Failure {
const InsufficientCreditsFailure([String message = '积分不足,请先充值'])
: super(message);
}
/// 缓存读写失败
class CacheFailure extends Failure {
const CacheFailure([String message = '本地数据读取失败,请重试'])
: super(message);
}
/// 未知错误
class UnknownFailure extends Failure {
const UnknownFailure(String message) : super(message);
}
3.3 统一响应模型
// lib/core/network/models/api_response.dart
/// API 统一响应模型
class ApiResponse<T> {
final int code;
final String message;
final T? data;
const ApiResponse({
required this.code,
required this.message,
this.data,
});
factory ApiResponse.fromJson(
Map<String, dynamic> json,
T Function(dynamic)? fromJsonT,
) {
return ApiResponse(
code: json['code'] as int,
message: json['message'] as String,
data: json['data'] != null && fromJsonT != null
? fromJsonT(json['data'])
: null,
);
}
/// 是否成功
bool get isSuccess => code == 200;
/// 转换为 Either
Either<Failure, T> toEither() {
if (isSuccess && data != null) {
return Right(data as T);
} else {
return Left(ServerFailure(message));
}
}
}
3.4 异常转换工具
// lib/core/network/utils/exception_handler.dart
import 'package:dio/dio.dart';
import '../../errors/failures.dart';
/// 异常处理工具类
class ExceptionHandler {
/// 将 DioException 转换为 Failure
static Failure handleDioException(DioException exception) {
switch (exception.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return const NetworkFailure('网络连接超时,请检查网络设置');
case DioExceptionType.badResponse:
return _handleStatusCode(exception.response);
case DioExceptionType.cancel:
return const NetworkFailure('请求已取消');
case DioExceptionType.connectionError:
return const NetworkFailure('网络连接失败,请检查网络设置');
case DioExceptionType.unknown:
return UnknownFailure(exception.message ?? '未知错误');
default:
return UnknownFailure(exception.message ?? '网络请求失败');
}
}
/// 处理 HTTP 状态码
static Failure _handleStatusCode(Response? response) {
final statusCode = response?.statusCode;
final message = response?.data?['message'] as String?;
switch (statusCode) {
case 400:
return ValidationFailure(message ?? '请求参数错误');
case 401:
return const AuthFailure();
case 402:
return const InsufficientCreditsFailure();
case 403:
return const ServerFailure('没有权限访问');
case 404:
return const ServerFailure('请求的资源不存在');
case 500:
return ServerFailure(message ?? '服务器内部错误');
case 502:
return const ServerFailure('网关错误');
case 503:
return const ServerFailure('服务暂时不可用');
default:
return ServerFailure(message ?? '服务器错误 ($statusCode)');
}
}
/// 将通用异常转换为 Failure
static Failure handleException(Object exception) {
if (exception is DioException) {
return handleDioException(exception);
}
return UnknownFailure(exception.toString());
}
}
本篇小结(第三部分)
这部分我们完成了:
- ✅ API 常量管理(统一管理所有接口路径)
- ✅ 错误类型定义(7 种 Failure 类型)
- ✅ 统一响应模型(ApiResponse 封装)
- ✅ 异常转换工具(DioException → Failure)
下一部分我会继续讲解实际使用示例和最佳实践。
四、实际使用示例
4.1 DataSource 中使用
// lib/features/auth/data/datasources/auth_remote_datasource.dart
import 'package:dio/dio.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/network/utils/exception_handler.dart';
import '../../../../core/constants/api_constants.dart';
import '../../models/user_model.dart';
class AuthRemoteDataSource {
final ApiClient _apiClient;
AuthRemoteDataSource(this._apiClient);
/// 登录
Future<UserModel> login(String email, String password) async {
try {
final response = await _apiClient.post(
ApiConstants.login,
data: {
'email': email,
'password': password,
},
);
return UserModel.fromJson(response.data);
} on DioException catch (e) {
throw ExceptionHandler.handleDioException(e);
} catch (e) {
throw ExceptionHandler.handleException(e);
}
}
/// 注册
Future<UserModel> register(String email, String password) async {
try {
final response = await _apiClient.post(
ApiConstants.register,
data: {
'email': email,
'password': password,
},
);
return UserModel.fromJson(response.data);
} on DioException catch (e) {
throw ExceptionHandler.handleDioException(e);
} catch (e) {
throw ExceptionHandler.handleException(e);
}
}
}
4.2 Repository 中处理
// lib/features/auth/data/repositories/auth_repository_impl.dart
import 'package:dartz/dartz.dart';
import '../../../../core/errors/failures.dart';
import '../../domain/repositories/auth_repository.dart';
import '../../domain/entities/user_entity.dart';
import '../datasources/auth_remote_datasource.dart';
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource _remoteDataSource;
AuthRepositoryImpl(this._remoteDataSource);
Future<Either<Failure, UserEntity>> login(
String email,
String password,
) async {
try {
final userModel = await _remoteDataSource.login(email, password);
return Right(userModel.toEntity());
} on Failure catch (failure) {
return Left(failure);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
Future<Either<Failure, UserEntity>> register(
String email,
String password,
) async {
try {
final userModel = await _remoteDataSource.register(email, password);
return Right(userModel.toEntity());
} on Failure catch (failure) {
return Left(failure);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}
4.3 UI 层错误展示
// lib/features/auth/presentation/pages/login_page.dart
Future<void> _handleLogin() async {
final result = await ref.read(authProvider.notifier).login(
_emailController.text,
_passwordController.text,
);
if (!mounted) return;
result.fold(
(failure) {
// 根据错误类型显示不同提示
String message;
if (failure is NetworkFailure) {
message = failure.message;
} else if (failure is AuthFailure) {
message = '邮箱或密码错误';
} else if (failure is ValidationFailure) {
message = failure.message;
} else {
message = '登录失败,请重试';
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: AppColors.red,
),
);
},
(user) {
// 登录成功,跳转首页
context.go('/home');
},
);
}
五、最佳实践
5.1 环境配置
使用 --dart-define 传递环境变量:
# 开发环境
flutter run --dart-define=ENV=dev
# 生产环境
flutter run --dart-define=ENV=prod
在代码中读取:
const env = String.fromEnvironment('ENV', defaultValue: 'dev');
5.2 超时配置
// 不同接口设置不同超时时间
Future<Response> uploadFile(File file) async {
return await _apiClient.post(
'/api/upload',
data: formData,
options: Options(
sendTimeout: const Duration(minutes: 5), // 上传文件超时 5 分钟
),
);
}
5.3 取消请求
class ImageUploadPage extends StatefulWidget {
final CancelToken _cancelToken = CancelToken();
void dispose() {
_cancelToken.cancel('页面已关闭');
super.dispose();
}
Future<void> _uploadImage() async {
await _apiClient.post(
'/api/upload',
data: formData,
cancelToken: _cancelToken,
);
}
}
5.4 请求去重
// lib/core/network/utils/request_cache.dart
class RequestCache {
static final Map<String, Future<Response>> _cache = {};
/// 带缓存的请求
static Future<Response> cachedRequest(
String key,
Future<Response> Function() request,
) {
if (_cache.containsKey(key)) {
return _cache[key]!;
}
final future = request();
_cache[key] = future;
// 请求完成后清除缓存
future.whenComplete(() => _cache.remove(key));
return future;
}
}
// 使用示例
Future<Response> getUserInfo() {
return RequestCache.cachedRequest(
'user_info',
() => _apiClient.get('/api/user/me'),
);
}
5.5 Mock 数据
// lib/core/network/mock/mock_interceptor.dart
class MockInterceptor extends Interceptor {
final bool enableMock;
MockInterceptor({this.enableMock = false});
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) {
if (!enableMock) {
return handler.next(options);
}
// 根据路径返回 Mock 数据
if (options.path == '/api/user/me') {
return handler.resolve(
Response(
requestOptions: options,
data: {
'id': '1',
'email': 'test@example.com',
'credits': 10,
},
),
);
}
handler.next(options);
}
}
本篇小结(第四部分)
这部分我们完成了:
- ✅ DataSource 使用示例
- ✅ Repository 错误处理
- ✅ UI 层错误展示
- ✅ 环境配置管理
- ✅ 超时、取消、去重、Mock 等最佳实践
下一部分我会总结本篇的关键要点。
六、完整小结
本篇完整小结
这篇文章我们完成了:
- ✅ 网络层架构设计(ApiClient + 拦截器链)
- ✅ ApiClient 封装(Dio 配置 + 基础请求方法)
- ✅ 四大拦截器(Token、日志、错误、重试)
- ✅ API 常量管理(统一管理接口路径)
- ✅ 错误类型定义(7 种 Failure 类型)
- ✅ 统一响应模型(ApiResponse 封装)
- ✅ 异常转换工具(DioException → Failure)
- ✅ 实际使用示例(DataSource、Repository、UI)
- ✅ 最佳实践(环境配置、超时、取消、去重、Mock)
关键要点:
架构设计
拦截器链模式:
Request
↓
TokenInterceptor(注入 Token)
↓
LogInterceptor(记录日志)
↓
ErrorInterceptor(统一错误)
↓
RetryInterceptor(自动重试)
↓
Network
↓
Response(反向经过拦截器)
职责分离:
- ApiClient:封装 Dio,提供基础请求方法
- Interceptors:处理横切关注点(Token、日志、错误、重试)
- DataSource:调用 ApiClient,处理业务异常
- Repository:转换异常为 Failure,返回 Either
- UI:根据 Failure 类型展示友好提示
Token 管理
自动刷新机制:
- 请求返回 401 → Token 过期
- 调用
/api/auth/refresh刷新 Token - 保存新 Token 到本地
- 重试原请求
- 刷新失败 → 清除 Token,跳转登录页
if (err.response?.statusCode == 401) {
final newToken = await _refreshToken();
if (newToken != null) {
// 保存新 Token
await prefs.setString('auth_token', newToken);
// 重试原请求
final response = await Dio().fetch(err.requestOptions);
return handler.resolve(response);
}
}
错误处理策略
三层错误处理:
- DataSource 层:捕获 DioException,转换为业务异常
try {
final response = await _apiClient.post(...);
return UserModel.fromJson(response.data);
} on DioException catch (e) {
throw ExceptionHandler.handleDioException(e);
}
- Repository 层:捕获业务异常,转换为 Failure
try {
final userModel = await _remoteDataSource.login(...);
return Right(userModel.toEntity());
} on Failure catch (failure) {
return Left(failure);
}
- UI 层:根据 Failure 类型展示友好提示
result.fold(
(failure) {
if (failure is NetworkFailure) {
showSnackBar('网络连接失败');
} else if (failure is AuthFailure) {
showSnackBar('邮箱或密码错误');
}
},
(user) => navigateToHome(),
);
重试机制
指数退避策略:
// 第 1 次重试:延迟 1 秒
// 第 2 次重试:延迟 2 秒
// 第 3 次重试:延迟 3 秒
await Future.delayed(retryDelay * (retryCount + 1));
重试条件:
- 超时错误(connectionTimeout、receiveTimeout)
- 网络错误(connectionError)
- 5xx 服务器错误
不重试条件:
- 4xx 客户端错误(参数错误、未授权等)
- 请求被取消
- 已达最大重试次数
日志管理
开发环境:
- 详细日志(请求头、请求体、响应体)
- 彩色输出,便于调试
生产环境:
- 关键日志(错误、异常)
- 上报到日志服务(Sentry、Firebase Crashlytics)
// 根据环境配置日志级别
final logger = Logger(
level: kDebugMode ? Level.debug : Level.error,
);
性能优化
1. 请求去重
// 相同请求正在进行时,复用 Future
RequestCache.cachedRequest('user_info', () => _apiClient.get(...));
2. 连接池复用
// Dio 默认启用连接池,无需额外配置
3. 压缩传输
// 启用 gzip 压缩
headers: {
'Accept-Encoding': 'gzip',
}
4. 取消无用请求
// 页面销毁时取消请求
_cancelToken.cancel('页面已关闭');
测试策略
1. Mock 拦截器
// 开发阶段使用 Mock 数据
ApiClient(enableMock: true);
2. 单元测试
test('登录成功时,应返回用户实体', () async {
when(() => mockDataSource.login(any(), any()))
.thenAnswer((_) async => mockUserModel);
final result = await repository.login('test@example.com', 'password');
expect(result.isRight(), true);
});
3. 集成测试
// 使用真实 API 测试
testWidgets('登录流程测试', (tester) async {
await tester.pumpWidget(MyApp());
await tester.enterText(find.byKey(Key('email')), 'test@example.com');
await tester.tap(find.byKey(Key('login_button')));
await tester.pumpAndSettle();
expect(find.text('登录成功'), findsOneWidget);
});
思考题
- 如何实现请求优先级队列?(提示:自定义拦截器 + 队列管理)
- Token 刷新时如何避免并发请求重复刷新?(提示:单例 + 锁机制)
- 如何实现离线缓存,网络恢复后自动同步?(提示:本地队列 + 网络监听)
下一篇预告:第14篇 - 性能优化与内存管理
更多推荐



所有评论(0)