美文网首页Flutter圈子flutter_学习资料
跟我学企业级flutter项目:如何用dio封装一套企业级可扩展

跟我学企业级flutter项目:如何用dio封装一套企业级可扩展

作者: 王二蛋和他的狗 | 来源:发表于2022-02-28 16:26 被阅读0次

    前言

    网上有很多,比如说“Flutter Dio 亲妈级别封装教程”这篇文章,该文章上有几点问题:

    1. 重试机制代码错误
    2. token存取耦合很高
    3. 网络请求只能针对单一地址进行访问
    4. 网络请求缓存机制也不是很完美。

    一旦依照这样的封装去做,那么项目后期的扩展性和易用性会有一定的阻碍,那么如何做到token存取无耦合,而且还能让app多种网络地址一同请求,还可以做到针对不同请求不同超时时长处理,网络缓存还加入可自动清理的lru算法呢?那么今天这篇文章为你揭晓企业级flutter dio网络层封装。

    搭建前夕准备

    三方库:

    dio_cache_interceptor lru缓存库
    dio 网络库
    retrofit 网络生成库
    connectivity_plus 网络情况判断

    技能:

    单例模式
    享元模式
    迭代

    文章:

    持久化:跟我学企业级flutter项目:dio网络框架增加公共请求参数&header

    准备好如上技能,我们来封装一套优秀的网络层

    一、准备好几个基本拦截器

    1、超时拦截器

    import 'dart:collection';
    
    import 'package:dio/dio.dart';
    import 'package:flutter_base_lib/src/app/contants.dart';
    import 'package:flutter_base_lib/src/tools/net/cache_object.dart';
    
    class TimeInterceptor extends Interceptor {
    
      @override
      void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
        Map<String, dynamic> extra = options.extra;
        bool connect = extra.containsKey(SysConfig.connectTimeout);
        bool receive = extra.containsKey(SysConfig.receiveTimeOut);
        if(connect||receive){
          if(connect){
            int connectTimeout = options.extra[SysConfig.connectTimeout];
            options.connectTimeout = connectTimeout;
          }
          if(receive){
            int receiveTimeOut = options.extra[SysConfig.receiveTimeOut];
            options.receiveTimeout = receiveTimeOut;
          }
        }
        super.onRequest(options, handler);
    
      }
    
    }
    
    

    作用:单独针对个别接口进行超时时长设定,如(下载,长链接接口)

    2、缓存拦截器

    dio_cache_interceptor 这个库中有lru算法缓存拦截库,可直接集成

    3、持久化拦截器

    跟我学企业级flutter项目:dio网络框架增加公共请求参数&header 本篇文章介绍了如何持久化

    4、重试拦截器

    
    import 'dart:async';
    import 'dart:io';
    
    import 'package:connectivity_plus/connectivity_plus.dart';
    import 'package:dio/dio.dart';
    import 'package:flutter_base_lib/src/app/application.dart';
    import 'package:flutter_base_lib/src/app/contants.dart';
    import 'package:flutter_ulog/flutter_ulog.dart';
    
    import '../dio_utli.dart';
    
    /// 重试拦截器
    class RetryOnConnectionChangeInterceptor extends Interceptor {
      Dio? dio;
    
      RequestInterceptorHandler? mHandler;
      // RetryOnConnectionChangeInterceptor(){
      //
      // }
    
      @override
      void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
        mHandler = handler;
        super.onRequest(options, handler);
      }
    
    
      @override
      Future onError(DioError err, ErrorInterceptorHandler handler) async{
        if (dio!=null&&Application.config.httpConfig.retry&&await _shouldRetry(err)) {
          return await retryLoop(err,handler,1);
        }
        return super.onError(err, handler);
      }
    
      Future retryLoop(DioError err, ErrorInterceptorHandler handler,int retry) async {
        try {
          ULog.d("${err.requestOptions.uri.toString()} retry : ${retry}",tag: "${SysConfig.libNetTag}Retry");
          await retryHttp(err,handler);
        } on DioError catch (err) {
          if(await _shouldRetry(err)&&retry<Application.config.httpConfig.retryCount){
            await retryLoop(err,handler,retry+1);
          }else{
            return super.onError(err, handler);
          }
        }
      }
    
      Future retryHttp(DioError err, ErrorInterceptorHandler handler) async {
        RequestOptions requestOptions = err.requestOptions;
        Options options = Options(
          method: requestOptions.method,
          sendTimeout: requestOptions.sendTimeout,
          receiveTimeout: requestOptions.receiveTimeout,
          extra: requestOptions.extra,
          headers: requestOptions.headers,
          responseType: requestOptions.responseType,
          contentType: requestOptions.contentType,
          validateStatus: requestOptions.validateStatus,
          receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
          followRedirects: requestOptions.followRedirects,
          maxRedirects: requestOptions.maxRedirects,
          requestEncoder: requestOptions.requestEncoder,
          responseDecoder: requestOptions.responseDecoder,
          listFormat: requestOptions.listFormat,
        );
    
        var res = await dio?.request(
          requestOptions.path,
          cancelToken: requestOptions.cancelToken,
          data: requestOptions.data,
          onReceiveProgress: requestOptions.onReceiveProgress,
          onSendProgress: requestOptions.onSendProgress,
          queryParameters: requestOptions.queryParameters,
          options: options,
        );
    
        return handler.resolve(res!);
      }
    
      ///要重试的类型
      Future<bool> _shouldRetry(DioError err) async{
        return err.error != null && err.error is SocketException && await isConnected();
      }
    
      Future<bool> isConnected() async {
        var connectivityResult = await (Connectivity().checkConnectivity());
        return connectivityResult != ConnectivityResult.none;
      }
    }
    

    该重试拦截器与其他文章封装不同,主要是用重试次数来管理重试机制。

    5、日志拦截器

    
    import 'dart:convert';
    
    import 'package:dio/dio.dart';
    import 'package:flutter_base_lib/src/app/contants.dart';
    import 'package:flutter_ulog/flutter_ulog.dart';
    typedef void LibLogPrint(String message);
    class LibLogInterceptor extends Interceptor {
      LibLogInterceptor({
        this.request = true,
        this.requestHeader = true,
        this.requestBody = false,
        this.responseHeader = true,
        this.responseBody = false,
        this.error = true
      });
    
      /// Print request [Options]
      bool request;
    
      /// Print request header [Options.headers]
      bool requestHeader;
    
      /// Print request data [Options.data]
      bool requestBody;
    
      /// Print [Response.data]
      bool responseBody;
    
      /// Print [Response.headers]
      bool responseHeader;
    
      /// Print error message
      bool error;
    
      @override
      void onRequest(
          RequestOptions options, RequestInterceptorHandler handler) async {
        var builder = StringBuffer('*** Request *** \n');
        builder.write(_printKV('uri', options.uri));
        //options.headers;
    
        if (request) {
          builder.write(_printKV('method', options.method));
          builder.write(_printKV('responseType', options.responseType.toString()));
          builder.write(_printKV('followRedirects', options.followRedirects));
          builder.write(_printKV('connectTimeout', options.connectTimeout));
          builder.write(_printKV('sendTimeout', options.sendTimeout));
          builder.write(_printKV('receiveTimeout', options.receiveTimeout));
          builder.write(_printKV(
              'receiveDataWhenStatusError', options.receiveDataWhenStatusError));
              builder.write(_printKV('extra', options.extra));
        }
        if (requestHeader) {
          builder.write('headers:\n');
          options.headers.forEach((key, v) => builder.write(_printKV(' $key', v)));
        }
        if (requestBody) {
          var res = options.data;
          builder.write('data:\n');
          builder.write(_message(res));
          // try{
          //   ULog.json(res.toString(),tag: "${SysConfig.libNetTag}RequestJson");
          // } on Exception catch (e) {
          //   ULog.d(res,tag: "${SysConfig.libNetTag}RequestJson");
          // }
        }
        ULog.d(builder.toString(),tag: "${SysConfig.libNetTag}Request");
        handler.next(options);
      }
    
      // Handles any object that is causing JsonEncoder() problems
      Object toEncodableFallback(dynamic object) {
        return object.toString();
      }
    
      String _message(dynamic res) {
        if (res is Map || res is Iterable) {
          var encoder = JsonEncoder.withIndent('  ', toEncodableFallback);
          return encoder.convert(res);
        } else {
          return res.toString();
        }
      }
    
      @override
      void onResponse(Response response, ResponseInterceptorHandler handler) async {
        var builder = StringBuffer('*** Response *** \n');
        _printResponse(response,builder,(message){
          ULog.d(message,tag: "${SysConfig.libNetTag}Response");
        });
        handler.next(response);
      }
    
      @override
      void onError(DioError err, ErrorInterceptorHandler handler) async {
        if (error) {
          var builder = StringBuffer('*** DioError *** \n');
          builder.write('uri: ${err.requestOptions.uri}\n');
          builder.write('$err');
          if (err.response != null) {
            _printResponse(err.response!,builder,(message){
              ULog.e(message,tag: "${SysConfig.libNetTag}Error");
            });
          }else{
            ULog.e(builder.toString(),tag: "${SysConfig.libNetTag}Error");
          }
        }
    
        handler.next(err);
      }
    
      void _printResponse(Response response,StringBuffer builder,LibLogPrint pr) {
        builder.write(_printKV('uri', response.requestOptions.uri));
        if (responseHeader) {
          builder.write(_printKV('statusCode', response.statusCode));
          if (response.isRedirect == true) {
            builder.write(_printKV('redirect', response.realUri));
          }
    
          builder.write('headers:\n');
          response.headers.forEach((key, v) => builder.write(_printKV(' $key', v.join('\r\n\t'))));
        }
        if (responseBody) {
          var res = response.toString();
          builder.write('Response Text:\r\n');
          var resJ = res.trim();
          if (resJ.startsWith("{")) {
            Map<String, dynamic> decode = JsonCodec().decode(resJ);
            builder.write(_message(decode));
          }else if (resJ.startsWith("[")) {
            List decode = JsonCodec().decode(resJ);
            builder.write(_message(decode));
          }else {
            builder.write(res);
          }
    
          // try{
          //   ULog.json(res,tag: "${SysConfig.libNetTag}ResponseJson");
          // } on Exception catch (e) {
          //   ULog.d(res,tag: "${SysConfig.libNetTag}ResponseJson");
          // }
        }
        pr(builder.toString());
    
      }
    
      String _printKV(String key, Object? v) {
        return '$key: $v \n';
      }
    
    }
    
    
    在这里插入图片描述

    主要是日志拦截打印

    6、错误拦截器

    
    import 'dart:io';
    
    import 'package:dio/dio.dart';
    import 'package:flutter_base_lib/src/app/contants.dart';
    import 'package:flutter_base_lib/src/exception/lib_network_exception.dart';
    import 'package:flutter_base_lib/src/tools/net/dio_utli.dart';
    import 'package:flutter_ulog/flutter_ulog.dart';
    import 'package:connectivity_plus/connectivity_plus.dart';
    import 'package:flutter_base_lib/src/lib_localizations.dart';
    
    /// 错误处理拦截器
    class ErrorInterceptor extends Interceptor {
    // 是否有网
      Future<bool> isConnected() async {
        var connectivityResult = await (Connectivity().checkConnectivity());
        return connectivityResult != ConnectivityResult.none;
      }
      @override
      Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
        if (err.type == DioErrorType.other) {
          bool isConnectNetWork = await isConnected();
          if (!isConnectNetWork && err.error is SocketException) {
            err.error = SocketException(LibLocalizations.getLibString().libNetWorkNoConnect!);
          }else if (err.error is SocketException){
            err.error = SocketException(LibLocalizations.getLibString().libNetWorkError!);
          }
        }
        err.error = LibNetWorkException.create(err);
        ULog.d('DioError : ${err.error.toString()}',tag: "${SysConfig.libNetTag}Interceptor");
        super.onError(err, handler);
      }
    
    }
    

    与其他人封装不同,服务器请求异常code,我将其抛到业务层自主处理。常规异常则走库文案。

    
    import 'dart:io';
    
    import 'package:dio/dio.dart';
    import 'package:flutter_base_lib/src/lib_localizations.dart';
    
    class LibNetWorkException implements Exception{
    
      final String _message;
      final int _code;
    
      int get code{
        return _code;
      }
    
      String get message{
        return _message;
      }
    
      LibNetWorkException( this._code,this._message);
    
      @override
      String toString() {
        return "$_code : $_message";
      }
    
    
    
      factory LibNetWorkException.create(DioError error) {
        switch (error.type) {
          case DioErrorType.cancel:{
              return LibNetWorkException(-1, LibLocalizations.getLibString().libNetRequestCancel!);
            }
          case DioErrorType.connectTimeout:{
              return LibNetWorkException(-1, LibLocalizations.getLibString().libNetFailCheck!);
            }
          case DioErrorType.sendTimeout:{
              return LibNetWorkException(-1, LibLocalizations.getLibString().libNetTimeOutCheck!);
            }
          case DioErrorType.receiveTimeout:{
              return LibNetWorkException(-1, LibLocalizations.getLibString().libNetResponseTimeOut!);
            }
          case DioErrorType.response:{
              try{
                return LibNetWorkException(error.response!.statusCode!,"HTTP ${error.response!.statusCode!}:${LibLocalizations.getLibString().libNetServerError!}");
              } on Exception catch (_) {
                return LibNetWorkException(-1, error.error.message);
              }
            }
          default:
            {
              return LibNetWorkException(-1, error.error.message);
            }
        }
      }
    }
    
    

    二、工具类封装

    1、主要类

    
    
    import 'dart:io';
    
    import 'package:dio/adapter.dart';
    import 'package:dio/dio.dart';
    import 'package:flutter_base_lib/src/tools/net/interceptor/error_interceptor.dart';
    import 'package:flutter_base_lib/src/tools/net/interceptor/lib_log_interceptor.dart';
    
    import '../../../flutter_base_lib.dart';
    import 'interceptor/presistent_interceptor.dart';
    import 'interceptor/retry_on_connection_change_interceptor.dart';
    import 'interceptor/time_interceptor.dart';
    
    class DioUtil{
    
      final String _baseUrl;
      final HttpConfig _config;
      final List<Interceptor> _interceptors;
    
      late Dio _dio;
    
      Dio get dio{
        return _dio;
      }
      DioUtil._internal(this._baseUrl, this._config, this._interceptors){
        BaseOptions options = new BaseOptions(
          baseUrl: _baseUrl,
          connectTimeout: _config.connectTimeout,
          receiveTimeout: _config.receiveTimeOut,
        );
        _dio = new Dio(options);
        var retry = new Dio(options);
        _interceptors.forEach((element) {
          if(element is RetryOnConnectionChangeInterceptor){
            element.dio = retry;
          }else{
            if(!(element is ErrorInterceptor)){
              retry.interceptors.add(element);
            }
          }
          _dio.interceptors.add(element);
        });
        proxy(_dio);
        proxy(retry);
      }
    
      void proxy(Dio dio){
        if (SpSotre.instance.getBool(SysConfig.PROXY_ENABLE)??false) {
          String? porxy = SpSotre.instance.getString(SysConfig.PROXY_IP_PROT)??null;
          if(porxy!=null){
            (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
                (client) {
              client.findProxy = (uri) {
                return "PROXY $porxy";
              };
              //代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
              client.badCertificateCallback =
                  (X509Certificate cert, String host, int port) => true;
            };
          }
        }
      }
    
      static late Map<String,DioUtil> _dioUtils = Map();
    
      static DioUtil instance(String baseUrl,{HttpConfig? config, List<Interceptor>? interceptors,List<Interceptor>? applyInterceptors}){
        if(!_dioUtils.containsKey(baseUrl)){
          List<Interceptor> list = [PresistentInterceptor(),TimeInterceptor(),RetryOnConnectionChangeInterceptor(),LibLogInterceptor(requestBody: Application.config.debugState,responseBody: Application.config.debugState),ErrorInterceptor()];
          // List<Interceptor> list = [ErrorInterceptor(),PresistentInterceptor()];
          var inter = interceptors??list;
          if(applyInterceptors!=null){
            inter.addAll(applyInterceptors);
          }
          _dioUtils[baseUrl] = DioUtil._internal(baseUrl,config??Application.config.httpConfig,inter);
        }
        return _dioUtils[baseUrl]!;
      }
    
      // CancelToken _cancelToken = new CancelToken();
    
    
    }
    

    工具类封装,主要运用享元模式,可以支持多种url进行访问,不同的url有不同的配置。(灵活可用)

    2、辅助类:

    
    
    class HttpConfig{
      final int _connectTimeout ;
      final int _receiveTimeOut ;
      final bool _retry;
      final int _retryCount;
    
      get connectTimeout{
        return _connectTimeout;
      }
    
      get receiveTimeOut{
        return _receiveTimeOut;
      }
    
      get retry{
        return _retry;
      }
      get retryCount{
        return _retryCount;
      }
    
      HttpConfig(HttpConfigBuilder builder): _connectTimeout = builder._connectTimeout,_receiveTimeOut = builder._receiveTimeOut,_retry = builder._retry,_retryCount = builder._retryCount;
    }
    
    class HttpConfigBuilder {
      int _connectTimeout = 10000;//连接超时时间
      int _receiveTimeOut = 30000;//接收超时时间
      bool _retry = false;
      int _retryCount = 3;
    
      // var maxRetry = 1 重试次数
    
      HttpConfigBuilder setConnectTimeout(int connectTimeout){
        _connectTimeout = connectTimeout;
        return this;
      }
    
      HttpConfigBuilder setReceiveTimeOut(int receiveTimeOut){
        _receiveTimeOut = receiveTimeOut;
        return this;
      }
    
      HttpConfigBuilder setRetry(bool retry){
        _retry = retry;
        return this;
      }
    
      HttpConfigBuilder setRetryCount(int retryCount){
        _retryCount = _retryCount;
        return this;
      }
    
      HttpConfig build() => HttpConfig(this);
    }
    

    三、使用

    
    import 'package:flutter_app_me/data/model/api_result.dart';
    import 'package:flutter_app_me/data/model/user.dart';
    import 'package:flutter_app_me/data/model/user_infos.dart';
    import 'package:flutter_base_lib/flutter_base_lib.dart';
    import 'package:retrofit/retrofit.dart';
    import 'package:dio/dio.dart';
    
    import 'api_methods.dart';
    
    part 'api_service.g.dart';
    
    @RestApi()
    abstract class RestClient {
      factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
    
      @GET(ApiMethods.userinfoJson)
      Future<ApiResult<UserInfos>> userinfoJson();
    
      // "test123332","123456"
      @POST(ApiMethods.login)
      @Extra({SysConfig.connectTimeout:100000})
      Future<ApiResult<UserInfos>> userLogin(@Queries() User user);
    }
    
    

    网络请求配置

    
    
    
    class BusinessErrorException implements Exception {
      final int _errorCode;
      final String? _errorMsg;
    
      BusinessErrorException(this._errorCode, this._errorMsg);
    
      int get errorCode {
        return _errorCode;
      }
    
      String? get errorMsg => _errorMsg;
    }
    
    
    class TokenTimeOutException implements Exception {
      final String? _errorMsg;
      TokenTimeOutException(this._errorMsg);
      String? get errorMsg => _errorMsg;
    
    }
    
    class RequestCodeErrorException implements Exception {
      final String? _errorMsg;
      final int _errorCode;
      RequestCodeErrorException(this._errorCode, this._errorMsg);
    
      int get errorCode {
        return _errorCode;
      }
    
      String? get errorMsg => _errorMsg;
    }
    

    业务基本异常

    
    import 'package:business_package_auth/business_package_auth.dart';
    import 'package:flutter_base_lib/flutter_base_lib.dart';
    import 'package:flutter_base_ui/flutter_base_ui.dart';
    import 'package:dio/dio.dart';
    import 'package:wisdomwork_lib/src/model/api_result.dart';
    
    const int httpSuccessCode = 0;
    const int httpErrorCode = 1;
    const int httpTokenExt = 10001;
    
    extension SuccessExt<T> on Success<T> {
      Success<T> appSuccess() {
        var data = this.data;
        if (data is ApiResult) {
          if (data.code != httpSuccessCode) {
            switch (data.code){
              case httpTokenExt:
                TipToast.instance.tip(data.msg ?? LibLocalizations.getLibString().libBussinessTokenTimeOut!,tipType: TipType.warning);
                BlocProvider.of<AuthenticationBloc>(LibRouteNavigatorObserver.instance.navigator!.context).add(LogOut());
                throw TokenTimeOutException(data.msg);
              case httpErrorCode:
                TipToast.instance.tip(data.msg ?? LibLocalizations.getLibString().libBussinessRequestCodeError!,tipType: TipType.error);
                throw RequestCodeErrorException(data.code!,data.msg);
              default:
                throw BusinessErrorException(data.code!, data.msg);
            }
    
          }
        }
        return this;
      }
    }
    
    extension ErrorExt<T> on Error<T> {
      void appError() {
        var exception = this.exception;
        if (exception is LibNetWorkException) {
          TipToast.instance.tip(exception.message, tipType: TipType.error);
        }
      }
    }
    
    
    typedef ResultF<T> = Future<ApiResult<T>> Function();
    
    mixin RemoteBase {
    
      Future<DataResult<ApiResult<T>>> remoteDataResult<T>(ResultF<T> resultF) async {
        try {
          var data = await resultF.call();
          return Success(data).appSuccess();
        } on DioError catch (err, stack) {
          var e = err.error;
          ULog.e(e.toString(), error: e, stackTrace: stack);
          return Error(e)..appError();
        } on Exception catch (e, stack) {
          ULog.e(e.toString(), error: e, stackTrace: stack);
          return Error(e)..appError();
        }
      }
    
    }
    

    业务基本异常处理方式

    
    import 'package:flutter_base_lib/flutter_base_lib.dart';
    import 'package:flutter_base_ui/flutter_base_ui.dart';
    import 'package:wisdomwork/data/services/api_service.dart';
    import 'package:wisdomwork_lib/wisdomwork_lib.dart';
    
    mixin WisdomworkRemoteBase{
      var rest = RestClient(DioUtil.instance(
          AppEnvironment.envConfig![AppConfig.apiName]!,
          applyInterceptors: [UiNetInterceptor()]).dio);
    }
    

    业务请求接口,实现

         final data = await AppResponsitory.instance.login(state.phoneText, state.codeText);
         if (userResult != null) {
                    if (userResult is Success) {
                      if (userResult.data!.data!= null) {
                        onGetUser(userResult.data!.data!, context);
                      }
                    } else if(userResult is Error){
                      var exception = (userResult as Error).exception;
                      if(exception is BusinessErrorException){
                        Fluttertoast.showToast(msg: exception.errorMsg.toString());
                      }
                    }
                  }
    

    业务请求与异常处理

    相关文章

      网友评论

        本文标题:跟我学企业级flutter项目:如何用dio封装一套企业级可扩展

        本文链接:https://www.haomeiwen.com/subject/uyblrrtx.html