网络层是应用稳定性的底座。一个好的网络封装,不是把 Dio 或 http 包一层,而是解决三件事:统一入口、统一错误、统一可观测。下面给出一套可直接复用的网络层范式。
1. 先定义统一返回模型
无论后端风格怎样,客户端应该先把返回统一成一个模型:
class ApiResponse<T> {
final T? data;
final ApiError? error;
final int? code;
final String? message;
const ApiResponse({this.data, this.error, this.code, this.message});
bool get isSuccess => error == null;
}
class ApiError {
final int code;
final String message;
const ApiError(this.code, this.message);
}
2. 封装统一请求入口
所有请求通过一个 Client 走,不让业务层直接调用 Dio.get:
class ApiClient {
final Dio dio;
ApiClient(this.dio);
Future<ApiResponse<T>> get<T>(
String path, {
Map<String, dynamic>? query,
T Function(dynamic json)? parser,
}) async {
try {
final res = await dio.get(path, queryParameters: query);
final data = parser != null ? parser(res.data) : res.data as T;
return ApiResponse<T>(data: data, code: res.statusCode);
} on DioException catch (e) {
return ApiResponse<T>(error: _mapError(e), code: e.response?.statusCode);
}
}
}
3. 错误规范化
把各种错误映射成统一错误:
ApiError _mapError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return const ApiError(1001, '连接超时');
case DioExceptionType.receiveTimeout:
return const ApiError(1002, '响应超时');
case DioExceptionType.badResponse:
return ApiError(e.response?.statusCode ?? 500, '服务端错误');
default:
return const ApiError(1000, '网络异常');
}
}
业务层只关心 ApiError,不用面对多种异常类型。
4. 拦截器统一处理
常见的处理逻辑可以放在拦截器里:
Dio buildDio() {
final dio = Dio(BaseOptions(connectTimeout: const Duration(seconds: 10)));
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer xxx';
handler.next(options);
},
onResponse: (response, handler) {
handler.next(response);
},
onError: (error, handler) {
handler.next(error);
},
));
return dio;
}
统一 token、日志、埋点,这些都应该在这一层处理。
5. 重试策略(谨慎使用)
不建议对所有请求自动重试,只对幂等请求使用:
Future<T> retry<T>(Future<T> Function() task, {int times = 2}) async {
for (var i = 0; i <= times; i++) {
try {
return await task();
} catch (_) {
if (i == times) rethrow;
}
}
throw Exception('unreachable');
}
6. 业务层使用方式
final api = ApiClient(buildDio());
final res = await api.get('/user/profile', parser: (json) => User.fromJson(json));
if (res.isSuccess) {
// 使用 res.data
} else {
// 处理 res.error
}
业务层再也不用 try/catch 捕获各种 Dio 异常。
7. 网络层清单
- 所有请求走统一入口
- 统一错误映射
- 拦截器处理 token / 日志
- 只对幂等请求重试
- 业务层只处理
ApiResponse
总结
好的网络层封装会让业务代码更“干净”,而不是在每个页面里重复处理异常和状态。统一入口、统一错误、统一可观测,是长期可维护的关键。