The Android API automatically generates the Plugin based on Android Gradle, as well as Maven, the interface based on Swagger, which generates code for the Kotlin programming language. The generated netnol interface uses OKhttp+ RetroFIT

An API automatically generates Gradle plug-ins

Plug-in Project Directory

1.1 Gradle File Configuration

apply plugin: 'groovy' apply plugin: 'maven' dependencies { implementation gradleApi() implementation localGroovy() } repositories { mavenCentral() } group = UploadArchives {repository. com.yryz.plugin' version = '1.0.0' archivesBaseName = 'module-api-plugin' uploadArchives {repositories { mavenDeployer { repository(url: uri('.. / module_API_repository '))// Local repository maven configuration}}}Copy the code

1.2 configure ApiModulePlugin META – IN the corresponding com. Yryz. Plugin. The properties files

implementation-class=com.yryz.plugin.ApiModulePlugin
Copy the code

1.3 Custom Plug-ins: ApiModulePlugin is a plug-in entry file that inherits Gradle Plugin and defines an apiTask Gradle Task by itself

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)
    }
}
Copy the code

1.4 Custom Task: ApiModuleTask is a custom Task that inherits 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 start execution ") 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 error ${e.message}" Println (message)}}} // Parse the configuration file to get the API service address needed to generate, and other extension information, Private Object readerConfig() {def file = new file ("./ apiconfig.json ") def jsonSlurper = new JsonSlurper() return jsonSlurper.parse(file) } }Copy the code

1.5 THE logic of API generation is to parse JSON and generate corresponding API files and entity files.

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)}"
    }

}
Copy the code

Corresponding entity

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
}
Copy the code

1.6 Upload only local Mevan, click uploadArchives command, the plug-in automatically upload to local Meven

The application of plug-in in engineering

2.1 Configuring Projiect build.gradle. For build.gradle, you need to configure the local meven path.

Buildscript {repositories {maven {url uri('./ module_API_repository ') // Configure local meven} Google () jcenter()} dependencies {classpath 'com.yryz.plugin:module-api-plugin:1.0.0'// config plugin}}Copy the code

2.2 Use of plug-ins in local API Modules

2.2.1 Module_API build.gradle file Configuration

apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'com.yryz.plugin'// Import API automatically generated pluginCopy the code

2.2.2 apiConfig. Json configuration file, baseUrl+serviceName+version in the configuration is the complete URL to obtain Swagger API entity interface

{"serviceName": ["platform-behavior",// API under microservice to be generated], "serializable": [" NewsInfoVO ", / / generated entity needs to serialization, inherit the serializable], "custom" : {" instance ": {" vo" : "returns the entity", "params" : "request parameter"}}, "API" : {"baseUrl": "swagger base URL https://xxx.xxx.xxx", "version": "service version v4/xx"}}Copy the code

2.2.3 Using the API TAK Run the apiTask command to generate the corresponding API file

2.2.4 Examples of generated files

/ / generated API server example interface PlatformBehaviorApiServer {interface CommentInfoServer {/ * * * query resources preview comment (home) / / * * * * @query schemeType:String (optional) @query size:Int (optional) @query targetKid:List<String> (mandatory) @query targetType:String (mandatory) **/ @get (" url") fun searchPreview(@querymap params: MutableMap<String, Any>): Observable<BaseModel<Map<String,PageList<CommentContentDTO>>>> /** * Query your comments **/ ** @body dto:CommentQueryDTO (mandatory) **/ @post (" interface URL ") Fun searchUser(@body dto: CommentQueryDTO): Observable<BaseModel<PageList<CommentContentDTO>> /** * Delete comments (user) **/ /** @path commentKid:String (mandatory) **/ @http (method =) "DELETE", path = "interface URL /{commentKid}", hasBody = true) fun DELETE (@path ("commentKid") commentKid: String): Observable<BaseModel<Long>>}} data class CommentContentDTO(/** parent user object */ var parent: CommentUserDTO? = null, /** createUserId: String? = null, /** subs: List<CommentContentDTO>? = null, /** root comment identifier */ /** Parameter format: int64 */ var rootKid: Long? = null, /** comment unique identifier */ /** parameter format: int64 */ var kid: Long? = null, /** delete flag */ /** Parameter format: int32 */ var delFlag: Int? Int64 */ var hotValue: Long? = null, /** comments */ var content: String? = null, /** auditFlag (0 no audit required/audit passed, 1 to be audited, 2 audit rejected) */ /** parameter format: int32 */ var auditFlag: Int? = null, var statisticResult: StatisticResult? = null, /** target resource unique identifier */ var targetKid: String? = null, /** Comment types (1 normal comment, 2 sock comment, 3 Flood (bot) comment */ var commentType: String? = null, /** total number of subcomments */ /** Parameter format: int64 */ var subTotalCount: Long? = null, /** createUserType (1 normal user, 2 fake user, 3 robot user) */ var createUserType: String? = null, /** Parent comment identifier */ /** Parameter format: int64 */ var parentKid: Long? = null, /** create time */ /** Parameter format: date-time */ var createDate: String? = null, /** current user object/var creator: CommentUserDTO? = null, /** Comment author user type (1 normal user, 2 puppet user, 3 robot user) */ var parentUserType: String? = null, /** Merchant association information object */ var relationalWithMerchant: CommentUserDTO? = null, /** Parent comment author */ var parentUserId: String? = null, /** target resource type */ var targetType: String? = null, /** target resource author */ var targetUserId: String? = null, /** Tenant unique identifier */ var tenantId: String? = null, /** relationKid: String? = null, var behaviorResult: UserBehaviorResult? = null) : the Serializable / / generated API API method sample object ProvidePlatformBehaviorApiServer {fun provideCommentInfoServer () : PlatformBehaviorApiServer.CommentInfoServer = RetrofitManage.instance.createService(PlatformBehaviorApiServer.CommentInfoServer::class.java) }Copy the code

Finally, it’s time for pleasant use in projects.

Finally, post the dart version of the Flutter API: juejin.cn/post/686485…