commit:同步2.0版本(添加指定审批人功能)

This commit is contained in:
Jerry
2021-10-22 09:50:54 +08:00
parent 3be97f79c0
commit 45de390cc2
321 changed files with 19171 additions and 40 deletions

View File

@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="web" name="Web">
<configuration>
<webroots />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations" isTestSource="false" generated="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="common-core" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.11.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.45" level="project" />
<orderEntry type="library" name="Maven: org.glassfish:jakarta.el:3.0.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.45" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:29.0-jre" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:failureaccess:1.0.1" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:3.0.2" level="project" />
<orderEntry type="library" name="Maven: org.checkerframework:checker-qual:2.11.1" level="project" />
<orderEntry type="library" name="Maven: com.google.errorprone:error_prone_annotations:2.3.4" level="project" />
<orderEntry type="library" name="Maven: com.google.j2objc:j2objc-annotations:1.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.10" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.14" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.6" level="project" />
<orderEntry type="library" name="Maven: commons-fileupload:commons-fileupload:1.3.3" level="project" />
<orderEntry type="library" name="Maven: joda-time:joda-time:2.9.9" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-collections4:4.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-csv:1.8" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.6.4" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.76" level="project" />
<orderEntry type="library" name="Maven: com.github.ben-manes.caffeine:caffeine:2.8.8" level="project" />
<orderEntry type="library" name="Maven: cn.jimmyshi:bean-query:1.1.5" level="project" />
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-all:1.3" level="project" />
<orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.9.3" level="project" />
<orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jcl-over-slf4j:1.7.30" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi-ooxml:3.17" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi:3.17" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi-ooxml-schemas:3.17" level="project" />
<orderEntry type="library" name="Maven: org.apache.xmlbeans:xmlbeans:2.6.0" level="project" />
<orderEntry type="library" name="Maven: stax:stax-api:1.0.1" level="project" />
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.04" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: mysql:mysql-connector-java:8.0.23" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid-spring-boot-starter:1.2.6" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid:1.2.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.baomidou:mybatis-plus-boot-starter:3.4.2" level="project" />
<orderEntry type="library" name="Maven: com.baomidou:mybatis-plus:3.4.2" level="project" />
<orderEntry type="library" name="Maven: com.baomidou:mybatis-plus-extension:3.4.2" level="project" />
<orderEntry type="library" name="Maven: com.baomidou:mybatis-plus-core:3.4.2" level="project" />
<orderEntry type="library" name="Maven: com.baomidou:mybatis-plus-annotation:3.4.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:3.4.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.3.0" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis:3.5.5" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis-spring:2.0.5" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-autoconfigure:1.3.0" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper:5.2.0" level="project" />
<orderEntry type="library" name="Maven: com.github.jsqlparser:jsqlparser:3.2" level="project" />
<orderEntry type="library" name="Maven: com.thoughtworks.qdox:qdox:2.0.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-freemarker:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.26" level="project" />
<orderEntry type="library" name="Maven: org.freemarker:freemarker:2.3.31" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-log4j2:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-slf4j-impl:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-core:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-jul:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.30" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.9.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-cache:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-configuration-processor:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-actuator:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-actuator-autoconfigure:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-actuator:2.3.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.4" level="project" />
<orderEntry type="library" name="Maven: io.micrometer:micrometer-core:1.5.13" level="project" />
<orderEntry type="library" name="Maven: org.hdrhistogram:HdrHistogram:2.1.12" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.latencyutils:LatencyUtils:2.0.3" level="project" />
<orderEntry type="library" name="Maven: de.codecentric:spring-boot-admin-starter-client:2.3.1" level="project" />
<orderEntry type="library" name="Maven: de.codecentric:spring-boot-admin-client:2.3.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.spring:spring-context-support:1.0.10" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.nacos:nacos-client:1.4.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.nacos:nacos-common:1.4.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpasyncclient:4.1.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore-nio:4.4.14" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.nacos:nacos-api:1.4.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.11.4" level="project" />
<orderEntry type="library" name="Maven: io.prometheus:simpleclient:0.5.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-commons:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.3.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-context:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-starter-openfeign:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-starter:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-rsa:1.0.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.bouncycastle:bcpkix-jdk15on:1.59" level="project" />
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-jdk15on:1.59" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-openfeign-core:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: io.github.openfeign.form:feign-form-spring:3.8.0" level="project" />
<orderEntry type="library" name="Maven: io.github.openfeign.form:feign-form:3.8.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: io.github.openfeign:feign-core:10.10.1" level="project" />
<orderEntry type="library" name="Maven: io.github.openfeign:feign-slf4j:10.10.1" level="project" />
<orderEntry type="library" name="Maven: io.github.openfeign:feign-hystrix:10.10.1" level="project" />
<orderEntry type="library" name="Maven: com.netflix.archaius:archaius-core:0.7.6" level="project" />
<orderEntry type="library" name="Maven: io.github.openfeign:feign-httpclient:10.10.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.13" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.14" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-starter-netflix-hystrix:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-netflix-hystrix:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-netflix-ribbon:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-netflix-archaius:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.cloud:spring-cloud-starter-netflix-archaius:2.2.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: commons-configuration:commons-configuration:1.8" level="project" />
<orderEntry type="library" name="Maven: commons-lang:commons-lang:2.6" level="project" />
<orderEntry type="library" name="Maven: com.netflix.hystrix:hystrix-core:1.5.18" level="project" />
<orderEntry type="library" name="Maven: io.reactivex:rxjava:1.3.8" level="project" />
<orderEntry type="library" name="Maven: com.netflix.hystrix:hystrix-serialization:1.5.18" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: com.fasterxml.jackson.module:jackson-module-afterburner:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.netflix.hystrix:hystrix-metrics-event-stream:1.5.18" level="project" />
<orderEntry type="library" name="Maven: com.netflix.hystrix:hystrix-javanica:1.5.18" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
<orderEntry type="library" name="Maven: io.reactivex:rxjava-reactive-streams:1.2.1" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.reactivestreams:reactive-streams:1.0.3" level="project" />
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.2.0.Final" level="project" />
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.1.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.mapstruct:mapstruct:1.4.2.Final" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.mapstruct:mapstruct-processor:1.4.2.Final" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.18.20" level="project" />
<orderEntry type="library" name="Maven: org.apache.curator:curator-recipes:4.3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.curator:curator-framework:4.0.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.curator:curator-client:4.0.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.zookeeper:zookeeper:3.5.3-beta" level="project" />
<orderEntry type="library" name="Maven: commons-cli:commons-cli:1.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.kafka:kafka-clients:2.4.0" level="project" />
<orderEntry type="library" name="Maven: com.github.luben:zstd-jni:1.4.3-1" level="project" />
<orderEntry type="library" name="Maven: org.lz4:lz4-java:1.6.0" level="project" />
<orderEntry type="library" name="Maven: org.xerial.snappy:snappy-java:1.1.7.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" name="Maven: org.scala-lang:scala-library:2.12.10" level="project" />
<orderEntry type="library" name="Maven: com.lmax:disruptor:3.4.3" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-metadata:2.0.0.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.3.10.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.3.10.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.3.10.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: jakarta.xml.bind:jakarta.xml.bind-api:2.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: jakarta.activation:jakarta.activation-api:1.2.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.16.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest:2.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter:5.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-params:5.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.vintage:junit-vintage-engine:5.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.6.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.13.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:3.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.10.22" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.10.22" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-junit-jupiter:3.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.2.14.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.2.14.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.2.14.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.7.0" level="project" />
</component>
</module>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>framework</artifactId>
<groupId>com.orange.demo</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apidoc-tools</artifactId>
<version>1.0.0</version>
<name>apidoc-tools</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.orange.demo</groupId>
<artifactId>common-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.qdox</groupId>
<artifactId>qdox</artifactId>
<version>${qdox.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,28 @@
package com.orange.demo.apidoc.tools;
import com.alibaba.fastjson.JSON;
import com.orange.demo.apidoc.tools.codeparser.ApiCodeConfig;
import com.orange.demo.apidoc.tools.codeparser.ApiCodeParser;
import com.orange.demo.apidoc.tools.export.ApiPostmanExporter;
import freemarker.template.TemplateException;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class ExportApiApp {
public static void main(String[] args) throws IOException, TemplateException {
// 在第一次导出时需要打开export-api-config.json配置文件
// 修改其中的工程根目录配置项(projectRootPath),其他配置保持不变即可。
InputStream in = ExportApiApp.class.getResourceAsStream("/export-api-config.json");
String jsonData = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
ApiCodeConfig apiCodeConfig = JSON.parseObject(jsonData, ApiCodeConfig.class);
ApiCodeParser apiCodeParser = new ApiCodeParser(apiCodeConfig);
ApiCodeParser.ApiProject project = apiCodeParser.doParse();
ApiPostmanExporter exporter = new ApiPostmanExporter();
// 将下面的目录改为实际输出目录。
exporter.doGenerate(project, "/xxx/Desktop/1.json");
}
}

View File

@@ -0,0 +1,28 @@
package com.orange.demo.apidoc.tools;
import com.alibaba.fastjson.JSON;
import com.orange.demo.apidoc.tools.codeparser.ApiCodeConfig;
import com.orange.demo.apidoc.tools.codeparser.ApiCodeParser;
import com.orange.demo.apidoc.tools.export.ApiDocExporter;
import freemarker.template.TemplateException;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class ExportDocApp {
public static void main(String[] args) throws IOException, TemplateException {
// 在第一次导出时需要打开export-api-config.json配置文件
// 修改其中的工程根目录配置项(projectRootPath),其他配置保持不变即可。
InputStream in = ExportDocApp.class.getResourceAsStream("/export-api-config.json");
String jsonData = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
ApiCodeConfig apiCodeConfig = JSON.parseObject(jsonData, ApiCodeConfig.class);
ApiCodeParser apiCodeParser = new ApiCodeParser(apiCodeConfig);
ApiCodeParser.ApiProject project = apiCodeParser.doParse();
ApiDocExporter exporter = new ApiDocExporter();
// 将下面的目录改为实际输出目录。
exporter.doGenerate(project, "/xxx/Desktop/2.md");
}
}

View File

@@ -0,0 +1,83 @@
package com.orange.demo.apidoc.tools.codeparser;
import lombok.Data;
import java.util.List;
import java.util.Set;
/**
* 解析项目中接口信息的配置对象。
*
* @author Jerry
* @date 2020-08-08
*/
@Data
public class ApiCodeConfig {
/**
* 项目名称。
*/
private String projectName;
/**
* 项目的基础包名,如(com.demo.multi)。
*/
private String basePackage;
/**
* 项目在本地文件系统中的根目录。这里需要注意的是Windows用户请务必使用反斜杠作为目录分隔符。
* 如:"e:/mypath/OrangeSingleDemo""/Users/xxx/OrangeSingleDemo"。
*/
private String projectRootPath;
/**
* 是否为微服务项目。
*/
private Boolean microService;
/**
* 服务配置列表。对于单体服务至少也会有一个ServiceConfig对象。
*/
private List<ServiceConfig> serviceList;
@Data
public static class ServiceConfig {
/**
* 服务名称。
*/
private String serviceName;
/**
* 服务中文显示名称。
*/
private String showName;
/**
* 服务所在目录,相对于工程目录的子目录。
*/
private String servicePath;
/**
* 仅用于微服务工程。通常为服务路由路径,如:/admin/coursepaper。服务内的接口都会加上该路径前缀。
*/
private String serviceRequestPath;
/**
* 服务的端口号。
*/
private String port;
/**
* Api Controller信息列表。
*/
private List<ControllerInfo> controllerInfoList;
}
@Data
public static class ControllerInfo {
/**
* Controller.java等接口文件的所在目录。该目录仅为相对于服务代码目录的子目录。
* 目录分隔符请务必使用反斜杠。如:"/com/orange/demo/app/controller"。
*/
private String path;
/**
* 如果一个服务内存在多个Controller目录将再次生成二级子目录目录名为groupName。(可使用中文)
*/
private String groupName;
/**
* 在当前Controller目录下需要忽略的Controller列表 (只写类名即可)。如LoginController。
*/
private Set<String> skipControllers;
}
}

View File

@@ -0,0 +1,672 @@
package com.orange.demo.apidoc.tools.codeparser;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.orange.demo.common.core.object.Tuple2;
import com.orange.demo.apidoc.tools.exception.ApiCodeConfigParseException;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.*;
import com.thoughtworks.qdox.model.impl.DefaultJavaParameterizedType;
import lombok.Data;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* 解析项目中的接口信息以及关联的Model、Dto和Mapper主要用于生成接口文档。
*
* @author Jerry
* @date 2020-08-08
*/
public class ApiCodeParser {
private static final String PATH_SEPERATOR = "/";
private static final String REQUEST_MAPPING = "RequestMapping";
private static final String FULL_REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping";
private static final String GET_MAPPING = "GetMapping";
private static final String FULL_GET_MAPPING = "org.springframework.web.bind.annotation.GetMapping";
private static final String POST_MAPPING = "PostMapping";
private static final String FULL_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping";
private static final String VALUE_PROP = "value";
private static final String REQUIRED_PROP = "required";
private static final String DELETED_COLUMN = "DeletedFlagColumn";
/**
* 忽略微服务间标准调用接口的导出。
*/
private static final Set<String> IGNORED_API_METHOD_SET = new HashSet<>(8);
static {
IGNORED_API_METHOD_SET.add("listByIds");
IGNORED_API_METHOD_SET.add("getById");
IGNORED_API_METHOD_SET.add("existIds");
IGNORED_API_METHOD_SET.add("existId");
IGNORED_API_METHOD_SET.add("deleteById");
IGNORED_API_METHOD_SET.add("deleteBy");
IGNORED_API_METHOD_SET.add("listBy");
IGNORED_API_METHOD_SET.add("listMapBy");
IGNORED_API_METHOD_SET.add("listByNotInList");
IGNORED_API_METHOD_SET.add("getBy");
IGNORED_API_METHOD_SET.add("countBy");
IGNORED_API_METHOD_SET.add("aggregateBy");
}
/**
* 基础配置。
*/
private ApiCodeConfig config;
/**
* 工程对象。
*/
private ApiProject apiProject;
/**
* 项目中所有的解析后Java文件key是Java对象的全名com.orange.demo.xxxx.Student。
*/
private final Map<String, JavaClass> projectJavaClassMap = new HashMap<>(128);
/**
* 存储服务数据。key为配置的serviceName。
*/
private final Map<String, InternalServiceData> serviceDataMap = new HashMap<>(8);
/**
* 构造函数。
*
* @param config 配置对象。
*/
public ApiCodeParser(ApiCodeConfig config) {
this.config = config;
// 验证配置中的数据是否正确,出现错误直接抛出运行时异常。
this.verifyConfigData();
// 将配置文件中所有目录相关的参数,全部规格化处理,后续的使用中不用再做处理了。
this.normalizeConfigPath();
for (ApiCodeConfig.ServiceConfig serviceConfig : config.getServiceList()) {
InternalServiceData serviceData = new InternalServiceData();
// 仅有微服务项目,需要添加服务路由路径。
if (StrUtil.isNotBlank(serviceConfig.getServiceRequestPath())) {
String serviceRequestPath = "";
if (!serviceRequestPath.equals(PATH_SEPERATOR)) {
serviceRequestPath = normalizePath(serviceConfig.getServiceRequestPath());
}
serviceData.setServiceRequestPath(serviceRequestPath);
}
serviceDataMap.put(serviceConfig.getServiceName(), serviceData);
}
}
/**
* 执行解析操作。
*
* @return 解析后的工程对象。
*/
public ApiProject doParse() throws IOException {
// 先把工程完整编译一遍以便工程内的Java对象的引用信息更加完整。
this.parseProject();
// 开始逐级推演。
apiProject = new ApiProject();
apiProject.setProjectName(config.getProjectName());
apiProject.setMicroService(config.getMicroService());
apiProject.setServiceList(new LinkedList<>());
for (ApiCodeConfig.ServiceConfig serviceConfig : config.getServiceList()) {
ApiService apiService = this.parseService(serviceConfig);
apiProject.getServiceList().add(apiService);
}
return apiProject;
}
private void parseProject() throws IOException {
JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();
javaProjectBuilder.setEncoding(StandardCharsets.UTF_8.name());
javaProjectBuilder.addSourceTree(new File(config.getProjectRootPath()));
// 全部导入,便于后续解析中使用和检索。
for (JavaClass javaClass : javaProjectBuilder.getClasses()) {
projectJavaClassMap.put(javaClass.getFullyQualifiedName(), javaClass);
}
}
private ApiService parseService(ApiCodeConfig.ServiceConfig serviceConfig) {
InternalServiceData serviceData = serviceDataMap.get(serviceConfig.getServiceName());
ApiService apiService = new ApiService();
apiService.setServiceName(serviceConfig.getServiceName());
apiService.setShowName(serviceConfig.getShowName());
apiService.setPort(serviceConfig.getPort());
List<ApiCodeConfig.ControllerInfo> controllerInfoList = serviceConfig.getControllerInfoList();
// 准备解析接口文件
for (ApiCodeConfig.ControllerInfo controllerInfo : controllerInfoList) {
JavaProjectBuilder javaControllerBuilder = new JavaProjectBuilder();
javaControllerBuilder.addSourceTree(new File(controllerInfo.getPath()));
for (JavaClass javaClass : javaControllerBuilder.getClasses()) {
if (controllerInfo.getSkipControllers() != null
&& controllerInfo.getSkipControllers().contains(javaClass.getName())) {
continue;
}
ApiClass apiClass = this.parseApiClass(controllerInfo, javaClass.getFullyQualifiedName(), serviceData);
if (apiClass != null) {
// 如果配置中为当前ControllerInfo添加了groupName属性
// 所有的生成后接口都会位于serviceName/groupName子目录否则都直接位于当前服务的子目录。
if (StrUtil.isBlank(apiClass.getGroupName())) {
apiService.getDefaultGroupClassSet().add(apiClass);
} else {
Set<ApiClass> groupedClassList = apiService.getGroupedClassMap()
.computeIfAbsent(apiClass.getGroupName(), k -> new TreeSet<>());
groupedClassList.add(apiClass);
}
}
}
}
return apiService;
}
private ApiClass parseApiClass(
ApiCodeConfig.ControllerInfo controllerInfo,
String classFullname,
InternalServiceData serviceData) {
// 去包含工程全部Class的Map中找到当前ControllerClass。
// 之所以这样做主要是因为全工程分析controller文件会包含更多更精确的对象关联信息。
JavaClass controllerClass = this.projectJavaClassMap.get(classFullname);
List<JavaAnnotation> classAnnotations = controllerClass.getAnnotations();
boolean hasControllerAnnotation = false;
String requestPath = "";
for (JavaAnnotation annotation : classAnnotations) {
String annotationName = annotation.getType().getValue();
if (this.isRequestMapping(annotationName) && annotation.getNamedParameter(VALUE_PROP) != null) {
requestPath = StrUtil.removeAll(
annotation.getNamedParameter(VALUE_PROP).toString(), "\"");
if (requestPath.equals(PATH_SEPERATOR) || StrUtil.isBlank(requestPath)) {
requestPath = "";
} else {
requestPath = normalizePath(requestPath);
}
}
if (isController(annotationName)) {
hasControllerAnnotation = true;
}
}
if (!hasControllerAnnotation) {
return null;
}
requestPath = serviceData.getServiceRequestPath() + requestPath;
ApiClass apiClass = new ApiClass();
apiClass.setName(controllerClass.getName());
apiClass.setFullName(controllerClass.getFullyQualifiedName());
apiClass.setComment(controllerClass.getComment());
apiClass.setGroupName(controllerInfo.getGroupName());
apiClass.setRequestPath(requestPath);
List<ApiMethod> methodList = this.parseApiMethodList(apiClass, controllerClass);
apiClass.setMethodList(methodList);
return apiClass;
}
private boolean needToIgnore(JavaMethod method) {
return !method.isPublic() || method.isStatic() || IGNORED_API_METHOD_SET.contains(method.getName());
}
private List<ApiMethod> parseApiMethodList(ApiClass apiClass, JavaClass javaClass) {
List<ApiMethod> apiMethodList = new LinkedList<>();
List<JavaMethod> methodList = javaClass.getMethods();
for (JavaMethod method : methodList) {
if (this.needToIgnore(method)) {
continue;
}
List<JavaAnnotation> methodAnnotations = method.getAnnotations();
Tuple2<String, String> result = this.parseRequestPathAndHttpMethod(methodAnnotations);
String methodRequestPath = result.getFirst();
String httpMethod = result.getSecond();
if (StrUtil.isNotBlank(methodRequestPath)) {
ApiMethod apiMethod = new ApiMethod();
apiMethod.setName(method.getName());
apiMethod.setComment(method.getComment());
apiMethod.setHttpMethod(httpMethod);
methodRequestPath = StrUtil.removeAll(methodRequestPath, "\"");
methodRequestPath = apiClass.getRequestPath() + normalizePath(methodRequestPath);
apiMethod.setRequestPath(methodRequestPath);
apiMethod.setPathList(StrUtil.splitTrim(apiMethod.getRequestPath(), PATH_SEPERATOR));
if (apiMethod.getRequestPath().contains("/listDict")) {
apiMethod.setListDictUrl(true);
} else if (apiMethod.getRequestPath().endsWith("/list")
|| apiMethod.getRequestPath().endsWith("/listWithGroup")
|| apiMethod.getRequestPath().contains("/listNotIn")
|| apiMethod.getRequestPath().contains("/list")) {
apiMethod.setListUrl(true);
} else if (apiMethod.getRequestPath().contains("/doLogin")) {
apiMethod.setLoginUrl(true);
}
JavaClass returnClass = method.getReturns();
if (returnClass.isVoid()) {
apiMethod.setReturnString("void");
} else {
apiMethod.setReturnString(returnClass.getGenericValue());
}
apiMethodList.add(apiMethod);
List<ApiArgument> apiArgumentList = this.parseApiMethodArgumentList(method);
apiMethod.setArgumentList(apiArgumentList);
this.classifyArgumentList(apiMethod, apiArgumentList);
}
}
return apiMethodList;
}
private void classifyArgumentList(ApiMethod apiMethod, List<ApiArgument> apiArgumentList) {
for (ApiArgument arg : apiArgumentList) {
if (arg.getAnnotationType() == ApiArgumentAnnotationType.REQUEST_PARAM) {
if (arg.uploadFileParam) {
apiMethod.getUploadParamArgumentList().add(arg);
} else {
apiMethod.getQueryParamArgumentList().add(arg);
}
}
if (arg.getAnnotationType() != ApiArgumentAnnotationType.REQUEST_PARAM) {
apiMethod.getJsonParamArgumentList().add(arg);
}
}
}
private Tuple2<String, String> parseRequestPathAndHttpMethod(List<JavaAnnotation> methodAnnotations) {
for (JavaAnnotation annotation : methodAnnotations) {
String annotationName = annotation.getType().getValue();
if (GET_MAPPING.equals(annotationName) || FULL_GET_MAPPING.equals(annotationName)) {
String methodRequestPath = annotation.getNamedParameter(VALUE_PROP).toString();
String httpMethod = "GET";
return new Tuple2<>(methodRequestPath, httpMethod);
}
if (POST_MAPPING.equals(annotationName) || FULL_POST_MAPPING.equals(annotationName)) {
String methodRequestPath = annotation.getNamedParameter(VALUE_PROP).toString();
String httpMethod = "POST";
return new Tuple2<>(methodRequestPath, httpMethod);
}
}
return new Tuple2<>(null, null);
}
private List<ApiArgument> parseApiMethodArgumentList(JavaMethod javaMethod) {
List<ApiArgument> apiArgumentList = new LinkedList<>();
List<JavaParameter> parameterList = javaMethod.getParameters();
if (CollUtil.isEmpty(parameterList)) {
return apiArgumentList;
}
for (JavaParameter parameter : parameterList) {
String typeName = parameter.getType().getValue();
// 该类型的参数为Validator的验证结果对象因此忽略。
if ("BindingResult".equals(typeName) || this.isServletArgument(typeName)) {
continue;
}
ApiArgument apiArgument = this.parseApiMethodArgument(parameter);
apiArgumentList.add(apiArgument);
}
return apiArgumentList;
}
private String parseMethodArgmentComment(JavaParameter parameter) {
String comment = null;
JavaExecutable executable = parameter.getExecutable();
List<DocletTag> tags = executable.getTagsByName("param");
if (CollUtil.isNotEmpty(tags)) {
for (DocletTag tag : tags) {
if (tag.getValue().startsWith(parameter.getName())) {
comment = StrUtil.removePrefix(tag.getValue(), parameter.getName()).trim();
break;
}
}
}
return comment;
}
private ApiArgument parseApiMethodArgument(JavaParameter parameter) {
String typeName = parameter.getType().getValue();
ApiArgument apiArgument = new ApiArgument();
ApiArgumentAnnotation argumentAnnotation =
this.parseArgumentAnnotationTypeAndName(parameter.getAnnotations(), parameter.getName());
apiArgument.setAnnotationType(argumentAnnotation.getType());
apiArgument.setName(argumentAnnotation.getName());
apiArgument.setTypeName(typeName);
apiArgument.setFullTypeName(parameter.getFullyQualifiedName());
if (argumentAnnotation.getType() == ApiArgumentAnnotationType.REQUEST_PARAM) {
apiArgument.setRequired(argumentAnnotation.isRequired());
}
String comment = parseMethodArgmentComment(parameter);
apiArgument.setComment(comment);
// 文件上传字段,是必填参数。
if ("MultipartFile".equals(typeName)) {
apiArgument.setUploadFileParam(true);
apiArgument.setRequired(true);
return apiArgument;
}
// 对于内置类型,则无需继续处理了。所有和内置类型参数相关的处理,应该在之前完成。
if (this.verifyAndSetBuiltinParam(apiArgument, typeName)) {
return apiArgument;
}
// 判断是否为集合类型的参数。
if (this.isCollectionType(typeName)) {
apiArgument.setCollectionParam(true);
if (parameter.getType() instanceof DefaultJavaParameterizedType) {
DefaultJavaParameterizedType javaType = (DefaultJavaParameterizedType) parameter.getType();
JavaType genericType = javaType.getActualTypeArguments().get(0);
ApiModel apiModel = this.buildApiModelForArgument(genericType.getFullyQualifiedName());
apiArgument.setModelData(apiModel);
apiArgument.setFullTypeName(parameter.getGenericFullyQualifiedName());
apiArgument.setTypeName(parameter.getGenericValue());
}
} else {
ApiModel apiModel = this.buildApiModelForArgument(parameter.getFullyQualifiedName());
apiArgument.setModelData(apiModel);
}
return apiArgument;
}
private boolean verifyAndSetBuiltinParam(ApiArgument apiArgument, String typeName) {
if ("MyOrderParam".equals(typeName)) {
apiArgument.setOrderParam(true);
} else if ("MyPageParam".equals(typeName)) {
apiArgument.setPageParam(true);
} else if ("MyGroupParam".equals(typeName)) {
apiArgument.setGroupParam(true);
} else if ("MyQueryParam".equals(typeName)) {
apiArgument.setQueryParam(true);
} else if ("MyAggregationParam".equals(typeName)) {
apiArgument.setAggregationParam(true);
}
return apiArgument.isOrderParam()
|| apiArgument.isPageParam()
|| apiArgument.isGroupParam()
|| apiArgument.isQueryParam()
|| apiArgument.isAggregationParam();
}
private ApiArgumentAnnotation parseArgumentAnnotationTypeAndName(
List<JavaAnnotation> annotationList, String defaultName) {
ApiArgumentAnnotation argumentAnnotation = new ApiArgumentAnnotation();
argumentAnnotation.setType(ApiArgumentAnnotationType.REQUEST_PARAM);
argumentAnnotation.setName(defaultName);
for (JavaAnnotation annotation : annotationList) {
String annotationName = annotation.getType().getValue();
if ("RequestBody".equals(annotationName)) {
argumentAnnotation.setType(ApiArgumentAnnotationType.REQUEST_BODY);
return argumentAnnotation;
} else if ("MyRequestBody".equals(annotationName)) {
String annotationValue = this.getArgumentNameFromAnnotationValue(annotation, VALUE_PROP);
argumentAnnotation.setType(ApiArgumentAnnotationType.MY_REQUEST_BODY);
argumentAnnotation.setName(annotationValue != null ? annotationValue : defaultName);
return argumentAnnotation;
} else if ("RequestParam".equals(annotationName)) {
String annotationValue = this.getArgumentNameFromAnnotationValue(annotation, VALUE_PROP);
argumentAnnotation.setType(ApiArgumentAnnotationType.REQUEST_PARAM);
argumentAnnotation.setName(annotationValue != null ? annotationValue : defaultName);
String requiredValue = this.getArgumentNameFromAnnotationValue(annotation, REQUIRED_PROP);
if (StrUtil.isNotBlank(requiredValue)) {
argumentAnnotation.setRequired(Boolean.parseBoolean(requiredValue));
}
return argumentAnnotation;
}
}
// 缺省为@RequestParam
return argumentAnnotation;
}
private String getArgumentNameFromAnnotationValue(JavaAnnotation annotation, String attribute) {
Object value = annotation.getNamedParameter(attribute);
if (value == null) {
return null;
}
String paramAlias = value.toString();
if (StrUtil.isNotBlank(paramAlias)) {
paramAlias = StrUtil.removeAll(paramAlias, "\"");
}
return paramAlias;
}
private ApiModel buildApiModelForArgument(String fullJavaClassName) {
// 先从当前服务内的Model中找如果参数是Model类型的对象微服务和单体行为一致。
ApiModel apiModel = apiProject.getFullNameModelMap().get(fullJavaClassName);
if (apiModel != null) {
return apiModel;
}
// 判断工程全局对象映射中是否包括该对象类型,如果不包含,就直接返回了。
JavaClass modelClass = projectJavaClassMap.get(fullJavaClassName);
if (modelClass == null) {
return apiModel;
}
// 先行解析对象中的字段。
apiModel = parseModel(modelClass);
apiProject.getFullNameModelMap().put(fullJavaClassName, apiModel);
return apiModel;
}
private ApiModel parseModel(JavaClass javaClass) {
ApiModel apiModel = new ApiModel();
apiModel.setName(javaClass.getName());
apiModel.setFullName(javaClass.getFullyQualifiedName());
apiModel.setComment(javaClass.getComment());
apiModel.setFieldList(new LinkedList<>());
List<JavaField> fieldList = javaClass.getFields();
for (JavaField field : fieldList) {
if (field.isStatic()) {
continue;
}
ApiField apiField = new ApiField();
apiField.setName(field.getName());
apiField.setComment(field.getComment());
apiField.setTypeName(field.getType().getSimpleName());
apiModel.getFieldList().add(apiField);
}
return apiModel;
}
private void verifyConfigData() {
if (StrUtil.isBlank(config.getProjectName())) {
throw new ApiCodeConfigParseException("ProjectName field can't be EMPTY.");
}
if (StrUtil.isBlank(config.getBasePackage())) {
throw new ApiCodeConfigParseException("BasePackage field can't be EMPTY.");
}
if (StrUtil.isBlank(config.getProjectRootPath())) {
throw new ApiCodeConfigParseException("ProjectRootPath field can't be EMPTY.");
}
if (!FileUtil.exist(config.getProjectRootPath())) {
throw new ApiCodeConfigParseException(
"ProjectRootPath doesn't exist, please check ./resources/export-api-config.json as DEFAULT.");
}
if (config.getMicroService() == null) {
throw new ApiCodeConfigParseException("MicroService field can't be NULL.");
}
if (CollUtil.isEmpty(config.getServiceList())) {
throw new ApiCodeConfigParseException("ServiceList field can't be EMPTY.");
}
this.verifyServiceConfig(config.getServiceList());
}
private void verifyServiceConfig(List<ApiCodeConfig.ServiceConfig> serviceConfigList) {
Set<String> serviceNameSet = new HashSet<>(8);
Set<String> servicePathSet = new HashSet<>(8);
for (ApiCodeConfig.ServiceConfig serviceConfig : serviceConfigList) {
if (StrUtil.isBlank(serviceConfig.getServiceName())) {
throw new ApiCodeConfigParseException("One of the ServiceName Field in Services List is NULL.");
}
String serviceName = serviceConfig.getServiceName();
if (StrUtil.isBlank(serviceConfig.getServicePath())) {
throw new ApiCodeConfigParseException(
"The ServicePath Field in Service [" + serviceName + "] is NULL.");
}
if (serviceNameSet.contains(serviceName)) {
throw new ApiCodeConfigParseException("The ServiceName [" + serviceName + "] is duplicated.");
}
serviceNameSet.add(serviceName);
if (servicePathSet.contains(serviceConfig.getServicePath())) {
throw new ApiCodeConfigParseException(
"The ServicePath [" + serviceConfig.getServicePath() + "] is duplicated.");
}
servicePathSet.add(serviceConfig.getServicePath());
if (StrUtil.isBlank(serviceConfig.getPort())) {
throw new ApiCodeConfigParseException(
"The Port Field in Service [" + serviceName + "] is NULL.");
}
this.verifyServiceControllerConfig(serviceConfig.getControllerInfoList(), serviceName);
}
}
private void verifyServiceControllerConfig(
List<ApiCodeConfig.ControllerInfo> controllerInfoList, String serviceName) {
if (CollUtil.isEmpty(controllerInfoList)) {
throw new ApiCodeConfigParseException(
"The ControllerInfoList Field of Service [" + serviceName + "] is EMPTY");
}
for (ApiCodeConfig.ControllerInfo controllerInfo : controllerInfoList) {
if (StrUtil.isBlank(controllerInfo.getPath())) {
throw new ApiCodeConfigParseException(
"One of the ControllerInfo.Path Field of Service [" + serviceName + "] is EMPTY");
}
}
}
private void normalizeConfigPath() {
config.setProjectRootPath(normalizePath(config.getProjectRootPath()));
for (ApiCodeConfig.ServiceConfig serviceConfig : config.getServiceList()) {
serviceConfig.setServicePath(config.getProjectRootPath() + normalizePath(serviceConfig.getServicePath()));
for (ApiCodeConfig.ControllerInfo controllerInfo : serviceConfig.getControllerInfoList()) {
controllerInfo.setPath(serviceConfig.getServicePath() + normalizePath(controllerInfo.getPath()));
}
}
}
private String normalizePath(String path) {
if (!path.startsWith(PATH_SEPERATOR)) {
path = PATH_SEPERATOR + path;
}
return StrUtil.removeSuffix(path, PATH_SEPERATOR);
}
private boolean isCollectionType(String typeName) {
return "List".equals(typeName) || "Set".equals(typeName) || "Collection".equals(typeName);
}
private boolean isServletArgument(String typeName) {
return "HttpServletResponse".equals(typeName) || "HttpServletRequest".equals(typeName);
}
private boolean isController(String annotationName) {
return "Controller".equals(annotationName)
|| "org.springframework.stereotype.Controller".equals(annotationName)
|| "RestController".equals(annotationName)
|| "org.springframework.web.bind.annotation.RestController".equals(annotationName);
}
private boolean isRequiredColumn(String annotationName) {
return "NotNull".equals(annotationName)
|| "javax.validation.constraints.NotNull".equals(annotationName)
|| "NotBlank".equals(annotationName)
|| "javax.validation.constraints.NotBlank".equals(annotationName)
|| "NotEmpty".equals(annotationName)
|| "javax.validation.constraints.NotEmpty".equals(annotationName);
}
private boolean isRequestMapping(String name) {
return REQUEST_MAPPING.equals(name) || FULL_REQUEST_MAPPING.equals(name);
}
@Data
public static class ApiProject {
private String projectName;
private Boolean microService;
private List<ApiService> serviceList;
private Map<String, ApiModel> fullNameModelMap = new HashMap<>(32);
private Map<String, ApiModel> simpleNameModelMap = new HashMap<>(32);
}
@Data
public static class ApiService {
private String serviceName;
private String showName;
private String port;
private Set<ApiClass> defaultGroupClassSet = new TreeSet<>();
private Map<String, Set<ApiClass>> groupedClassMap = new LinkedHashMap<>();
}
@Data
public static class ApiClass implements Comparable<ApiClass> {
private String name;
private String fullName;
private String groupName;
private String comment;
private String requestPath;
private List<ApiMethod> methodList;
@Override
public int compareTo(ApiClass o) {
return this.name.compareTo(o.name);
}
}
@Data
public static class ApiMethod {
private String name;
private String comment;
private String returnString;
private String requestPath;
private String httpMethod;
private boolean listDictUrl = false;
private boolean listUrl = false;
private boolean loginUrl = false;
private List<String> pathList = new LinkedList<>();
private List<ApiArgument> argumentList;
private List<ApiArgument> queryParamArgumentList = new LinkedList<>();
private List<ApiArgument> jsonParamArgumentList = new LinkedList<>();
private List<ApiArgument> uploadParamArgumentList = new LinkedList<>();
}
@Data
public static class ApiArgument {
private String name;
private String typeName;
private String fullTypeName;
private String comment;
private Integer annotationType;
private boolean required = true;
private boolean uploadFileParam = false;
private boolean collectionParam = false;
private boolean orderParam = false;
private boolean pageParam = false;
private boolean groupParam = false;
private boolean queryParam = false;
private boolean aggregationParam = false;
private boolean jsonData = false;
private ApiModel modelData;
}
@Data
public static class ApiArgumentAnnotation {
private String name;
private Integer type;
private boolean required = true;
}
@Data
public static class ApiModel {
private String name;
private String fullName;
private String comment;
private List<ApiField> fieldList;
}
@Data
public static class ApiField {
private String name;
private String comment;
private String typeName;
private boolean requiredColumn = false;
}
public static final class ApiArgumentAnnotationType {
public static final int REQUEST_PARAM = 0;
public static final int REQUEST_BODY = 1;
public static final int MY_REQUEST_BODY = 2;
private ApiArgumentAnnotationType() {
}
}
@Data
private static class InternalServiceData {
private String serviceRequestPath = "";
}
}

View File

@@ -0,0 +1,27 @@
package com.orange.demo.apidoc.tools.exception;
/**
* 解析接口信息配置对象中的异常。
*
* @author Jerry
* @date 2020-08-08
*/
public class ApiCodeConfigParseException extends RuntimeException {
/**
* 构造函数。
*/
public ApiCodeConfigParseException() {
}
/**
* 构造函数。
*
* @param msg 错误信息。
*/
public ApiCodeConfigParseException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,27 @@
package com.orange.demo.apidoc.tools.exception;
/**
* 解析Mybatis XML Mapper中的异常。
*
* @author Jerry
* @date 2020-08-08
*/
public class MapperParseException extends RuntimeException {
/**
* 构造函数。
*/
public MapperParseException() {
}
/**
* 构造函数。
*
* @param msg 错误信息。
*/
public MapperParseException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,84 @@
package com.orange.demo.apidoc.tools.export;
import com.orange.demo.apidoc.tools.codeparser.ApiCodeParser;
import com.orange.demo.apidoc.tools.util.FreeMarkerUtils;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateModelException;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* 根据代码解析后的工程对象数据导出到Markdown格式的接口文档文件。
*
* @author Jerry
* @date 2020-08-08
*/
public class ApiDocExporter {
private final Configuration config;
public ApiDocExporter() throws TemplateModelException {
config = new Configuration(Configuration.VERSION_2_3_28);
config.setNumberFormat("0.####");
config.setClassicCompatible(true);
config.setAPIBuiltinEnabled(true);
config.setClassForTemplateLoading(ApiPostmanExporter.class, "/templates/");
config.setDefaultEncoding("UTF-8");
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
config.setSharedVariable("freemarkerUtils", new FreeMarkerUtils());
config.unsetCacheStorage();
config.clearTemplateCache();
}
/**
* 生成Markdown格式的API接口文档。
*
* @param apiProject 解析后的工程对象。
* @param outputFile 生成后的、包含全路径的输出文件名。
* @throws IOException 文件操作异常。
* @throws TemplateException 模板实例化异常。
*/
public void doGenerate(ApiCodeParser.ApiProject apiProject, String outputFile) throws IOException, TemplateException {
Map<String, Object> paramMap = new HashMap<>(1);
paramMap.put("project", apiProject);
List<ApiCodeParser.ApiService> newServiceList = new LinkedList<>();
if (apiProject.getMicroService()) {
// 在微服务场景中我们需要把upms服务放到最前面显示。
for (ApiCodeParser.ApiService apiService : apiProject.getServiceList()) {
if ("upms".equals(apiService.getServiceName())) {
newServiceList.add(apiService);
break;
}
}
for (ApiCodeParser.ApiService apiService : apiProject.getServiceList()) {
if (!"upms".equals(apiService.getServiceName())) {
newServiceList.add(apiService);
}
}
} else {
ApiCodeParser.ApiService appService = apiProject.getServiceList().get(0);
ApiCodeParser.ApiService newUpmsService = new ApiCodeParser.ApiService();
newUpmsService.setDefaultGroupClassSet(appService.getGroupedClassMap().get("upms"));
newUpmsService.setServiceName("upms");
newUpmsService.setShowName("用户权限模块");
newServiceList.add(newUpmsService);
ApiCodeParser.ApiService newAppService = new ApiCodeParser.ApiService();
newAppService.setDefaultGroupClassSet(appService.getGroupedClassMap().get("app"));
newAppService.setServiceName("app");
newAppService.setShowName("业务应用模块");
newServiceList.add(newAppService);
}
apiProject.setServiceList(newServiceList);
FileUtils.forceMkdirParent(new File(outputFile));
config.getTemplate("./api-doc.md.ftl").process(paramMap, new FileWriter(outputFile));
}
}

View File

@@ -0,0 +1,53 @@
package com.orange.demo.apidoc.tools.export;
import com.orange.demo.apidoc.tools.codeparser.ApiCodeParser;
import com.orange.demo.apidoc.tools.util.FreeMarkerUtils;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateModelException;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 根据代码解析后的工程对象数据导出到Postman支持的JSON格式的文件。
*
* @author Jerry
* @date 2020-08-08
*/
public class ApiPostmanExporter {
private final Configuration config;
public ApiPostmanExporter() throws TemplateModelException {
config = new Configuration(Configuration.VERSION_2_3_28);
config.setNumberFormat("0.####");
config.setClassicCompatible(true);
config.setAPIBuiltinEnabled(true);
config.setClassForTemplateLoading(ApiPostmanExporter.class, "/templates/");
config.setDefaultEncoding("UTF-8");
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
config.setSharedVariable("freemarkerUtils", new FreeMarkerUtils());
config.unsetCacheStorage();
config.clearTemplateCache();
}
/**
* 生成Postman支持的JSON文档。
* @param apiProject 解析后的工程对象。
* @param outputFile 生成后的、包含全路径的输出文件名。
* @throws IOException 文件操作异常。
* @throws TemplateException 模板实例化异常。
*/
public void doGenerate(ApiCodeParser.ApiProject apiProject, String outputFile) throws IOException, TemplateException {
Map<String, Object> paramMap = new HashMap<>(1);
paramMap.put("project", apiProject);
FileUtils.forceMkdirParent(new File(outputFile));
config.getTemplate("./postman_collection.json.ftl").process(paramMap, new FileWriter(outputFile));
}
}

View File

@@ -0,0 +1,28 @@
package com.orange.demo.apidoc.tools.util;
import java.util.UUID;
/**
* 仅供Freemarker模板内部使用的Java工具函数。
*
* @author Jerry
* @date 2020-08-08
*/
public class FreeMarkerUtils {
/**
* 生成GUID。
*
* @return 生成后的GUID。
*/
public static String generateGuid() {
return UUID.randomUUID().toString();
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
public FreeMarkerUtils() {
// FreeMarker的工具对象Sonarqube建议给出空构造的注释。
}
}

View File

@@ -0,0 +1,44 @@
{
"projectName": "橙单微服务开源版",
"basePackage": "com.orange.demo",
"projectRootPath": "这里请使用当前工程的根目录e:/xxx/OrangeDemo 或者 /Users/xxx/OrangeDemo",
"microService": "true",
"serviceList": [
{
"serviceName": "course-class",
"showName": "班级服务",
"servicePath": "/application/course-class",
"serviceRequestPath": "/admin/CourseClass",
"port": "8082",
"controllerInfoList": [
{
"path": "/course-class-service/src/main/java/com/orange/demo/courseclassservice/controller"
}
]
},
{
"serviceName": "stats",
"showName": "统计服务",
"servicePath": "/application/stats",
"serviceRequestPath": "/admin/stats",
"port": "8082",
"controllerInfoList": [
{
"path": "/stats-service/src/main/java/com/orange/demo/statsservice/controller"
}
]
},
{
"serviceName": "upms",
"showName": "用户权限服务",
"servicePath": "/application/upms",
"serviceRequestPath": "/admin/upms",
"port": "8082",
"controllerInfoList": [
{
"path": "/upms-service/src/main/java/com/orange/demo/upmsservice/controller"
}
]
}
]
}

View File

@@ -0,0 +1,144 @@
## 用户登录
### 登录接口
#### 登录
- **URI:** /admin/upms/login/doLogin
- **Type:** GET
- **Content-Type:** multipart/form-data
- **Request-Headers:**
Name|Type|Description
--|--|--
Authorization|String|身份验证的Token
- **Request-Parameters:**
Parameter|Type|Required|Description
--|--|--|--
loginName|string|true|用户名
password|string|true|加密后的用户密码
#### 退出
- **URI:** /admin/upms/login/logout
- **Type:** POST
- **Content-Type:** application/json; chartset=utf-8
- **Request-Headers:**
Name|Type|Description
--|--|--
Authorization|String|身份验证的Token
#### 修改密码
- **URI:** /admin/upms/login/changePassword
- **Type:** POST
- **Content-Type:** application/json; chartset=utf-8
- **Request-Headers:**
Name|Type|Description
--|--|--
Authorization|String|身份验证的Token
- **Request-Parameters:**
Parameter|Type|Required|Description
--|--|--|--
oldPass|string|true|加密后的原用户密码
newPass|string|true|加密后的新用户密码
<#list project.serviceList as service>
## ${service.showName}
<#list service.defaultGroupClassSet as apiClass>
### ${apiClass.name}
<#list apiClass.methodList as apiMethod>
#### ${apiMethod.name}
- **URI:** ${apiMethod.requestPath}
- **Type:** ${apiMethod.httpMethod}
- **Content-Type:** <#if apiMethod.httpMethod == "GET" || apiMethod.queryParamArgumentList?size gt 0 || apiMethod.uploadParamArgumentList?size gt 0>multipart/form-data<#else>application/json; chartset=utf-8</#if>
- **Request-Headers:**
Name|Type|Description
--|--|--
Authorization|String|身份验证的Token
<#if apiMethod.queryParamArgumentList?size gt 0 || apiMethod.uploadParamArgumentList?size gt 0>
- **Request-Parameters:**
Parameter|Type|Required|Description
--|--|--|--
<#list apiMethod.queryParamArgumentList as apiArgument>
<#if apiArgument.modelData??>
<#list apiArgument.modelData.tableFieldList as apiField>
${apiField.name}|${apiField.typeName}|<#if apiMethod.listDictUrl>false<#else><#if apiField.requiredColumn>true<#else>false</#if></#if>|${apiField.comment}
</#list>
<#else>
${apiArgument.name}|${apiArgument.typeName}|<#if apiMethod.listDictUrl>false<#else><#if apiArgument.required>true<#else>false</#if></#if>|${apiArgument.comment}
</#if><#-- apiArgument.modelData?? -->
</#list>
</#if>
<#list apiMethod.uploadParamArgumentList as apiArgument>
${apiArgument.name}|File|true|${apiArgument.comment}
</#list>
<#if apiMethod.jsonParamArgumentList?size gt 0>
- **Request-Body:**
``` json
{
<#list apiMethod.jsonParamArgumentList as apiArgument>
<#if apiArgument.modelData??>
<#if apiArgument.collectionParam>
"${apiArgument.name}" : [
{
<#if apiMethod.listUrl>
<#list apiArgument.modelData.filteredFieldList as apiField>
"${apiField.name}" : "${apiField.typeName} | false | <#if apiField.name == "searchString">模糊搜索字符串。<#else>${apiField.comment}</#if>"<#if apiField_has_next>,</#if>
</#list>
<#else><#-- apiMethod.listUrl -->
<#list apiArgument.modelData.tableFieldList as apiField>
<#if !apiMethod.addUrl || !apiField.primaryKey>
"${apiField.name}" : "${apiField.typeName} | <#if apiField.requiredColumn>true<#else>false</#if> | ${apiField.comment}"<#if apiField_has_next>,</#if>
</#if>
</#list>
</#if><#-- apiMethod.listUrl -->
}
]<#if apiArgument_has_next>,</#if>
<#else><#-- apiArgument.collectionParam -->
"${apiArgument.name}" : {
<#if apiMethod.listUrl>
<#list apiArgument.modelData.filteredFieldList as apiField>
"${apiField.name}" : "${apiField.typeName} | false | <#if apiField.name == "searchString">模糊搜索字符串。<#else>${apiField.comment}</#if>"<#if apiField_has_next>,</#if>
</#list>
<#else><#-- apiMethod.listUrl -->
<#list apiArgument.modelData.tableFieldList as apiField>
<#if !apiMethod.addUrl || !apiField.primaryKey>
"${apiField.name}" : "${apiField.typeName} | <#if apiField.requiredColumn>true<#else>false</#if> | ${apiField.comment}"<#if apiField_has_next>,</#if>
</#if>
</#list>
</#if><#-- apiMethod.listUrl -->
}<#if apiArgument_has_next>,</#if>
</#if><#-- apiArgument.collectionParam -->
<#elseif apiArgument.orderParam>
"${apiArgument.name}" : [
{
"fieldName" : "String | false | 排序字段名",
"asc" : "Boolean | false | 是否升序"
}
]<#if apiArgument_has_next>,</#if>
<#elseif apiArgument.groupParam>
"${apiArgument.name}" : [
{
"fieldName" : "String | false | 分组字段名",
"aliasName" : "String | false | 分组字段别名",
"dateAggregateBy" : "String | false | 是否按照日期聚合,可选项(day|month|year)"
}
]<#if apiArgument_has_next>,</#if>
<#elseif apiArgument.pageParam>
"${apiArgument.name}" : {
"pageNum": "Integer | false | 分页页号",
"pageSize": "Integer | false | 每页数据量"
}<#if apiArgument_has_next>,</#if>
<#elseif apiArgument.queryParam || apiArgument.aggregationParam>
${apiArgument.name}" : {
}<#if apiArgument_has_next>,</#if>
<#else><#-- apiArgument.modelData?? -->
<#if apiArgument.collectionParam>
"${apiArgument.name}" : [ "${apiArgument.typeName} | ${apiArgument.required}<#if apiArgument.comment??> | ${apiArgument.comment}</#if>" ]<#if apiArgument_has_next>,</#if>
<#else>
"${apiArgument.name}" : "${apiArgument.typeName} | ${apiArgument.required}<#if apiArgument.comment??> | ${apiArgument.comment}</#if>"<#if apiArgument_has_next>,</#if>
</#if>
</#if><#-- apiArgument.modelData?? -->
</#list>
}
```
</#if>
</#list><#-- apiClass.methodList as apiMethod -->
</#list><#-- upmsClassList as apiClass -->
</#list>

View File

@@ -0,0 +1,42 @@
<#import "postman_common.ftl" as Common>
{
"info": {
"_postman_id": "92b51dc5-3611-49ac-8d94-a0718dba5bf1",
"name": "${project.projectName}",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
<#list project.serviceList as service>
{
"name": "${service.serviceName}",
"item": [
<#if service.groupedClassMap?size gt 0>
<#list service.groupedClassMap?keys as groupName>
<#assign groupedClassList=service.groupedClassMap[groupName] />
{
"name": "${groupName}",
"item": [
<#list groupedClassList as apiClass>
{
<@Common.generateControllerRequest service apiClass 7/>
}<#if apiClass_has_next>,</#if>
</#list>
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}<#if groupName_has_next>,</#if>
</#list>
</#if>
<#list service.defaultGroupClassSet as apiClass>
{
<@Common.generateControllerRequest service apiClass 5/>
}<#if apiClass_has_next>,</#if>
</#list>
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}<#if service_has_next>,</#if>
</#list><#-- project.serviceList as service -->
],
"protocolProfileBehavior": {}
}

View File

@@ -0,0 +1,120 @@
<#macro doIndent level><#if level != 0><#list 0..(level-1) as i> </#list></#if></#macro>
<#macro generateControllerRequest service apiClass indentLevel>
<@doIndent indentLevel/>"name": "${apiClass.name}",
<@doIndent indentLevel/>"item": [
<#list apiClass.methodList as apiMethod>
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "name": "${apiMethod.name}",
<#if apiMethod.loginUrl>
<@doIndent indentLevel/> "event": [
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "listen": "test",
<@doIndent indentLevel/> "script": {
<@doIndent indentLevel/> "id": "${freemarkerUtils.generateGuid()}",
<@doIndent indentLevel/> "type": "text/javascript",
<@doIndent indentLevel/> "exec": [
<@doIndent indentLevel/> "pm.test(\"登录操作\", function () {",
<@doIndent indentLevel/> " var jsonData = pm.response.json();",
<@doIndent indentLevel/> " var token = jsonData.data.tokenData;",
<@doIndent indentLevel/> " pm.environment.set(\"token\", token);",
<@doIndent indentLevel/> " console.log(\"login token \" + token);",
<@doIndent indentLevel/> "});",
<@doIndent indentLevel/> ""
<@doIndent indentLevel/> ]
<@doIndent indentLevel/> }
<@doIndent indentLevel/> },
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "listen": "prerequest",
<@doIndent indentLevel/> "script": {
<@doIndent indentLevel/> "id": "${freemarkerUtils.generateGuid()}",
<@doIndent indentLevel/> "type": "text/javascript",
<@doIndent indentLevel/> "exec": [
<@doIndent indentLevel/> ""
<@doIndent indentLevel/> ]
<@doIndent indentLevel/> }
<@doIndent indentLevel/> }
<@doIndent indentLevel/> ],
</#if>
<@doIndent indentLevel/> "request": {
<@doIndent indentLevel/> "method": "${apiMethod.httpMethod}",
<#if apiMethod.loginUrl>
<@doIndent indentLevel/> "header": [],
<#else>
<@doIndent indentLevel/> "header": [
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "key": "Authorization",
<@doIndent indentLevel/> "value": "{{token}}",
<@doIndent indentLevel/> "type": "text"
<@doIndent indentLevel/> }
<@doIndent indentLevel/> ],
</#if>
<@doIndent indentLevel/> "url": {
<@doIndent indentLevel/> "raw": "http://{{host}}:${service.port}/${apiMethod.requestPath}",
<@doIndent indentLevel/> "protocol": "http",
<@doIndent indentLevel/> "host": [
<@doIndent indentLevel/> "{{host}}"
<@doIndent indentLevel/> ],
<@doIndent indentLevel/> "port": "${service.port}",
<@doIndent indentLevel/> "path": [
<#list apiMethod.pathList as path>
<@doIndent indentLevel/> "${path}"<#if path_has_next>,</#if>
</#list>
<@doIndent indentLevel/> ]<#if apiMethod.queryParamArgumentList?size gt 0>,</#if>
<#if apiMethod.queryParamArgumentList?size gt 0>
<@doIndent indentLevel/> "query": [
<#list apiMethod.queryParamArgumentList as apiArgument>
<#if apiArgument.modelData??>
<#list apiArgument.modelData.tableFieldList as apiField>
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "key": "${apiField.name}",
<@doIndent indentLevel/> "value": ""
<@doIndent indentLevel/> }<#if apiArgument_has_next || apiField_has_next>,</#if>
</#list>
<#else>
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "key": "${apiArgument.name}",
<@doIndent indentLevel/> "value": ""
<@doIndent indentLevel/> }<#if apiArgument_has_next>,</#if>
</#if>
</#list>
<@doIndent indentLevel/> ]
</#if>
<@doIndent indentLevel/> }<#if (apiMethod.httpMethod == "POST" && apiMethod.jsonParamArgumentList?size gt 0) || apiMethod.uploadParamArgumentList?size gt 0>,</#if>
<#if apiMethod.uploadParamArgumentList?size gt 0>
<@doIndent indentLevel/> "body": {
<@doIndent indentLevel/> "mode": "formdata",
<@doIndent indentLevel/> "formdata": [
<#list apiMethod.uploadParamArgumentList as apiArgument>
<@doIndent indentLevel/> {
<@doIndent indentLevel/> "key": "${apiArgument.name}",
<@doIndent indentLevel/> "type": "file",
<@doIndent indentLevel/> "src": []
<@doIndent indentLevel/> }<#if apiArgument_has_next>,</#if>
</#list>
<@doIndent indentLevel/> ]
<@doIndent indentLevel/> }<#if apiMethod.httpMethod == "POST" && apiMethod.jsonParamArgumentList?size gt 0>,</#if>
</#if><#-- apiMethod.uploadParamArgumentList?size gt 0 -->
<#if apiMethod.httpMethod == "POST" && apiMethod.jsonParamArgumentList?size gt 0>
<@doIndent indentLevel/> "body": {
<@doIndent indentLevel/> "mode": "raw",
<#if !apiMethod.loginUrl>
<@doIndent indentLevel/> "raw": "{\n<#list apiMethod.jsonParamArgumentList as apiArgument><#if apiArgument.modelData??><#if apiArgument.collectionParam>\t\"${apiArgument.name}\" : [\n\t\t{\n<#list apiArgument.modelData.fieldList as apiField><#if apiMethod.listUrl>\t\t\t\"${apiField.name}\" : \"\"<#if apiField_has_next>,</#if>\n<#else>\t\t\t\"${apiField.name}\" : \"<#if apiField.typeName == "Integer" || apiField.typeName == "Long">0</#if>\"<#if apiField_has_next>,</#if>\n</#if><#-- apiMethod.listUrl --></#list>\t\t}\n\t]<#if apiArgument_has_next>,</#if>\n<#else><#-- apiArgument.collectionParam -->\t\"${apiArgument.name}\" : {\n<#list apiArgument.modelData.fieldList as apiField><#if apiMethod.listUrl>\t\t\"${apiField.name}\" : \"\"<#if apiField_has_next>,</#if>\n<#else>\t\t\"${apiField.name}\" : \"<#if apiField.typeName == "Integer" || apiField.typeName == "Long">0</#if>\"<#if apiField_has_next>,</#if>\n</#if><#-- apiMethod.listUrl --></#list>\t}<#if apiArgument_has_next>,</#if>\n</#if><#-- apiArgument.collectionParam --><#elseif apiArgument.orderParam>\t\"${apiArgument.name}\" : [\n\t\t{\n\t\t\t\"fieldName\" : \"\",\n\t\t\t\"asc\" : \"true\"\n\t\t}\n\t]<#if apiArgument_has_next>,</#if>\n<#elseif apiArgument.groupParam>\t\"${apiArgument.name}\" : [\n\t\t{\n\t\t\t\"fieldName\" : \"\",\n\t\t\t\"aliasName\" : \"\",\n\t\t\t\"dateAggregateBy\" : \"\"\n\t\t}\n\t]<#if apiArgument_has_next>,</#if>\n<#elseif apiArgument.pageParam>\t\"${apiArgument.name}\" : {\n\t\t\"pageNum\": \"1\",\n\t\t\"pageSize\": \"10\"\n\t}<#if apiArgument_has_next>,</#if>\n<#elseif apiArgument.queryParam || apiArgument.aggregationParam>\t\"${apiArgument.name}\" : {\n\t}<#if apiArgument_has_next>,</#if>\n<#else><#if apiArgument.collectionParam>\t\"${apiArgument.name}\" : [ ]<#if apiArgument_has_next>,</#if>\n<#else>\t\"${apiArgument.name}\" : \"\"<#if apiArgument_has_next>,</#if>\n</#if></#if><#-- apiArgument.modelData?? --></#list><#-- apiMethod.jsonParamArgumentList?size gt 0 -->}\n",
<#else>
<@doIndent indentLevel/> "raw": "{\n \"loginName\":\"admin\",\n \"password\":\"IP3ccke3GhH45iGHB5qP9p7iZw6xUyj28Ju10rnBiPKOI35sc%2BjI7%2FdsjOkHWMfUwGYGfz8ik31HC2Ruk%2Fhkd9f6RPULTHj7VpFdNdde2P9M4mQQnFBAiPM7VT9iW3RyCtPlJexQ3nAiA09OqG%2F0sIf1kcyveSrulxembARDbDo%3D\"\n}",
</#if>
<@doIndent indentLevel/> "options": {
<@doIndent indentLevel/> "raw": {
<@doIndent indentLevel/> "language": "json"
<@doIndent indentLevel/> }
<@doIndent indentLevel/> }
<@doIndent indentLevel/> }
</#if>
<@doIndent indentLevel/> },
<@doIndent indentLevel/> "response": []
<@doIndent indentLevel/> }<#if apiMethod_has_next>,</#if>
</#list><#-- apiClass.methodList as apiMethod -->
<@doIndent indentLevel/>],
<@doIndent indentLevel/>"protocolProfileBehavior": {},
<@doIndent indentLevel/>"_postman_isSubFolder": true
</#macro>