1. 架构概述
本网络框架基于 GetX 和 Dio 实现,采用分层架构设计,实现了请求拦截、统一错误处理、国际化支持等功能。
1.1 架构图
graph TD
A[UI Layer] --> B[Controller Layer]
B --> C[Service Layer]
C --> D[Network Layer]
D --> E[HTTP Client/Dio]
F[Interceptors] --> D
G[Error Handler] --> D
H[Config] --> D
1.2 数据流图
sequenceDiagram
participant UI
participant Controller
participant ApiService
participant HttpUtil
participant Server
UI->>Controller: 触发请求
Controller->>ApiService: 调用API方法
ApiService->>HttpUtil: 执行HTTP请求
Note over HttpUtil: 添加请求头和签名
HttpUtil->>Server: 发送请求
Server-->>HttpUtil: 返回响应
Note over HttpUtil: 错误处理/响应拦截
HttpUtil-->>ApiService: 返回处理后的数据
ApiService-->>Controller: 返回业务模型
Controller-->>UI: 更新界面
2. 核心组件
2.1 配置层 (Configuration Layer)
2.1.1 基础配置
- 环境配置
enum Environment { dev, test, prod }
class HttpConfig {
static Environment environment = Environment.dev;
static String get baseUrl {
switch (environment) {
case Environment.dev:
return 'https://dev-api.example.com';
case Environment.test:
return 'https://test-api.example.com';
case Environment.prod:
return 'https://api.example.com';
}
}
}
2.1.2 安全配置
- 证书配置
class SecurityConfig {
static String? certificatePath;
static bool validateCertificate = true;
static SecurityContext? getSecurityContext() {
if (certificatePath != null) {
final context = SecurityContext.defaultContext;
context.setTrustedCertificates(certificatePath!);
return context;
}
return null;
}
}
2.1.3 缓存配置
class CacheConfig {
static const int maxAge = 7200; // 缓存有效期(秒)
static const int maxCount = 100; // 最大缓存数量
static bool shouldCache(String path) {
// 配置需要缓存的接口
return path.startsWith('/api/config') ||
path.startsWith('/api/static');
}
}
2.2 网络层 (Network Layer)
2.2.1 请求重试机制
class RetryInterceptor extends Interceptor {
final int maxRetries;
final Duration retryDelay;
RetryInterceptor({
this.maxRetries = 3,
this.retryDelay = const Duration(seconds: 1),
});
@override
Future onError(DioError err, ErrorInterceptorHandler handler) async {
var extra = err.requestOptions.extra;
var retryCount = extra['retryCount'] ?? 0;
if (_shouldRetry(err) && retryCount < maxRetries) {
await Future.delayed(retryDelay);
var options = err.requestOptions;
options.extra['retryCount'] = retryCount + 1;
try {
final response = await dio.fetch(options);
return handler.resolve(response);
} catch (e) {
return handler.next(err);
}
}
return handler.next(err);
}
bool _shouldRetry(DioError error) {
return error.type == DioErrorType.connectionTimeout ||
error.type == DioErrorType.receiveTimeout;
}
}
2.2.2 缓存实现
class CacheInterceptor extends Interceptor {
final Cache cache;
CacheInterceptor(this.cache);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (options.method == 'GET' && CacheConfig.shouldCache(options.path)) {
final cacheData = cache.get(options.uri.toString());
if (cacheData != null) {
return handler.resolve(
Response(
requestOptions: options,
data: cacheData,
statusCode: 200,
),
);
}
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (response.requestOptions.method == 'GET' &&
CacheConfig.shouldCache(response.requestOptions.path)) {
cache.set(
response.requestOptions.uri.toString(),
response.data,
CacheConfig.maxAge,
);
}
handler.next(response);
}
}
2.3 服务层 (Service Layer)
2.3.1 响应数据处理
class ApiResponse<T> {
final int code;
final String message;
final T? data;
final bool success;
ApiResponse({
required this.code,
required this.message,
this.data,
this.success = false,
});
factory ApiResponse.fromJson(Map<String, dynamic> json, T Function(Map<String, dynamic>) fromJson) {
return ApiResponse(
code: json['code'] ?? -1,
message: json['message'] ?? '',
success: json['code'] == 200,
data: json['data'] == null ? null : fromJson(json['data']),
);
}
}
2.3.2 业务异常处理
class BusinessException implements Exception {
final String message;
final int code;
BusinessException({
required this.message,
required this.code,
});
@override
String toString() => 'BusinessException: $message (code: $code)';
}
class ApiService extends GetxService {
Future<T> handleResponse<T>(Future<Response> Function() request) async {
try {
final response = await request();
final apiResponse = ApiResponse.fromJson(
response.data,
(json) => json as T,
);
if (!apiResponse.success) {
throw BusinessException(
message: apiResponse.message,
code: apiResponse.code,
);
}
return apiResponse.data as T;
} on DioError catch (e) {
throw _handleDioError(e);
} catch (e) {
rethrow;
}
}
}
3. 关键特性
3.1 安全性
3.1.1 请求防重放
class NonceManager {
static final _instance = NonceManager._internal();
factory NonceManager() => _instance;
NonceManager._internal();
final _usedNonces = <String>{};
final _lock = Lock();
Future<bool> validateNonce(String nonce, String timestamp) async {
return await _lock.synchronized(() async {
// 检查时间戳是否在有效期内
final requestTime = int.parse(timestamp);
final now = DateTime.now().millisecondsSinceEpoch;
if ((now - requestTime).abs() > 300000) { // 5分钟有效期
return false;
}
// 检查nonce是否已使用
if (_usedNonces.contains(nonce)) {
return false;
}
_usedNonces.add(nonce);
// 清理过期的nonce
_cleanExpiredNonces();
return true;
});
}
void _cleanExpiredNonces() {
// 清理逻辑
}
}
3.2 可维护性
-
统一的 API 管理
-
规范的错误处理
-
完善的日志系统
3.3 扩展性
-
支持自定义拦截器
-
灵活的配置项
-
易于添加新接口
3.3.1 认证拦截器
class AuthInterceptor extends Interceptor {
final AuthService _authService = Get.find<AuthService>();
final NavigationService _navigationService = Get.find<NavigationService>();
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 添加token到请求头
final token = _authService.token;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 401) {
// 清除本地token
_authService.clearToken();
// 取消所有请求
CancelTokenManager().cancelAll();
// 保存当前路由
final currentRoute = Get.currentRoute;
// 跳转到登录页
_navigationService.toLogin().then((_) {
// 登录成功后返回原页面
if (_authService.isLoggedIn) {
Get.offAllNamed(currentRoute);
}
});
return handler.reject(err);
}
handler.next(err);
}
}
3.3.2 认证服务
class AuthService extends GetxService {
final _storage = Get.find<StorageService>();
final _isLoggedIn = false.obs;
String? _token;
bool get isLoggedIn => _isLoggedIn.value;
String? get token => _token;
@override
void onInit() {
super.onInit();
_loadToken();
}
Future<void> _loadToken() async {
_token = await _storage.getToken();
_isLoggedIn.value = _token != null;
}
Future<void> setToken(String token) async {
_token = token;
await _storage.saveToken(token);
_isLoggedIn.value = true;
}
Future<void> clearToken() async {
_token = null;
await _storage.removeToken();
_isLoggedIn.value = false;
}
}
3.3.3 导航服务
class NavigationService extends GetxService {
// 存储需要登录的路由
static final Set<String> _authRequiredRoutes = {
Routes.PROFILE,
Routes.SETTINGS,
// ... 其他需要登录的路由
};
// 路由守卫
Future<bool> canNavigate(String route) async {
final authService = Get.find<AuthService>();
if (_authRequiredRoutes.contains(route) && !authService.isLoggedIn) {
await toLogin();
return false;
}
return true;
}
// 跳转到登录页
Future<void> toLogin() async {
await Get.toNamed(Routes.LOGIN);
}
}
3.3.3 路由配置
class AppPages {
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomePage(),
middlewares: [AuthGuard()],
),
GetPage(
name: Routes.PROFILE,
page: () => ProfilePage(),
middlewares: [AuthGuard()],
),
GetPage(
name: Routes.LOGIN,
page: () => LoginPage(),
),
];
}
class AuthGuard extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
final authService = Get.find<AuthService>();
return !authService.isLoggedIn && route != Routes.LOGIN
? RouteSettings(name: Routes.LOGIN)
: null;
}
}
3.4 易用性
-
简单的调用方式
-
统一的错误提示
-
支持进度回调
3.4.1 请求取消管理
class CancelTokenManager {
static final CancelTokenManager _instance = CancelTokenManager._internal();
factory CancelTokenManager() => _instance;
CancelTokenManager._internal();
// 存储所有请求的CancelToken
final Map<String, CancelToken> _tokens = {};
// 创建新的CancelToken
CancelToken createToken(String tag) {
cancelToken(tag); // 如果已存在,先取消旧的
final token = CancelToken();
_tokens[tag] = token;
return token;
}
// 取消指定请求
void cancelToken(String tag) {
if (_tokens.containsKey(tag)) {
if (!_tokens[tag]!.isCancelled) {
_tokens[tag]!.cancel('Request cancelled');
}
_tokens.remove(tag);
}
}
// 取消所有请求
void cancelAll() {
_tokens.forEach((key, token) {
if (!token.isCancelled) {
token.cancel('All requests cancelled');
}
});
_tokens.clear();
}
}
3.4.2 使用示例
class HomeController extends GetxController {
final _apiService = Get.find<ApiService>();
final _cancelManager = CancelTokenManager();
Future<void> fetchData() async {
try {
final token = _cancelManager.createToken('fetchData');
final result = await _apiService.getData(cancelToken: token);
// 处理数据
} catch (e) {
if (e is DioException && CancelToken.isCancel(e)) {
print('Request was cancelled');
}
}
}
void cancelFetch() {
_cancelManager.cancelToken('fetchData');
}
@override
void onClose() {
_cancelManager.cancelAll();
super.onClose();
}
}
4. 详细设计
4.1 目录结构
lib/
├── core/
│ ├── network/ # 网络相关
│ │ ├── config/
│ │ │ ├── http_config.dart # HTTP配置
│ │ │ └── api_endpoints.dart # API端点
│ │ │
│ │ ├── interceptors/
│ │ │ ├── auth_interceptor.dart # 认证拦截器
│ │ │ └── error_interceptor.dart # 错误拦截器
│ │ │
│ │ └── http_util.dart # 网络工具类
│ │
│ └── utils/
│ └── token_manager.dart # Token管理
│
├── services/
│ ├── api_service.dart # API服务
│ ├── auth_service.dart # 认证服务
│ └── storage_service.dart # 存储服务
│
├── i18n/ # 国际化
│ └── translations.dart
│
├── routes/ # 路由
│ ├── app_pages.dart
│ └── app_routes.dart
│
├── models/ # 数据模型
│ └── response/
│ └── api_response.dart
│
└── main.dart # 入口文件
注意事项:1. 所有网络请求必须通过 ApiService 进行
4.2 核心组件
4.2.1 配置层 (HttpConfig)
-
基础URL配置
-
超时设置
-
请求头管理
-
安全签名生成
4.2.2 网络层 (HttpUtil)
-
请求方法封装
-
拦截器管理
-
错误处理
-
文件下载
4.2.3 服务层 (ApiService)
-
业务接口封装
-
数据模型转换
-
统一调用入口
5. 代码示例
5.1 网络配置
import 'dart:convert';
import 'package:crypto/crypto.dart';
class HttpConfig {
// 基础配置
static const String baseUrl = 'https://api.example.com';
static const int connectTimeout = 15000;
static const int receiveTimeout = 15000;
static const String apiVersion = '/v1';
// 基础请求头
static Map<String, dynamic> headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
// 密钥配置
static const String secretKey = 'YOUR_SECRET_KEY'; // 实际项目中应从安全位置获取
// 获取加密请求头
static Map<String, dynamic> getEncryptHeaders(String method, dynamic body) {
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
final nonce = _generateNonce();
final sign = generateSign(method, body, timestamp, nonce);
return {
...headers,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Sign': sign,
};
}
// 生成随机字符串
static String _generateNonce() {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
final random = Random();
return List.generate(16, (index) => chars[random.nextInt(chars.length)]).join();
}
// 生成签名
static String generateSign(String method, dynamic body, String timestamp, String nonce) {
String content = '';
if (body != null) {
if (body is Map) {
content = json.encode(body);
} else if (body is String) {
content = body;
}
}
final String rawStr = method + content + timestamp + nonce + secretKey;
return md5.convert(utf8.encode(rawStr)).toString();
}
}
5.2 网络工具类
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'http_config.dart';
class HttpUtil {
static HttpUtil get instance => _instance;
static final HttpUtil _instance = HttpUtil._internal();
late Dio dio;
HttpUtil._internal() {
_initDio();
}
void _initDio() {
dio = Dio(BaseOptions(
baseUrl: HttpConfig.baseUrl,
connectTimeout: Duration(milliseconds: HttpConfig.connectTimeout),
receiveTimeout: Duration(milliseconds: HttpConfig.receiveTimeout),
headers: HttpConfig.headers,
));
_addInterceptors();
}
void _addInterceptors() {
dio.interceptors.add(InterceptorsWrapper(
onRequest: _requestInterceptor,
onResponse: _responseInterceptor,
onError: _errorInterceptor,
));
// 添加日志拦截器
if (kDebugMode) {
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
}
}
void _requestInterceptor(RequestOptions options, RequestInterceptorHandler handler) {
// 添加加密请求头
options.headers.addAll(
HttpConfig.getEncryptHeaders(options.method, options.data),
);
// 添加token
final token = Get.find<StorageService>().getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
void _responseInterceptor(Response response, ResponseInterceptorHandler handler) {
// 统一响应处理
if (response.data['code'] == 200) {
handler.next(response);
} else {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
error: response.data['message'] ?? 'error.unknown'.tr,
),
);
}
}
void _errorInterceptor(DioException error, ErrorInterceptorHandler handler) {
// 错误处理
final errorMessage = _getLocalizedErrorMessage(error);
Get.snackbar('error.title'.tr, errorMessage);
handler.next(error);
}
String _getLocalizedErrorMessage(DioException error) {
String key = 'error.unknown';
switch (error.type) {
case DioExceptionType.connectionTimeout:
key = 'error.connectiontimeout';
break;
case DioExceptionType.receiveTimeout:
key = 'error.receivetimeout';
break;
case DioExceptionType.badResponse:
key = 'error.badresponse${error.response?.statusCode ?? ""}';
break;
// ... 其他错误类型
}
return key.tr;
}
// GET请求
Future<T?> get<T>(
String path, {
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await dio.get(
path,
queryParameters: params,
options: options,
cancelToken: cancelToken,
);
return response.data;
} catch (e) {
rethrow;
}
}
// POST请求
Future<T?> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await dio.post(
path,
data: data,
queryParameters: params,
options: options,
cancelToken: cancelToken,
);
return response.data;
} catch (e) {
rethrow;
}
}
// PUT请求
Future<T?> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await dio.put(
path,
data: data,
queryParameters: params,
options: options,
cancelToken: cancelToken,
);
return response.data;
} catch (e) {
rethrow;
}
}
// DELETE请求
Future<T?> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await dio.delete(
path,
data: data,
queryParameters: params,
options: options,
cancelToken: cancelToken,
);
return response.data;
} catch (e) {
rethrow;
}
}
// 下载文件
Future<String?> download(
String url,
String savePath, {
ProgressCallback? onReceiveProgress,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
try {
await dio.download(
url,
savePath,
onReceiveProgress: onReceiveProgress,
queryParameters: params,
options: options,
cancelToken: cancelToken,
);
return savePath;
} catch (e) {
rethrow;
}
}
}
5.3 API管理
class ApiEndpoints {
// Auth
static const String auth = '/auth';
static const String login = '$auth/login';
static const String register = '$auth/register';
static const String logout = '$auth/logout';
// User
static const String user = '/user';
static const String profile = '$user/profile';
static const String updateProfile = '$user/update';
// File
static const String file = '/file';
static const String upload = '$file/upload';
static const String download = '$file/download';
}
5.4 API服务类
class ApiService extends GetxService {
final _http = HttpUtil.instance;
// Auth APIs
Future<UserModel?> login(String username, String password) async {
try {
final response = await _http.post(
ApiEndpoints.login,
data: {
'username': username,
'password': password,
},
);
return response != null ? UserModel.fromJson(response) : null;
} catch (e) {
rethrow;
}
}
// User APIs
Future<UserModel?> getUserProfile() async {
try {
final response = await _http.get(ApiEndpoints.profile);
return response != null ? UserModel.fromJson(response) : null;
} catch (e) {
rethrow;
}
}
Future<UserModel?> updateProfile(Map<String, dynamic> data) async {
try {
final response = await _http.put(
ApiEndpoints.updateProfile,
data: data,
);
return response != null ? UserModel.fromJson(response) : null;
} catch (e) {
rethrow;
}
}
// File APIs
Future<String?> downloadFile(
String fileId,
String savePath, {
ProgressCallback? onReceiveProgress,
}) async {
try {
return await _http.download(
'${ApiEndpoints.download}/$fileId',
savePath,
onReceiveProgress: onReceiveProgress,
);
} catch (e) {
rethrow;
}
}
}
5.5 国际化支持
import 'package:get/get.dart';
class AppTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'error.title': 'Error',
'error.unknown': 'Unknown error',
'error.connectiontimeout': 'Connection timeout',
'error.receivetimeout': 'Receive timeout',
'error.badresponse401': 'Unauthorized, please login again',
'error.badresponse403': 'Access denied',
'error.badresponse404': 'Resource not found',
'error.badresponse500': 'Internal server error',
},
'zh_CN': {
'error.title': '错误',
'error.unknown': '未知错误',
'error.connectiontimeout': '连接超时',
'error.receivetimeout': '接收超时',
'error.badresponse401': '未授权,请重新登录',
'error.badresponse403': '拒绝访问',
'error.badresponse404': '请求错误,未找到该资源',
'error.badresponse500': '服务器内部错误',
},
};
}
5.6 使用示例
class LoginController extends GetxController {
final _apiService = Get.find<ApiService>();
final username = ''.obs;
final password = ''.obs;
final isLoading = false.obs;
Future<void> login() async {
if (username.isEmpty || password.isEmpty) {
Get.snackbar('提示', '请输入用户名和密码');
return;
}
try {
isLoading(true);
final user = await _apiService.login(
username.value,
password.value,
);
if (user != null) {
Get.offAllNamed(Routes.HOME);
}
} finally {
isLoading(false);
}
}
}
5.7 文件下载示例
class FileController extends GetxController {
final _apiService = Get.find<ApiService>();
final downloadProgress = 0.0.obs;
Future<void> downloadFile(String fileId) async {
try {
final savePath = await getApplicationDocumentsDirectory();
final filePath = '${savePath.path}/downloaded_file.pdf';
await _apiService.downloadFile(
fileId,
filePath,
onReceiveProgress: (received, total) {
if (total != -1) {
downloadProgress(received / total);
}
},
);
Get.snackbar('Success', 'File downloaded successfully');
} catch (e) {
// 错误已在 HttpUtil 中统一处理
}
}
}
5.8 初始化配置
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initServices();
await initConfigs();
runApp(MyApp());
}
/// 初始化服务
Future<void> initServices() async {
try {
// 按依赖顺序初始化
await Get.putAsync(() => StorageService().init());
await Get.putAsync(() => AuthService().init());
await Get.putAsync(() => NavigationService().init());
await Get.putAsync(() => ApiService().init());
debugPrint('所有服务初始化完成');
} catch (e) {
debugPrint('服务初始化失败: $e');
rethrow;
}
}
/// 初始化配置
Future<void> initConfigs() async {
try {
// 设置环境
HttpConfig.environment = Environment.dev;
// 加载本地存储的配置
final storage = Get.find<StorageService>();
final savedLocale = await storage.getLocale();
if (savedLocale != null) {
Get.updateLocale(savedLocale);
}
debugPrint('所有配置初始化完成');
} catch (e) {
debugPrint('配置初始化失败: $e');
rethrow;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'App Name',
// 国际化配置
translations: AppTranslations(),
locale: Get.deviceLocale,
fallbackLocale: const Locale('zh', 'CN'),
// 路由配置
initialRoute: Routes.INITIAL,
getPages: AppPages.routes,
// 主题配置
theme: AppTheme.light,
darkTheme: AppTheme.dark,
// 全局绑定
initialBinding: AppBinding(),
// 路由观察者
navigatorObservers: [
GetObserver((route) async {
if (route is GetPageRoute) {
final canNavigate = await Get.find<NavigationService>()
.canNavigate(route.routeName!);
if (!canNavigate) {
return const RouteSettings(name: Routes.LOGIN);
}
}
return null;
}),
],
// 错误处理
onUnknownRoute: (settings) {
return GetPageRoute(
page: () => const NotFoundPage(),
);
},
// 调试配置
debugShowCheckedModeBanner: false,
);
}
}
/// 全局依赖绑定
class AppBinding extends Bindings {
@override
void dependencies() {
// 注入全局控制器
Get.put(GlobalController());
Get.put(ThemeController());
// ... 其他全局控制器
}
}
/// 全局控制器示例
class GlobalController extends GetxController {
final _authService = Get.find<AuthService>();
@override
void onInit() {
super.onInit();
// 监听登录状态
ever(_authService.isLoggedIn, _handleAuthChanged);
}
void _handleAuthChanged(bool isLoggedIn) {
if (!isLoggedIn) {
Get.offAllNamed(Routes.LOGIN);
}
}
}
/// 主题控制器示例
class ThemeController extends GetxController {
final _storage = Get.find<StorageService>();
final isDarkMode = false.obs;
@override
void onInit() {
super.onInit();
_loadThemeMode();
}
Future<void> _loadThemeMode() async {
final isDark = await _storage.getDarkMode();
isDarkMode.value = isDark ?? false;
}
Future<void> toggleTheme() async {
isDarkMode.value = !isDarkMode.value;
await _storage.saveDarkMode(isDarkMode.value);
Get.changeThemeMode(isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
}
}