flutter api 自动生成脚本

1,550 阅读6分钟

flutter api 自动生成基于source_gen和build_runner,网诺接口,基于swagger。文笔不行,能贴图和贴代码的,绝对不多BB。代码中都有注释,代码中的敏感信息全部替换了,但是,代码全部分享出来了。

一 项目目录以及脚本工程图示

1.1 api_code_generator 工程代码以及项目结构

ApiSite 类,用来在主工程中注解,生成对应的服务下的api

class ApiSite {
  final String serviceName;
  final String version;

  const ApiSite({this.serviceName, this.version});
}

1.2 api_code_generator_gen 工程代码以及项目结构

yaml 依赖文件代码

name: api_code_generator_gen #项目命名,主工程需要用到
description: A new Flutter package.
version: 0.0.1
author:
homepage:

environment:
  sdk: ">=2.2.0 <3.0.0"

dependencies:
  source_gen: ^0.9.6
  flutter:
    sdk: flutter
  api_code_generator:
    path: ../api_code_generator/
  dio: ^3.0.6

dev_dependencies:
  build_runner: ^1.10.0
  flutter_test:
    sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:

build.yaml 文件,该文件为配置文件

targets:
  $default: #定义目标库,关键字$default默认为当前库
    builders: #构建的两个库
      api_code_generator|api_code_generator_gen:
        enabled: true #可选,是否将构建器应用于此目标

builders:
  code_generator:
    target: ":api_code_generator_gen" #目标库
    import: "package:api_code_generator_gen/builder.dart" #build文件
    builder_factories: ["apiCodeGenerator"] #build文件中对应的方法
    build_extensions: {".dart": [".api_code_generator_gen.g.part"]}
    auto_apply: dependents #将此Builder应用于包,直接依赖于公开构建起的包
    build_to: cache #输出转到隐藏的构建缓存,不会发布
    applies_builders: ["source_gen|combining_builder"] #指定是否可以延迟运行构建器

builder.dart 文件代码

import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'api_code_generator_gen.dart';

//code_generator 用于区别其它库
Builder apiCodeGenerator(BuilderOptions options) =>
    SharedPartBuilder([ApiCodeGenerator()], 'api_code_generator');

api_code_generator_gen.dart 文件代码

library api_code_generator_gen;

export 'src/api_code_generator_gen_base.dart';

api_response.dart 文件代码  swagger 返回的api 数据实体,蛋疼,全部手解

import 'dart:math';

class ApiResponse {
  String version;
  String swagger;
  String host;
  String basePath;
  Info info;
  List<Tags> tags;
  Map<String, Map<String, PathInfo>> paths;
  Map<String, Definitions> definitions;

  ApiResponse.fromJson(Map<String, dynamic> json) {
    this.swagger = json['swagger'];
    this.host = json['host'];
    this.basePath = json['basePath'];
    this.info = Info.fromJson(json['info']);

    var pathsJson = json['paths'];
    if (pathsJson != null) {
      this.paths = Map<String, Map<String, PathInfo>>();
      (pathsJson as Map).forEach((key, value) {
        if (value != null) {
          var m = Map<String, PathInfo>();
          (value as Map).forEach((k2, v2) {
            m[k2 as String] = PathInfo.fromJson(v2);
          });
          this.paths[key as String] = m;
        }
      });
    }
    var definitionsJson = json['definitions'];
    if (definitionsJson != null) {
      this.definitions = Map();
      (definitionsJson as Map).forEach((key, value) {
        this.definitions[key] = Definitions.fromJson(value);
      });
    }

    var tagsJson = json['tags'];
    if (tagsJson != null) {
      this.tags = List<Tags>();
      (tagsJson as List).forEach((v) {
        this.tags.add(Tags.fromJson(v));
      });
    }
  }
}

class Info {
  String description;
  String version;

  Info.fromJson(Map<String, dynamic> json) {
    this.description = json['description'];
    this.version = json['version'];
  }
}

class Tags {
  String name;
  String description;

  Tags.fromJson(Map<String, dynamic> json) {
    this.name = json['name'];
    this.description = json['description'];
  }
}

class PathInfo {
  List<String> tags;
  String summary;
  String operationId;
  List<String> produces;
  List<Parameters> parameters;
  Map<String, Responses> responses;

  PathInfo.fromJson(Map<String, dynamic> dynamic) {
    var tagsJson = dynamic['tags'];
    if (tagsJson != null) {
      this.tags = List<String>();
      (dynamic['tags'] as List).forEach((v) {
        this.tags.add(v);
      });
    }
    this.summary = dynamic["summary"];
    this.operationId = dynamic["operationId"];

    var producesJson = dynamic['produces'];
    if (producesJson != null) {
      this.produces = List<String>();
      (producesJson as List).forEach((v) {
        this.produces.add(v);
      });
    }

    var parametersJosn = dynamic['parameters'];
    if (parametersJosn != null) {
      this.parameters = List<Parameters>();
      (parametersJosn as List).forEach((v) {
        this.parameters.add(Parameters.fromJson(v));
      });
    }

    var responsesJson = dynamic['responses'];
    if (responsesJson != null) {
      this.responses = Map();
      (responsesJson as Map).forEach((key, value) {
        this.responses[key] = Responses.fromJson(value);
      });
    }
  }
}

class Parameters {
  String name;
  String ins;
  String description;
  bool required;
  String type;
  dynamic defaults;
  String format;
  List<String> enums;
  String typeName;
  Parameters items;
  String $ref;
  Object schema;

  Parameters.fromJson(Map<dynamic, dynamic> dynamic) {
    this.name = dynamic["name"];
    this.ins = dynamic["in"];
    this.description = dynamic["description"];
    this.required = dynamic["required"];
    this.type = dynamic["type"];
    this.defaults = dynamic["default"];
    this.format = dynamic["format"];
    this.$ref = dynamic['\$ref'];
    if (dynamic.containsKey("enums") && dynamic["enum"] != null) {
      this.enums = List();
      (dynamic["enum"] as List).forEach((f) => {this.enums.add(f)});
    }
    if (dynamic.containsKey("items") && dynamic["items"] != null) {
      this.items = Parameters.fromJson(dynamic["items"]);
    }
    if (dynamic.containsKey("schema") && dynamic["schema"] != null) {
      var schemaJson = dynamic["schema"];
      if (schemaJson is Map) {
        this.schema = Map();
        schemaJson.forEach((k, v) {
          (schema as Map)[k] = v;
        });
      } else if (schemaJson is String) {
        this.schema = schemaJson;
      }
    }
  }
}

class Responses {
  String description;
  Parameters schema;

  Responses.fromJson(Map<String, dynamic> dynamic) {
    this.description = dynamic["description"];
    var schemaJson = dynamic["schema"];
    if (schemaJson != null) {
      this.schema = Parameters.fromJson(schemaJson);
    }
  }
}

class Definitions {
  String type;
  Map<String, Parameters> properties;
  String title;
  String description;

  Definitions.fromJson(Map<String, dynamic> dynamic) {
    this.type = dynamic["type"];
    this.title = dynamic["title"];
    this.description = dynamic["description"];

    var propertiesJson = dynamic['properties'];
    if (propertiesJson != null) {
      this.properties = Map();
      (propertiesJson as Map).forEach((key, value) {
        this.properties[key] = Parameters.fromJson(value);
      });
    }
  }
}

class HttpFunction {
  String className;
  String typeName;
  String url;
  String method;
  List<Parameters> parameters;
  String summary;
}

class TsType {
  String typeName;
  String description;
  String title;
  Map<String, Parameters> properties;
}

api_code_generator_gen_base.dart 文件代码,画重点,里面是基于GeneratorForAnnotation 的代码自动生成的逻辑,吐槽一下,dart 写这个脚本真的没有Android基于 gradle plugin 用 groovey写的爽,groovey里面封装的网诺请求,直接可以解析成需要的实体map,而且,groovey是脚本语言,对象的属性字段,取用非常灵活。

import 'dart:math';

import 'package:analyzer/dart/element/element.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:dio/dio.dart';
import 'package:source_gen/source_gen.dart';
import 'package:api_code_generator/index.dart';
import './api_response.dart';

/*
 *  flutter packages pub run build_runner build
 */
class ApiCodeGenerator extends GeneratorForAnnotation<ApiSite> {
  @override
  generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) async {
    print("element is $element");
    if (element is! ClassElement) {
      throw InvalidGenerationSourceError(
          "Request class is not ok for ${element.displayName}");
    }
    var classElement = (element as ClassElement);
    var metadatas = classElement.metadata;
    var apiSiteMeta = metadatas
        .firstWhere((e) => (e.computeConstantValue().type.name == "ApiSite"));
    var apiSiteConstantValue = apiSiteMeta.computeConstantValue();
    var serviceName =
        apiSiteConstantValue.getField("serviceName").toStringValue();
    var version = apiSiteConstantValue.getField("version").toStringValue();
    print("ApiSite apiName  $serviceName");
    var singGenerator = SingGenerator();
    var code = await singGenerator.buildSing(serviceName, version);
    return code;
  }
}

class SingGenerator {
  var apiPaths = Map<String, Map<String, HttpFunction>>();

  Map<String, TsType> typeDefinitionMap = Map();

  Future<String> buildSing(String serviceName, String version) async {
    var apiResponse = await _getApi(serviceName);
    apiResponse.version = version;
    return _buildCode(apiResponse).toString();
  }

  Future<ApiResponse> _getApi(String serviceName) async {
    var dio = Dio()
      ..options = BaseOptions(
          baseUrl: "对不起,baseUrl 我不能提供",
          connectTimeout: 10000,
          receiveTimeout: 10000)
      ..interceptors
          .add(LogInterceptor(responseBody: false, requestBody: false));
    var url = "/$serviceName/v2/api-docs";
    Response<dynamic> response = await dio.get(url);
    return ApiResponse.fromJson(response.data);
  }

  StringBuffer _buildCode(ApiResponse apiResponse) {
    var stringBuffer = StringBuffer();
    _buildDescription(apiResponse, stringBuffer);
    _buildRef(apiResponse);
    _buildDefinitionMap(apiResponse);
    _buildApiCode(stringBuffer);
    _buildEntityCode(stringBuffer);

    return stringBuffer;
  }

  /// 生成注解
  void _buildDescription(ApiResponse apiResponse, StringBuffer stringBuffer) {
    stringBuffer.write("/*");
    stringBuffer.write("\n");
    stringBuffer.write("basePath: " + apiResponse.basePath);
    stringBuffer.write("\n");
    stringBuffer.write("description: " + apiResponse.info.description);
    stringBuffer.write("\n");
    stringBuffer.write("*/");
  }

  ///生成API文件
  void _buildApiCode(StringBuffer stringBuffer) {
    void buildSingApi(String methodName, HttpFunction httpFunction) {
      var summary = httpFunction.summary;
      var parameters = httpFunction.parameters;
      var method = httpFunction.method;
      var typeName = httpFunction.typeName;
      var paramsQuery = [];
      if (summary != null && summary.isNotEmpty) {
        stringBuffer.writeln("  /*");
        stringBuffer.writeln("  * $summary");
        stringBuffer.writeln("  */");
      }
      String data = "";
      var paramsPath = Map<String, String>();

      parameters?.forEach((p) {
        var nameAndType = "${p.name}: ${p.typeName}";
        if (p.ins == 'query') {
          paramsQuery.add(nameAndType);
        } else if (p.ins == 'body') {
          data = p.typeName;
        } else if (p.ins == 'path') {
          if (p.name != "version") {
            paramsPath[p.name] = p.typeName;
          }
        } else if (p.ins == 'formData') {}
      });

      if (parameters != null && parameters.isNotEmpty) {
        stringBuffer.writeln("  /*");
        parameters.forEach((p) {
          if (p.ins == "header") {
            return;
          }
          if (p.name == "version") {
            return;
          }
          var required = p.required ? '必填' : '选填';
          var nameAndType = "${p.ins} ${p.name}:${p.typeName}($required)";
          stringBuffer.writeln("   $nameAndType");
        });

        stringBuffer.writeln("  */");
      }

      var paramsComment = "";
      var params = "";
      if (method == "get") {
        if (paramsQuery.isNotEmpty) {
          paramsComment += "Map<String,dynamic> params";
          params = ",queryParameters:params";
        }
      } else if (method == "post" || method == "delete" || method == "put") {
        paramsComment += "$data data";
        params = ",data";
      }
      //复杂对象,调用方写josn解析器
      if (typeName.startsWith("Map<")) {
        paramsComment += ",{Function(dynamic dynamic) beanCreator}";
      }
      stringBuffer.writeln(
          "   static  Future<BaseEntrty<$typeName>> $methodName($paramsComment) async{");
      stringBuffer.writeln(
          "   Response<dynamic> response = await HttpRequest.instance");
      stringBuffer.writeln(".$method('${httpFunction.url}'$params);");
      stringBuffer.writeln(
          "return BaseEntrty<$typeName>.fromJson(response.data,(dynamic)=>");
      if (typeName == "bool" ||
          typeName == "int" ||
          typeName == "double" ||
          typeName == "String") {
        stringBuffer.writeln("dynamic as $typeName");
      } else if (typeName.startsWith("Map<")) {
        stringBuffer.writeln("beanCreator(dynamic)");
      } else if (typeName.startsWith("PageList<")) {
        var beanType = typeName
            .replaceAll("PageList", "")
            .replaceFirst("<", "")
            .replaceFirst(">", "");
        var code = _isBasics(beanType)
            ? "dynamic as $beanType"
            : "$beanType.fromJson(dynamic)";
        stringBuffer.writeln(
            "PageList<$beanType>.fromJson(dynamic, (dynamic) =>$code)");
      } else if (typeName.startsWith("List<")) {
        var beanType = typeName
            .replaceAll("List", "")
            .replaceFirst("<", "")
            .replaceFirst(">", "");
        var code = _isBasics(beanType)
            ? "dynamic as $beanType"
            : "$beanType.fromJson(dynamic)";
        stringBuffer.writeln("$code , listCreator : () => List<$beanType>()");
      } else {
        stringBuffer.writeln("$typeName.fromJson(dynamic)");
      }
      stringBuffer.writeln(");");
      stringBuffer.writeln("}");
    }

    this.apiPaths?.forEach((className, entity) {
      stringBuffer.writeln("class ${_toUpperCaseOne(className)}Server {");
      entity.forEach(buildSingApi);
      stringBuffer.writeln("}");
    });
  }

  /// 生成实体文件
  void _buildEntityCode(StringBuffer stringBuffer) {
    //from json 方法
    void _buildFromJsonFunction(
        String className, Map<String, Parameters> properties) {
      //
      var beanCreator = "";
      properties?.forEach((filedName, parameters) {
        if (parameters.typeName == "T") {
          beanCreator = "Function(dynamic dynamic) ${filedName}Creator";
          beanCreator = "," + beanCreator;
        }
      });
      stringBuffer.writeln("");
      stringBuffer.writeln(
          "${className.replaceAll("<T>", "")}.fromJson(Map<dynamic, dynamic> dynamic $beanCreator) {");
      properties?.forEach((filedName, parameters) {
        var ref = parameters.$ref;
        if (ref != null && ref.isNotEmpty) {
          if (parameters.typeName == "T") {
            stringBuffer.writeln(
                "this.$filedName=${filedName}Creator(dynamic['$filedName']);");
          } else {
            stringBuffer.writeln(
                "this.$filedName=${parameters.typeName}.fromJson(dynamic['$filedName']);");
          }
        } else {
          switch (parameters.type) {
            case 'array':
            case 'list':
              stringBuffer
                  .writeln("var ${filedName}Json = dynamic['$filedName'];");
              stringBuffer.writeln("if (${filedName}Json != null) {");
              stringBuffer.writeln("this.$filedName = List();");
              stringBuffer.writeln(
                  "(dynamic['$filedName'] as List).forEach((f) => this.$filedName.add(f));");
              stringBuffer.writeln("}");
              break;
//            case 'object':
//              stringBuffer.writeln(
//                  "    this.$filedName=${parameters.typeName}.fromJson(dynamic['$filedName']);");
//              break;
            default:
              stringBuffer.writeln("this.$filedName=dynamic['$filedName'];");
              break;
          }
        }
      });

      stringBuffer.writeln("}");
    }

    void _buildFiled(String name, Parameters parameters) {
      stringBuffer.writeln("  /*");
      stringBuffer.writeln("  description: ${parameters?.description}");
      stringBuffer.writeln("  type       : ${parameters?.type}");
      stringBuffer.writeln("  */");
      //print("  ${parameters?.typeName} $name ;");
      stringBuffer.writeln("");
      stringBuffer.writeln("  ${parameters?.typeName} $name ;");
    }

    void _buildSingEntity(String name, TsType tsType) {
      stringBuffer.writeln("/*");
      stringBuffer.writeln("description ${tsType.description}");
      stringBuffer.writeln("*/");
      stringBuffer.writeln("class $name {");
      tsType.properties?.forEach(_buildFiled);
      //构造方法
      stringBuffer.writeln("${name.replaceAll("<T>", "")}();");
      //fromJson 方法
      _buildFromJsonFunction(name, tsType.properties);
      stringBuffer.writeln("}");
    }

    typeDefinitionMap.forEach(_buildSingEntity);
  }

  void _buildRef(ApiResponse apiResponse) {
    var paths = apiResponse.paths;
    var version = apiResponse.version;
    /**
     * 解析具体的方法参数
     */
    void _analysisMethodInfo(
        String path, List<String> pathParts, String method, PathInfo info) {
      var produces = info.produces;
      if (produces.contains("application/octet-stream")) {
        return;
      }
      var tags = info.tags;
      if (tags.contains("ignore")) {
        return;
      }
      var className = "";
      if (path.contains('/action/')) {
        className = pathParts[pathParts.length - 3];
      } else if (pathParts[pathParts.length - 1].contains('{')) {
        className = pathParts[pathParts.length - 2];
      } else {
        className = pathParts[pathParts.length - 1];
      }
      className = _transformCamelCase(className);
      if (className == 'geetest') {
        return;
      }
      if (apiPaths[className] == null) {
        apiPaths[className] = Map();
      }
      var responseOk = info.responses["200"];
      try {
        var ref = responseOk.schema.$ref;
        if (ref == null || ref.isEmpty) {
          ref = responseOk.schema?.items?.$ref;
        }
        if (ref == null || ref.indexOf('Response«') < 0) {
          throw new Exception("接口返回类型出错");
        }
        var typeName = _formatType(_getType(responseOk.schema));
        var methodName = path.contains('/action/')
            ? _transformCamelCase(pathParts[pathParts.length - 1])
            : method;
        if (apiPaths[className][methodName] != null) {
          var result = apiPaths[className][methodName];
          var message = "解析出相同的方法名 $result,$path";
          throw new Exception(message);
        }

        var parameters = info.parameters;
        parameters.forEach((it) {
          it.typeName = _formatType(_getParametersType(it));
        });

        var summary = info.summary;

        var httpFunction = HttpFunction();
        httpFunction.className = className;
        httpFunction.typeName = typeName;
        httpFunction.url = path.replaceFirst("{version}", version);
        httpFunction.method = method;
        httpFunction.parameters = parameters;
        httpFunction.summary = summary;

        apiPaths[className][methodName] = httpFunction;
      } catch (e) {
        var message = "解析出错 $e path:$path  responseSchema:$responseOk";
        // throw `${ex}${this.serviceName} path:${path} responseSchema:${JSON.stringify(responseOk)}`
        throw new Exception(message);
      }
    }

    /**
     * 解析path info
     */
    void _analysisPathInfo(String path, Map<String, PathInfo> pathInfo) {
      print("path $path");
      var pathParts = path.split('/');

      pathInfo.forEach((method, info) =>
          {_analysisMethodInfo(path, pathParts, method, info)});
    }

    //去除不符合条件的API
    paths.removeWhere((key, value) => (key == null ||
        key.isEmpty ||
        !key.contains('/{version}/') ||
        key.contains('/pv/') ||
        key.contains('/pvs/') ||
        key.contains('pb/images/action/download') ||
        value.isEmpty));

    paths.forEach((path, pathInfo) => _analysisPathInfo(path, pathInfo));
  }

  void _buildDefinitionMap(ApiResponse apiResponse) {
    var definitions = apiResponse.definitions;

    TsType _buildTsType(String name, Definitions definitions) {
      var tsType = TsType();
      tsType.typeName = name;
      tsType.description = definitions.description;
      tsType.title = definitions.title;
      tsType.properties = definitions.properties;
      definitions.properties?.forEach((properties, parameters) {
        if (name.contains("<T>") && properties == "data") {
          parameters.typeName = "T";
        } else {
          var typeName = _getType(parameters);
          parameters.typeName = typeName;
        }
      });

      return tsType;
    }

    definitions.removeWhere((key, value) {
      return key.contains("PageList«") ||
          key.contains("PageRequest«") ||
          key.contains("Response«") ||
          key.contains("ObjectNode") ||
          key.contains("List«") ||
          key.contains("Map«");
    });

    definitions?.forEach((name, vo) {
      var regExp = new RegExp(r"«.*»");
      if (name.contains(regExp)) {
        name = name.replaceAll(regExp, "<T>");
        if (!typeDefinitionMap.containsKey(name)) {
          typeDefinitionMap[name] = _buildTsType(name, vo);
        }
      } else {
        typeDefinitionMap[name] = _buildTsType(name, vo);
      }
    });
  }

  String _formatType(String type) {
    if (type == "string") {
      return "String";
    }
    if (type.toLowerCase() == "boolean") {
      return "bool";
    }
    if (type.toLowerCase() == "long") {
      return "int";
    }
    if (type.toLowerCase() == "bigdecimal") {
      return "double";
    }
    return type
        .replaceAll("<long", "<int")
        .replaceAll("<Long", "<int")
        .replaceAll("<Double", "<double")
        .replaceAll("<float", "<double")
        .replaceAll("<Float", "<double")
        .replaceAll("<string", "<String")
        .replaceAll("<Boolean", "<bool")
        .replaceAll("<boolean>", "<bool");
  }

  bool _isBasics(String type) {
    return type == "int" ||
        type == "double" ||
        type == "String" ||
        type == "int" ||
        type == "bool";
  }

  String _getType(Object parameters) {
    var refKey = '\$ref';

    String _getRefType(String refStr) {
      var refType = refStr.replaceFirst('#/definitions/', '');
      refType = refType.replaceFirst('Response«', '').replaceFirst('»', '');
      refType = refType.replaceAll("«", "<").replaceAll("»", ">");
      if (refType.contains("<") && !refType.contains(">")) {
        refType += ">";
      }
      return refType;
    }

    if (parameters is String) {
      return _getRefType(parameters);
    }
    if (parameters is Map && parameters.containsKey(refKey)) {
      var ref = parameters[refKey] as String;
      return _getRefType(ref);
    }
    if (parameters is Map) {
      print("parameters is map " + parameters.toString());
      return "";
    }
    return _getParametersType(parameters as Parameters);
  }

  String _getParametersType(Parameters parameters) {
    var ref = parameters.$ref;
    if (ref != null && ref.isNotEmpty) {
      return _getType(ref);
    }
    var schema = parameters.schema;
    if (schema != null) {
      if (schema is Map) {
        return _getType(schema);
      }
    }

    var type = parameters.type;
    var format = parameters.format;
    var items = parameters.items;
    switch (type) {
      case 'int':
      case 'int32':
      case 'integer':
      case 'number':
      case 'long':
      case 'int64':
        if (format == "double" || format == "float") {
          return "double";
        }
        return "int";
      case 'bigdecimal':
      case 'double':
      case 'float':
        return 'double';
      case 'string':
        return 'String';
      case 'boolean':
        return 'bool';
      case 'array':
      case 'list':
        var refType = _getParametersType(items);
        return "List<$refType>";
      case 'object':
        return 'Object';
      default:
        return "";
    }
  }

  String _transformCamelCase(String str) {
    var array = str.split("-");
    String result = "";
    for (int i = 0; i < array.length; i++) {
      var indexValue = array[i];
      if (i != 0) {
        var index0 = indexValue.substring(0, 1);
        indexValue = indexValue.replaceFirst(index0, index0.toUpperCase());
      }
      result += indexValue;
    }
    return result;
  }

  String _toUpperCaseOne(String string) {
    return "${string.substring(0, 1).toUpperCase()}${string.substring(1)}";
  }
}

上面的代码也没什么好多的,就是JSON 解析,生成代码的逻辑。

二:在主工程中的应用。

主工程的yaml文件需要做以下配置

dev_dependencies:
  build_runner: ^1.10.0#build_runner 用来运行命令行

  flutter_test:
    sdk: flutter
  api_code_generator_gen:#脚本依赖
    path: ./api_code_generator_gen/
dependencies:
  flutter:
    sdk: flutter
  dio: ^3.0.6
  flutter_bloc:
  api_code_generator:#注解依赖
    path: ./api_code_generator/

主工程中的网诺框架采用dio,因为我是做Android 开发的,里面的所有实体封装,都是基于java 面向对象的思想,dart 的数据解析,没人教,不知道,只能自己摸索,悲哀。自动生成的api 基于下面的网诺框架和实体类。

import 'package:dio/dio.dart';

class HttpRequest {
  static Dio dio;

  static HttpRequest get instance => _getInstance();

  static HttpRequest _instance;

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

  HttpRequest._internal() {

    dio = Dio()
      ..options = BaseOptions(
          baseUrl: "bserUrl",
          connectTimeout: 10000,
          receiveTimeout: 10000)
      ..interceptors.add(LogInterceptor(responseBody: true, requestBody: true))
      ..interceptors
          .add(InterceptorsWrapper(onRequest: (RequestOptions options) {
        var headers = options.headers;
        headers["appversion"] = "1.1.1";

        return options;
      }));
  }

  get(String path, {Map<String, dynamic> queryParameters}) async {
    if (queryParameters == null || queryParameters.isEmpty) {
      return dio.get(path);
    }
    await dio.get(path, queryParameters: queryParameters);
  }

  post(String path, dynamic data) async {
    Options options = Options();
    options.contentType = "";
    options.method = "POST";
    return await dio.post(path, data: data, options: options);
  }

  delete(String path, dynamic data) async {
    Options options = Options();
    options.contentType = "";
    options.method = "DELETE";
    return await dio.post(path, data: data, options: options);
  }

  put(String path, dynamic data) async {
    Options options = Options();
    options.contentType = "";
    options.method = "PUT";
    return await dio.post(path, data: data, options: options);
  }
}

基于服务端数据封装的标准实体类

base_entrty.dart 标准实体类,该实体类,解析泛型的方法,全部暴露出去,泛型强转,dart报错,跟java 有区别,包括泛型为List的泛型,new List() 也必须暴露出去,在外面加泛型,不然,强转也报错,悲哀,我也搞不懂是为什么。

import 'dart:math';

class BaseEntrty<T> {
  BaseEntrty();

  T data;
  bool status;
  String code;
  String msg;
  String errorMsg;
  int currentTime;

  BaseEntrty.fromJson(
      Map<String, dynamic> json, Function(dynamic dynamic) beanCreator,
      {Function() listCreator}) {
    this.status = json["status"];
    this.code = json["code"];
    this.msg = json["msg"];
    this.errorMsg = json["errorMsg"];
    this.currentTime = json["currentTime"];
    dynamic jsonData = json["data"];
    if (jsonData != null) {
      if (jsonData is List<dynamic>) {
        //获取具有实际泛型的list集合,不然list as T 会报错
        List list = listCreator();
        jsonData.forEach((v) {
          list.add(beanCreator(v));
        });
        this.data = list as T;
      }  else {
        var bean = beanCreator(jsonData);
        this.data = bean as T;
      }
    }
  }
}

base_page_entity.dart  分页接口标准实体

class PageList<T> {
  List<T> entities;
  int pageNo;
  int count;
  int pageSize;

  //dynamic excludeFields;

  PageList({
    this.entities,
    this.pageNo,
    this.count,
    this.pageSize,
    // this.excludeFields
  });

  PageList.fromJson(
      Map<String, dynamic> json, Function(dynamic dynamic) beanCreator) {
    if (json['entities'] != null) {
      entities = List<T>();
      (json['entities'] as List).forEach((v) {
        entities.add(beanCreator(v) as T);
      });
    }
    pageNo = json['pageNo'];
    count = json['count'];
    pageSize = json['pageSize'];
    //  excludeFields = json['excludeFields'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
//    if (this.entities != null) {
//      data['entities'] = this.entities.map((v) => v.toJson()).toList();
//    }
    data['pageNo'] = this.pageNo;
    data['count'] = this.count;
    data['pageSize'] = this.pageSize;
    // data['excludeFields'] = this.excludeFields;
    return data;
  }
}

class PageRequest<T> {
/* 参数对象 */
  T param;

/* 页码 */
/* 参数格式: int32 */
  int pageNo;

/* 每页条数 */
/* 参数格式: int32 */
  int pageSize;

/* 开始时间 */
  String startTime;

/* 截止时间 */
  String endTime;

/* 时间范围 */
  String timeRange;
}

应用(画重点)

定义一个文件,例如api_platform_behavior.dart,代码中的part 'api_platform_behavior.g.dart'; 为自动生成的文件的目标文件,ApiSite为注解,自动生成会基于该注解。

import 'package:api_code_generator/index.dart';//导入网诺框架包以及实体,自动生成的代码,会自动导入包里面的代码
import 'package:nutritionplan/http/http.dart';

part 'api_platform_behavior.g.dart';

@ApiSite(serviceName: "platform-behavior", version: "v5.0")
class PlatformBehavior {}

在 命令行中输入,脚本自动运行,会生成api_platform_behavior.g.dart文件

flutter packages pub run build_runner build

下面贴出部分生成的代码,url 我全部屏蔽了,希望谅解

part of 'api_platform_behavior.dart';

class CommentInfoServer {
  /*
 query commentKid:String(必填)
 query pageNo:int(选填)
 query pageSize:int(选填)
*/
// BaseEntrty<PageList<T>> 示例
  static Future<BaseEntrty<PageList<CommentContentDTO>>> searchChild(
      Map<String, dynamic> params) async {
    Response<dynamic> response = await HttpRequest.instance.get(
        '/url',
        queryParameters: params);
    return BaseEntrty<PageList<CommentContentDTO>>.fromJson(
        response.data,
        (dynamic) => PageList<CommentContentDTO>.fromJson(
            dynamic, (dynamic) => CommentContentDTO.fromJson(dynamic)));
  }
  //BaseEntrty<int>> 示例
  static Future<BaseEntrty<int>> delete(data) async {
    Response<dynamic> response = await HttpRequest.instance
        .delete('/url', data);
    return BaseEntrty<int>.fromJson(response.data, (dynamic) => dynamic as int);
  }
  //BaseEntrty<T>> 示例
  static Future<BaseEntrty<StatisticResult>> simple(
      Map<String, dynamic> params) async {
    Response<dynamic> response = await HttpRequest.instance
        .get('/url', queryParameters: params);
    return BaseEntrty<StatisticResult>.fromJson(
        response.data, (dynamic) => StatisticResult.fromJson(dynamic));
  }

  //复杂实体示例,将数据解析的方法,抛出去
  static Future<BaseEntrty<Map<String, PageList<CommentContentDTO>>>>
  searchPreview(Map<String, dynamic> params,
      {Function(dynamic dynamic) beanCreator}) async {
    Response<dynamic> response = await HttpRequest.instance.get(
        '/v5.0/pb/comment-info/action/search-preview',
        queryParameters: params);
    return BaseEntrty<Map<String, PageList<CommentContentDTO>>>.fromJson(
        response.data, (dynamic) => beanCreator(dynamic));
  }
}
  //生成的实体示例
  class StatisticResult {
  /*
  description: 评论数
  type       : integer
  */

  int commentCount;

  /*
  description: 自定义打点统计数
  type       : object
  */

  Object customMapper;

  /*
  description: 收藏数
  type       : integer
  */

  int favoriteCount;

  /*
  description: 完成数
  type       : integer
  */

  int finishCount;

  /*
  description: 关注数
  type       : integer
  */

  int followCount;

  /*
  description: 参加数
  type       : integer
  */

  int joinCount;

  /*
  description: 点赞数
  type       : integer
  */

  int likeCount;

  /*
  description: 分享数
  type       : integer
  */

  int shareCount;

  /*
  description: 目标资源唯一标识
  type       : string
  */

  String targetKid;

  /*
  description: 目标资源类型
  type       : string
  */

  String targetType;

  /*
  description: 浏览数
  type       : integer
  */

  int viewCount;

  StatisticResult();

  StatisticResult.fromJson(Map<dynamic, dynamic> dynamic) {
  this.commentCount = dynamic['commentCount'];
  this.customMapper = dynamic['customMapper'];
  this.favoriteCount = dynamic['favoriteCount'];
  this.finishCount = dynamic['finishCount'];
  this.followCount = dynamic['followCount'];
  this.joinCount = dynamic['joinCount'];
  this.likeCount = dynamic['likeCount'];
  this.shareCount = dynamic['shareCount'];
  this.targetKid = dynamic['targetKid'];
  this.targetType = dynamic['targetType'];
  this.viewCount = dynamic['viewCount'];
  }
  }

}

flutter 基于GeneratorForAnnotation 自动生成代码,就是这个思路,我的数据解析的方法不够好,如果,有更好的数据解析的方法,希望学习。

最后附上 Android 版api 生成脚本:juejin.cn/post/686522…