阅读 221

flutter 网络请求封装 dio(4.0.0)

dart版本:(stable) 2.2.2(空安全)
connectivity:^3.0.6
shared_preferences:^2.0.6
注:我是抄后改成适合自己的,不过网上太多一模一样的,不知道谁才是原创,在此感谢原创作者。
现有功能。
就看http,再分别一个个看,大概能看懂了。


image.png

代码里有些做了解释,其他有些我也是抄的,也不是很懂哈哈哈,就说了,直接上代码。

api_response.dart

import 'app_exceptions.dart';
class ApiResponse<T> implements Exception {
  Status status;
  T? data;
  AppException? exception;
  ApiResponse.completed(this.data) : status = Status.COMPLETED;
  ApiResponse.error(this.exception) : status = Status.ERROR;

  @override
  String toString() {
    return "Status : $status \n Message : $exception \n Data : $data";
  }
}

enum Status { COMPLETED, ERROR }

app_exceptions.dart

import 'package:dio/dio.dart';

/// 自定义异常
class AppException implements Exception {
  final String? _message;
  final int? _code;
  AppException([
    this._code,
    this._message,
  ]);

  String toString() {
    return "$_message($_code)";
  }

  factory AppException.create(DioError error) {
    switch (error.type) {
      case DioErrorType.cancel:
        {
          return BadRequestException(-1, "请求取消");
        }
        break;
      case DioErrorType.connectTimeout:
        {
          return BadRequestException(-1, "连接超时");
        }
        break;
      case DioErrorType.sendTimeout:
        {
          return BadRequestException(-1, "请求超时");
        }
        break;
      case DioErrorType.receiveTimeout:
        {
          return BadRequestException(-1, "响应超时");
        }
        break;
      case DioErrorType.response:
        {
          try {
            int? errCode = error.response?.statusCode!;
            // String errMsg = error.response.statusMessage;
            // return ErrorEntity(code: errCode, message: errMsg);
            switch (errCode) {
              case 400:
                {
                  return BadRequestException(errCode, "请求语法错误");
                }
                break;
              case 401:
                {
                  return UnauthorisedException(errCode!, "没有权限");
                }
                break;
              case 403:
                {
                  return UnauthorisedException(errCode!, "服务器拒绝执行");
                }
                break;
              case 404:
                {
                  return UnauthorisedException(errCode!, "无法连接服务器");
                }
                break;
              case 405:
                {
                  return UnauthorisedException(errCode!, "请求方法被禁止");
                }
                break;
              case 500:
                {
                  return UnauthorisedException(errCode!, "服务器内部错误");
                }
                break;
              case 502:
                {
                  return UnauthorisedException(errCode!, "无效的请求");
                }
                break;
              case 503:
                {
                  return UnauthorisedException(errCode!, "服务器挂了");
                }
                break;
              case 505:
                {
                  return UnauthorisedException(errCode!, "不支持HTTP协议请求");
                }
                break;
              default:
                {
                  // return ErrorEntity(code: errCode, message: "未知错误");
                  return AppException(
                      errCode, error.response?.statusMessage ?? '');
                }
            }
          } on Exception catch (_) {
            return AppException(-1, "未知错误");
          }
        }
        break;
      default:
        {
          return AppException(-1, error.message);
        }
    }
  }
}

/// 请求错误
class BadRequestException extends AppException {
  BadRequestException([int? code, String? message]) : super(code!, message!);
}

/// 未认证异常
class UnauthorisedException extends AppException {
  UnauthorisedException([int code = -1, String message = ''])
      : super(code, message);
}

cache.dart

// 是否启用缓存
const CACHE_ENABLE = true;

// 缓存的最长时间,单位(秒)
const CACHE_MAXAGE = 1000;

// 最大缓存数
const CACHE_MAXCOUNT = 100;

connections_interceptor.dart

import 'package:connectivity/connectivity.dart';
import 'package:dio/dio.dart';

class ConnectsInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // TODO: implement onRequest
    super.onRequest(options, handler);
    _request();
  }

  //不知道是不是这样写,网络的
  _request() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.mobile) {
      // I am connected to a mobile network.
    } else if (connectivityResult == ConnectivityResult.wifi) {
      // I am connected to a wifi network.
    } else {
      print('没有网络');
      //在这里加一个错误弹窗
    }
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // TODO: implement onResponse
    super.onResponse(response, handler);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    // TODO: implement onError
    super.onError(err, handler);
  }
}

http.dart

import 'dart:io';

import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:pp/MyHttp/connections_interceptor.dart';
import 'package:pp/MyHttp/net_cache.dart';
import 'package:pp/MyHttp/proxy.dart';
import 'package:pp/MyHttp/request_interceptor.dart';

class Https {
  ///超时时间
  static const int CONNECT_TIMEOUT = 10000;
  static const int RECEIVE_TIMEOUT = 10000;

  static Https instance = Https._internal();

  factory Https() => instance;

  Dio dio = Dio();
  CancelToken _cancelToken = new CancelToken();
  Https._internal() {
    dio.options
      ..baseUrl = 'https://mobile.pku-hit.com/smc/'
      ..connectTimeout = CONNECT_TIMEOUT
      ..receiveTimeout = RECEIVE_TIMEOUT
      ..validateStatus = (int? status) {
        return status != null && status > 0;
      }
      ..headers = {};
    dio.interceptors.add(RequestInterceptor()); //自定义拦截
    dio.interceptors.add(ConnectsInterceptor());//拦截网络
    dio.interceptors.add(LogInterceptor()); //打开日志
    dio.interceptors.add(NetCacheInterceptor()); //缓存

    // 在调试模式下需要抓包调试,所以我们使用代理,并禁用HTTPS证书校验
    if (PROXY_ENABLE) {
      (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        client.findProxy = (uri) {
          return "PROXY $PROXY_IP:$PROXY_PORT";
        };
        //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) => true;
      };
    }
  }
  void init(//这个在main或者初始化的时候先调用一下
      {String? baseUrl,
      int? connectTimeout,
      int? receiveTimeout,
      List<Interceptor>? interceptors}) {
    dio.options = dio.options.copyWith(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
    );
    if (interceptors != null && interceptors.isNotEmpty) {
      dio.interceptors.addAll(interceptors);
    }
  }

  /// 设置headers
  void setHeaders(Map<String, dynamic> map) {
    dio.options.headers.addAll(map);
  }

/*
   * 取消请求
   *
   * 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
   * 所以参数可选
   */
  void cancelRequests({CancelToken? token}) {
    token ?? _cancelToken.cancel("cancelled");
  }

  /// restful get 操作
  Future get(
    String path, {
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
    bool refresh = false,
    bool noCache = true,
    String? cacheKey,
    bool cacheDisk = false,
  }) async {
    Options requestOptions = options ?? Options();
    requestOptions = requestOptions.copyWith(extra: {
      "refresh": refresh,
      "noCache": noCache,
      "cacheKey": cacheKey,
      "cacheDisk": cacheDisk,
    });
    Response response;
    response = await dio.get(path,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful post 操作
  Future post(
    String path, {
    Map<String, dynamic>? params,
    data,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    var response = await dio.post(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful put 操作
  Future put(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();

    var response = await dio.put(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful patch 操作
  Future patch(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    // Map<String, dynamic> _authorization = getAuthorizationHeader();
    // if (_authorization != null) {
    //   requestOptions = requestOptions.merge(headers: _authorization);
    // }
    var response = await dio.patch(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful delete 操作
  Future delete(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    var response = await dio.delete(path,
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }

  /// restful post form 表单提交操作
  Future postForm(
    String path, {
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    Options requestOptions = options ?? Options();
    var data = FormData.fromMap(params!);
    var response = await dio.post(path,
        data: data,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken);
    return response.data;
  }
}

net_cache.dart

import 'dart:collection';
import 'package:dio/dio.dart';
import 'package:pp/MyHttp/cache.dart';
import 'package:pp/MyHttp/sp.dart';

class CacheObject {
  CacheObject(this.response)
      : timeStamp = DateTime.now().millisecondsSinceEpoch;
  Response response;
  int timeStamp;

  @override
  bool operator ==(other) {
    return response.hashCode == other.hashCode;
  }

  @override
  int get hashCode => response.realUri.hashCode;
}

class NetCacheInterceptor extends Interceptor {
  var cache = LinkedHashMap<String, CacheObject>();
  @override
  Future<void> onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    // TODO: implement onRequest
    super.onRequest(options, handler);
    if (!CACHE_ENABLE) handler.next(options);

    // refresh标记是否是刷新缓存
    bool refresh = options.extra["refresh"] == true;

    // 是否磁盘缓存
    bool cacheDisk = options.extra["cacheDisk"] == true;

    // 如果刷新,先删除相关缓存
    if (refresh) {
      // 删除uri相同的内存缓存
      delete(options.uri.toString());

      // 删除磁盘缓存
      if (cacheDisk) {
        await SpUtil().remove(options.uri.toString());
      }

      handler.next(options);
    }

    // get 请求,开启缓存
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == 'get') {
      String key = options.extra["cacheKey"] ?? options.uri.toString();

      // 策略 1 内存缓存优先,2 然后才是磁盘缓存

      // 1 内存缓存
      var ob = cache[key];
      if (ob != null) {
        //若缓存未过期,则返回缓存内容
        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
            CACHE_MAXAGE) {
          handler.resolve(cache[key]!.response);
        } else {
          //若已过期则删除缓存,继续向服务器请求
          cache.remove(key);
        }
      }

      // 2 磁盘缓存
      if (cacheDisk) {
        var cacheData = SpUtil().getJSON(key);
        if (cacheData != null) {
          handler.resolve(Response(
            statusCode: 200,
            data: cacheData,
            requestOptions: options,
          ));
        }
      }
    }
  }

  @override
  Future<void> onResponse(
      Response response, ResponseInterceptorHandler handler) async {
    // TODO: implement onResponse
    super.onResponse(response, handler);
    // 如果启用缓存,将返回结果保存到缓存
    if (CACHE_ENABLE) {
      await _saveCache(response);
    }
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    // TODO: implement onError
    super.onError(err, handler);
  }

  void delete(String key) {
    cache.remove(key);
  }

  Future<void> _saveCache(Response object) async {
    RequestOptions options = object.requestOptions;

    // 只缓存 get 的请求
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == "get") {
      // 策略:内存、磁盘都写缓存

      // 缓存key
      String key = options.extra["cacheKey"] ?? options.uri.toString();

      // 磁盘缓存
      if (options.extra["cacheDisk"] == true) {
        await SpUtil().setJSON(key, object.data);
      }

      // 内存缓存
      // 如果缓存数量超过最大数量限制,则先移除最早的一条记录
      if (cache.length == CACHE_MAXCOUNT) {
        cache.remove(cache[cache.keys.first]);
      }

      cache[key] = CacheObject(object);
    }
  }
}

proxy.dart

// 是否启用代理
const PROXY_ENABLE = false;

/// 代理服务IP
const PROXY_IP = '192.168.2.237';

/// 代理服务端口
const PROXY_PORT = 8888;

request_interceptor.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'app_exceptions.dart';

/// 请求处理拦截器
class RequestInterceptor extends Interceptor {
  onRequest(options, handle) {
    debugPrint(
        '======================\n*** Request *** \nData:\n ${options.data.toString()} \nQuery:\n ${options.queryParameters.toString()} \n======================');
    // // 设置cookie
    // var cookie = SpUtil.getStringList('cookie');登录时保存cookie

    // if (options.path != 'api/auth/login' &&
    //     cookie != null &&
    //     cookie.length > 0) {
    //   options.headers['cookie'] = cookie;//这里就是除了登录的情况其他都加cookie
    // }
    // options.headers['User-Agent'] = 'gzzoc-1';//
    //
    // if (options.data?.runtimeType == FormData) {
    //   options.headers['content-type'] = 'multipart/form-data';//FormData这种情况改content-type
    // }

    // // 加载动画----这个就是请求页面时的那个loading窗 --处理逻辑 我是用options?.data['showLoading']或options?.queryParameters['showLoading'],
    //就是我们在传参的时候多加一个参数,这个因为前人就这样做的,也跟后端约定的,后端见showLoading不做处理。这样不是很好,反正options是有其他字段加的
    // if (options?.data?.runtimeType == FormData) {
    //   Alert.showLoading();
    // } else if ((options?.data != null &&
    //         false == options?.data['showLoading']) ||
    //     (options?.queryParameters != null &&
    //         false == options?.queryParameters['showLoading'])) {
    //   // 不显示加载动画
    // } else {
    //   Alert.showLoading();
    // }
    ///在这做请求时显不显示Loading的处理

    handle.next(options);
    //return super.onRequest(options);
  }

  @override
  onResponse(response, handle) {
    debugPrint(
        '======================\n*** Response *** \n${response.toString()}');
    if (response.data != null &&
        response.data is Map &&
        response.data['code'] == '0') {// 这个条件也是根据自己情况加的
      ///    Alert.hide();请求成功后关闭loading窗

      // 登录请求
      if (response.requestOptions.path == 'api/auth/login') {
        // 缓存cookie
        var cookie = response.headers['set-cookie'];
        //   SpUtil.putStringList('cookie', cookie!);缓存cookie
      }
      handle.next(response);
      //     return super.onResponse(response);
    } else if (response.requestOptions.path ==
            'api/auth/login' && // 登录登录成功, 但没有默认就诊人// 缓存cookie以便后续创建默认就诊人(需求)
        response.data != null &&
        response.data is Map &&
        response.data['code'] == '11') {
      // 缓存cookie
      var cookie = response.headers['set-cookie'];
      //    SpUtil.putStringList('cookie', cookie!);

      //     Alert.hide();
      handle.next(response);
    } else {
      handle.reject(DioError(
          requestOptions: response.requestOptions,
          error: response.data != null &&
                  response.data is Map &&
                  response.data['msg'] != null &&
                  response.data['msg'].length > 0
              ? response.data['msg']
              : '未知错误',
          response: response));
    }
  }

  @override
  onError(err, handle) {
    // Alert.hide();关闭弹窗

    // 账户登录异常
    if (err.response != null &&
        err.response?.data != null &&
        err.response?.data is Map &&
        err.response?.data != null &&
        err.response?.data['code'] == '2') {
      // Alert.showAlert(
      //   message: err.message ?? '未知错误',
      //   showCancel: false,
      //   onConfirm: () {
      //     // 清除账号缓存
      //     SpUtil.putString("account_phone", '');
      //     SpUtil.putString("account_password", '');
      //     SpUtil.putObject("account", '');
      //
      //     // 退出到登录页面
      //     //  push(Routes.login, replace: true, clearStack: true);
      //   },
      // );
    } else {
      //    Alert.showAlert(message: err.message ?? '未知错误', showCancel: false);//在页面显示一个错误弹窗
    }
    AppException appException = AppException.create(err);
    err.error = appException;
    return super.onError(err, handle);
  }
}

sp.dart

import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

/// 本地存储
class SpUtil {
  static SpUtil _instance = new SpUtil._();
  factory SpUtil() => _instance;
  static SharedPreferences? _prefs;

  SpUtil._();

  static Future<void> init() async {
    if (_prefs == null) {
      _prefs = await SharedPreferences.getInstance();
    }
  }

  Future<bool> setJSON(String key, dynamic jsonVal) {
    String jsonString = jsonEncode(jsonVal);
    return _prefs!.setString(key, jsonString);
  }

  dynamic getJSON(String key) {
    String? jsonString = _prefs?.getString(key);
    return jsonDecode(jsonString!);
  }

  Future<bool> setBool(String key, bool val) {
    return _prefs!.setBool(key, val);
  }

  bool? getBool(String key) {
    bool? val = _prefs?.getBool(key);
    return val;
  }

  Future<bool> remove(String key) {
    return _prefs!.remove(key);
  }
}

下面就是使用了:
首先先建个model(为了方便哪种)我一般用https://javiercbk.github.io/json_to_dart/建滴。
get_science_article_entity.dart

class GetScienceArticleEntity {
  String? code;
  String? msg;
  Data? data;

  GetScienceArticleEntity({this.code, this.msg, this.data});

  GetScienceArticleEntity.fromJson(Map<String, dynamic> json) {
    code = json['code'];
    msg = json['msg'];
    data = json['data'] != null ? new Data.fromJson(json['data']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['code'] = this.code;
    data['msg'] = this.msg;
    if (this.data != null) {
      data['data'] = this.data?.toJson();
    }
    return data;
  }
}

class Data {
  int? pageCount;
  int? pageTotal;
  int? pageSize;
  List<PageList>? pageList;
  int? pageNum;

  Data(
      {this.pageCount,
      this.pageTotal,
      this.pageSize,
      this.pageList,
      this.pageNum});

  Data.fromJson(Map<String, dynamic> json) {
    pageCount = json['pageCount'];
    pageTotal = json['pageTotal'];
    pageSize = json['pageSize'];
    if (json['pageList'] != null) {
      pageList = <PageList>[];
      json['pageList'].forEach((v) {
        pageList?.add(new PageList.fromJson(v));
      });
    }
    pageNum = json['pageNum'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['pageCount'] = this.pageCount;
    data['pageTotal'] = this.pageTotal;
    data['pageSize'] = this.pageSize;
    if (this.pageList != null) {
      data['pageList'] = this.pageList?.map((v) => v.toJson()).toList();
    }
    data['pageNum'] = this.pageNum;
    return data;
  }
}

class PageList {
  int? articleId;
  String? articleTitle;
  int? articleType;
  String? articleContent;
  int? articleContentType;
  int? articleStatus;
  String? articleCreateTime;
  String? articleUpdateTime;
  int? articleSort;

  PageList(
      {this.articleId,
      this.articleTitle,
      this.articleType,
      this.articleContent,
      this.articleContentType,
      this.articleStatus,
      this.articleCreateTime,
      this.articleUpdateTime,
      this.articleSort});

  PageList.fromJson(Map<String, dynamic> json) {
    articleId = json['articleId'];
    articleTitle = json['articleTitle'];
    articleType = json['articleType'];
    articleContent = json['articleContent'];
    articleContentType = json['articleContentType'];
    articleStatus = json['articleStatus'];
    articleCreateTime = json['articleCreateTime'];
    articleUpdateTime = json['articleUpdateTime'];
    articleSort = json['articleSort'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['articleId'] = this.articleId;
    data['articleTitle'] = this.articleTitle;
    data['articleType'] = this.articleType;
    data['articleContent'] = this.articleContent;
    data['articleContentType'] = this.articleContentType;
    data['articleStatus'] = this.articleStatus;
    data['articleCreateTime'] = this.articleCreateTime;
    data['articleUpdateTime'] = this.articleUpdateTime;
    data['articleSort'] = this.articleSort;
    return data;
  }
}

创一个类(为了方便而已)
testApi.dart

import 'package:dio/dio.dart';
import 'package:pp/MyHttp/api_response.dart';
import 'package:pp/MyHttp/http.dart';
import 'package:pp/get_science_article_entity.dart';

class TestApi {
  static String _article = 'api/article/getScienceArticle';

  static Future<ApiResponse<GetScienceArticleEntity>> getScienceArticle(
      {int? pageNum}) async {
    try {
      final response =
          await Https.instance.get(_article, params: {"pageNum": pageNum});
      var data = GetScienceArticleEntity.fromJson(response);
      return ApiResponse.completed(data);
    } on DioError catch (e) {
      return ApiResponse.error(e.error);
    }
  }
}

使用:

  void _do() async {
    ApiResponse<GetScienceArticleEntity> res =
        await TestApi.getScienceArticle();
    if (res.status != Status.COMPLETED) return;
    print(res.data?.data?.pageCount);
  }

结果:


171625219598_.pic_hd.jpg

作者:小小淮

原文链接:https://www.jianshu.com/p/927869b81059

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐