android api 自动生成基于Android gradle plugin,以及maven,接口基于swagger,该插件,生成的代码为 kotlin编程语言。生成的网诺接口使用OKhttp+retrofit
一 api 自动生成 gradle 插件
插件工程目录
1.1 gradle 文件配置
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
implementation gradleApi()
implementation localGroovy()
}
repositories {
mavenCentral()
}
group = 'com.yryz.plugin'
version = '1.0.0'
archivesBaseName = 'module-api-plugin'
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('../module_api_repository'))//本地仓库maven配置
}
}
}
1.2 配置 ApiModulePlugin 的META-IN 对应 com.yryz.plugin.properties 文件
implementation-class=com.yryz.plugin.ApiModulePlugin
1.3 自定义插件:ApiModulePlugin为插件入口文件,继承gradle Plugin, 自己定义一个apiTask 的gradle Task
package com.yryz.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class ApiModulePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println("ApiModulePlugin --apply")
project.task("apiTask", type: ApiModuleTask)
}
}
1.4 自定义Task :ApiModuleTask为自定义Task,继承gradle DefaultTask。
package com.yryz.plugin
import groovy.json.JsonSlurper
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class ApiModuleTask extends DefaultTask {
@TaskAction
void apiTask() {
println("ApiModuleTask --apiTask 开始执行")
def classList = new ArrayList<String>()
def config = readerConfig()
def api = config["api"]
config["serviceName"].each {
try {
def apiDocsStr = "${api["baseUrl"]}/${it}/${api["version"]}"
def apiGenerator = new ApiGenerator(it, apiDocsStr, config, classList)
apiGenerator.generate()
} catch (Exception e) {
def message = "ApiModuleTask 解析出错 ${e.message}"
println(message)
}
}
}
//解析配置文件,得到需要生成api服务地址,以及其他扩展信息,文件配置在依赖工程的目录下面
private Object readerConfig() {
def file = new File("./apiConfig.json")
def jsonSlurper = new JsonSlurper()
return jsonSlurper.parse(file)
}
}
1.5 api 生成的逻辑,就是对 JSON 的解析,生成对应的api文件,实体文件。
package com.yryz.plugin
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.codehaus.groovy.runtime.ResourceGroovyMethods
class ApiGenerator {
private def spaces = " "
private def spacesInterface = " "
private def spacesInterfaceDouble = " "
private def serviceName
private def apiDocsStr
private Map apiPaths = new HashMap()
private Map<String, TsType> typeDefinitionMap = new HashMap()
private Object apiDocs
private List<String> configList
private Map<String, Object> customMap
private List<String> classList
ApiGenerator(String serviceName, String apiDocsStr, Object config, List<String> classList) {
this.serviceName = serviceName
this.apiDocsStr = apiDocsStr
this.classList = classList
this.configList = config["serializable"] ?: new ArrayList()
this.customMap = config["custom"] ?: new HashMap()
}
public void generate() {
//根据网诺 url 获取对应的api 实体map
def jsonSlurper = new JsonSlurper()
def apiDocsUrl = new URL(apiDocsStr)
def map = jsonSlurper.parse(apiDocsUrl, "utf-8")
this.apiDocs = map
def paths = map["paths"]
paths.each {
String path = it.key
def pathInfo = it.value
if (!path
|| !path.contains('/{version}/')
|| path.contains('/pv/')
|| path.contains('/pvs/')
|| path.contains('pb/images/action/download')) {
return true
}
def url = path.replace('/{version}/', "/[api_version]/")
def pathParts = url.split('/')
println "##### ${url} "
pathInfo.each {
def httpMethod = it.key
def httpMethodVlue = it.value
def produces = httpMethodVlue["produces"]
if (produces instanceof ArrayList) {
if (produces.size() && produces[0] == "application/octet-stream") {
return true
}
}
def tags = httpMethodVlue["tags"]
if (tags instanceof ArrayList) {
if (tags.contains("ignore")) {
return true
}
}
def 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 true
}
apiPaths[className] = apiPaths[className] ?: new HashMap()
def responses = httpMethodVlue["responses"]
def responseOk = responses["200"]
try {
def ref = responseOk["schema"]["\$ref"]
// def ref = responseOk["schema"]['$ref']
if (!ref || ref.indexOf('Response«') < 0) {
throw new Exception("接口返回类型出错")
}
def typeName = getType(responseOk)
def methodName = path.contains('/action/') ? transformCamelCase(pathParts[pathParts.length - 1]) : httpMethod
if (apiPaths[className][methodName]) {
def jsonOutput = new JsonOutput()
def result = jsonOutput.toJson(apiPaths[className][methodName])
def message = "解析出相同的方法名 ${result},${path}"
throw new Exception(message)
}
def parameters = httpMethodVlue["parameters"]
parameters.each {
it["typeName"] = getType(it)
}
def summary = httpMethodVlue["summary"]
def hashMap = new HashMap<>()
hashMap["className"] = className
hashMap["typeName"] = typeName
hashMap["url"] = url
hashMap["method"] = httpMethod
hashMap["parameters"] = parameters
hashMap["summary"] = summary
apiPaths[className][methodName] = hashMap
} catch (Exception e) {
def jsonOutput = new JsonOutput()
def result = jsonOutput.toJson(responseOk)
def message = "解析出错 ${e.class.simpleName} ${e.message} ${serviceName} path:${path} responseSchema:${result}"
// throw `${ex}${this.serviceName} path:${path} responseSchema:${JSON.stringify(responseOk)}`
throw new Exception(message)
}
}
}
this.generateFile()
}
private String transformCamelCase(String str) {
String[] array = str.split("-")
String result = ""
for (int i = 0; i < array.length; i++) {
def indexValue = array[i]
if (i != 0) {
def index0 = indexValue.substring(0, 1)
indexValue = indexValue.replace(index0, index0.toUpperCase())
}
result += indexValue
}
return result
}
private String getType(Object property) {
return getType(property, null)
}
private String getType(Object property, Object ref) {
if (property instanceof String) {
def refType = property.replace('#/definitions/', '')
if (refType.contains('Response«')) {
refType = refType.replace('Response«', '').replaceFirst('»', '')
}
def typeName = this.getRefType(refType, ref)
return typeName
}
def type = property["type"]
def schema = property["schema"]
//def $ref = property['$ref']
def $ref = property["\$ref"]
def format = property['format']
if ($ref) {
def returnRef = getType($ref, property)
return returnRef
}
if (schema) {
def returnSchema = getType(schema, ref)
return returnSchema
}
switch (type) {
case 'int':
case 'int32':
case 'integer':
case 'number':
if (format == "int64") {
return "Long"
}
if (format == "double") {
return "Double"
}
if (format == "float") {
return "Float"
}
if (format == "int" || format == "int32") {
return "Int"
}
return "Double"
case 'long':
case 'int64':
return 'Long'
case 'bigdecimal':
case 'double':
return 'Double'
case 'float':
return 'Float'
case 'string':
return 'String'
case 'boolean':
return 'Boolean'
case 'file':
return 'Any'
case 'array':
case 'list':
def refType = getType(property["items"])
return "List<${refType}>"
case 'object':
return 'Any'
// if (property["additionalProperties"]) return 'Any'
// if (ref.$ref.includes('ReportUnit')) return 'Any'
// def jsonOutput = new JsonOutput()
// def result = jsonOutput.toJson(property)
// throw new Exception("case 'object': 无法识别的属性类型 ${result}")
default:
if (type.contains(',')) {
def typeArr = type.split(',')
def defaultStr = ""
typeArr.each {
defaultStr += this.apiDocs["definitions"][it] ? this.getType(it, ref) : this.getType(["type": it])
defaultStr += ","
}
defaultStr = defaultStr.substring(0, defaultStr.length() - 1)
return defaultStr
}
def jsonOutput = new JsonOutput()
def result1 = jsonOutput.toJson(property)
def result2 = jsonOutput.toJson(ref)
throw new Exception("case default 无法识别的属性类型 ${type} ${result1} ${result2}")
}
}
private String getRefType(String refType, Object ref) {
if (this.typeDefinitionMap.containsKey(refType)) {
return this.typeDefinitionMap.get(refType).typeName
}
def generatorClass = ''
def baseClass = ''
if (refType.indexOf('«') > -1) {
def gen = '<T>'
if (refType.contains(',')) gen = '<T,T1>'
if (gen == 'Map<T,T1>') return refType
generatorClass = refType.substring(0, refType.indexOf('«')) + gen
baseClass = refType.substring(refType.indexOf('«') + 1)
baseClass = baseClass.substring(0, baseClass.lastIndexOf('»'))
this.getRefType(baseClass, ref)
if (this.typeDefinitionMap.containsKey(generatorClass)) return getGenerateName(refType)
}
def definition = this.apiDocs["definitions"][refType]
if (!definition) {
if (refType.indexOf('«') > -1) {
println('invalidateTypes:' + refType)
return refType
}
return this.getType(["type": refType], ref)
}
TsType typeDefinition = new TsType()
typeDefinition.setDescription(definition["description"])
typeDefinition.setTypeName(refType)
def properties = new HashMap<String, TsTypeProperty>()
typeDefinition.setProperties(properties)
if (generatorClass) {
typeDefinition.setTypeName(generatorClass)
this.typeDefinitionMap.put(generatorClass, typeDefinition)
} else {
this.typeDefinitionMap.put(refType, typeDefinition)
}
if (definition["type"] != 'object') {
throw new Exception("checkType 无法识别的类型${definition.type}")
}
definition["properties"].each {
def propertyName = it.key
def property = it.value
try {
def tsType = this.getType(property, ref)
if (baseClass && (tsType == baseClass || tsType == "List<${baseClass}>")) {
tsType = tsType.replace(baseClass, 'T')
}
def name = getGenerateName(propertyName)
def tsTypeProperty = new TsTypeProperty()
tsTypeProperty.setName(name)
tsTypeProperty.setType(getSimpleClass(tsType))
tsTypeProperty.setFormat(property["format"])
tsTypeProperty.setDescription(property["description"])
typeDefinition.properties[name] = tsTypeProperty
} catch (ex) {
println(ex.message)
def jsonOutput = new JsonOutput()
def result = jsonOutput.toJson(property)
throw new Exception("无法识别的属性类型 ${propertyName} ${result} ${refType} ")
}
}
if (generatorClass) return getGenerateName(refType)
return typeDefinition.typeName
}
private String getSimpleClass(String tsType) {
return tsType?.replaceAll("<int", "<Int")
.replaceAll("<integer", "<Int")
.replaceAll("<number", "<Long")
.replaceAll("<long", "<Long")
.replaceAll("<string", "<String")
.replaceAll("<boolean", "<Boolean")
.replaceAll("int>", "Int>")
.replaceAll("integer>", "Int>")
.replaceAll("number>", "Long>")
.replaceAll("long>", "Long>")
.replaceAll("string>", "String>")
.replaceAll("boolean>", "Boolean>")
?: tsType
}
private String getGenerateName(String className) {
return className.replace("«", '<').replace("»", '>')
}
/**
* 生成文件
*/
private void generateFile() {
this.generateEntityFile()
this.generateApiFile()
this.generateProviderFileFile()
}
/**
* 生成类文件
*/
private void generateEntityFile() {
def parentPath = "./src/main/java/com/yryz/api/entity"
def parentFile = new File(parentPath)
if (!parentFile.exists()) {
parentFile.mkdirs()
}
def file = new File(parentFile, "${serviceName}.kt")
def tsContent = new StringBuilder()
tsContent.append("package com.yryz.api.entity")
tsContent.append("\n\n")
tsContent.append("import java.io.Serializable")
tsContent.append("\n\n")
this.typeDefinitionMap.each {
def typeInfoKey = it.key
def typeInfo = it.value
//排除Map定义
if (typeInfo.typeName == 'Map<T>') return true
if (typeInfo.typeName == 'Map<T,T1>') return true
if (typeInfo.typeName == 'List<T>') return true
if (classList.contains(typeInfo.typeName)) {
return true
}
if (typeInfo.typeName == 'JsonNode') {
classList.add("JsonNode")
tsContent.append("class JsonNode(){}")
tsContent.append("\n")
return true
}
def properties = typeInfo.properties
if (!properties || properties.isEmpty()) {
classList.add(typeInfo.typeName)
tsContent.append("class ${typeInfo.typeName}(){}")
tsContent.append("\n")
return true
}
classList.add(typeInfo.typeName)
tsContent.append("data class ${typeInfo.typeName}(")
tsContent.append("\n")
def indexOf = 0
properties.each {
def propertyName = it.key
def property = it.value
if (property.description) {
tsContent.append("${spaces}/** ${property.description} */")
if (property.format != "" && property.format != null && property.format != "null") {
tsContent.append("\n")
tsContent.append("${spaces}/** 参数格式: ${property.format} */")
}
tsContent.append("\n")
}
tsContent.append("${spaces}var ${propertyName}: ${property.type}? = null")
if (indexOf < properties.size() - 1) {
tsContent.append(",")
}
tsContent.append("\n")
indexOf++
}
tsContent.append(')')
if (configList.contains(typeInfo.typeName)) {
tsContent.append(':Serializable')
}
tsContent.append("\n")
}
//生成类文件
ResourceGroovyMethods.write(file, tsContent.toString(), "utf-8")
}
/**
* 生成api文件
*/
private void generateApiFile() {
def parentPath = "./src/main/java/com/yryz/api/apiserver"
def parentFile = new File(parentPath)
if (!parentFile.exists()) {
parentFile.mkdirs()
}
def fileName = transformCamelCase(serviceName)
fileName = "${toUpperCaseOne(fileName)}ApiServer"
def file = new File(parentFile, "${fileName}.kt")
def tsContent = new StringBuilder()
tsContent.append("package com.yryz.api.apiserver")
tsContent.append("\n\n")
tsContent.append("import com.yryz.network.http.model.BaseModel")
tsContent.append("\n")
tsContent.append("import io.reactivex.Observable")
tsContent.append("\n")
tsContent.append("import retrofit2.http.*")
tsContent.append("\n")
tsContent.append("import com.yryz.api.entity.*")
tsContent.append("\n\n")
tsContent.append("interface ${fileName} {")
tsContent.append("\n\n")
this.apiPaths.each {
def className = it.key
tsContent.append("${spacesInterface}interface ${toUpperCaseOne(className)}Server {")
it.value.each {
def methodName = it.key
def apiInfo = it.value
def url = apiInfo["url"]
String method = apiInfo["method"]
def parameters = apiInfo["parameters"]//[]
def typeName = apiInfo["typeName"]
def summary = apiInfo["summary"]
//TODO parameters
def paramsQuery = []
def paramsPath = new HashMap<String, String>()
String data = null
parameters.each {
def p = it
def required = p["required"] ? '' : '?'
//def nameAndType = "${p["name"]}${required}: ${p["typeName"]}"
def nameAndType = "${p["name"]}: ${p["typeName"]}"
if (p["in"] == 'query') {
paramsQuery.add(nameAndType)
} else if (p["in"] == 'body') {
data = nameAndType
} else if (p["in"] == 'path') {
if (p["name"] != "version") {
paramsPath.put(p["name"], p["typeName"])
}
} else if (p["in"] == 'formData') {
}
}
def methodParams = ''
if (summary && summary != methodName) {
tsContent.append("\n")
tsContent.append("${spacesInterfaceDouble}/**")
tsContent.append("\n")
tsContent.append("${spacesInterfaceDouble} * ${summary}")
tsContent.append("\n")
tsContent.append("${spacesInterfaceDouble} **/")
tsContent.append("\n")
}
if (parameters) {
tsContent.append("${spacesInterfaceDouble}/**")
tsContent.append("\n")
parameters.each {
def p = it
if (p["in"] == "header") {
return true
}
if (p["name"] == "version") {
return true
}
def required = p["required"] ? '必填' : '选填'
def nameAndType = "${p["in"]} ${p["name"]}:${p["typeName"]}(${required})"
tsContent.append("${spacesInterfaceDouble}@${nameAndType}")
tsContent.append("\n")
}
tsContent.append("${spacesInterfaceDouble} **/")
tsContent.append("\n")
}
// def appendUrl = "${this.serviceName}${url}/${d2}${p2}"
//设置注解
def methodComment = method.toUpperCase()
if (method == "delete") {
methodComment = "HTTP"
}
def urlComment = "${this.serviceName}${url}"
//设置请求
def paramsComment = ""
if (paramsPath) {
paramsPath.each {
def key = it.key
def value = it.value
if (!urlComment.contains("/{${key}}")) {
urlComment += "/{${key}}"
}
paramsComment += "@Path(\"${key}\") ${key}: String, "
}
paramsComment = paramsComment.substring(0, paramsComment.length() - ", ".length())
}
if (method == "delete") {
urlComment = "method = \"DELETE\", path = \"${urlComment}\", hasBody = true"
} else {
urlComment = "\"${urlComment}\""
}
tsContent.append("${spacesInterfaceDouble}@${methodComment}(${urlComment})")
tsContent.append("\n")
if (method == "get") {
if (paramsQuery.size() > 0) {
paramsComment += paramsComment.length() > 0 ? ", " : ""
paramsComment += "@QueryMap params: MutableMap<String, Any>"
}
} else if (method == "post"
|| method == "delete"
|| method == "put"
) {
if (data) {
paramsComment += paramsComment.length() > 0 ? ", " : ""
paramsComment += "@Body ${getSimpleClass(data)}"
}
}
tsContent.append("${spacesInterfaceDouble}fun ${methodName}(${paramsComment}): Observable<BaseModel<${getSimpleClass(getGenerateName(typeName))}>>")
tsContent.append("\n")
}
tsContent.append("${spacesInterface}}")
tsContent.append("\n\n")
}
tsContent.append("\n")
tsContent.append("}")
ResourceGroovyMethods.write(file, tsContent.toString(), "utf-8")
}
private void generateProviderFileFile() {
def parentPath = "./src/main/java/com/yryz/api/provider"
def parentFile = new File(parentPath)
if (!parentFile.exists()) {
parentFile.mkdirs()
}
def fileName = transformCamelCase(serviceName)
fileName = "Provide${toUpperCaseOne(fileName)}ApiServer"
def file = new File(parentFile, "${fileName}.kt")
def tsContent = new StringBuilder()
tsContent.append("package com.yryz.api.provider")
tsContent.append("\n\n")
tsContent.append("import com.yryz.network.http.retrofit.RetrofitManage")
tsContent.append("\n")
tsContent.append("import com.yryz.api.apiserver.*")
tsContent.append("\n")
tsContent.append("\n\n")
tsContent.append("object ${fileName} {")
tsContent.append("\n\n")
def interfaceName = transformCamelCase(serviceName)
interfaceName = "${toUpperCaseOne(interfaceName)}ApiServer"
this.apiPaths.each {
def className = it.key
className = toUpperCaseOne(className)
def functionName = "provide${className}Server"
tsContent.append("${spacesInterface}fun ${functionName}(): ${interfaceName}.${className}Server =")
tsContent.append("\n")
tsContent.append("${spacesInterface}${spaces}RetrofitManage.instance.createService(${interfaceName}.${className}Server::class.java)")
tsContent.append("\n\n")
}
tsContent.append("\n")
tsContent.append("}")
ResourceGroovyMethods.write(file, tsContent.toString(), "utf-8")
}
private String toUpperCaseOne(String string) {
return "${string.substring(0, 1).toUpperCase()}${string.substring(1)}"
}
}
对应的实体
package com.yryz.plugin
class TsTypeProperty {
String name
String type
String description
String format
}
package com.yryz.plugin
class TsType {
String typeName
String description
Map<String, TsTypeProperty> properties
}
1.6 上传只本地mevan ,点击 uploadArchives 命令,插件自动上传至本地meven
二 插件在工程中的应用
2.1 配置 projiect build.gradle,项目的build.gradle 需要配置本地meven 路径。
buildscript {
repositories {
maven {
url uri('./module_api_repository/') //配置本地meven
}
google()
jcenter()
}
dependencies {
classpath 'com.yryz.plugin:module-api-plugin:1.0.0'//配置插件
}
}
2.2 插件在本地api module 中的使用,
2.2.1 module_api build.gradle 文件配置
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.yryz.plugin'//引入api 自动生成插件
2.2.2 apiConfig.json 配置文件,配置中的baseUrl+serviceName+version 为完整的获取swagger api 实体接口的url
{
"serviceName": [
"platform-behavior",//需要生成的微服务下的api
],
"serializable": [
"NewsInfoVO",//生成的实体需要序列化,继承serializable
],
"custom": {
"实例": {
"vo": "返回实体",
"params": "请求参数"
}
},
"api": {
"baseUrl": "swagger的base url https://xxx.xxx.xxx",
"version": "服务对应的版本 v4/xx"
}
}
2.2.3 api tak 的使用 执行 apiTask 命令,会生成对应的api 文件
2.2.4 生成的文件示例
//生成的api server 示例
interface PlatformBehaviorApiServer {
interface CommentInfoServer {
/**
* 查询资源预览评论内容(首页)
**/
/**
@query schemeType:String(选填)
@query size:Int(选填)
@query targetKid:List<String>(必填)
@query targetType:String(必填)
**/
@GET("接口的url")
fun searchPreview(@QueryMap params: MutableMap<String, Any>): Observable<BaseModel<Map<String,PageList<CommentContentDTO>>>>
/**
* 查询自己评论内容
**/
/**
@body dto:CommentQueryDTO(必填)
**/
@POST("接口的url")
fun searchUser(@Body dto: CommentQueryDTO): Observable<BaseModel<PageList<CommentContentDTO>>>
/**
* 删除评论(用户)
**/
/**
@path commentKid:String(必填)
**/
@HTTP(method = "DELETE", path = "接口的url/{commentKid}", hasBody = true)
fun delete(@Path("commentKid") commentKid: String): Observable<BaseModel<Long>>
}
}
//生成的实体示例
data class CommentContentDTO(
/** 父级用户对象 */
var parent: CommentUserDTO? = null,
/** 评论作者唯一标识 */
var createUserId: String? = null,
/** 子评论集合 */
var subs: List<CommentContentDTO>? = null,
/** 根级评论标识 */
/** 参数格式: int64 */
var rootKid: Long? = null,
/** 评论唯一标识 */
/** 参数格式: int64 */
var kid: Long? = null,
/** 删除标识 */
/** 参数格式: int32 */
var delFlag: Int? = null,
/** 热度值 */
/** 参数格式: int64 */
var hotValue: Long? = null,
/** 评论内容 */
var content: String? = null,
/** 审核标识(0无需审核/审核通过,1待审核,2审核拒绝) */
/** 参数格式: int32 */
var auditFlag: Int? = null,
var statisticResult: StatisticResult? = null,
/** 目标资源唯一标识 */
var targetKid: String? = null,
/** 评论类型(1 正常评论,2马甲评论,3灌水(机器人)评论 */
var commentType: String? = null,
/** 子评论总数 */
/** 参数格式: int64 */
var subTotalCount: Long? = null,
/** 评论作者用户类型(1正常用户,2马甲用户,3机器人用户) */
var createUserType: String? = null,
/** 父级评论标识 */
/** 参数格式: int64 */
var parentKid: Long? = null,
/** 创建时间 */
/** 参数格式: date-time */
var createDate: String? = null,
/** 当前用户对象 */
var creator: CommentUserDTO? = null,
/** 评论作者用户类型(1正常用户,2马甲用户,3机器人用户) */
var parentUserType: String? = null,
/** 商户关联信息对象 */
var relationalWithMerchant: CommentUserDTO? = null,
/** 父级评论作者标识 */
var parentUserId: String? = null,
/** 目标资源类型 */
var targetType: String? = null,
/** 目标资源作者标识 */
var targetUserId: String? = null,
/** 租户唯一标识 */
var tenantId: String? = null,
/** 关联标示 */
var relationKid: String? = null,
var behaviorResult: UserBehaviorResult? = null
):Serializable
//生成的api api 方法示例
object ProvidePlatformBehaviorApiServer {
fun provideCommentInfoServer(): PlatformBehaviorApiServer.CommentInfoServer =
RetrofitManage.instance.createService(PlatformBehaviorApiServer.CommentInfoServer::class.java)
}
最后,就是在项目中愉快的使用了。
最后贴上 flutter api 生成的dart 版地址:juejin.cn/post/686485…