flutter笔记-网络请求(dio)

5 阅读2分钟

网络请求(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('无数据'),
          ),
        ],
      ),
    );
  }
}