Bootstrap

Flutter 13 网络层框架架构设计,支持dio等框架。

在移动APP开发过程中,进行数据交互时,大多数情况下必须通过网络请求来实现。客户端与服务端常用的数据交互是通过HTTP请求完成。面对繁琐业务网络层,我们该如何通过网络层架构设计来有效解决这些问题,这便是网络层框架架构设计的初衷。

设计要求:

1. 支持网络库插拔设计,且不干扰业务层

2. 简洁易用,支持配置来进行请求

3. Adapter设计,扩展性强

4. 统一异常和返回处理

解决问题:

切换成本高:网络操作使用的三方库存在不维护切换成本高的风险;

接口管理不便:对于大中型APP接口众多,不方便管理;

重复代码多:APP中进行数据交互的场景很多,网络请求存在大量的重复代码;

扩展性差:网络操作和业务代码耦合严重,不利于扩展;

开发效率低:不同三方库使用方式不统一,步骤繁琐开发效率低;

一、搭建基础的网络请求框架HiNet

1)创建基础请求

创建抽象的基础请求类BaseRequest,并向上层提供获取请求路径,请求方式等抽象方法,及添加参数和请求头等能力。

base_request.dart

enum HttpMethod { GET, POST, DELETE }

/// 基础请求
abstract class BaseRequest {
  var pathParams;
  var userHttps = true;

  /// 域名
  String authority() {
    return "api.devio.org";
  }

  HttpMethod httpMethod();

  String path();

  String url() {
    Uri uri;
    var pathStr = path();
    // 拼接路径参数
    if (pathParams != null) {
      if (pathStr.endsWith("/")) {
        pathStr = "$pathStr$pathParams";
      } else {
        pathStr = "$pathStr/$pathParams";
      }
    }

    // http和https的切换
    if (userHttps) {
      uri = Uri.https(authority(), pathStr, params);
    } else {
      uri = Uri.http(authority(), pathStr, params);
    }
    print("url:${uri.toString()}");
    return uri.toString();
  }

  bool needLogin();

  Map<String, String> params = {};

  /// 添加参数
  BaseRequest add(String k, Object v) {
    params[k] = v.toString();
    return this;
  }

  Map<String, dynamic> header = {};

  /// 添加请求头
  BaseRequest addHeader(String k, Object v) {
    header[k] = v.toString();
    return this;
  }
}

2)创建测试请求

创建测试请求类TextRequest继承自 基础请求抽象类BaseRequest,实现请求路径和请求方式等。

test_request.dart

import 'package:hi_net/http/request/base_request.dart';

class TestRequest extends BaseRequest{
  @override
  HttpMethod httpMethod() {
    return HttpMethod.GET;
  }

  @override
  bool needLogin() {
    return false;
  }

  @override
  String path() {
    return "uapi/test/test";
  }
}

3)创建核心网络请求类

创建核心网络请求类HiNet,提供发送请求,接收响应,解析返回数据,统一错误处理等功能。当前使用模拟响应请求的方式,下面内容会带着大家一步一步进行完善。

hi_net.dart

import 'package:hi_net/http/request/base_request.dart';

class HiNet {
  HiNet._internal();

  static HiNet? _instance;

  static HiNet get getInstance {
    _instance ??= HiNet._internal();
    return _instance!;
  }

  Future fire(BaseRequest request) async {
    var respones = await send(request);
    var result = respones['data'];
    print("result:$result");
    return result;
  }

  Future<dynamic> send<T>(BaseRequest request) {
    print("url:${request.url()}");
    print("httpMethod:${request.httpMethod()}");
    request.addHeader("aaaa", "bbbb");
    print("header:${request.header}");
    return Future.value({
      "statusCode": 200,
      "data": {"code": 0, "message": "success"}
    });
  }
}

4)发送测试请求

创建TestRequest测试请求对象,使用HiNet网络请求核心单例类进行模拟Http请求。

TestRequest testRequest = TestRequest();
testRequest.add("1111", "2222").add("3333", "4444");
HiNet.getInstance.fire(testRequest);

模拟接口请求成功,输出log:

二、增加统一异常和响应数据处理,及Adapter模式设计

1)创建网络异常统一格式类

创建网络异常统一格式类HiNetError,包含code、message和data信息。

创建登录异常NeedLogin 和 授权异常NeedAuth 继承自HiNetError。

hi_net_error.dart

/// 需要登录的异常
class NeedLogin extends HiNetError {
  NeedLogin({int code = 401, String message = "请先登录"}) : super(code, message);
}

/// 需要授权的异常
class NeedAuth extends HiNetError {
  NeedAuth(String message, {int code = 403, dynamic data})
      : super(code, message, data: data);
}

/// 网络异常统一格式类
class HiNetError implements Exception {
  final int code;
  final String message;
  final dynamic data;

  HiNetError(this.code, this.message, {this.data});
}

2)创建统一网络层返回格式

创建统一网络层返回格式HiNetResponse,包含request、statusCode、statusMessage和data等信息。

hi_net_adapter.dart

/// 统一网络层返回格式
class HiNetResponse<T> {
  HiNetResponse(
      {this.data,
      this.request,
      this.statusCode,
      this.statusMessage,
      this.extra});

  T? data;

  BaseRequest? request;
  int? statusCode;
  String? statusMessage;

  dynamic extra;

  @override
  String toString() {
    if (data is Map) {
      return json.encode(data);
    }
    return data.toString();
  }
}

3)创建网络请求抽象类

网络请求抽象类HiNetAdapter,提供发送请求能力。

hi_net_adapter.dart

import 'dart:convert';

import 'package:hi_net/http/request/base_request.dart';

/// 网络请求抽象类
abstract class HiNetAdapter {
  Future<HiNetResponse<T>> send<T>(BaseRequest request);
}

/// 统一网络层返回格式
class HiNetResponse<T> {
  HiNetResponse(
      {this.data,
      this.request,
      this.statusCode,
      this.statusMessage,
      this.extra});

  T? data;

  BaseRequest? request;
  int? statusCode;
  String? statusMessage;

  dynamic extra;

  @override
  String toString() {
    if (data is Map) {
      return json.encode(data);
    }
    return data.toString();
  }
}

4)创建测试适配器

创建测试适配器MockAdapter ,mock数据。

mock_adapter.dart

import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/request/base_request.dart';

/// 测试适配器,mock数据
class MockAdapter extends HiNetAdapter {
  @override
  Future<HiNetResponse<T>> send<T>(BaseRequest request) {
    return Future.delayed(const Duration(milliseconds: 1000), () {
      return HiNetResponse(
          data: {"code": 0, "message": "success"} as T, statusCode: 200);
    });
  }
}

5)完善核心网络请求类

完善核心网络请求类HiNet,使用mock适配器MockAdapter发送请求,使用HiNetResponse接收请求响应数据,增加统一错误处理。

hi_net.dart

import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/core/hi_net_error.dart';
import 'package:hi_net/http/core/mock_adapter.dart';
import 'package:hi_net/http/request/base_request.dart';

class HiNet {
  HiNet._internal();

  static HiNet? _instance;

  static HiNet get getInstance {
    _instance ??= HiNet._internal();
    return _instance!;
  }

  Future fire(BaseRequest request) async {
    HiNetResponse? response;
    var error;
    try {
      response = await send(request);
    } on HiNetError catch (e) {
      error = e;
      response = e.data;
      print("HiNetError:${e.message}");
    } catch (e) {
      // 其他错误
      error = e;
      print("OtherError:$e");
    }

    if (response == null) {
      print("error:$error");
    }
    var result = response?.data;
    print("result:$result");

    var status = response?.statusCode ?? 0;
    switch (status) {
      case 200:
        return result;
      case 401:
        throw NeedLogin();
      case 403:
        throw NeedAuth(result.toString(), data: result);
      default:
        throw HiNetError(status, result.toString(), data: result);
    }
  }

  Future<dynamic> send<T>(BaseRequest request) {
    print("url:${request.url()}");
    /// 使用mock发送请求
    HiNetAdapter adapter = MockAdapter();
    return adapter.send(request);
  }
}

6)发送测试请求

创建TestRequest测试请求对象,使用HiNet网络请求核心单例类进行模拟Http请求。

    TestRequest testRequest = TestRequest();
    testRequest.add("1111", "2222").add("3333", "4444");
    try{
      var result = await HiNet.getInstance.fire(testRequest);
      print(result);
    } on NeedAuth catch (e) {
      print(e);
    } on NeedAuth catch (e) {
      print(e);
    } on HiNetError catch (e) {
      print(e);
    }

模拟接口请求成功,输出log:

三、 扩展hi_net添加对dio的支持

1)创建dio适配器

创建dio适配器DioAdapter,重写发送请求的send方法,采用dio框架进行真正的Http网络请求;根据服务器响应数据构建HiNetError 和 HiNetResponse。

添加dio依赖:

pubspec.yaml

dio: ^5.7.0

dio_adapter.dart

import 'package:dio/dio.dart';
import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/core/hi_net_error.dart';
import 'package:hi_net/http/request/base_request.dart';

/// Dio适配器
class DioAdapter extends HiNetAdapter {
  @override
  Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
    Response? response;
    var error, options = Options(headers: request.header);
    try {
      if (request.httpMethod() == HttpMethod.GET) {
        response = await Dio().get(request.url(), options: options);
      } else if (request.httpMethod() == HttpMethod.POST) {
        response = await Dio()
            .post(request.url(), data: request.params, options: options);
      } else if (request.httpMethod() == HttpMethod.DELETE) {
        response = await Dio()
            .delete(request.url(), data: request.params, options: options);
      }
    } on DioError catch (e) {
      error = e;
      response = e.response;
    }

    if (error != null) {
      /// 抛出HiNetError异常
      throw HiNetError(response?.statusCode ?? -1, error.toString(),
          data: buildResponse(response, request));
    }

    return buildResponse(response, request);
  }

  /// 构建HiNetResponse
  HiNetResponse<T> buildResponse<T>(Response? response, BaseRequest request) {
    return HiNetResponse(
        data: response?.data as T,
        request: request,
        statusCode: response?.statusCode,
        statusMessage: response?.statusMessage,
        extra: response);
  }
}

2)使用dio发送请求

修改HiNet 的send方法,使用dio适配器发送请求;Adapter设计,可轻便的更换三方网络请求库,加强了网络请求架构的扩展性。

hi_net.dart

import 'package:hi_net/http/core/hi_net_adapter.dart';
import 'package:hi_net/http/core/hi_net_error.dart';
import 'package:hi_net/http/core/adapter/mock_adapter.dart';
import 'package:hi_net/http/request/base_request.dart';

import 'adapter/dio_adapter.dart';

///1.支持网络库插拔设计,且不干扰业务层
///2.基于配置请求请求,简洁易用
///3.Adapter设计,扩展性强
///4.统一异常和返回处理
class HiNet {

  Future<dynamic> send<T>(BaseRequest request) {
    print("url:${request.url()}");
    /// 使用mock发送请求
    /// HiNetAdapter adapter = MockAdapter();
    /// 使用dio发送请求
    HiNetAdapter adapter = DioAdapter();
    return adapter.send(request);
  }
}

3)发送测试请求

创建TestRequest测试请求对象,使用HiNet网络请求核心单例类进行Http请求。

注意:该测试接口requestPrams是必传字段,不传接口会返回失败。

    TestRequest testRequest = TestRequest();
    testRequest.add("1111", "2222").add("3333", "4444").add("requestPrams", "5555");
    try{
      var result = await HiNet.getInstance.fire(testRequest);
      print("testRequest result: $result");
    } on NeedAuth catch (e) {
      print(e);
    } on NeedAuth catch (e) {
      print(e);
    } on HiNetError catch (e) {
      print(e);
    }

Http接口请求成功,输出log:

Http接口请求失败,输出log: 

四、JSON编码器和解码器

1)使用 json_serializable 框架

使用 json_serializable 框架,对JSON数据进行解析。

添加 json_serializable 依赖:

pubspec.yaml

  json_serializable: ^6.8.0
  json_annotation: ^4.9.0
  build_runner: ^2.1.11

2)创建测试接口返回数据bean

测试接口返回接送数据:

{code: 0, method: GET, requestPrams: 5555}

创建TestModel,编写好属性、构造方法、TestModel.fromJson() 和 toJson();增加注解:@JsonSerializable()

test_model.dart

import 'package:json_annotation/json_annotation.dart';

part 'test_model.g.dart';

/// 测试接口返回数据bean
/// {code: 0, method: GET, requestPrams: 5555}
@JsonSerializable()
class TestModel {
  int code;
  String method;
  String requestPrams;

  TestModel(this.code, this.method, this.requestPrams);

  //固定格式,不同的类使用不同的mixin即可
  factory TestModel.fromJson(Map<String, dynamic> json) => _$TestModelFromJson(json);

  //固定格式,
  Map<String, dynamic> toJson() => _$TestModelToJson(this);
}

执行 :dart run build_runner build

提示:需要在 TestModel 增加 part 'test_model.g.dart';

加上后再次执行 :dart run build_runner build ,接口自动生成 test_model.g.dart 文件

总结:使用 json_serializable 框架构建JSON解析类时,手动创建的基类Model(TestModel),必须满足以下要求:

1. 在Model类编写 part '同Model类文件名.g.dart'; 如 TestModel类文件名est_model.dart ,则编写 part 'test_model.g.dart';

2.增加注解: @JsonSerializable()

3.编写固定格式的代码,名字换成基类名字,这里是TestModel:

  //固定格式,不同的类使用不同的mixin即可
  factory TestModel.fromJson(Map<String, dynamic> json) => _$TestModelFromJson(json);

  //固定格式,
  Map<String, dynamic> toJson() => _$TestModelToJson(this);

3)使用

    TestRequest testRequest = TestRequest();
    testRequest.add("1111", "2222").add("3333", "4444").add("requestPrams", "5555");
    try{
      var result = await HiNet.getInstance.fire(testRequest);
      var testModel = TestModel.fromJson(result['data']);
      print("testRequest requestPrams: ${testModel.requestPrams}");
    } on NeedAuth catch (e) {
      print(e);
    } on NeedAuth catch (e) {
      print(e);
    } on HiNetError catch (e) {
      print(e);
    }

JSON解析,输出log: 

至此,完成了网络请求框架的基本功能,持续完善。。。

;