Dio 中的拦截器原来有这些用法

目录

前言

添加或者授权的Token

记录完整的请求日志信息

统一处理后端返回的数据格式

小结


前言

在 Flutter 的第三方网络请求库中,Dio的使用人数应该算是使用最多的,而网络请求过程中有很多额外的事情要做,比如 Request 前需要授权信息,发送网络请求前需要带上令牌(token);请求的日志记录,方便在测试的同学可以在不用抓包的情况下直接看到完整的请求信息;拿到 Response 进行数据解析、业务错误码判断,拿到正确的业务数据后进行 JSON 转 Model 等等。

添加或者授权的Token

在 Request Header 中添加授权的 Token,一般情况下登录成功后会将 Token 存入本地,退出登录时将 Token 清除掉。

class AuthInterceptor extends Interceptor {
    @override
    onRequest(RequestOptions options, RequestInterceptorHandler handler) {
      // 从本地缓存中取出 Token 
      String accessToken = SaveDataTool().getToken();
      if (accessToken != null &&
          accessToken.isNotEmpty) {
        options.headers['Authorization'] = "Token mobile:$accessToken";
      }
      return super.onRequest(options, handler);
    }
 }

如果 Token 过期了,一般会返回401的错误码,此时需要重新获取一下Token,再缓存到本地。

    var dio = Dio();
    var tokenDio = Dio();
    String? csrfToken;
    dio.options.baseUrl = 'xxx.example.com';
    tokenDio.options = dio.options;
    dio.interceptors.add(QueuedInterceptorsWrapper(
      onRequest: (options, handler) {
        if (csrfToken == null) {
          // 首次获取 csrfToken
          tokenDio.get('/token').then((d) {
            // 这里模拟拿到最新的 csrfToken,并加到 Request headers 中
            options.headers['csrfToken'] = csrfToken = d.data['token'];
            handler.next(options);
          }).catchError((error, stackTrace) {
            handler.reject(error, true);
          });
        } else {
          options.headers['csrfToken'] = csrfToken;
          return handler.next(options);
        }
      },
      onError: (error, handler) {
        if (error.response?.statusCode == 401) {
          var options = error.response!.requestOptions;
          // 如果是401且当前不是最新的csrfToken,那就更新一下 csrfToken
          if (csrfToken != options.headers['csrfToken']) {
            options.headers['csrfToken'] = csrfToken;
            //再发送请求
            dio.fetch(options).then(
                  (r) => handler.resolve(r),
              onError: (e) {
                handler.reject(e);
              },
            );
            return;
          }
          tokenDio.get('/token').then((d) {
            // 更新到最新的 csrfToken,并加到 Request headers 中
            options.headers['csrfToken'] = csrfToken = d.data['token'];
          }).then((e) {
            //再发送请求
            dio.fetch(options).then(
                  (r) => handler.resolve(r),
              onError: (e) {
                handler.reject(e);
              },
            );
          });
          return;
        }
        return handler.next(error);
      },
    ));

记录完整的请求日志信息

有时候需要看一下完整的请求日志信息,也自己来定制一套。

class LoggingInterceptor extends Interceptor {
  DebugModel debugModel;

  @override
  onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    debugModel = DebugModel();
    debugModel.startTime = DateTime.now();
    debugModel.headers = options.headers.toString();
    debugModel.requestMethod = options.method;
    debugModel.headers = options.headers.toString();
    if (options.queryParameters.isEmpty) {
      debugModel.url = options.baseUrl + options.path;
    } else {
      String re = options.baseUrl +
          options.path +
          "?" +
          Transformer.urlEncodeMap(options.queryParameters);
      debugModel.url = re;
    }
    debugModel.params = options.data != null ? options.data.toString() : null;
    return super.onRequest(options, handler);
  }

  @override
  onResponse(Response response, ResponseInterceptorHandler handler) {
    debugModel.endTime = DateTime.now();
    int duration = endTime.difference(startTime).inMilliseconds;
    if (response != null) {
      debugModel.statusCode = response.statusCode;
      if (response.data != null) {
        debugModel.responseString = response.data.toString();
      }
    }
    // DebugUtil 是全局的请求日志管理工具类,每一次的网络请求都会添加到其logs这个数组中
    // 这里只是简单做了一个去重的处理,日常开发中不建议这样判断
    if (!DebugUtil.instance.logs.contains(debugModel)) {
      DebugUtil.instance.addLog(debugModel);
    }
    return super.onResponse(response, handler);
  }

  @override
  onError(DioError err, ErrorInterceptorHandler handler) {
    debugModel.error = err.toString();
    if (!DebugUtil.instance.logs.contains(debugModel)) {
      DebugUtil.instance.addLog(debugModel);
    }
    return super.onError(err, handler);
  }
}

保存数据的 DebugModel 和工具类 DebugUtil 的实现。

class DebugModel {
  String url = "";
  String headers = "";
  String requestMethod = "";
  int statusCode = 0;
  String params = "";
  List<Map<String, dynamic>> responseList = [];
  Map<String, dynamic> responseMap = {};
  String responseString = "";
  DateTime startTime = DateTime.now();
  DateTime endTime = DateTime.now();
  String error = "";

  DebugModel({this.url, this.params, this.responseList, this.responseMap});
}

class DebugUtil {
  factory DebugUtil() => _getInstance();

  static DebugUtil get instance => _getInstance();
  static DebugUtil _instance;

  DebugUtil._internal();

  static DebugUtil _getInstance() {
    if (_instance == null) {
      _instance = DebugUtil._internal();
    }
    return _instance;
  }

  List<DebugModel> logs = [];

  void addLog(DebugModel log){
    if (logs.length > 10) {
      logs.removeLast();
    }
    logs.insert(0, log);
  }

  void clearLogs(){
    logs = [];
  }
}

统一处理后端返回的数据格式

后端返回的数据格式统一处理,也可以通过Interceptor来实现。日常开发过程中,如果前期没有对Response数据做统一的适配,在业务层做解析判断,而后端API接口返回的JSON数据不够规范,又或者随着业务的迭代,API返回JSON数据的结构有了比较大的调整,客户端需要修改所有涉及到网络请求的地方,增加了不少工作量。

{"code":0,"data":{},"message":""}

上面是我们希望的数据格式,而如果后端接口不规范的话,可能是这样的:

{"id":"","tree_id":0,"level":1,"parent":""}

或者直接是这样的:

{"result":{"id":"","tree_id":0,"level":1,"parent":""}}

而在业务查询错误时,返回的又是这样的:

{"detail": "xxxxxx", "err_code": 10004}

这种情况下,最好的办法是直接和后端的开发人员沟通协调,让其做到接口数据规范化,从源头上掐掉将来会出现的隐患,而且API接口如果多端使用,比如有Web、App和小程序都有用到,API做到规范统一能节省很多工作量,减少出错的概率。还有一种情况就是随着业务的迭代,API返回JSON数据的结构有了调整,这时就需要我们可以在拦截器里面做处理。

class AdapterInterceptor extends Interceptor {
  static const String msg = "msg";
  static const String detail = "detail";
  static const String non_field_errors = "non_field_errors";
  static const String defaultText = "\"无返回信息\"";
  static const String notFound = "未找到查询信息";
  static const String failureFormat = "{\"code\":%d,\"message\":\"%s\"}";
  static const String successFormat =
      "{\"code\":0,\"data\":%s,\"message\":\"\"}";

  @override
  onResponse(Response response, ResponseInterceptorHandler handler) {
    return super.onResponse(adapterData(response), handler);
  }

  @override
  onError(DioError err, ErrorInterceptorHandler handler) {
    if (err.response != null) {
      adapterData(err.response);
    }
    return super.onError(err, handler);
  }

  Response adapterData(Response response) {
    String result;
    String content = response.data == null ? "" : response.data.toString();
    /// 成功时,直接格式化返回
    if (response.statusCode == 200) {
      if (content == null || content.isEmpty) {
        content = defaultText;
      }
      
      // 这里还需要对业务错误码的判断
      // .......
      
      // 这里的 sprintf 用的是第三方插件 sprintf: ^6.0.0 来格式化字符串
      result = sprintf(successFormat, [content]);
      response.statusCode = ExceptionHandle.success;
    } else { // 这里一般的是网络错误逻辑
      if (response.statusCode == 404) {
        /// 错误数据格式化后,按照成功数据返回
        result = sprintf(failureFormat, [response.statusCode, notFound]);
      } else {
        // 解析异常直接按照返回原数据处理(一般为返回500,503 HTML页面代码)
        result = sprintf(failureFormat,[response.statusCode, "服务器异常(${response.statusCode})"]);
      }
    }
    response.data = result;
    return response;
  }
}

需要特别提醒的是业务错误码和网络错误码的区别,业务错误码是网络请求是正常的,但数据查询出错,通常是后端自己定义的;而网络错误码就是网络请求报错,如401、404等。客户端需要根据不同的错误码给用户提示或者其它的处理。

小结

这些是我在开发中对于网络请求需要经常处理的地方,如有不严谨的地方,欢迎指正,或者发表自己的看法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在autosarDio(Digital Input/Output)是一个用于控制数字输入/输出的模块。使用Dio模块可以读取和写入特定的通道或端口的状态。 在具体使用Dio模块时,需要按照一定的步骤进行配置和操作。首先,根据所需的通道或端口,打开相应的Dio Channel或Dio Port。通道的ID是由Port口的位置决定的,例如,在DioPort_CDio Channel的ID是13 。 接下来,可以使用不同的功能接口函数来读取和写入通道或端口的状态。例如,Dio_ReadChannel函数可以读取指定通道的状态,Dio_WriteChannel函数可以写入指定通道的状态 。同时,还可以使用其他函数来读取和写入端口的状态,以及执行其他操作,如翻转通道的状态、屏蔽写入端口等。 总结起来,在autosarDio模块提供了一套用于控制数字输入/输出的接口和函数,通过配置和操作这些接口和函数,可以实现对特定通道或端口的读写操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [AUTOSAR实验二 DIO的配置和应用](https://blog.csdn.net/wx601056818/article/details/102785538)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【AutoSAR】【MCAL】Dio](https://blog.csdn.net/anwei20000/article/details/118678950)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值