smart_dio 1.0.3
smart_dio: ^1.0.3 copied to clipboard
Dio wrapper with pluggable pipelines, sealed 8-case errors, multilayer localization fallback, and SmartClientManager for multiple named clients.
smart_dio #
基于 Dio 的插件化 HTTP 客户端,专为 Flutter 设计。
核心特性 #
- 插件化架构 - 全局插件 + 单次请求热插热拔,灵活组合日志/缓存/重试等能力
- 类型安全错误 - Dart 3 sealed class,8 种错误类型,exhaustive 模式匹配
- 多语言本地化 - 可配置错误消息,内置默认中文,支持部分覆盖
- 多客户端管理 - 支持多后端、灰度环境,统一管理生命周期
环境要求 #
- Dart SDK:
>=3.0.0 <4.0.0 - Flutter:
>=3.10.0
安装 #
dependencies:
smart_dio: ^<latest>
目录 #
快速开始 #
import 'package:smart_dio/smart_dio.dart';
void main() async {
// 1. 初始化
SmartDio.init(SmartConfig(
options: BaseOptions(baseUrl: 'https://api.example.com'),
globalPlugins: [
LoggerPlugin(),
RetryPlugin(retries: 3),
],
));
// 2. 发起请求
try {
final response = await SmartDio.get<Map>('/users');
print(response.data);
} on SmartError catch (e) {
// 3. 类型安全的错误处理
switch (e) {
case NetworkError():
print('网络不可用');
case TimeoutError():
print('请求超时');
case ResponseError(:final statusCode):
print('服务器错误: $statusCode');
default:
print(e.message);
}
}
}
核心概念 #
| 概念 | 说明 |
|---|---|
SmartConfig |
全局配置,包含 BaseOptions 和 globalPlugins |
SmartPlugin |
插件基类,定义 onStart/onRequest/onResponse/onError/onFinish 生命周期 |
SmartError |
sealed 错误基类,8 种子类型,支持 exhaustive 模式匹配 |
SmartClientManager |
多客户端管理器,支持注册/获取/释放命名客户端 |
ResponseKeyMap |
响应 key 映射配置,支持自定义 code/msg/data 字段名 |
与直接使用 Dio 的对比 #
| 能力 | 原生 Dio | smart_dio |
|---|---|---|
| 插件组合 | 手动管理 Interceptor 列表 | globalPlugins + extraPlugins 声明式配置 |
| 错误处理 | DioExceptionType 枚举,易遗漏分支 | sealed class + exhaustive 匹配,编译期检查 |
| 重试/缓存/日志 | 需分别配置拦截器 | 内置插件,开箱即用 |
| 多语言错误消息 | 无内置支持 | 内置默认中文 + 可配置覆盖 |
| 多客户端 | 手动管理多个 Dio 实例 | SmartClientManager 统一管理 |
配置与插件 #
SmartConfig #
SmartDio.init(SmartConfig(
// Dio 基础配置
options: BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 30),
headers: {'Accept': 'application/json'},
),
// 全局插件列表
globalPlugins: [
LoggerPlugin(),
HeaderPlugin({'Authorization': () => 'Bearer $token'}),
ParamsPlugin({'platform': () => 'ios'}),
CachePlugin.memory(),
RetryPlugin(retries: 3),
],
// 错误本地化器(可选)
errorLocalizer: DefaultErrorLocalizer(),
));
热插热拔 #
// 单次请求添加插件(热插)
await SmartDio.get('/api',
extraPlugins: [CustomPlugin()],
);
// 单次请求禁用插件(热拔)
await SmartDio.get('/realtime',
excludePlugins: {CachePlugin},
);
// 热插 + 热拔
await SmartDio.post('/sensitive',
extraPlugins: [EncryptPlugin()],
excludePlugins: {LoggerPlugin},
);
插件优先级 #
插件按 priority 数值从小到大执行:
| 插件 | Priority | 说明 |
|---|---|---|
| LoggerPlugin | 10 | 最先执行,记录原始请求 |
| HeaderPlugin | 50 | 注入固定头部 |
| ParamsPlugin | 50 | 注入固定参数 |
| CachePlugin | 60 | 缓存处理 |
| RetryPlugin | 80 | 重试逻辑 |
内置插件 #
LoggerPlugin #
LoggerPlugin(
// 自定义 Talker 实例(可选)
talker: Talker(),
)
HeaderPlugin #
// 静态头部
HeaderPlugin({
'X-App-Version': '1.0.0',
})
// 动态头部(每次请求时计算)
HeaderPlugin({
'Authorization': () => 'Bearer ${getToken()}',
'X-Timestamp': () => DateTime.now().millisecondsSinceEpoch.toString(),
})
ParamsPlugin #
// GET 请求:添加到 queryParameters
// POST 请求:添加到 body data
ParamsPlugin({
'platform': () => Platform.isIOS ? 'ios' : 'android',
'version': () => packageInfo.version,
})
CachePlugin #
// 内存缓存
CachePlugin.memory()
// 自定义缓存配置
CachePlugin(
options: CacheOptions(
store: MemCacheStore(),
policy: CachePolicy.request,
maxStale: Duration(days: 7),
),
)
RetryPlugin #
// 基本用法
RetryPlugin(retries: 3)
// 自定义重试逻辑(基于 SmartError)
RetryPlugin(
retries: 3,
shouldRetry: (error) => switch (error) {
NetworkError() => true,
TimeoutError() => true,
ResponseError(:final statusCode) => statusCode >= 500,
_ => false,
},
)
// 使用默认重试逻辑
RetryPlugin(shouldRetry: RetryPlugin.defaultShouldRetry)
ResponseParserPlugin #
自动解析后端统一响应格式(code/msg/data),业务失败时抛出 BusinessError。
// 全局配置(默认 key: code/msg/data)
ResponseParserPlugin(
successCodes: [0, '0'], // 成功码列表
)
// 自定义 key 映射
ResponseParserPlugin(
keyMap: ResponseKeyMap(
codeKey: 'status', // 默认 'code'
msgKey: 'message', // 默认 'msg'
dataKey: 'result', // 默认 'data'
),
successCodes: [200, 'success'],
)
// 自定义成功判断(优先于 successCodes)
ResponseParserPlugin(
successChecker: (code) => code == 0 || code == 'OK',
)
单次请求覆盖 key 映射:
final response = await SmartDio.get(
'/api/legacy',
extra: {
kResponseKeyMapKey: ResponseKeyMap(codeKey: 'errCode'),
},
);
工作原理:
- 解析响应中的
code、msg、data字段 - 根据
successCodes或successChecker判断业务是否成功 - 成功:将
data字段作为SmartResponse.data返回 - 失败:抛出
BusinessError,包含code、serverMessage、rawResponse
错误处理 #
SmartError 类型 #
| 类型 | 说明 | 关键字段 |
|---|---|---|
NetworkError |
网络不可达 | host, errorCode |
TimeoutError |
超时(连接/发送/接收) | phase, timeout |
ResponseError |
HTTP 错误响应 | statusCode, statusMessage, responseData |
ParseError |
数据解析失败 | source, expectedType, rawSnippet |
BusinessError |
业务逻辑错误 | code, serverMessage, traceId |
CancelError |
请求被取消 | reason, cancelToken |
CertificateError |
SSL 证书错误 | host |
UnknownError |
未知错误 | originalError |
模式匹配 #
try {
final response = await SmartDio.get('/api');
} on SmartError catch (e) {
final message = switch (e) {
NetworkError() => '请检查网络连接',
TimeoutError(:final phase) => switch (phase) {
TimeoutPhase.connect => '连接超时',
TimeoutPhase.send => '发送超时',
TimeoutPhase.receive => '接收超时',
},
ResponseError(:final statusCode) when statusCode == 401 => '请重新登录',
ResponseError(:final statusCode) when statusCode == 404 => '资源不存在',
ResponseError(:final statusCode) => '服务器错误 ($statusCode)',
BusinessError(:final code, :final serverMessage) => '[$code] $serverMessage',
CancelError() => '请求已取消',
ParseError() => '数据解析失败',
CertificateError() => '证书验证失败',
UnknownError(:final originalError) => '未知错误: $originalError',
};
showToast(message);
}
从任意异常转换 #
try {
// 可能抛出任何异常的代码
} catch (e, stackTrace) {
final error = SmartError.from(e, stackTrace);
// error 是 SmartError 的某个子类型
}
多语言本地化 #
默认中文消息 #
// 使用默认本地化器
SmartDio.init(SmartConfig(
errorLocalizer: DefaultErrorLocalizer(),
));
// 获取本地化消息
final message = config.errorLocalizer.localize(error);
自定义消息 #
// 使用 ConfigurableErrorLocalizer 部分覆盖
final localizer = ConfigurableErrorLocalizer(
overrides: {
// 覆盖 NetworkError 的消息
ErrorKey.network: (_, __) => 'Please check your network connection',
// 使用模板
ErrorKey.response: template('Server error: {statusCode}'),
// 覆盖特定超时类型
ErrorKey.timeout(TimeoutPhase.connect): (_, __) => 'Connection timed out',
},
);
三层回退机制 #
1. overrides 中的覆盖配置
↓ (未找到)
2. fallback 本地化器
↓ (未设置)
3. 默认中文消息
Flutter intl 集成 #
class IntlErrorLocalizer implements SmartErrorLocalizer {
final BuildContext context;
IntlErrorLocalizer(this.context);
@override
String localize(SmartError error) => switch (error) {
NetworkError() => S.of(context).error_network,
TimeoutError() => S.of(context).error_timeout,
// ...
};
}
// 使用
SmartDio.init(SmartConfig(
errorLocalizer: ConfigurableErrorLocalizer(
fallback: IntlErrorLocalizer(context),
),
));
多客户端管理 #
注册多个客户端 #
// 注册主 API
SmartDio.init(SmartConfig(
options: BaseOptions(baseUrl: 'https://api.main.com'),
));
// 注册其他 API
SmartDio.register('payment', SmartConfig(
options: BaseOptions(baseUrl: 'https://api.payment.com'),
globalPlugins: [LoggerPlugin(), EncryptPlugin()],
));
SmartDio.register('analytics', SmartConfig(
options: BaseOptions(baseUrl: 'https://api.analytics.com'),
));
使用指定客户端 #
// 使用默认客户端
await SmartDio.get('/users');
// 使用命名客户端
await SmartDio.of('payment').doGet('/transactions');
await SmartDio.of('analytics').doPost('/events', data: eventData);
切换默认客户端 #
// 切换到灰度环境
SmartDio.setDefault('beta');
// 之后的请求使用 beta 客户端
await SmartDio.get('/api');
释放资源 #
// 释放指定客户端
SmartClientManager.instance.dispose('payment');
// 释放所有客户端
SmartClientManager.instance.disposeAll();
高级用法 #
自定义插件 #
class AuthPlugin extends SmartPlugin {
@override
int get priority => 40; // 在 HeaderPlugin 之前
@override
String get name => 'AuthPlugin';
@override
Future<void> onRequest(RequestContext ctx, RequestOptions options) async {
// 注入认证头
final token = await TokenManager.getToken();
options.headers['Authorization'] = 'Bearer $token';
}
@override
Future<void> onError(RequestContext ctx, SmartError error) async {
// 处理 401 错误
if (error is ResponseError && error.statusCode == 401) {
await TokenManager.refreshToken();
// 可以通过抛出特定异常触发重试
}
}
}
生命周期 Hooks #
await SmartDio.get('/api',
onStart: (ctx) async {
print('请求开始: ${ctx.request.path}');
},
onSuccess: (ctx, response) async {
print('请求成功: ${response.statusCode}');
},
onError: (ctx, error) async {
print('请求失败: $error');
// 上报错误
},
onFinish: (ctx) async {
print('请求结束');
},
);
与原生 Dio 互操作 #
// 获取底层 Dio 实例
final dio = SmartClientManager.instance.getDio('default');
// 添加自定义 Interceptor
dio.interceptors.add(MyCustomInterceptor());
// 直接使用 Dio
final response = await dio.get('/raw-api');
常见问题 #
Q: 如何处理超时? #
// 全局超时配置
SmartDio.init(SmartConfig(
options: BaseOptions(
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 30),
sendTimeout: Duration(seconds: 30),
),
));
// 单次请求超时
await SmartDio.get('/api',
optionsOverride: BaseOptions(
receiveTimeout: Duration(minutes: 5),
),
);
Q: 如何取消请求? #
final cancelToken = CancelToken();
// 发起请求
SmartDio.get('/api', cancelToken: cancelToken);
// 取消请求
cancelToken.cancel('User navigated away');
Q: 如何处理证书校验? #
// 通过 Dio 配置
final dio = SmartClientManager.instance.getDio('default');
(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
client.badCertificateCallback = (cert, host, port) => true; // 仅开发环境
return client;
};
Q: 插件执行顺序是怎样的? #
onStart (priority 升序)
↓
onRequest (priority 升序)
↓
[发起 HTTP 请求]
↓
onResponse (priority 降序) 或 onError (priority 降序)
↓
onFinish (priority 降序,始终执行)
许可证 #
本项目采用 MIT License。
致谢 #
smart_dio 基于以下优秀开源项目构建(均采用 MIT License):
- dio - 强大的 Dart HTTP 客户端
- dio_cache_interceptor - Dio 缓存拦截器
- dio_smart_retry - 智能重试拦截器
- talker_dio_logger - 优雅的日志记录器