网络请求(dio)
依赖(pubspec.yaml)
dependencies:
flutter:
sdk: flutter
# 请求
dio: ^5.8.0
# 缓存
flutter_secure_storage: ^10.0.0
请求封装
lib / http / request.dart
lib/http/http.dart 中使用
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// 缓存获取token
final storage = FlutterSecureStorage();
/// 自定义异常类
class HttpException implements Exception {
final String message;
final int? statusCode;
final dynamic data;
HttpException(this.message, {this.statusCode, this.data});
@override
String toString() =>
'HttpException: $message${statusCode != null ? ' ($statusCode)' : ''}';
}
/// 请求配置类
class HttpConfig {
final String baseUrl;
final Duration connectTimeout;
final Duration receiveTimeout;
final Map<String, dynamic>? defaultHeaders;
HttpConfig({
required this.baseUrl,
this.connectTimeout = const Duration(seconds: 10),
this.receiveTimeout = const Duration(seconds: 10),
this.defaultHeaders,
});
}
/// 请求拦截器接口
abstract class HttpInterceptor {
/// 请求前拦截
Future<void> onRequest(RequestOptions options);
/// 响应拦截
Future<Response> onResponse(Response response);
/// 错误拦截
Future<HttpException> onError(HttpException error);
}
/// 日志拦截器
class LoggingInterceptor implements HttpInterceptor {
@override
Future<void> onRequest(RequestOptions options) async {
print('🌐 请求开始 ====');
print('📤 URL: ${options.uri}');
print('📤 Method: ${options.method}');
print('📤 Headers: ${options.headers}');
if (options.data != null) {
print('📤 Body: ${options.data}');
}
}
@override
Future<Response> onResponse(Response response) async {
print('📥 响应结束 ====');
print('📥 Status: ${response.statusCode}');
return response;
}
@override
Future<HttpException> onError(HttpException error) async {
print('❌ 请求错误: $error');
return error;
}
}
/// Token 拦截器
class TokenInterceptor implements HttpInterceptor {
String? _token;
TokenInterceptor();
@override
Future<void> onRequest(RequestOptions options) async {
// 从外部获取 token 或使用缓存
_token = await storage.read(key: "flutter-token");
if (_token != '' && _token != null) {
options.headers['Authorization'] = 'Bearer $_token';
}
}
@override
Future<Response> onResponse(Response response) async {
// 如果需要刷新 token,可以在这里处理
return response;
}
@override
Future<HttpException> onError(HttpException error) async {
// Token 过期处理
if (error.statusCode == 401) {
// 可以在这里触发 token 刷新逻辑
storage.delete(key: 'flutter-token');
// Navigator.pushReplacementNamed(context, '/login');
// navigatorKey.currentState?.pushReplacementNamed('/login');
return error;
}
return error;
}
}
/// 主 HTTP 客户端
class HttpClient {
late Dio _dio;
final HttpConfig config;
final List<HttpInterceptor> _interceptors = [];
HttpClient({required this.config, List<HttpInterceptor>? interceptors}) {
_initDio();
if (interceptors != null) {
_interceptors.addAll(interceptors);
}
// 添加请求拦截器
_interceptors.add(TokenInterceptor());
// 默认添加日志拦截器
_interceptors.add(LoggingInterceptor());
}
void _initDio() {
_dio = Dio(
BaseOptions(
baseUrl: config.baseUrl,
connectTimeout: config.connectTimeout,
receiveTimeout: config.receiveTimeout,
headers: config.defaultHeaders,
),
);
// 添加 Dio 拦截器
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
try {
// 执行自定义拦截器
for (var interceptor in _interceptors) {
await interceptor.onRequest(options);
}
handler.next(options);
} catch (e) {
handler.reject(DioException(requestOptions: options, error: e));
}
},
onResponse: (response, handler) async {
try {
Response processedResponse = response;
for (var interceptor in _interceptors) {
processedResponse = await interceptor.onResponse(
processedResponse,
);
}
handler.resolve(processedResponse);
} catch (e) {
handler.reject(
DioException(requestOptions: response.requestOptions, error: e),
);
}
},
onError: (error, handler) async {
try {
HttpException httpError = HttpException(
error.message ?? '网络请求失败',
statusCode: error.response?.statusCode,
data: error.response?.data,
);
for (var interceptor in _interceptors) {
httpError = await interceptor.onError(httpError);
}
handler.reject(
DioException(
requestOptions: error.requestOptions,
error: httpError,
response: error.response,
),
);
} catch (e) {
handler.reject(error);
}
},
),
);
}
/// 添加拦截器
void addInterceptor(HttpInterceptor interceptor) {
_interceptors.add(interceptor);
}
/// GET 请求
Future<dynamic> get(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
try {
final response = await _dio.get(
path,
queryParameters: queryParameters,
options:
options?.copyWith(headers: headers) ?? Options(headers: headers),
);
return response.data;
} on DioException catch (e) {
throw HttpException(
e.message ?? 'GET 请求失败',
statusCode: e.response?.statusCode,
data: e.response?.data,
);
}
}
/// POST 请求
Future<dynamic> post(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
try {
final response = await _dio.post(
path,
data: data,
queryParameters: queryParameters,
options:
options?.copyWith(headers: headers) ?? Options(headers: headers),
);
return response.data;
} on DioException catch (e) {
throw HttpException(
e.message ?? 'POST 请求失败',
statusCode: e.response?.statusCode,
data: e.response?.data,
);
}
}
/// PUT 请求
Future<dynamic> put(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
try {
final response = await _dio.put(
path,
data: data,
queryParameters: queryParameters,
options:
options?.copyWith(headers: headers) ?? Options(headers: headers),
);
return response.data;
} on DioException catch (e) {
throw HttpException(
e.message ?? 'PUT 请求失败',
statusCode: e.response?.statusCode,
data: e.response?.data,
);
}
}
/// DELETE 请求
Future<dynamic> delete(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
try {
final response = await _dio.delete(
path,
data: data,
queryParameters: queryParameters,
options:
options?.copyWith(headers: headers) ?? Options(headers: headers),
);
return response.data;
} on DioException catch (e) {
throw HttpException(
e.message ?? 'DELETE 请求失败',
statusCode: e.response?.statusCode,
data: e.response?.data,
);
}
}
/// 上传文件
Future<dynamic> upload(
String path,
String filePath, {
Map<String, dynamic>? data,
Map<String, dynamic>? headers,
ProgressCallback? onSendProgress,
}) async {
try {
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
...?data,
});
final response = await _dio.post(
path,
data: formData,
options: Options(headers: headers),
onSendProgress: onSendProgress,
);
return response.data;
} on DioException catch (e) {
throw HttpException(
e.message ?? '文件上传失败',
statusCode: e.response?.statusCode,
data: e.response?.data,
);
}
}
/// 下载文件
Future<String> download(
String url,
String savePath, {
Map<String, dynamic>? headers,
ProgressCallback? onReceiveProgress,
}) async {
try {
await _dio.download(
url,
savePath,
options: Options(headers: headers),
onReceiveProgress: onReceiveProgress,
);
return savePath;
} on DioException catch (e) {
throw HttpException(
e.message ?? '文件下载失败',
statusCode: e.response?.statusCode,
data: e.response?.data,
);
}
}
/// 取消请求
void cancelRequests({CancelToken? token}) {
token?.cancel('请求被取消');
}
}
请求实例
lib / http / http.dart
import 'package:flutter_template/http/request.dart';
// 请求地址
const baseUrl = '【请求地址】';
final http = HttpClient(
config: HttpConfig(
baseUrl: baseUrl,
connectTimeout: Duration(seconds: 15),
receiveTimeout: Duration(seconds: 15),
defaultHeaders: {'Content-Type': 'application/json'},
),
);
页面使用
lib / components / request.dart
import 'package:flutter/material.dart';
import 'package:flutter_template/http/request.dart';
import 'package:flutter_template/http/http.dart';
class RequestPage extends StatefulWidget {
const RequestPage({super.key});
@override
_ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<RequestPage> {
List<Map<String, dynamic>> _data = [];
// 请求数据的函数
void _getData() async {
try {
_data = [];
const url = '/note/getNote';
final res = await http.post(url, data: {'pageNum': 1});
setState(() {
// 必须走 List.from()
_data = List.from(res['data']['data']);
});
print('获取数据成功: ${e.message}');
} on HttpException catch (e) {
// 处理异常
print('获取数据失败: ${e.message}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: _getData,
child: Text('请求数据'),
),
// ListView.builder 渲染列表
Expanded(
child: _data.isNotEmpty
? ListView.builder(
itemCount: _data.length,
itemBuilder: (context, index) {
return Text(_data[index]['title']);
},
)
: Text('无数据'),
),
],
),
);
}
}