diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8e4fb1b4732c611d92659bcb5723e83a99400aed --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +hs_err_pid* + +.idea +*.iml +target +.gradle +out +build +gradlew* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ab51a51f749746066fde63eb4531cc2aa0b64802 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Apiggs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 22aa8b5d1f56b7553f2d223e655a814f5c558146..b206df2f9b86bac12f9a035ba89a8fb6e4a63d5c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ -# apidoc +# 🐷 Apigcc - 非侵入的RestDoc文档生成工具 -基于javadoc注释自动生成api接口文档 \ No newline at end of file +![](https://img.shields.io/badge/Language-Java-yellow.svg) + +### 前言 +程序员一直以来都有一个烦恼,只想写代码,不想写文档。代码就表达了我的思想和灵魂。 + +Python提出了一个方案,叫**docstring**,来试图解决这个问题。即编写代码,同时也能写出文档,保持代码和文档的一致。docstring说白了就是一堆代码中的注释。Python的docstring可以通过help函数直接输出一份有格式的文档,本工具的思想与此类似。 + +### 代码即文档 + +Apigcc是一个**非侵入**的RestDoc文档生成工具。工具通过分析代码和注释,获取文档信息,生成RestDoc文档。 + +### 有这样一段代码 + +```java +/** + * 欢迎使用Apigcc + * @index 1 + */ +@RestController +public class HelloController { + + /** + * 示例接口 + * @param name 名称 + * @return + */ + @RequestMapping("/greeting") + public HelloDTO greeting(@RequestParam(defaultValue="apigcc") String name) { + return new HelloDTO("hello "+name); + } + +} +``` + + +### 生成文档效果 +![示例](https://apigcc-1252473972.cos.ap-shanghai.myqcloud.com/apigcc-hub-demo.png) + +### 使用方式 + +[Hub](https://github.com/apigcc/apigcc-hub) + +[Gradle插件](https://github.com/apigcc/apigcc-gradle-plugin) + +[Maven插件](https://github.com/apigcc/apigcc-maven-plugin) \ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..c4192631f25b34d77a7f159aa0da0e3ae99c4ef4 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/apidoc-core/build.gradle b/apidoc-core/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..2a1eec758c4a575880e12bf087c805da7b9c3017 --- /dev/null +++ b/apidoc-core/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile 'com.github.javaparser:javaparser-symbol-solver-core:3.14.4' + compile 'com.fasterxml.jackson.core:jackson-databind:2.5.2' + compile 'org.asciidoctor:asciidoctorj:2.1.0' +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/ApiDoc.java b/apidoc-core/src/main/java/com/apidoc/core/ApiDoc.java new file mode 100644 index 0000000000000000000000000000000000000000..7912a315041b7d12e0f48fe8d3d10a1ca6942bfd --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/ApiDoc.java @@ -0,0 +1,183 @@ +package com.apidoc.core; + +import com.apidoc.core.common.helper.StringHelper; +import com.apidoc.core.parser.ParserStrategy; +import com.apidoc.core.parser.VisitorParser; +import com.apidoc.core.render.ProjectRender; +import com.apidoc.core.resolver.TypeResolvers; +import com.apidoc.core.schema.Project; +import com.github.javaparser.ParseResult; +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.symbolsolver.JavaSymbolSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; +import com.github.javaparser.utils.SourceRoot; +import com.google.common.collect.Lists; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.ServiceLoader; + +/** + * The type ApiDoc. + * + * @author fengyuchenglun + * @version 1.0.0 + */ +@Slf4j +public class ApiDoc { + + /** + * ApiDoc实例 + */ + private static ApiDoc INSTANCE; + + + /** + * Get instance api doc. + * + * @return the api doc + */ + public static ApiDoc getInstance(){ + return INSTANCE; + } + + /** + * 上下文 + */ + @Getter + private Context context; + /** + * 项目信息 + */ + @Getter + private Project project = new Project(); + + /** + * The Visitor parser. + */ + private VisitorParser visitorParser = new VisitorParser(); + /** + * The Parser configuration. + */ + private ParserConfiguration parserConfiguration; + + /** + * The Type resolvers. + */ + @Getter + private TypeResolvers typeResolvers = new TypeResolvers(); + + /** + * Instantiates a new ApiDoc. + */ + private ApiDoc(){ + init(new Context()); + } + + /** + * Instantiates a new ApiDoc. + * + * @param context the context + */ + public ApiDoc(Context context){ + init(context); + } + + /** + * 初始化环境配置 + * + * @param context the context + */ + private void init(Context context){ + INSTANCE = this; + this.context = context; + project.setId(context.getId()); + project.setName(context.getName()); + project.setDescription(context.getDescription()); + project.setVersion(context.getVersion()); + + CombinedTypeSolver typeSolver = new CombinedTypeSolver(); + for (Path dependency : context.getDependencies()) { + typeSolver.add(new JavaParserTypeSolver(dependency)); + } + for (Path jar : context.getJars()) { + try { + typeSolver.add(new JarTypeSolver(jar)); + } catch (IOException e) { + log.warn("exception on {} {}", jar, e.getMessage()); + } + } + typeSolver.add(new ReflectionTypeSolver()); + + parserConfiguration = new ParserConfiguration(); + parserConfiguration.setSymbolResolver(new JavaSymbolSolver(typeSolver)); + + ParserStrategy strategy = loadParserStrategy(); + strategy.onLoad(); + visitorParser.setParserStrategy(strategy); + + } + + /** + * 加载并设置解析框架 + * null时,使用读取到的第一个框架解析器 + * 找不到时,报错 + * + * @return the parser strategy + */ + private ParserStrategy loadParserStrategy(){ + ServiceLoader serviceLoader = ServiceLoader.load(ParserStrategy.class); + List strategies = Lists.newArrayList(serviceLoader); + if(strategies.isEmpty()){ + throw new IllegalArgumentException("no ParserStrategy implements found"); + } + if(StringHelper.isBlank(context.framework)){ + return strategies.get(0); + } + for (ParserStrategy strategy : strategies) { + if(Objects.equals(context.framework,strategy.name())){ + return strategy; + } + } + throw new IllegalArgumentException("no ParserStrategy implements found for "+context.framework); + } + + /** + * 解析源代码 + * + * @return project project + */ + public Project parse(){ + for (Path source : this.context.getSources()) { + SourceRoot root = new SourceRoot(source, parserConfiguration); + try { + for (ParseResult result : root.tryToParse()) { + if(result.isSuccessful() && result.getResult().isPresent()){ + result.getResult().get().accept(visitorParser, project); + } + } + } catch (IOException e) { + log.warn("parse root {} error {}", source, e.getMessage()); + } + } + return project; + } + + /** + * 渲染解析结果 + */ + public void render(){ + for (ProjectRender render : this.context.getRenders()) { + render.render(project); + } + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/Context.java b/apidoc-core/src/main/java/com/apidoc/core/Context.java new file mode 100644 index 0000000000000000000000000000000000000000..eef875dd674bc0a1943f492bb666445fb0aeea86 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/Context.java @@ -0,0 +1,142 @@ +package com.apidoc.core; + +import com.apidoc.core.common.helper.FileHelper; +import com.apidoc.core.render.AsciiDocRender; +import com.apidoc.core.render.PostmanRender; +import com.apidoc.core.render.ProjectRender; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.Getter; +import lombok.Setter; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; + +/** + * The type Context. + * + * @author fengyuchenglun + * @version 1.0.0 + */ +@Getter +public class Context { + + /** + * 默认-节点索引 + */ + public static final Integer DEFAULT_NODE_INDEX = 99; + /** + * 默认-项目编号 + */ + public static final String DEFAULT_PROJECT_ID = "api"; + /** + * 默认-编译路径 + */ + public static final String DEFAULT_BUILD_PATH = "build"; + /** + * 默认-代码结构 + */ + public static final String DEFAULT_CODE_STRUCTURE = "src/main/java"; + + /** + * 设置当前解析框架 + */ + @Setter + public String framework; + + /** + * The Renders. + */ + @Setter + public List renders = Lists.newArrayList( + new AsciiDocRender(), + new PostmanRender()); + + /** + * 编译目录 + */ + @Setter + private Path buildPath = Paths.get(DEFAULT_BUILD_PATH); + + /** + * 源码目录 + */ + private List sources = Lists.newArrayList(); + + /** + * 依赖源码 + */ + private List dependencies = Lists.newArrayList(); + + /** + * 依赖jar包 + */ + private List jars = Lists.newArrayList(); + + /** + * 项目编号 + */ + @Setter + private String id = DEFAULT_PROJECT_ID; + /** + * 名称 + */ + @Setter + private String name; + /** + * 描述 + */ + @Setter + private String description; + /** + * 版本 + */ + @Setter + private String version; + + /** + * 渲染html时的css + */ + @Setter + private String css; + + /** + * 自定义扩展参数 + */ + private Map ext= Maps.newHashMap(); + + /** + * Add source. + * + * @param path the path + */ + public void addSource(Path path){ + sources.add(path); + sources.addAll(FileHelper.find(path, DEFAULT_CODE_STRUCTURE)); + addDependency(path); + } + + /** + * Add dependency. + * + * @param path the path + */ + public void addDependency(Path path){ + dependencies.add(path); + dependencies.addAll(FileHelper.find(path, DEFAULT_CODE_STRUCTURE)); + } + + /** + * Add jar. + * + * @param path the path + */ + public void addJar(Path path){ + jars.add(path); + } + + + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/Assert.java b/apidoc-core/src/main/java/com/apidoc/core/common/Assert.java new file mode 100644 index 0000000000000000000000000000000000000000..4c4bc5bd4100d54bf7341f34776978288d51db71 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/Assert.java @@ -0,0 +1,24 @@ +package com.apidoc.core.common; + +import com.google.common.base.Strings; + +public class Assert { + + public static boolean isBlank(String text) { + return Strings.isNullOrEmpty(text) || Strings.isNullOrEmpty(text.trim()); + } + + public static void notBlank(String text, String message) { + if (Strings.isNullOrEmpty(text) || Strings.isNullOrEmpty(text.trim())) { + throw new IllegalArgumentException(message); + } + } + + public static void between(int num, int min, int max, String message) { + if (num < min || num > max) { + throw new IllegalArgumentException(message); + } + + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/ObjectMappers.java b/apidoc-core/src/main/java/com/apidoc/core/common/ObjectMappers.java new file mode 100644 index 0000000000000000000000000000000000000000..0f2407ba0ab1f7c22c102dac36be42b57254b85b --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/ObjectMappers.java @@ -0,0 +1,39 @@ +package com.apidoc.core.common; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.Iterator; + +public class ObjectMappers { + + public static final ObjectMapper instance; + + static { + instance = new ObjectMapper(); + instance.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + public static String pretty(Object node) { + try { + return instance.writerWithDefaultPrettyPrinter().writeValueAsString(node); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public static ObjectNode merge(ObjectNode result, ObjectNode... nodes) { + if (result != null) { + for (ObjectNode node : nodes) { + Iterator iterator = node.fieldNames(); + while (iterator.hasNext()){ + String key = iterator.next(); + result.set(key,node.get(key)); + } + } + } + return result; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/QueryStringBuilder.java b/apidoc-core/src/main/java/com/apidoc/core/common/QueryStringBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..3dd22570ea04d7fe35d1a308bdabd7f1c6efaa43 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/QueryStringBuilder.java @@ -0,0 +1,39 @@ +package com.apidoc.core.common; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.Iterator; + +public class QueryStringBuilder { + + private StringBuilder builder = new StringBuilder(); + + public QueryStringBuilder append(Object key, Object value){ + if(builder.length()>0){ + builder.append("&"); + } + builder.append(key); + builder.append("="); + builder.append(value); + return this; + } + + public QueryStringBuilder append(ObjectNode objectNode){ + Iterator iterator = objectNode.fieldNames(); + while (iterator.hasNext()){ + String key = iterator.next(); + JsonNode valueNode = objectNode.get(key); + String value = valueNode.isTextual()?valueNode.asText():valueNode.toString(); + append(key,value); + } + return this; + } + + public String toString(){ + return builder.toString(); + } + + + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/URI.java b/apidoc-core/src/main/java/com/apidoc/core/common/URI.java new file mode 100644 index 0000000000000000000000000000000000000000..c34de99778a35df298f3595a58c68dd471283de8 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/URI.java @@ -0,0 +1,70 @@ +package com.apidoc.core.common; + +import com.apidoc.core.common.helper.StringHelper; +import com.google.common.collect.Lists; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Getter +@EqualsAndHashCode +public class URI { + + private String text; + + private URI next; + + public URI() { + } + + public URI(String text) { + this.text = text; + } + + public URI add(String text){ + return add(new URI(text)); + } + + public URI add(URI uri){ + if(next!=null){ + next.add(uri); + }else{ + next = uri; + } + return this; + } + + public void remove(URI uri){ + if(Objects.equals(next,uri)){ + next = null; + }else{ + next.remove(uri); + } + } + + @Override + public String toString(){ + List list = new ArrayList<>(); + appendTo(list); + StringBuilder builder = new StringBuilder(); + for (String text : list) { + if(StringHelper.nonBlank(text)){ + builder.append("/"); + builder.append(text); + } + } + return builder.toString(); + } + + private void appendTo(List list){ + if(Objects.nonNull(text)){ + list.addAll(Lists.newArrayList(text.split("/"))); + } + if(next!=null){ + next.appendTo(list); + } + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/description/ArrayTypeDescription.java b/apidoc-core/src/main/java/com/apidoc/core/common/description/ArrayTypeDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..a26eb64a1c2bb3f9a2bb9bc36143ee2f15e2bde9 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/description/ArrayTypeDescription.java @@ -0,0 +1,94 @@ +package com.apidoc.core.common.description; + +import com.apidoc.core.common.ObjectMappers; +import com.apidoc.core.schema.Row; +import com.fasterxml.jackson.databind.node.ArrayNode; + +import java.util.ArrayList; +import java.util.Collection; + +public class ArrayTypeDescription extends TypeDescription { + + protected ArrayNode value; + protected TypeDescription component; + + public ArrayTypeDescription(TypeDescription component) { + this.component = component; + this.value = ObjectMappers.instance.createArrayNode(); + if(component.isAvailable()){ + this.type = component.getType() + "[]"; + if(component.isPrimitive()){ + primitive(component.asPrimitive()); + }else if(component.isString()){ + value.add(component.asString().getValue()); + }else if(component.isArray()){ + value.add(component.asArray().getValue()); + }else if(component.isObject()){ + value.add(component.asObject().getValue()); + } + }else{ + this.type = "[]"; + } + } + + public void primitive(PrimitiveTypeDescription typeDescription){ + switch (typeDescription.getType()){ + case "byte": + value.add((byte)typeDescription.getValue()); + break; + case "short": + value.add((short)typeDescription.getValue()); + break; + case "char": + value.add((char)typeDescription.getValue()); + break; + case "int": + value.add((int)typeDescription.getValue()); + break; + case "long": + value.add((long)typeDescription.getValue()); + break; + case "boolean": + value.add((boolean)typeDescription.getValue()); + break; + case "float": + value.add((float)typeDescription.getValue()); + break; + case "double": + value.add((double)typeDescription.getValue()); + break; + } + } + + @Override + public void setKey(String key) { + super.setKey(key); + if (component.isAvailable()) { + component.setPrefix(fullKey()); + } + } + + @Override + public void setPrefix(String prefix) { + super.setPrefix(prefix); + if (component.isAvailable()) { + component.setPrefix(fullKey()); + } + } + + public ArrayNode getValue(){ + return value; + } + + @Override + public Collection rows() { + ArrayList rows = new ArrayList<>(); + if(key != null){ + rows.addAll(super.rows()); + } + if(component.isAvailable()){ + rows.addAll(component.rows()); + } + return rows; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/description/ObjectTypeDescription.java b/apidoc-core/src/main/java/com/apidoc/core/common/description/ObjectTypeDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..e4a248938b28ab613d5f4491bdbdd7d591ec2672 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/description/ObjectTypeDescription.java @@ -0,0 +1,104 @@ +package com.apidoc.core.common.description; + +import com.apidoc.core.common.ObjectMappers; +import com.apidoc.core.schema.Row; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; +import lombok.Getter; +import lombok.Setter; + +import java.util.Collection; +import java.util.List; + +@Setter +@Getter +public class ObjectTypeDescription extends TypeDescription { + + private ObjectNode value = ObjectMappers.instance.createObjectNode(); + + protected List members = Lists.newArrayList(); + + public void merge(ObjectTypeDescription other) { + value.setAll(other.getValue()); + members.addAll(other.members); + } + + public void add(TypeDescription component) { + members.add(component); + if (component.isPrimitive()) { + primitive(component.asPrimitive()); + } else if (component.isString()) { + value.put(component.getKey(), component.asString().getValue()); + } else if (component.isArray()) { + value.set(component.getKey(), component.asArray().getValue()); + } else if (component.isObject()) { + value.set(component.getKey(), component.asObject().getValue()); + } + } + + public void primitive(PrimitiveTypeDescription typeDescription) { + switch (typeDescription.getType()) { + case "byte": + value.put(typeDescription.getKey(), (byte) typeDescription.getValue()); + break; + case "short": + value.put(typeDescription.getKey(), (short) typeDescription.getValue()); + break; + case "char": + value.put(typeDescription.getKey(), (char) typeDescription.getValue()); + break; + case "int": + value.put(typeDescription.getKey(), (int) typeDescription.getValue()); + break; + case "long": + value.put(typeDescription.getKey(), (long) typeDescription.getValue()); + break; + case "boolean": + value.put(typeDescription.getKey(), (boolean) typeDescription.getValue()); + break; + case "float": + value.put(typeDescription.getKey(), (float) typeDescription.getValue()); + break; + case "double": + value.put(typeDescription.getKey(), (double) typeDescription.getValue()); + break; + } + } + + @Override + public void setKey(String key) { + super.setKey(key); + String memberPrefix = fullKey(); + for (TypeDescription member : members) { + if (member.isAvailable()) { + member.setPrefix(memberPrefix); + } + } + } + + @Override + public void setPrefix(String prefix) { + super.setPrefix(prefix); + String memberPrefix = fullKey(); + for (TypeDescription member : members) { + if (member.isAvailable()) { + member.setPrefix(memberPrefix); + } + } + } + + public ObjectNode getValue() { + return value; + } + + @Override + public Collection rows() { + Collection rows = super.rows(); + for (TypeDescription member : members) { + if (member.isAvailable()) { + rows.addAll(member.rows()); + } + } + return rows; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/description/PrimitiveTypeDescription.java b/apidoc-core/src/main/java/com/apidoc/core/common/description/PrimitiveTypeDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..99d13487377e3f7349950ec849d1d0743dfacc03 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/description/PrimitiveTypeDescription.java @@ -0,0 +1,75 @@ +package com.apidoc.core.common.description; + +import com.github.javaparser.resolution.types.ResolvedPrimitiveType; +import com.github.javaparser.resolution.types.ResolvedReferenceType; + + +public class PrimitiveTypeDescription extends TypeDescription { + + public PrimitiveTypeDescription(ResolvedReferenceType referenceType){ + switch (referenceType.getId()){ + case "java.lang.Byte": + value = (byte) 0; + type = "byte"; + break; + case "java.lang.Short": + value = (short) 0; + type = "short"; + break; + case "java.lang.Integer": + value = 0; + type = "int"; + break; + case "java.lang.Long": + value = 0L; + type = "long"; + break; + case "java.lang.Float": + value = 0f; + type = "float"; + break; + case "java.lang.Double": + value = 0d; + type = "double"; + break; + case "java.lang.Character": + value = (char)0; + type = "char"; + break; + case "java.lang.Boolean": + value = false; + type = "boolean"; + break; + } + + } + public PrimitiveTypeDescription(ResolvedPrimitiveType resolvedPrimitiveType){ + type = resolvedPrimitiveType.describe(); + switch (resolvedPrimitiveType){ + case BYTE: + value = (byte) 0; + break; + case SHORT: + value = (short) 0; + break; + case INT: + value = 0; + break; + case LONG: + value = 0L; + break; + case FLOAT: + value = 0f; + break; + case DOUBLE: + value = 0d; + break; + case CHAR: + value = (char)0; + break; + case BOOLEAN: + value = false; + break; + } + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/description/StringTypeDescription.java b/apidoc-core/src/main/java/com/apidoc/core/common/description/StringTypeDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..ad56e82dfc9f688cca0850f86c71316597b9fb8a --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/description/StringTypeDescription.java @@ -0,0 +1,13 @@ +package com.apidoc.core.common.description; + +public class StringTypeDescription extends TypeDescription { + + public StringTypeDescription(String type, CharSequence charSequence) { + this.type = type; + value = charSequence.toString(); + } + + public String getValue(){ + return (String)value; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/description/TypeDescription.java b/apidoc-core/src/main/java/com/apidoc/core/common/description/TypeDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..eee1377600b5a08bcd9b5cbc5361aad6f5038d7c --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/description/TypeDescription.java @@ -0,0 +1,100 @@ +package com.apidoc.core.common.description; + +import com.apidoc.core.common.helper.StringHelper; +import com.apidoc.core.schema.Row; +import com.google.common.collect.Lists; +import lombok.Getter; +import lombok.Setter; + +import java.util.Collection; + +@Setter +@Getter +public abstract class TypeDescription { + + protected String prefix = ""; + protected String key = ""; + protected String type; + protected StringBuilder condition = new StringBuilder(); + protected String remark; + protected Object value; + protected Object defaultValue; + protected Boolean required; + + public boolean isAvailable() { + return !isUnAvailable(); + } + + public boolean isUnAvailable() { + return this instanceof UnAvailableTypeDescription; + } + + public boolean isPrimitive() { + return this instanceof PrimitiveTypeDescription; + } + + public PrimitiveTypeDescription asPrimitive() { + return (PrimitiveTypeDescription) this; + } + + public boolean isString() { + return this instanceof StringTypeDescription; + } + + public StringTypeDescription asString() { + return (StringTypeDescription) this; + } + + public boolean isArray() { + return this instanceof ArrayTypeDescription; + } + + public ArrayTypeDescription asArray() { + return (ArrayTypeDescription) this; + } + + public boolean isObject() { + return this instanceof ObjectTypeDescription; + } + + public ObjectTypeDescription asObject() { + return (ObjectTypeDescription) this; + } + + public void addRemark(String value){ + if(value==null){ + return; + } + if(remark==null){ + remark = value; + }else{ + remark += " " + value; + } + } + + public String fullKey(){ + return StringHelper.join(".",prefix,key); + } + + public Collection rows() { + String key = fullKey(); + if(StringHelper.isBlank(key)){ + return Lists.newArrayList(); + } + String def; + if(defaultValue!=null){ + def = String.valueOf(defaultValue); + }else if(value!=null){ + def = String.valueOf(value); + }else{ + def = ""; + } + + if(required!=null){ + condition.append("required=").append(required); + } + + return Lists.newArrayList(new Row(key, type, condition.toString(), def, remark)); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/description/UnAvailableTypeDescription.java b/apidoc-core/src/main/java/com/apidoc/core/common/description/UnAvailableTypeDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..2e18b8abb80fc7d46fbcc9f862b554d289628550 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/description/UnAvailableTypeDescription.java @@ -0,0 +1,24 @@ +package com.apidoc.core.common.description; + +import com.apidoc.core.schema.Row; + +import java.util.Collection; + +/** + * 未知类型,应该忽略 + */ +public class UnAvailableTypeDescription extends TypeDescription { + + public UnAvailableTypeDescription() { + } + + @Override + public Object getValue() { + throw new IllegalArgumentException("unAvailable type not support"); + } + + @Override + public Collection rows() { + throw new IllegalArgumentException("unAvailable type not support"); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/diff/FileMatcher.java b/apidoc-core/src/main/java/com/apidoc/core/common/diff/FileMatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..a1be9ee954316757f96079c4d2f96a15061970ee --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/diff/FileMatcher.java @@ -0,0 +1,109 @@ +package com.apidoc.core.common.diff; + +import com.google.common.base.Charsets; +import lombok.Getter; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.util.List; + +/** + * 文件对比工具 + */ +@Getter +public class FileMatcher { + + MatchPatcher matchPatcher = new MatchPatcher(); + + /** + * 变化的点 + */ + private int changs; + + private List diffs; + + public int compare(Path template, Path build) { + return compare(readFile(template), readFile(build)); + } + + public int compare(String templateText, String buildText) { + diffs = matchPatcher.diff_main(templateText, buildText, true); + for (MatchPatcher.Diff diff : diffs) { + if (!diff.operation.equals(MatchPatcher.Operation.EQUAL)) { + changs++; + } + } + return changs; + } + + public void rederHtml(Path templateHtml, Path resultHtml) { + String results = matchPatcher.diff_prettyHtml(diffs); + String[] lines = br(results).replaceAll("|", "").split("\n"); + String html = readFile(templateHtml); + html = html.replace("${content}", lines(lines)); + writeFile(resultHtml, html, Charsets.UTF_8); + FileSystem.open(resultHtml); + } + + private String lines(String[] lines) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < lines.length; i++) { + stringBuilder.append("").append(i) + .append("") + .append(lines[i]).append(""); + } + return stringBuilder.toString(); + } + + private static String br(String text) { + return text.replaceAll("¶", ""); + } + + + /** + * 读取文件内容 + * + * @param path + * @return + * @throws IOException + */ + public static String readFile(Path path) { + // Read a file from disk and return the text contents. + StringBuilder sb = new StringBuilder(); + try (FileReader input = new FileReader(path.toFile()); + BufferedReader bufRead = new BufferedReader(input)) { + String line = bufRead.readLine(); + while (line != null) { + sb.append(line).append('\n'); + line = bufRead.readLine(); + } + } catch (Exception e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + return sb.toString(); + } + + public void writeFile(Path file, String content, Charset charset, OpenOption... openOptions) { + if (file.getParent() != null) { + try { + Files.createDirectories(file.getParent()); + } catch (IOException e) { + throw new RuntimeException("Failed create directory", e); + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(file, charset, openOptions)) { + writer.write(content); + writer.flush(); + } catch (IOException e) { + throw new RuntimeException("Failed to write file", e); + } + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/diff/FileSystem.java b/apidoc-core/src/main/java/com/apidoc/core/common/diff/FileSystem.java new file mode 100644 index 0000000000000000000000000000000000000000..83c9797c9e4418dd513f6a6585dcb188be0ad067 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/diff/FileSystem.java @@ -0,0 +1,71 @@ +package com.apidoc.core.common.diff; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileSystem { + + public static boolean open(Path path) { + if(!Files.exists(path)){ + return false; + } + if (cmd(path.toString())) { + return true; + } + return jdk(path.toFile()); + } + + private static boolean cmd(String file) { + return cmd(currentOS().getCommand(), file); + } + + + private static boolean cmd(String command, String args) { + try { + Process p = Runtime.getRuntime().exec(new String[]{command,args}); + return p!=null; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + private static boolean jdk(File file) { + try { + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().open(file); + return true; + } + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + public enum OS { + mac("open"), + win("explorer"); + + private String command; + + OS(String command) { + this.command = command; + } + + public String getCommand(){ + return command; + } + } + + private static OS currentOS() { + String s = System.getProperty("os.name").toLowerCase(); + if (s.contains("win")) { + return OS.win; + } + return OS.mac; + } +} + diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/diff/MatchPatcher.java b/apidoc-core/src/main/java/com/apidoc/core/common/diff/MatchPatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..7c2adf53ae063aaecdf79101e5b658f9f108725d --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/diff/MatchPatcher.java @@ -0,0 +1,2469 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.apidoc.core.common.diff; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/* + * Functions for diff, match and patch. + * Computes the difference between two texts to create a patch. + * Applies the patch onto another text, allowing for errors. + * + * @author fraser@google.com (Neil Fraser) + */ + +/** + * Class containing the diff, match and patch methods. + * Also contains the behaviour settings. + */ +public class MatchPatcher { + + // Defaults. + // Set these on your diff_match_patch instance to override the defaults. + + /** + * Number of seconds to map a diff before giving up (0 for infinity). + */ + public float Diff_Timeout = 1.0f; + /** + * Cost of an empty edit operation in terms of edit characters. + */ + public short Diff_EditCost = 4; + /** + * At what point is no match declared (0.0 = perfection, 1.0 = very loose). + */ + public float Match_Threshold = 0.5f; + /** + * How far to search for a match (0 = exact location, 1000+ = broad match). + * A match this many characters away from the expected location will add + * 1.0 to the score (0.0 is a perfect match). + */ + public int Match_Distance = 1000; + /** + * When deleting a large block of text (over ~64 characters), how close do + * the contents have to be to match the expected contents. (0.0 = perfection, + * 1.0 = very loose). Note that Match_Threshold controls how closely the + * end points of a delete need to match. + */ + public float Patch_DeleteThreshold = 0.5f; + /** + * Chunk size for context length. + */ + public short Patch_Margin = 4; + + /** + * The number of bits in an int. + */ + private short Match_MaxBits = 32; + + /** + * Internal class for returning results from diff_linesToChars(). + * Other less paranoid languages just use a three-element array. + */ + protected static class LinesToCharsResult { + protected String chars1; + protected String chars2; + protected List lineArray; + + protected LinesToCharsResult(String chars1, String chars2, + List lineArray) { + this.chars1 = chars1; + this.chars2 = chars2; + this.lineArray = lineArray; + } + } + + + // DIFF FUNCTIONS + + + /** + * The data structure representing a diff is a Linked list of Diff objects: + * {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"), + * Diff(Operation.EQUAL, " world.")} + * which means: delete "Hello", add "Goodbye" and keep " world." + */ + public enum Operation { + DELETE, INSERT, EQUAL + } + + /** + * Find the differences between two texts. + * Run a faster, slightly less optimal diff. + * This method allows the 'checklines' of diff_main() to be optional. + * Most of the time checklines is wanted, so default to true. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return Linked List of Diff objects. + */ + public LinkedList diff_main(String text1, String text2) { + return diff_main(text1, text2, true); + } + + /** + * Find the differences between two texts. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @return Linked List of Diff objects. + */ + public LinkedList diff_main(String text1, String text2, + boolean checklines) { + // Set a deadline by which time the diff must be complete. + long deadline; + if (Diff_Timeout <= 0) { + deadline = Long.MAX_VALUE; + } else { + deadline = System.currentTimeMillis() + (long) (Diff_Timeout * 1000); + } + return diff_main(text1, text2, checklines, deadline); + } + + /** + * Find the differences between two texts. Simplifies the problem by + * stripping any common prefix or suffix off the texts before diffing. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. Used + * internally for recursive calls. Users should set DiffTimeout instead. + * @return Linked List of Diff objects. + */ + private LinkedList diff_main(String text1, String text2, + boolean checklines, long deadline) { + // Check for null inputs. + if (text1 == null || text2 == null) { + throw new IllegalArgumentException("Null inputs. (diff_main)"); + } + + // Check for equality (speedup). + LinkedList diffs; + if (text1.equals(text2)) { + diffs = new LinkedList(); + if (text1.length() != 0) { + diffs.add(new Diff(Operation.EQUAL, text1)); + } + return diffs; + } + + // Trim off common prefix (speedup). + int commonlength = diff_commonPrefix(text1, text2); + String commonprefix = text1.substring(0, commonlength); + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(text1, text2); + String commonsuffix = text1.substring(text1.length() - commonlength); + text1 = text1.substring(0, text1.length() - commonlength); + text2 = text2.substring(0, text2.length() - commonlength); + + // Compute the diff on the middle block. + diffs = diff_compute(text1, text2, checklines, deadline); + + // Restore the prefix and suffix. + if (commonprefix.length() != 0) { + diffs.addFirst(new Diff(Operation.EQUAL, commonprefix)); + } + if (commonsuffix.length() != 0) { + diffs.addLast(new Diff(Operation.EQUAL, commonsuffix)); + } + + diff_cleanupMerge(diffs); + return diffs; + } + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private LinkedList diff_compute(String text1, String text2, + boolean checklines, long deadline) { + LinkedList diffs = new LinkedList(); + + if (text1.length() == 0) { + // Just add some text (speedup). + diffs.add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + if (text2.length() == 0) { + // Just delete some text (speedup). + diffs.add(new Diff(Operation.DELETE, text1)); + return diffs; + } + + String longtext = text1.length() > text2.length() ? text1 : text2; + String shorttext = text1.length() > text2.length() ? text2 : text1; + int i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup). + Operation op = (text1.length() > text2.length()) ? + Operation.DELETE : Operation.INSERT; + diffs.add(new Diff(op, longtext.substring(0, i))); + diffs.add(new Diff(Operation.EQUAL, shorttext)); + diffs.add(new Diff(op, longtext.substring(i + shorttext.length()))); + return diffs; + } + + if (shorttext.length() == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + diffs.add(new Diff(Operation.DELETE, text1)); + diffs.add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + // Check to see if the problem can be split in two. + String[] hm = diff_halfMatch(text1, text2); + if (hm != null) { + // A half-match was found, sort out the return data. + String text1_a = hm[0]; + String text1_b = hm[1]; + String text2_a = hm[2]; + String text2_b = hm[3]; + String mid_common = hm[4]; + // Send both pairs off for separate processing. + LinkedList diffs_a = diff_main(text1_a, text2_a, + checklines, deadline); + LinkedList diffs_b = diff_main(text1_b, text2_b, + checklines, deadline); + // Merge the results. + diffs = diffs_a; + diffs.add(new Diff(Operation.EQUAL, mid_common)); + diffs.addAll(diffs_b); + return diffs; + } + + if (checklines && text1.length() > 100 && text2.length() > 100) { + return diff_lineMode(text1, text2, deadline); + } + + return diff_bisect(text1, text2, deadline); + } + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private LinkedList diff_lineMode(String text1, String text2, + long deadline) { + // Scan the text on a line-by-line basis first. + LinesToCharsResult a = diff_linesToChars(text1, text2); + text1 = a.chars1; + text2 = a.chars2; + List linearray = a.lineArray; + + LinkedList diffs = diff_main(text1, text2, false, deadline); + + // Convert the diff back to original text. + diff_charsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + diff_cleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.add(new Diff(Operation.EQUAL, "")); + int count_delete = 0; + int count_insert = 0; + String text_delete = ""; + String text_insert = ""; + ListIterator pointer = diffs.listIterator(); + Diff thisDiff = pointer.next(); + while (thisDiff != null) { + switch (thisDiff.operation) { + case INSERT: + count_insert++; + text_insert += thisDiff.text; + break; + case DELETE: + count_delete++; + text_delete += thisDiff.text; + break; + case EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. + pointer.previous(); + for (int j = 0; j < count_delete + count_insert; j++) { + pointer.previous(); + pointer.remove(); + } + for (Diff subDiff : diff_main(text_delete, text_insert, false, + deadline)) { + pointer.add(subDiff); + } + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + diffs.removeLast(); // Remove the dummy entry at the end. + + return diffs; + } + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + protected LinkedList diff_bisect(String text1, String text2, + long deadline) { + // Cache the text lengths to prevent multiple calls. + int text1_length = text1.length(); + int text2_length = text2.length(); + int max_d = (text1_length + text2_length + 1) / 2; + int v_offset = max_d; + int v_length = 2 * max_d; + int[] v1 = new int[v_length]; + int[] v2 = new int[v_length]; + for (int x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + int delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will + // collide with the reverse path. + boolean front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + int k1start = 0; + int k1end = 0; + int k2start = 0; + int k2end = 0; + for (int d = 0; d < max_d; d++) { + // Bail out if deadline is reached. + if (System.currentTimeMillis() > deadline) { + break; + } + + // Walk the front path one step. + for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + int k1_offset = v_offset + k1; + int x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + int y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length + && text1.charAt(x1) == text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + int k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + int x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + int k2_offset = v_offset + k2; + int x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + int y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length + && text1.charAt(text1_length - x2 - 1) + == text2.charAt(text2_length - y2 - 1)) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + int k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + int x1 = v1[k1_offset]; + int y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + LinkedList diffs = new LinkedList(); + diffs.add(new Diff(Operation.DELETE, text1)); + diffs.add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param x Index of split point in text1. + * @param y Index of split point in text2. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + private LinkedList diff_bisectSplit(String text1, String text2, + int x, int y, long deadline) { + String text1a = text1.substring(0, x); + String text2a = text2.substring(0, y); + String text1b = text1.substring(x); + String text2b = text2.substring(y); + + // Compute both diffs serially. + LinkedList diffs = diff_main(text1a, text2a, false, deadline); + LinkedList diffsb = diff_main(text1b, text2b, false, deadline); + + diffs.addAll(diffsb); + return diffs; + } + + /** + * Split two texts into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text1 First string. + * @param text2 Second string. + * @return An object containing the encoded text1, the encoded text2 and + * the List of unique strings. The zeroth element of the List of + * unique strings is intentionally blank. + */ + protected LinesToCharsResult diff_linesToChars(String text1, String text2) { + List lineArray = new ArrayList(); + Map lineHash = new HashMap(); + // e.g. linearray[4] == "Hello\n" + // e.g. linehash.get("Hello\n") == 4 + + // "\x00" is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray.add(""); + + // Allocate 2/3rds of the space for text1, the rest for text2. + String chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash, 40000); + String chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash, 65535); + return new LinesToCharsResult(chars1, chars2, lineArray); + } + + /** + * Split a text into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text String to encode. + * @param lineArray List of unique strings. + * @param lineHash Map of strings to indices. + * @param maxLines Maximum length of lineArray. + * @return Encoded string. + */ + private String diff_linesToCharsMunge(String text, List lineArray, + Map lineHash, int maxLines) { + int lineStart = 0; + int lineEnd = -1; + String line; + StringBuilder chars = new StringBuilder(); + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + while (lineEnd < text.length() - 1) { + lineEnd = text.indexOf('\n', lineStart); + if (lineEnd == -1) { + lineEnd = text.length() - 1; + } + line = text.substring(lineStart, lineEnd + 1); + + if (lineHash.containsKey(line)) { + chars.append(String.valueOf((char) (int) lineHash.get(line))); + } else { + if (lineArray.size() == maxLines) { + // Bail out at 65535 because + // String.valueOf((char) 65536).equals(String.valueOf(((char) 0))) + line = text.substring(lineStart); + lineEnd = text.length(); + } + lineArray.add(line); + lineHash.put(line, lineArray.size() - 1); + chars.append(String.valueOf((char) (lineArray.size() - 1))); + } + lineStart = lineEnd + 1; + } + return chars.toString(); + } + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param diffs List of Diff objects. + * @param lineArray List of unique strings. + */ + protected void diff_charsToLines(List diffs, + List lineArray) { + StringBuilder text; + for (Diff diff : diffs) { + text = new StringBuilder(); + for (int j = 0; j < diff.text.length(); j++) { + text.append(lineArray.get(diff.text.charAt(j))); + } + diff.text = text.toString(); + } + } + + /** + * Determine the common prefix of two strings + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ + public int diff_commonPrefix(String text1, String text2) { + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + int n = Math.min(text1.length(), text2.length()); + for (int i = 0; i < n; i++) { + if (text1.charAt(i) != text2.charAt(i)) { + return i; + } + } + return n; + } + + /** + * Determine the common suffix of two strings + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ + public int diff_commonSuffix(String text1, String text2) { + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + int text1_length = text1.length(); + int text2_length = text2.length(); + int n = Math.min(text1_length, text2_length); + for (int i = 1; i <= n; i++) { + if (text1.charAt(text1_length - i) != text2.charAt(text2_length - i)) { + return i - 1; + } + } + return n; + } + + /** + * Determine if the suffix of one string is the prefix of another. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of the first + * string and the start of the second string. + */ + protected int diff_commonOverlap(String text1, String text2) { + // Cache the text lengths to prevent multiple calls. + int text1_length = text1.length(); + int text2_length = text2.length(); + // Eliminate the null case. + if (text1_length == 0 || text2_length == 0) { + return 0; + } + // Truncate the longer string. + if (text1_length > text2_length) { + text1 = text1.substring(text1_length - text2_length); + } else if (text1_length < text2_length) { + text2 = text2.substring(0, text1_length); + } + int text_length = Math.min(text1_length, text2_length); + // Quick check for the worst case. + if (text1.equals(text2)) { + return text_length; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + int best = 0; + int length = 1; + while (true) { + String pattern = text1.substring(text_length - length); + int found = text2.indexOf(pattern); + if (found == -1) { + return best; + } + length += found; + if (found == 0 || text1.substring(text_length - length).equals( + text2.substring(0, length))) { + best = length; + length++; + } + } + } + + /** + * Do the two texts share a substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * @param text1 First string. + * @param text2 Second string. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or null if there was no match. + */ + protected String[] diff_halfMatch(String text1, String text2) { + if (Diff_Timeout <= 0) { + // Don't risk returning a non-optimal diff if we have unlimited time. + return null; + } + String longtext = text1.length() > text2.length() ? text1 : text2; + String shorttext = text1.length() > text2.length() ? text2 : text1; + if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) { + return null; // Pointless. + } + + // First check if the second quarter is the seed for a half-match. + String[] hm1 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 3) / 4); + // Check again based on the third quarter. + String[] hm2 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 1) / 2); + String[] hm; + if (hm1 == null && hm2 == null) { + return null; + } else if (hm2 == null) { + hm = hm1; + } else if (hm1 == null) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.length() > text2.length()) { + return hm; + //return new String[]{hm[0], hm[1], hm[2], hm[3], hm[4]}; + } else { + return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]}; + } + } + + /** + * Does a substring of shorttext exist within longtext such that the + * substring is at least half the length of longtext? + * @param longtext Longer string. + * @param shorttext Shorter string. + * @param i Start index of quarter length substring within longtext. + * @return Five element String array, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or null if there was no match. + */ + private String[] diff_halfMatchI(String longtext, String shorttext, int i) { + // Start with a 1/4 length substring at position i as a seed. + String seed = longtext.substring(i, i + longtext.length() / 4); + int j = -1; + String best_common = ""; + String best_longtext_a = "", best_longtext_b = ""; + String best_shorttext_a = "", best_shorttext_b = ""; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + int prefixLength = diff_commonPrefix(longtext.substring(i), + shorttext.substring(j)); + int suffixLength = diff_commonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (best_common.length() < suffixLength + prefixLength) { + best_common = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + best_longtext_a = longtext.substring(0, i - suffixLength); + best_longtext_b = longtext.substring(i + prefixLength); + best_shorttext_a = shorttext.substring(0, j - suffixLength); + best_shorttext_b = shorttext.substring(j + prefixLength); + } + } + if (best_common.length() * 2 >= longtext.length()) { + return new String[]{best_longtext_a, best_longtext_b, + best_shorttext_a, best_shorttext_b, best_common}; + } else { + return null; + } + } + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupSemantic(LinkedList diffs) { + if (diffs.isEmpty()) { + return; + } + boolean changes = false; + Deque equalities = new ArrayDeque(); // Double-ended queue of qualities. + String lastEquality = null; // Always equal to equalities.peek().text + ListIterator pointer = diffs.listIterator(); + // Number of characters that changed prior to the equality. + int length_insertions1 = 0; + int length_deletions1 = 0; + // Number of characters that changed after the equality. + int length_insertions2 = 0; + int length_deletions2 = 0; + Diff thisDiff = pointer.next(); + while (thisDiff != null) { + if (thisDiff.operation == Operation.EQUAL) { + // Equality found. + equalities.push(thisDiff); + length_insertions1 = length_insertions2; + length_deletions1 = length_deletions2; + length_insertions2 = 0; + length_deletions2 = 0; + lastEquality = thisDiff.text; + } else { + // An insertion or deletion. + if (thisDiff.operation == Operation.INSERT) { + length_insertions2 += thisDiff.text.length(); + } else { + length_deletions2 += thisDiff.text.length(); + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastEquality != null && (lastEquality.length() + <= Math.max(length_insertions1, length_deletions1)) + && (lastEquality.length() + <= Math.max(length_insertions2, length_deletions2))) { + //System.out.println("Splitting: '" + lastEquality + "'"); + // Walk back to offending equality. + while (thisDiff != equalities.peek()) { + thisDiff = pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.set(new Diff(Operation.DELETE, lastEquality)); + // Insert a corresponding an insert. + pointer.add(new Diff(Operation.INSERT, lastEquality)); + + equalities.pop(); // Throw away the equality we just deleted. + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous equalities, walk back to the start. + while (pointer.hasPrevious()) { + pointer.previous(); + } + } else { + // There is a safe equality we can fall back to. + thisDiff = equalities.peek(); + while (thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + } + + length_insertions1 = 0; // Reset the counters. + length_insertions2 = 0; + length_deletions1 = 0; + length_deletions2 = 0; + lastEquality = null; + changes = true; + } + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + + // Normalize the diff. + if (changes) { + diff_cleanupMerge(diffs); + } + diff_cleanupSemanticLossless(diffs); + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = diffs.listIterator(); + Diff prevDiff = null; + thisDiff = null; + if (pointer.hasNext()) { + prevDiff = pointer.next(); + if (pointer.hasNext()) { + thisDiff = pointer.next(); + } + } + while (thisDiff != null) { + if (prevDiff.operation == Operation.DELETE && + thisDiff.operation == Operation.INSERT) { + String deletion = prevDiff.text; + String insertion = thisDiff.text; + int overlap_length1 = this.diff_commonOverlap(deletion, insertion); + int overlap_length2 = this.diff_commonOverlap(insertion, deletion); + if (overlap_length1 >= overlap_length2) { + if (overlap_length1 >= deletion.length() / 2.0 || + overlap_length1 >= insertion.length() / 2.0) { + // Overlap found. Insert an equality and trim the surrounding edits. + pointer.previous(); + pointer.add(new Diff(Operation.EQUAL, + insertion.substring(0, overlap_length1))); + prevDiff.text = + deletion.substring(0, deletion.length() - overlap_length1); + thisDiff.text = insertion.substring(overlap_length1); + // pointer.add inserts the element before the cursor, so there is + // no need to step past the new element. + } + } else { + if (overlap_length2 >= deletion.length() / 2.0 || + overlap_length2 >= insertion.length() / 2.0) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + pointer.previous(); + pointer.add(new Diff(Operation.EQUAL, + deletion.substring(0, overlap_length2))); + prevDiff.operation = Operation.INSERT; + prevDiff.text = + insertion.substring(0, insertion.length() - overlap_length2); + thisDiff.operation = Operation.DELETE; + thisDiff.text = deletion.substring(overlap_length2); + // pointer.add inserts the element before the cursor, so there is + // no need to step past the new element. + } + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + prevDiff = thisDiff; + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + } + + /** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The cat came. -> The cat came. + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupSemanticLossless(LinkedList diffs) { + String equality1, edit, equality2; + String commonString; + int commonOffset; + int score, bestScore; + String bestEquality1, bestEdit, bestEquality2; + // Create a new iterator at the start. + ListIterator pointer = diffs.listIterator(); + Diff prevDiff = pointer.hasNext() ? pointer.next() : null; + Diff thisDiff = pointer.hasNext() ? pointer.next() : null; + Diff nextDiff = pointer.hasNext() ? pointer.next() : null; + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != null) { + if (prevDiff.operation == Operation.EQUAL && + nextDiff.operation == Operation.EQUAL) { + // This is a single edit surrounded by equalities. + equality1 = prevDiff.text; + edit = thisDiff.text; + equality2 = nextDiff.text; + + // First, shift the edit as far left as possible. + commonOffset = diff_commonSuffix(equality1, edit); + if (commonOffset != 0) { + commonString = edit.substring(edit.length() - commonOffset); + equality1 = equality1.substring(0, equality1.length() - commonOffset); + edit = commonString + edit.substring(0, edit.length() - commonOffset); + equality2 = commonString + equality2; + } + + // Second, step character by character right, looking for the best fit. + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + bestScore = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + while (edit.length() != 0 && equality2.length() != 0 + && edit.charAt(0) == equality2.charAt(0)) { + equality1 += edit.charAt(0); + edit = edit.substring(1) + equality2.charAt(0); + equality2 = equality2.substring(1); + score = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + // The >= encourages trailing rather than leading whitespace on edits. + if (score >= bestScore) { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (!prevDiff.text.equals(bestEquality1)) { + // We have an improvement, save it back to the diff. + if (bestEquality1.length() != 0) { + prevDiff.text = bestEquality1; + } else { + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + pointer.next(); // Walk past nextDiff. + } + thisDiff.text = bestEdit; + if (bestEquality2.length() != 0) { + nextDiff.text = bestEquality2; + } else { + pointer.remove(); // Delete nextDiff. + nextDiff = thisDiff; + thisDiff = prevDiff; + } + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? pointer.next() : null; + } + } + + /** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 6 (best) to 0 (worst). + * @param one First string. + * @param two Second string. + * @return The score. + */ + private int diff_cleanupSemanticScore(String one, String two) { + if (one.length() == 0 || two.length() == 0) { + // Edges are the best. + return 6; + } + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + char char1 = one.charAt(one.length() - 1); + char char2 = two.charAt(0); + boolean nonAlphaNumeric1 = !Character.isLetterOrDigit(char1); + boolean nonAlphaNumeric2 = !Character.isLetterOrDigit(char2); + boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1); + boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2); + boolean lineBreak1 = whitespace1 + && Character.getType(char1) == Character.CONTROL; + boolean lineBreak2 = whitespace2 + && Character.getType(char2) == Character.CONTROL; + boolean blankLine1 = lineBreak1 && BLANKLINEEND.matcher(one).find(); + boolean blankLine2 = lineBreak2 && BLANKLINESTART.matcher(two).find(); + + if (blankLine1 || blankLine2) { + // Five points for blank lines. + return 5; + } else if (lineBreak1 || lineBreak2) { + // Four points for line breaks. + return 4; + } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { + // Three points for end of sentences. + return 3; + } else if (whitespace1 || whitespace2) { + // Two points for whitespace. + return 2; + } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { + // One point for non-alphanumeric. + return 1; + } + return 0; + } + + // Define some regex patterns for matching boundaries. + private Pattern BLANKLINEEND + = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL); + private Pattern BLANKLINESTART + = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL); + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupEfficiency(LinkedList diffs) { + if (diffs.isEmpty()) { + return; + } + boolean changes = false; + Deque equalities = new ArrayDeque(); // Double-ended queue of equalities. + String lastEquality = null; // Always equal to equalities.peek().text + ListIterator pointer = diffs.listIterator(); + // Is there an insertion operation before the last equality. + boolean pre_ins = false; + // Is there a deletion operation before the last equality. + boolean pre_del = false; + // Is there an insertion operation after the last equality. + boolean post_ins = false; + // Is there a deletion operation after the last equality. + boolean post_del = false; + Diff thisDiff = pointer.next(); + Diff safeDiff = thisDiff; // The last Diff that is known to be unsplittable. + while (thisDiff != null) { + if (thisDiff.operation == Operation.EQUAL) { + // Equality found. + if (thisDiff.text.length() < Diff_EditCost && (post_ins || post_del)) { + // Candidate found. + equalities.push(thisDiff); + pre_ins = post_ins; + pre_del = post_del; + lastEquality = thisDiff.text; + } else { + // Not a candidate, and can never become one. + equalities.clear(); + lastEquality = null; + safeDiff = thisDiff; + } + post_ins = post_del = false; + } else { + // An insertion or deletion. + if (thisDiff.operation == Operation.DELETE) { + post_del = true; + } else { + post_ins = true; + } + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (lastEquality != null + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastEquality.length() < Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { + //System.out.println("Splitting: '" + lastEquality + "'"); + // Walk back to offending equality. + while (thisDiff != equalities.peek()) { + thisDiff = pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.set(new Diff(Operation.DELETE, lastEquality)); + // Insert a corresponding an insert. + pointer.add(thisDiff = new Diff(Operation.INSERT, lastEquality)); + + equalities.pop(); // Throw away the equality we just deleted. + lastEquality = null; + if (pre_ins && pre_del) { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = true; + equalities.clear(); + safeDiff = thisDiff; + } else { + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous questionable equalities, + // walk back to the last known safe diff. + thisDiff = safeDiff; + } else { + // There is an equality we can fall back to. + thisDiff = equalities.peek(); + } + while (thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + post_ins = post_del = false; + } + + changes = true; + } + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + + if (changes) { + diff_cleanupMerge(diffs); + } + } + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupMerge(LinkedList diffs) { + diffs.add(new Diff(Operation.EQUAL, "")); // Add a dummy entry at the end. + ListIterator pointer = diffs.listIterator(); + int count_delete = 0; + int count_insert = 0; + String text_delete = ""; + String text_insert = ""; + Diff thisDiff = pointer.next(); + Diff prevEqual = null; + int commonlength; + while (thisDiff != null) { + switch (thisDiff.operation) { + case INSERT: + count_insert++; + text_insert += thisDiff.text; + prevEqual = null; + break; + case DELETE: + count_delete++; + text_delete += thisDiff.text; + prevEqual = null; + break; + case EQUAL: + if (count_delete + count_insert > 1) { + boolean both_types = count_delete != 0 && count_insert != 0; + // Delete the offending records. + pointer.previous(); // Reverse direction. + while (count_delete-- > 0) { + pointer.previous(); + pointer.remove(); + } + while (count_insert-- > 0) { + pointer.previous(); + pointer.remove(); + } + if (both_types) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength != 0) { + if (pointer.hasPrevious()) { + thisDiff = pointer.previous(); + assert thisDiff.operation == Operation.EQUAL + : "Previous diff should have been an equality."; + thisDiff.text += text_insert.substring(0, commonlength); + pointer.next(); + } else { + pointer.add(new Diff(Operation.EQUAL, + text_insert.substring(0, commonlength))); + } + text_insert = text_insert.substring(commonlength); + text_delete = text_delete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength != 0) { + thisDiff = pointer.next(); + thisDiff.text = text_insert.substring(text_insert.length() + - commonlength) + thisDiff.text; + text_insert = text_insert.substring(0, text_insert.length() + - commonlength); + text_delete = text_delete.substring(0, text_delete.length() + - commonlength); + pointer.previous(); + } + } + // Insert the merged records. + if (text_delete.length() != 0) { + pointer.add(new Diff(Operation.DELETE, text_delete)); + } + if (text_insert.length() != 0) { + pointer.add(new Diff(Operation.INSERT, text_insert)); + } + // Step forward to the equality. + thisDiff = pointer.hasNext() ? pointer.next() : null; + } else if (prevEqual != null) { + // Merge this equality with the previous one. + prevEqual.text += thisDiff.text; + pointer.remove(); + thisDiff = pointer.previous(); + pointer.next(); // Forward direction + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + prevEqual = thisDiff; + break; + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + if (diffs.getLast().text.length() == 0) { + diffs.removeLast(); // Remove the dummy entry at the end. + } + + /* + * Second pass: look for single edits surrounded on both sides by equalities + * which can be shifted sideways to eliminate an equality. + * e.g: ABAC -> ABAC + */ + boolean changes = false; + // Create a new iterator at the start. + // (As opposed to walking the current one back.) + pointer = diffs.listIterator(); + Diff prevDiff = pointer.hasNext() ? pointer.next() : null; + thisDiff = pointer.hasNext() ? pointer.next() : null; + Diff nextDiff = pointer.hasNext() ? pointer.next() : null; + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != null) { + if (prevDiff.operation == Operation.EQUAL && + nextDiff.operation == Operation.EQUAL) { + // This is a single edit surrounded by equalities. + if (thisDiff.text.endsWith(prevDiff.text)) { + // Shift the edit over the previous equality. + thisDiff.text = prevDiff.text + + thisDiff.text.substring(0, thisDiff.text.length() + - prevDiff.text.length()); + nextDiff.text = prevDiff.text + nextDiff.text; + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + thisDiff = pointer.next(); // Walk past nextDiff. + nextDiff = pointer.hasNext() ? pointer.next() : null; + changes = true; + } else if (thisDiff.text.startsWith(nextDiff.text)) { + // Shift the edit over the next equality. + prevDiff.text += nextDiff.text; + thisDiff.text = thisDiff.text.substring(nextDiff.text.length()) + + nextDiff.text; + pointer.remove(); // Delete nextDiff. + nextDiff = pointer.hasNext() ? pointer.next() : null; + changes = true; + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? pointer.next() : null; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + diff_cleanupMerge(diffs); + } + } + + /** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. "The cat" vs "The big cat", 1->1, 5->8 + * @param diffs List of Diff objects. + * @param loc Location within text1. + * @return Location within text2. + */ + public int diff_xIndex(List diffs, int loc) { + int chars1 = 0; + int chars2 = 0; + int last_chars1 = 0; + int last_chars2 = 0; + Diff lastDiff = null; + for (Diff aDiff : diffs) { + if (aDiff.operation != Operation.INSERT) { + // Equality or deletion. + chars1 += aDiff.text.length(); + } + if (aDiff.operation != Operation.DELETE) { + // Equality or insertion. + chars2 += aDiff.text.length(); + } + if (chars1 > loc) { + // Overshot the location. + lastDiff = aDiff; + break; + } + last_chars1 = chars1; + last_chars2 = chars2; + } + if (lastDiff != null && lastDiff.operation == Operation.DELETE) { + // The location was deleted. + return last_chars2; + } + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); + } + + /** + * Convert a Diff list into a pretty HTML report. + * @param diffs List of Diff objects. + * @return HTML representation. + */ + public String diff_prettyHtml(List diffs) { + StringBuilder html = new StringBuilder(); + for (Diff aDiff : diffs) { + String text = aDiff.text.replace("&", "&").replace("<", "<") + .replace(">", ">"); + switch (aDiff.operation) { + case INSERT: + html.append("").append(text.replace("\n","\n")).append(""); + break; + case DELETE: + html.append("").append(text.replace("\n","\n")).append(""); + break; + case EQUAL: + html.append("").append(text).append(""); + break; + } + } + return html.toString(); + } + + /** + * Compute and return the source text (all equalities and deletions). + * @param diffs List of Diff objects. + * @return Source text. + */ + public String diff_text1(List diffs) { + StringBuilder text = new StringBuilder(); + for (Diff aDiff : diffs) { + if (aDiff.operation != Operation.INSERT) { + text.append(aDiff.text); + } + } + return text.toString(); + } + + /** + * Compute and return the destination text (all equalities and insertions). + * @param diffs List of Diff objects. + * @return Destination text. + */ + public String diff_text2(List diffs) { + StringBuilder text = new StringBuilder(); + for (Diff aDiff : diffs) { + if (aDiff.operation != Operation.DELETE) { + text.append(aDiff.text); + } + } + return text.toString(); + } + + /** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * @param diffs List of Diff objects. + * @return Number of changes. + */ + public int diff_levenshtein(List diffs) { + int levenshtein = 0; + int insertions = 0; + int deletions = 0; + for (Diff aDiff : diffs) { + switch (aDiff.operation) { + case INSERT: + insertions += aDiff.text.length(); + break; + case DELETE: + deletions += aDiff.text.length(); + break; + case EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += Math.max(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + levenshtein += Math.max(insertions, deletions); + return levenshtein; + } + + /** + * Crush the diff into an encoded string which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx notation. + * @param diffs List of Diff objects. + * @return Delta text. + */ + public String diff_toDelta(List diffs) { + StringBuilder text = new StringBuilder(); + for (Diff aDiff : diffs) { + switch (aDiff.operation) { + case INSERT: + try { + text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8") + .replace('+', ' ')).append("\t"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } + break; + case DELETE: + text.append("-").append(aDiff.text.length()).append("\t"); + break; + case EQUAL: + text.append("=").append(aDiff.text.length()).append("\t"); + break; + } + } + String delta = text.toString(); + if (delta.length() != 0) { + // Strip off trailing tab character. + delta = delta.substring(0, delta.length() - 1); + delta = unescapeForEncodeUriCompatability(delta); + } + return delta; + } + + /** + * Given the original text1, and an encoded string which describes the + * operations required to transform text1 into text2, compute the full diff. + * @param text1 Source string for the diff. + * @param delta Delta text. + * @return Array of Diff objects or null if invalid. + * @throws IllegalArgumentException If invalid input. + */ + public LinkedList diff_fromDelta(String text1, String delta) + throws IllegalArgumentException { + LinkedList diffs = new LinkedList(); + int pointer = 0; // Cursor in text1 + String[] tokens = delta.split("\t"); + for (String token : tokens) { + if (token.length() == 0) { + // Blank tokens are ok (from a trailing \t). + continue; + } + // Each token begins with a one character parameter which specifies the + // operation of this token (delete, insert, equality). + String param = token.substring(1); + switch (token.charAt(0)) { + case '+': + // decode would change all "+" to " " + param = param.replace("+", "%2B"); + try { + param = URLDecoder.decode(param, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } catch (IllegalArgumentException e) { + // Malformed URI sequence. + throw new IllegalArgumentException( + "Illegal escape in diff_fromDelta: " + param, e); + } + diffs.add(new Diff(Operation.INSERT, param)); + break; + case '-': + // Fall through. + case '=': + int n; + try { + n = Integer.parseInt(param); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid number in diff_fromDelta: " + param, e); + } + if (n < 0) { + throw new IllegalArgumentException( + "Negative number in diff_fromDelta: " + param); + } + String text; + try { + text = text1.substring(pointer, pointer += n); + } catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Delta length (" + pointer + + ") larger than source text length (" + text1.length() + + ").", e); + } + if (token.charAt(0) == '=') { + diffs.add(new Diff(Operation.EQUAL, text)); + } else { + diffs.add(new Diff(Operation.DELETE, text)); + } + break; + default: + // Anything else is an error. + throw new IllegalArgumentException( + "Invalid diff operation in diff_fromDelta: " + token.charAt(0)); + } + } + if (pointer != text1.length()) { + throw new IllegalArgumentException("Delta length (" + pointer + + ") smaller than source text length (" + text1.length() + ")."); + } + return diffs; + } + + + // MATCH FUNCTIONS + + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + public int match_main(String text, String pattern, int loc) { + // Check for null inputs. + if (text == null || pattern == null) { + throw new IllegalArgumentException("Null inputs. (match_main)"); + } + + loc = Math.max(0, Math.min(loc, text.length())); + if (text.equals(pattern)) { + // Shortcut (potentially not guaranteed by the algorithm) + return 0; + } else if (text.length() == 0) { + // Nothing to match. + return -1; + } else if (loc + pattern.length() <= text.length() + && text.substring(loc, loc + pattern.length()).equals(pattern)) { + // Perfect match at the perfect spot! (Includes case of null pattern) + return loc; + } else { + // Do a fuzzy compare. + return match_bitap(text, pattern, loc); + } + } + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + protected int match_bitap(String text, String pattern, int loc) { + assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits) + : "Pattern too long for this application."; + + // Initialise the alphabet. + Map s = match_alphabet(pattern); + + // Highest score beyond which we give up. + double score_threshold = Match_Threshold; + // Is there a nearby exact match? (speedup) + int best_loc = text.indexOf(pattern, loc); + if (best_loc != -1) { + score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + // What about in the other direction? (speedup) + best_loc = text.lastIndexOf(pattern, loc + pattern.length()); + if (best_loc != -1) { + score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + } + } + + // Initialise the bit arrays. + int matchmask = 1 << (pattern.length() - 1); + best_loc = -1; + + int bin_min, bin_mid; + int bin_max = pattern.length() + text.length(); + // Empty initialization added to appease Java compiler. + int[] last_rd = new int[0]; + for (int d = 0; d < pattern.length(); d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at + // this error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + if (match_bitapScore(d, loc + bin_mid, loc, pattern) + <= score_threshold) { + bin_min = bin_mid; + } else { + bin_max = bin_mid; + } + bin_mid = (bin_max - bin_min) / 2 + bin_min; + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + int start = Math.max(1, loc - bin_mid + 1); + int finish = Math.min(loc + bin_mid, text.length()) + pattern.length(); + + int[] rd = new int[finish + 2]; + rd[finish + 1] = (1 << d) - 1; + for (int j = finish; j >= start; j--) { + int charMatch; + if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) { + // Out of range. + charMatch = 0; + } else { + charMatch = s.get(text.charAt(j - 1)); + } + if (d == 0) { + // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } else { + // Subsequent passes: fuzzy match. + rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; + } + if ((rd[j] & matchmask) != 0) { + double score = match_bitapScore(d, j - 1, loc, pattern); + // This match will almost certainly be better than any existing + // match. But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) { + // When passing loc, don't exceed our current distance from loc. + start = Math.max(1, 2 * loc - best_loc); + } else { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) { + // No hope for a (better) match at greater error levels. + break; + } + last_rd = rd; + } + return best_loc; + } + + /** + * Compute and return the score for a match with e errors and x location. + * @param e Number of errors in match. + * @param x Location of match. + * @param loc Expected location of match. + * @param pattern Pattern being sought. + * @return Overall score for match (0.0 = good, 1.0 = bad). + */ + private double match_bitapScore(int e, int x, int loc, String pattern) { + float accuracy = (float) e / pattern.length(); + int proximity = Math.abs(loc - x); + if (Match_Distance == 0) { + // Dodge divide by zero error. + return proximity == 0 ? accuracy : 1.0; + } + return accuracy + (proximity / (float) Match_Distance); + } + + /** + * Initialise the alphabet for the Bitap algorithm. + * @param pattern The text to encode. + * @return Hash of character locations. + */ + protected Map match_alphabet(String pattern) { + Map s = new HashMap(); + char[] char_pattern = pattern.toCharArray(); + for (char c : char_pattern) { + s.put(c, 0); + } + int i = 0; + for (char c : char_pattern) { + s.put(c, s.get(c) | (1 << (pattern.length() - i - 1))); + i++; + } + return s; + } + + + // PATCH FUNCTIONS + + + /** + * Increase the context until it is unique, + * but don't let the pattern expand beyond Match_MaxBits. + * @param patch The patch to grow. + * @param text Source text. + */ + protected void patch_addContext(Patch patch, String text) { + if (text.length() == 0) { + return; + } + String pattern = text.substring(patch.start2, patch.start2 + patch.length1); + int padding = 0; + + // Look for the first and last matches of pattern in text. If two different + // matches are found, increase the pattern length. + while (text.indexOf(pattern) != text.lastIndexOf(pattern) + && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { + padding += Patch_Margin; + pattern = text.substring(Math.max(0, patch.start2 - padding), + Math.min(text.length(), patch.start2 + patch.length1 + padding)); + } + // Add one chunk for good luck. + padding += Patch_Margin; + + // Add the prefix. + String prefix = text.substring(Math.max(0, patch.start2 - padding), + patch.start2); + if (prefix.length() != 0) { + patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix)); + } + // Add the suffix. + String suffix = text.substring(patch.start2 + patch.length1, + Math.min(text.length(), patch.start2 + patch.length1 + padding)); + if (suffix.length() != 0) { + patch.diffs.addLast(new Diff(Operation.EQUAL, suffix)); + } + + // Roll back the start points. + patch.start1 -= prefix.length(); + patch.start2 -= prefix.length(); + // Extend the lengths. + patch.length1 += prefix.length() + suffix.length(); + patch.length2 += prefix.length() + suffix.length(); + } + + /** + * Compute a list of patches to turn text1 into text2. + * A set of diffs will be computed. + * @param text1 Old text. + * @param text2 New text. + * @return LinkedList of Patch objects. + */ + public LinkedList patch_make(String text1, String text2) { + if (text1 == null || text2 == null) { + throw new IllegalArgumentException("Null inputs. (patch_make)"); + } + // No diffs provided, compute our own. + LinkedList diffs = diff_main(text1, text2, true); + if (diffs.size() > 2) { + diff_cleanupSemantic(diffs); + diff_cleanupEfficiency(diffs); + } + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text1 will be derived from the provided diffs. + * @param diffs Array of Diff objects for text1 to text2. + * @return LinkedList of Patch objects. + */ + public LinkedList patch_make(LinkedList diffs) { + if (diffs == null) { + throw new IllegalArgumentException("Null inputs. (patch_make)"); + } + // No origin string provided, compute our own. + String text1 = diff_text1(diffs); + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is ignored, diffs are the delta between text1 and text2. + * @param text1 Old text + * @param text2 Ignored. + * @param diffs Array of Diff objects for text1 to text2. + * @return LinkedList of Patch objects. + * @deprecated Prefer patch_make(String text1, LinkedList diffs). + */ + @Deprecated public LinkedList patch_make(String text1, String text2, + LinkedList diffs) { + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is not provided, diffs are the delta between text1 and text2. + * @param text1 Old text. + * @param diffs Array of Diff objects for text1 to text2. + * @return LinkedList of Patch objects. + */ + public LinkedList patch_make(String text1, LinkedList diffs) { + if (text1 == null || diffs == null) { + throw new IllegalArgumentException("Null inputs. (patch_make)"); + } + + LinkedList patches = new LinkedList(); + if (diffs.isEmpty()) { + return patches; // Get rid of the null case. + } + Patch patch = new Patch(); + int char_count1 = 0; // Number of characters into the text1 string. + int char_count2 = 0; // Number of characters into the text2 string. + // Start with text1 (prepatch_text) and apply the diffs until we arrive at + // text2 (postpatch_text). We recreate the patches one by one to determine + // context info. + String prepatch_text = text1; + String postpatch_text = text1; + for (Diff aDiff : diffs) { + if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) { + // A new patch starts here. + patch.start1 = char_count1; + patch.start2 = char_count2; + } + + switch (aDiff.operation) { + case INSERT: + patch.diffs.add(aDiff); + patch.length2 += aDiff.text.length(); + postpatch_text = postpatch_text.substring(0, char_count2) + + aDiff.text + postpatch_text.substring(char_count2); + break; + case DELETE: + patch.length1 += aDiff.text.length(); + patch.diffs.add(aDiff); + postpatch_text = postpatch_text.substring(0, char_count2) + + postpatch_text.substring(char_count2 + aDiff.text.length()); + break; + case EQUAL: + if (aDiff.text.length() <= 2 * Patch_Margin + && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) { + // Small equality inside a patch. + patch.diffs.add(aDiff); + patch.length1 += aDiff.text.length(); + patch.length2 += aDiff.text.length(); + } + + if (aDiff.text.length() >= 2 * Patch_Margin && !patch.diffs.isEmpty()) { + // Time for a new patch. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.add(patch); + patch = new Patch(); + // Unlike Unidiff, our patch lists have a rolling context. + // https://github.com/google/diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + prepatch_text = postpatch_text; + char_count1 = char_count2; + } + } + break; + } + + // Update the current character count. + if (aDiff.operation != Operation.INSERT) { + char_count1 += aDiff.text.length(); + } + if (aDiff.operation != Operation.DELETE) { + char_count2 += aDiff.text.length(); + } + } + // Pick up the leftover patch if not empty. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.add(patch); + } + + return patches; + } + + /** + * Given an array of patches, return another array that is identical. + * @param patches Array of Patch objects. + * @return Array of Patch objects. + */ + public LinkedList patch_deepCopy(LinkedList patches) { + LinkedList patchesCopy = new LinkedList(); + for (Patch aPatch : patches) { + Patch patchCopy = new Patch(); + for (Diff aDiff : aPatch.diffs) { + Diff diffCopy = new Diff(aDiff.operation, aDiff.text); + patchCopy.diffs.add(diffCopy); + } + patchCopy.start1 = aPatch.start1; + patchCopy.start2 = aPatch.start2; + patchCopy.length1 = aPatch.length1; + patchCopy.length2 = aPatch.length2; + patchesCopy.add(patchCopy); + } + return patchesCopy; + } + + /** + * Merge a set of patches onto the text. Return a patched text, as well + * as an array of true/false values indicating which patches were applied. + * @param patches Array of Patch objects + * @param text Old text. + * @return Two element Object array, containing the new text and an array of + * boolean values. + */ + public Object[] patch_apply(LinkedList patches, String text) { + if (patches.isEmpty()) { + return new Object[]{text, new boolean[0]}; + } + + // Deep copy the patches so that no changes are made to originals. + patches = patch_deepCopy(patches); + + String nullPadding = patch_addPadding(patches); + text = nullPadding + text + nullPadding; + patch_splitMax(patches); + + int x = 0; + // delta keeps track of the offset between the expected and actual location + // of the previous patch. If there are patches expected at positions 10 and + // 20, but the first patch was found at 12, delta is 2 and the second patch + // has an effective expected position of 22. + int delta = 0; + boolean[] results = new boolean[patches.size()]; + for (Patch aPatch : patches) { + int expected_loc = aPatch.start2 + delta; + String text1 = diff_text1(aPatch.diffs); + int start_loc; + int end_loc = -1; + if (text1.length() > this.Match_MaxBits) { + // patch_splitMax will only provide an oversized pattern in the case of + // a monster delete. + start_loc = match_main(text, + text1.substring(0, this.Match_MaxBits), expected_loc); + if (start_loc != -1) { + end_loc = match_main(text, + text1.substring(text1.length() - this.Match_MaxBits), + expected_loc + text1.length() - this.Match_MaxBits); + if (end_loc == -1 || start_loc >= end_loc) { + // Can't find valid trailing context. Drop this patch. + start_loc = -1; + } + } + } else { + start_loc = match_main(text, text1, expected_loc); + } + if (start_loc == -1) { + // No match found. :( + results[x] = false; + // Subtract the delta for this failed patch from subsequent patches. + delta -= aPatch.length2 - aPatch.length1; + } else { + // Found a match. :) + results[x] = true; + delta = start_loc - expected_loc; + String text2; + if (end_loc == -1) { + text2 = text.substring(start_loc, + Math.min(start_loc + text1.length(), text.length())); + } else { + text2 = text.substring(start_loc, + Math.min(end_loc + this.Match_MaxBits, text.length())); + } + if (text1.equals(text2)) { + // Perfect match, just shove the replacement text in. + text = text.substring(0, start_loc) + diff_text2(aPatch.diffs) + + text.substring(start_loc + text1.length()); + } else { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + LinkedList diffs = diff_main(text1, text2, false); + if (text1.length() > this.Match_MaxBits + && diff_levenshtein(diffs) / (float) text1.length() + > this.Patch_DeleteThreshold) { + // The end points match, but the content is unacceptably bad. + results[x] = false; + } else { + diff_cleanupSemanticLossless(diffs); + int index1 = 0; + for (Diff aDiff : aPatch.diffs) { + if (aDiff.operation != Operation.EQUAL) { + int index2 = diff_xIndex(diffs, index1); + if (aDiff.operation == Operation.INSERT) { + // Insertion + text = text.substring(0, start_loc + index2) + aDiff.text + + text.substring(start_loc + index2); + } else if (aDiff.operation == Operation.DELETE) { + // Deletion + text = text.substring(0, start_loc + index2) + + text.substring(start_loc + diff_xIndex(diffs, + index1 + aDiff.text.length())); + } + } + if (aDiff.operation != Operation.DELETE) { + index1 += aDiff.text.length(); + } + } + } + } + } + x++; + } + // Strip the padding off. + text = text.substring(nullPadding.length(), text.length() + - nullPadding.length()); + return new Object[]{text, results}; + } + + /** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * @param patches Array of Patch objects. + * @return The padding string added to each side. + */ + public String patch_addPadding(LinkedList patches) { + short paddingLength = this.Patch_Margin; + String nullPadding = ""; + for (short x = 1; x <= paddingLength; x++) { + nullPadding += String.valueOf((char) x); + } + + // Bump all the patches forward. + for (Patch aPatch : patches) { + aPatch.start1 += paddingLength; + aPatch.start2 += paddingLength; + } + + // Add some padding on start of first diff. + Patch patch = patches.getFirst(); + LinkedList diffs = patch.diffs; + if (diffs.isEmpty() || diffs.getFirst().operation != Operation.EQUAL) { + // Add nullPadding equality. + diffs.addFirst(new Diff(Operation.EQUAL, nullPadding)); + patch.start1 -= paddingLength; // Should be 0. + patch.start2 -= paddingLength; // Should be 0. + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } else if (paddingLength > diffs.getFirst().text.length()) { + // Grow first equality. + Diff firstDiff = diffs.getFirst(); + int extraLength = paddingLength - firstDiff.text.length(); + firstDiff.text = nullPadding.substring(firstDiff.text.length()) + + firstDiff.text; + patch.start1 -= extraLength; + patch.start2 -= extraLength; + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + // Add some padding on end of last diff. + patch = patches.getLast(); + diffs = patch.diffs; + if (diffs.isEmpty() || diffs.getLast().operation != Operation.EQUAL) { + // Add nullPadding equality. + diffs.addLast(new Diff(Operation.EQUAL, nullPadding)); + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } else if (paddingLength > diffs.getLast().text.length()) { + // Grow last equality. + Diff lastDiff = diffs.getLast(); + int extraLength = paddingLength - lastDiff.text.length(); + lastDiff.text += nullPadding.substring(0, extraLength); + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + return nullPadding; + } + + /** + * Look through the patches and break up any which are longer than the + * maximum limit of the match algorithm. + * Intended to be called only from within patch_apply. + * @param patches LinkedList of Patch objects. + */ + public void patch_splitMax(LinkedList patches) { + short patch_size = Match_MaxBits; + String precontext, postcontext; + Patch patch; + int start1, start2; + boolean empty; + Operation diff_type; + String diff_text; + ListIterator pointer = patches.listIterator(); + Patch bigpatch = pointer.hasNext() ? pointer.next() : null; + while (bigpatch != null) { + if (bigpatch.length1 <= Match_MaxBits) { + bigpatch = pointer.hasNext() ? pointer.next() : null; + continue; + } + // Remove the big old patch. + pointer.remove(); + start1 = bigpatch.start1; + start2 = bigpatch.start2; + precontext = ""; + while (!bigpatch.diffs.isEmpty()) { + // Create one of several smaller patches. + patch = new Patch(); + empty = true; + patch.start1 = start1 - precontext.length(); + patch.start2 = start2 - precontext.length(); + if (precontext.length() != 0) { + patch.length1 = patch.length2 = precontext.length(); + patch.diffs.add(new Diff(Operation.EQUAL, precontext)); + } + while (!bigpatch.diffs.isEmpty() + && patch.length1 < patch_size - Patch_Margin) { + diff_type = bigpatch.diffs.getFirst().operation; + diff_text = bigpatch.diffs.getFirst().text; + if (diff_type == Operation.INSERT) { + // Insertions are harmless. + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + patch.diffs.addLast(bigpatch.diffs.removeFirst()); + empty = false; + } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1 + && patch.diffs.getFirst().operation == Operation.EQUAL + && diff_text.length() > 2 * patch_size) { + // This is a large deletion. Let it pass in one chunk. + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + empty = false; + patch.diffs.add(new Diff(diff_type, diff_text)); + bigpatch.diffs.removeFirst(); + } else { + // Deletion or equality. Only take as much as we can stomach. + diff_text = diff_text.substring(0, Math.min(diff_text.length(), + patch_size - patch.length1 - Patch_Margin)); + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + if (diff_type == Operation.EQUAL) { + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + } else { + empty = false; + } + patch.diffs.add(new Diff(diff_type, diff_text)); + if (diff_text.equals(bigpatch.diffs.getFirst().text)) { + bigpatch.diffs.removeFirst(); + } else { + bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text + .substring(diff_text.length()); + } + } + } + // Compute the head context for the next patch. + precontext = diff_text2(patch.diffs); + precontext = precontext.substring(Math.max(0, precontext.length() + - Patch_Margin)); + // Append the end context for this patch. + if (diff_text1(bigpatch.diffs).length() > Patch_Margin) { + postcontext = diff_text1(bigpatch.diffs).substring(0, Patch_Margin); + } else { + postcontext = diff_text1(bigpatch.diffs); + } + if (postcontext.length() != 0) { + patch.length1 += postcontext.length(); + patch.length2 += postcontext.length(); + if (!patch.diffs.isEmpty() + && patch.diffs.getLast().operation == Operation.EQUAL) { + patch.diffs.getLast().text += postcontext; + } else { + patch.diffs.add(new Diff(Operation.EQUAL, postcontext)); + } + } + if (!empty) { + pointer.add(patch); + } + } + bigpatch = pointer.hasNext() ? pointer.next() : null; + } + } + + /** + * Take a list of patches and return a textual representation. + * @param patches List of Patch objects. + * @return Text representation of patches. + */ + public String patch_toText(List patches) { + StringBuilder text = new StringBuilder(); + for (Patch aPatch : patches) { + text.append(aPatch); + } + return text.toString(); + } + + /** + * Parse a textual representation of patches and return a List of Patch + * objects. + * @param textline Text representation of patches. + * @return List of Patch objects. + * @throws IllegalArgumentException If invalid input. + */ + public List patch_fromText(String textline) + throws IllegalArgumentException { + List patches = new LinkedList(); + if (textline.length() == 0) { + return patches; + } + List textList = Arrays.asList(textline.split("\n")); + LinkedList text = new LinkedList(textList); + Patch patch; + Pattern patchHeader + = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); + Matcher m; + char sign; + String line; + while (!text.isEmpty()) { + m = patchHeader.matcher(text.getFirst()); + if (!m.matches()) { + throw new IllegalArgumentException( + "Invalid patch string: " + text.getFirst()); + } + patch = new Patch(); + patches.add(patch); + patch.start1 = Integer.parseInt(m.group(1)); + if (m.group(2).length() == 0) { + patch.start1--; + patch.length1 = 1; + } else if (m.group(2).equals("0")) { + patch.length1 = 0; + } else { + patch.start1--; + patch.length1 = Integer.parseInt(m.group(2)); + } + + patch.start2 = Integer.parseInt(m.group(3)); + if (m.group(4).length() == 0) { + patch.start2--; + patch.length2 = 1; + } else if (m.group(4).equals("0")) { + patch.length2 = 0; + } else { + patch.start2--; + patch.length2 = Integer.parseInt(m.group(4)); + } + text.removeFirst(); + + while (!text.isEmpty()) { + try { + sign = text.getFirst().charAt(0); + } catch (IndexOutOfBoundsException e) { + // Blank line? Whatever. + text.removeFirst(); + continue; + } + line = text.getFirst().substring(1); + line = line.replace("+", "%2B"); // decode would change all "+" to " " + try { + line = URLDecoder.decode(line, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } catch (IllegalArgumentException e) { + // Malformed URI sequence. + throw new IllegalArgumentException( + "Illegal escape in patch_fromText: " + line, e); + } + if (sign == '-') { + // Deletion. + patch.diffs.add(new Diff(Operation.DELETE, line)); + } else if (sign == '+') { + // Insertion. + patch.diffs.add(new Diff(Operation.INSERT, line)); + } else if (sign == ' ') { + // Minor equality. + patch.diffs.add(new Diff(Operation.EQUAL, line)); + } else if (sign == '@') { + // Start of next patch. + break; + } else { + // WTF? + throw new IllegalArgumentException( + "Invalid patch mode '" + sign + "' in: " + line); + } + text.removeFirst(); + } + } + return patches; + } + + + /** + * Class representing one diff operation. + */ + public static class Diff { + /** + * One of: INSERT, DELETE or EQUAL. + */ + public Operation operation; + /** + * The text associated with this diff operation. + */ + public String text; + + /** + * Constructor. Initializes the diff with the provided values. + * @param operation One of INSERT, DELETE or EQUAL. + * @param text The text being applied. + */ + public Diff(Operation operation, String text) { + // Construct a diff with the specified operation and text. + this.operation = operation; + this.text = text; + } + + /** + * Display a human-readable version of this Diff. + * @return text version. + */ + public String toString() { + String prettyText = this.text.replace('\n', '\u00b6'); + return "Diff(" + this.operation + ",\"" + prettyText + "\")"; + } + + /** + * Create a numeric hash value for a Diff. + * This function is not used by DMP. + * @return Hash value. + */ + @Override + public int hashCode() { + final int prime = 31; + int result = (operation == null) ? 0 : operation.hashCode(); + result += prime * ((text == null) ? 0 : text.hashCode()); + return result; + } + + /** + * Is this Diff equivalent to another Diff? + * @param obj Another Diff to compare against. + * @return true or false. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Diff other = (Diff) obj; + if (operation != other.operation) { + return false; + } + if (text == null) { + if (other.text != null) { + return false; + } + } else if (!text.equals(other.text)) { + return false; + } + return true; + } + } + + + /** + * Class representing one patch operation. + */ + public static class Patch { + public LinkedList diffs; + public int start1; + public int start2; + public int length1; + public int length2; + + /** + * Constructor. Initializes with an empty list of diffs. + */ + public Patch() { + this.diffs = new LinkedList(); + } + + /** + * Emulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indices are printed as 1-based, not 0-based. + * @return The GNU diff string. + */ + public String toString() { + String coords1, coords2; + if (this.length1 == 0) { + coords1 = this.start1 + ",0"; + } else if (this.length1 == 1) { + coords1 = Integer.toString(this.start1 + 1); + } else { + coords1 = (this.start1 + 1) + "," + this.length1; + } + if (this.length2 == 0) { + coords2 = this.start2 + ",0"; + } else if (this.length2 == 1) { + coords2 = Integer.toString(this.start2 + 1); + } else { + coords2 = (this.start2 + 1) + "," + this.length2; + } + StringBuilder text = new StringBuilder(); + text.append("@@ -").append(coords1).append(" +").append(coords2) + .append(" @@\n"); + // Escape the body of the patch with %xx notation. + for (Diff aDiff : this.diffs) { + switch (aDiff.operation) { + case INSERT: + text.append('+'); + break; + case DELETE: + text.append('-'); + break; + case EQUAL: + text.append(' '); + break; + } + try { + text.append(URLEncoder.encode(aDiff.text, "UTF-8").replace('+', ' ')) + .append("\n"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } + } + return unescapeForEncodeUriCompatability(text.toString()); + } + } + + /** + * Unescape selected chars for compatability with JavaScript's encodeURI. + * In speed critical applications this could be dropped since the + * receiving application will certainly decode these fine. + * Note that this function is case-sensitive. Thus "%3f" would not be + * unescaped. But this is ok because it is only called with the output of + * URLEncoder.encode which returns uppercase hex. + * + * Example: "%3F" -> "?", "%24" -> "$", etc. + * + * @param str The string to escape. + * @return The escaped string. + */ + private static String unescapeForEncodeUriCompatability(String str) { + return str.replace("%21", "!").replace("%7E", "~") + .replace("%27", "'").replace("%28", "(").replace("%29", ")") + .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?") + .replace("%3A", ":").replace("%40", "@").replace("%26", "&") + .replace("%3D", "=").replace("%2B", "+").replace("%24", "$") + .replace("%2C", ",").replace("%23", "#"); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/AnnotationHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/AnnotationHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..ffd5a50bfb84fd210fc23a796473689aa4ec1f02 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/AnnotationHelper.java @@ -0,0 +1,45 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class AnnotationHelper { + + public static boolean isAnnotationPresent(NodeWithAnnotations node, List annotationNames) { + for (String annotationName : annotationNames) { + if (node.isAnnotationPresent(annotationName)) { + return true; + } + } + return false; + } + + public static Optional getAttribute(AnnotationExpr annotationExpr, String key) { + if (Objects.equals("value", key) && annotationExpr.isSingleMemberAnnotationExpr()) { + return Optional.of(annotationExpr.asSingleMemberAnnotationExpr().getMemberValue()); + } + if (annotationExpr.isNormalAnnotationExpr()) { + for (MemberValuePair pair : annotationExpr.asNormalAnnotationExpr().getPairs()) { + if (Objects.equals(key, pair.getNameAsString())){ + return Optional.of(pair.getValue()); + } + } + } + return Optional.empty(); + } + + public static Optional getAnyAttribute(AnnotationExpr annotationExpr, String ... keys) { + for (String key : keys) { + Optional optional = getAttribute(annotationExpr, key); + if(optional.isPresent()){ + return optional; + } + } + return Optional.empty(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/ClassDeclarationHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ClassDeclarationHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..9ad45908444fb5ff4a3bdb384c7258c36f5c5f2c --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ClassDeclarationHelper.java @@ -0,0 +1,42 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Slf4j +public class ClassDeclarationHelper { + + public static Optional getParent(ClassOrInterfaceDeclaration n) { + if (n.getExtendedTypes().isEmpty()) { + return Optional.empty(); + } + return tryToResolve(n.getExtendedTypes().get(0)); + } + + public static Optional tryToResolve(ClassOrInterfaceType type) { + try { + ResolvedReferenceType resolvedReferenceType = type.resolve(); + if (resolvedReferenceType.getTypeDeclaration() instanceof JavaParserClassDeclaration) { + JavaParserClassDeclaration typeDeclaration = (JavaParserClassDeclaration) resolvedReferenceType.getTypeDeclaration(); + return Optional.of(typeDeclaration.getWrappedNode()); + }else if(resolvedReferenceType.getTypeDeclaration() instanceof ReflectionClassDeclaration){ + ReflectionClassDeclaration typeDeclaration = (ReflectionClassDeclaration) resolvedReferenceType.getTypeDeclaration(); + System.out.println("type Declaration:"+typeDeclaration); + //TODO + } + } catch (Exception e) { + log.warn(e.getMessage()); + } + return Optional.empty(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/CommentHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/CommentHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..414216f042c62063377eff61618b9d9f21ae411b --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/CommentHelper.java @@ -0,0 +1,63 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.comments.Comment; +import com.github.javaparser.javadoc.description.JavadocDescription; +import com.github.javaparser.javadoc.description.JavadocDescriptionElement; +import com.github.javaparser.javadoc.description.JavadocInlineTag; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserFieldDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserMethodDeclaration; + +import java.util.Optional; +import java.util.stream.Collectors; + +public class CommentHelper { + + /** + * 获取完整注释字符串 + * @param description + * @return + */ + public static String getDescription(JavadocDescription description){ + return description.getElements() + .stream() + .filter(e -> !(e instanceof JavadocInlineTag)) + .map(JavadocDescriptionElement::toText).collect(Collectors.joining()); + } + + public static String getContent(Comment comment){ + if(!comment.isJavadocComment()){ + return comment.getContent(); + } + return getDescription(comment.asJavadocComment().parse().getDescription()); + } + + + public static String getComment(MethodUsage it){ + if (it.getDeclaration() instanceof JavaParserMethodDeclaration) { + MethodDeclaration wrappedNode = ((JavaParserMethodDeclaration) it.getDeclaration()).getWrappedNode(); + Optional optional = wrappedNode.getComment(); + if(optional.isPresent()){ + return CommentHelper.getContent(optional.get()); + } + } + return null; + } + public static String getComment(ResolvedFieldDeclaration it){ + if(it instanceof JavaParserFieldDeclaration){ + FieldDeclaration wrappedNode = ((JavaParserFieldDeclaration) it).getWrappedNode(); + Optional optional = wrappedNode.getComment(); + if(optional.isPresent()){ + return CommentHelper.getContent(optional.get()); + } + }else if(it instanceof JavaParserClassDeclaration){ + JavaParserClassDeclaration classDeclaration = (JavaParserClassDeclaration) it; + + } + return null; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/CompilationUnitHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/CompilationUnitHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e362e99a9aa49666e5a963a6718f44ee9916f930 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/CompilationUnitHelper.java @@ -0,0 +1,20 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; + +import java.util.Optional; + +public class CompilationUnitHelper { + + public static Optional getCompilationUnit(Node node){ + if(node instanceof CompilationUnit){ + return Optional.of((CompilationUnit) node); + } + if (node.getParentNode().isPresent()){ + return getCompilationUnit(node.getParentNode().get()); + } + return Optional.empty(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/ExpressionHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ExpressionHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..f381b49a70f6adc2016d8d2963d71421d53164aa --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ExpressionHelper.java @@ -0,0 +1,95 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.resolution.Resolvable; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserFieldDeclaration; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +public class ExpressionHelper { + + /** + * 解析表达式,获取表达式的值 + * TODO 更复杂的表达式解析 + * @param expr + * @return + */ + public static Object getValue(Expression expr) { + if (expr.isStringLiteralExpr()) { + return expr.asStringLiteralExpr().getValue(); + } + if (expr.isIntegerLiteralExpr()) { + return expr.asIntegerLiteralExpr().getValue(); + } + if (expr.isDoubleLiteralExpr()) { + return expr.asDoubleLiteralExpr().getValue(); + } + if (expr.isLongLiteralExpr()) { + return expr.asLongLiteralExpr().getValue(); + } + if (expr.isBooleanLiteralExpr()) { + return expr.asBooleanLiteralExpr().getValue(); + } + if (expr.isArrayInitializerExpr()) { + return expr.asArrayInitializerExpr().getValues().stream().map(ExpressionHelper::getValue).collect(Collectors.toList()); + } + if (expr instanceof Resolvable) { + return String.valueOf(resolve((Resolvable) expr)); + } + return expr.toString(); + } + + public static String getStringValue(Expression expression){ + if(expression.isStringLiteralExpr()){ + return expression.asStringLiteralExpr().getValue(); + }else if(expression.isArrayInitializerExpr()){ + NodeList values = expression.asArrayInitializerExpr().getValues(); + if(values.size()>0){ + return getStringValue(values.get(0)); + } + }else if(expression instanceof Resolvable){ + return String.valueOf(resolve((Resolvable)expression)); + } + return expression.toString(); + } + + public static List getStringValues(Expression expression){ + List results = new ArrayList<>(); + if(expression.isStringLiteralExpr()){ + results.add(expression.asStringLiteralExpr().getValue()); + }else if(expression.isArrayInitializerExpr()) { + NodeList values = expression.asArrayInitializerExpr().getValues(); + for (Expression value : values) { + results.addAll(getStringValues(value)); + } + }else if(expression instanceof Resolvable){ + results.add(String.valueOf(resolve((Resolvable)expression))); + }else{ + results.add(expression.toString()); + } + return results; + } + + private static Object resolve(Resolvable resolvable){ + try { + Object resolve = resolvable.resolve(); + if(resolve instanceof JavaParserFieldDeclaration){ + Optional initializer = ((JavaParserFieldDeclaration) resolve).getVariableDeclarator().getInitializer(); + if (initializer.isPresent()) { + return getStringValue(initializer.get()); + } + } + } catch (Exception e) { + log.warn("resolve expression {} error: {}",resolvable.toString(),e.getMessage()); + } + return resolvable; + } + + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/FieldHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/FieldHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e68f3ccaf1644c3dc3f27ceb4dbba819c1fd5bde --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/FieldHelper.java @@ -0,0 +1,38 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserFieldDeclaration; + +import java.util.Optional; + +public class FieldHelper { + + /** + * 通过access方法,获取属性名 + * @param methodName access方法名 + * @return 属性名 + */ + public static String getByAccessMethod(String methodName){ + if(methodName.startsWith("is") && methodName.length()>2){ + String first = methodName.substring(2, 3); + String less = methodName.substring(3); + return first.toLowerCase() + less; + } + if(methodName.startsWith("get") && methodName.length()>3){ + String first = methodName.substring(3, 4); + String less = methodName.substring(4); + return first.toLowerCase() + less; + } + return null; + } + + public static Optional getInitializer(ResolvedFieldDeclaration declaredField){ + if(declaredField instanceof JavaParserFieldDeclaration){ + JavaParserFieldDeclaration field = (JavaParserFieldDeclaration) declaredField; + return field.getVariableDeclarator().getInitializer(); + } + return Optional.empty(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/FileHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/FileHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..65b7ef73de09d1cb56e5100e34d7a16988b3067c --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/FileHelper.java @@ -0,0 +1,45 @@ +package com.apidoc.core.common.helper; + +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class FileHelper { + + public static void write(Path file, String content) { + + if (file.getParent() != null) { + try { + Files.createDirectories(file.getParent()); + } catch (IOException e) { + throw new RuntimeException("Failed create directory", e); + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { + writer.write(content); + writer.flush(); + } catch (IOException e) { + throw new RuntimeException("Failed to write file", e); + } + } + + public static List find(Path start, String structure){ + try { + return Files.walk(start) + .filter(p->p.endsWith(structure)).collect(Collectors.toList()); + } catch (IOException e) { + log.warn("find path error:{} {}", start, e.getMessage()); + } + return Lists.newArrayList(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/JsonPropertyHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/JsonPropertyHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..2e8a6c10767cb2520006e672c4e1ca98d51e5cd1 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/JsonPropertyHelper.java @@ -0,0 +1,40 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserFieldDeclaration; + +import java.util.Optional; + +public class JsonPropertyHelper { + + public static final String ANNOTAION_JSON_PROPERTY = "JsonProperty"; + public static final String ANNOTAION_JSON_FIELD = "JSONField"; + public static final String ANNOTAION_SERIALIZED_NAME = "SerializedName"; + + public static Optional getJsonName(ResolvedFieldDeclaration declaredField){ + if(declaredField instanceof JavaParserFieldDeclaration){ + FieldDeclaration fieldDeclaration = ((JavaParserFieldDeclaration) declaredField).getWrappedNode(); + return OptionalHelper.any( + getStringValue(fieldDeclaration, ANNOTAION_JSON_PROPERTY, "value"), + getStringValue(fieldDeclaration, ANNOTAION_JSON_FIELD, "name"), + getStringValue(fieldDeclaration, ANNOTAION_SERIALIZED_NAME, "value") + ); + } + return Optional.empty(); + } + + public static Optional getStringValue(FieldDeclaration fieldDeclaration, String anno, String attr){ + Optional optional = fieldDeclaration.getAnnotationByName(anno); + if (optional.isPresent()) { + Optional expr = AnnotationHelper.getAttribute(optional.get(),attr); + if (expr.isPresent()) { + return Optional.of(ExpressionHelper.getStringValue(expr.get())); + } + } + return Optional.empty(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/OptionalHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/OptionalHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..830c9f76fa7688b1a6282b16d3dd4a430156dda9 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/OptionalHelper.java @@ -0,0 +1,17 @@ +package com.apidoc.core.common.helper; + +import java.util.Optional; + +public class OptionalHelper { + + @SafeVarargs + public static Optional any(Optional ... optionals){ + for (Optional optional : optionals) { + if(optional.isPresent()){ + return optional; + } + } + return Optional.empty(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/ReferenceContext.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ReferenceContext.java new file mode 100644 index 0000000000000000000000000000000000000000..fe3294d1c051fce32d2ed55815a19790cd734a17 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ReferenceContext.java @@ -0,0 +1,37 @@ +package com.apidoc.core.common.helper; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * 解决循环依赖问题 + */ +public class ReferenceContext { + + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + public static ReferenceContext getInstance(){ + ReferenceContext context = threadLocal.get(); + if (context == null) { + context = new ReferenceContext(); + threadLocal.set(context); + } + return context; + } + + private final Set set = Sets.newHashSet(); + + public boolean push(Object object){ + return set.add(object); + } + + public boolean remove(Object object){ + return set.remove(object); + } + + public Set getValues(){ + return set; + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/StringHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/StringHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..40f2e971e396df4019feb8705e8cd68f212d049e --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/StringHelper.java @@ -0,0 +1,43 @@ +package com.apidoc.core.common.helper; + +import com.google.common.base.Strings; + +public class StringHelper { + + public static boolean isBlank(String text) { + return Strings.isNullOrEmpty(text) || Strings.isNullOrEmpty(text.trim()); + } + + public static boolean nonBlank(String text) { + return !isBlank(text); + } + + public static boolean isBlank(Object text) { + if(text instanceof String){ + return isBlank(((String) text)); + } + return isBlank(String.valueOf(text)); + } + + public static boolean nonBlank(Object text) { + if(text instanceof String){ + return nonBlank(((String) text)); + } + return nonBlank(String.valueOf(text)); + } + + public static String join(String delimiter, String ... values){ + StringBuilder builder = new StringBuilder(); + for (String value : values) { + if(isBlank(value)){ + continue; + } + if(builder.length()>0){ + builder.append(delimiter); + } + builder.append(value); + } + return builder.toString(); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/TypeNameHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/TypeNameHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..59a42c978c5a672dc12d51c97259a81fc264cdf2 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/TypeNameHelper.java @@ -0,0 +1,51 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.type.Type; + +import java.util.Optional; + +public class TypeNameHelper { + + public static String getName(Type type){ + String name = type.toString(); + if(type.isClassOrInterfaceType()){ + name = type.asClassOrInterfaceType().getNameAsString(); + } + Optional optional = CompilationUnitHelper.getCompilationUnit(type); + if(optional.isPresent()){ + CompilationUnit compilationUnit = optional.get(); + return getNameFromImport(name, compilationUnit); + } + return name; + } + + private static String getNameFromImport(String name, CompilationUnit compilationUnit){ + int dotPos = name.indexOf('.'); + String prefix = null; + if (dotPos > -1) { + prefix = name.substring(0, dotPos); + } + for (ImportDeclaration importDecl : compilationUnit.getImports()) { + if (!importDecl.isAsterisk()) { + String qName = importDecl.getNameAsString(); + boolean defaultPackage = !importDecl.getName().getQualifier().isPresent(); + boolean found = !defaultPackage && importDecl.getName().getIdentifier().equals(name); + if (!found) { + if (prefix != null) { + found = qName.endsWith("." + prefix); + if (found) { + qName = qName + name.substring(dotPos); + } + } + } + if (found) { + return qName; + } + } + } + return name; + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/TypeParameterHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/TypeParameterHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..fb055dc9eb33a47c884b1d25c40e6d28d40e477a --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/TypeParameterHelper.java @@ -0,0 +1,61 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.utils.Pair; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class TypeParameterHelper { + + /** + * 获取泛型信息 + * @param referenceType + * @param index 位置信息 + * @return + */ + public static Optional getTypeParameter(ResolvedReferenceType referenceType, int index){ + List> typeParameters = referenceType.getTypeParametersMap(); + if(typeParameters.size()>index){ + Pair pair = typeParameters.get(index); + return Optional.of(pair.b); + } + return Optional.empty(); + } + + /** + * 获取泛型信息 + * @param referenceType + * @param a 如 T E 等 + * @return + */ + public static Optional getTypeParameter(ResolvedReferenceType referenceType, String a){ + List> typeParameters = referenceType.getTypeParametersMap(); + for (Pair pair : typeParameters) { + if(Objects.equals(pair.a.getName(),a)){ + return Optional.of(pair.b); + } + } + return Optional.empty(); + } + + /** + * 使用父类的泛型 + * @param parent + * @param field + */ + public static ResolvedType useClassTypeParameter(ResolvedReferenceType parent, ResolvedReferenceType field ){ + for (Pair pair : field.getTypeParametersMap()) { + if(pair.b.isTypeVariable()){ + Optional typeParameter = TypeParameterHelper.getTypeParameter(parent, pair.b.asTypeVariable().describe()); + if (typeParameter.isPresent()) { + return field.replaceTypeVariables(pair.b.asTypeVariable().asTypeParameter(), typeParameter.get()); + } + } + } + return field; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/helper/ValidationHelper.java b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ValidationHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..7e2ccfd244fe630cd0ca8443c9fa1a6870ba9585 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/helper/ValidationHelper.java @@ -0,0 +1,43 @@ +package com.apidoc.core.common.helper; + +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserFieldDeclaration; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class ValidationHelper { + + public static final String NULL = "Null"; + public static final String NOT_NULL = "NotNull"; + public static final String ASSERT_TRUE = "AssertTrue"; + public static final String ASSERT_FALSE = "AssertFalse"; + public static final String NOT_EMPTY = "NotEmpty"; + public static final String NOT_BLANK = "NotBlank"; + public static final String EMAIL = "Email"; + + public static final String MIN = "Min"; + public static final String MAX = "Max"; + public static final String SIZE = "Size"; + + public static final List values = Lists.newArrayList(NULL,NOT_NULL,NOT_EMPTY,EMAIL,NOT_BLANK); + + public static List getValidations(ResolvedFieldDeclaration declaredField){ + List result = new ArrayList<>(); + if(declaredField instanceof JavaParserFieldDeclaration){ + FieldDeclaration fieldDeclaration = ((JavaParserFieldDeclaration) declaredField).getWrappedNode(); + for (String value : values) { + Optional optional = fieldDeclaration.getAnnotationByName(value); + if(optional.isPresent()){ + result.add(value); + } + } + } + return result; + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/markup/MarkupBuilder.java b/apidoc-core/src/main/java/com/apidoc/core/common/markup/MarkupBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..98c2aa487303d91c57817910edba437d696d2888 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/markup/MarkupBuilder.java @@ -0,0 +1,217 @@ +package com.apidoc.core.common.markup; + +import com.apidoc.core.common.markup.asciidoc.AsciiDocBuilder; + +import java.util.List; +import java.util.function.Consumer; + +/** + * 文档构建器 + * 只实现满足需求的部分即可 + */ +public interface MarkupBuilder { + + static MarkupBuilder getInstance(){ + return new AsciiDocBuilder(); + } + + /** + * 文档头 + * @param text 标题 + * @param attrs 文档属性 + * @return this builder + */ + MarkupBuilder header(String text, CharSequence... attrs); + + /** + * 文档标题 + * @param level [1,5] + * @param text 标题 + * @return this builder + */ + MarkupBuilder title(int level, String text); + + /** + * 行内文字 + * + * @param text text + * @return this builder + */ + MarkupBuilder text(String text); + + /** + * 单行文字 + * @param text text 换行将被替换为空格 + * @return this builder + */ + MarkupBuilder textLine(String text); + + /** + * 多行文字 + * @param text text + * @return this builder + */ + MarkupBuilder paragraph(String text, CharSequence... attrs); + /** + * 带icon的各种段落 + */ + MarkupBuilder note(String text); + + MarkupBuilder tip(String text); + + MarkupBuilder important(String text); + + MarkupBuilder warning(String text); + + MarkupBuilder caution(String text); + + /** + * 各种block + */ + MarkupBuilder block(Consumer consumer, CharSequence flag, CharSequence... attrs); + + MarkupBuilder listing(Consumer consumer, CharSequence... attrs); + + MarkupBuilder literal(Consumer consumer, CharSequence... attrs); + + MarkupBuilder sidebar(Consumer consumer, CharSequence... attrs); + + MarkupBuilder comment(Consumer consumer, CharSequence... attrs); + + MarkupBuilder passthrough(Consumer consumer, CharSequence... attrs); + + MarkupBuilder quote(Consumer consumer, CharSequence... attrs); + + MarkupBuilder example(Consumer consumer, CharSequence... attrs); + + /** + * 列表 默认数字 + * @param text + * @return + */ + MarkupBuilder list(String text); + + /** + * 列表 默认数字 + * @param text + * @param flag 数字,字母,罗马字母 + * @return + */ + MarkupBuilder list(String text, CharSequence flag); + + /** + * 链接 + * @param text + * @param url + * @return + */ + MarkupBuilder url(String text, String url); + + /** + * + * @param text + * @param url + * @return + */ + MarkupBuilder image(String text, String url); + + /** + * 表格 默认第一组数据为表头 + * @param data + * @return + */ + MarkupBuilder table(List> data); + + MarkupBuilder table(List> data, boolean header, boolean footer); + + + /** + * 强调 + * + * @param text text + * @return this builder + */ + MarkupBuilder emphasized(String text, CharSequence... textStyle); + + /** + * 加粗 + * + * @param text text + * @return this builder + */ + MarkupBuilder strong(String text, CharSequence... textStyle); + + /** + * 等宽 + * + * @param text text + * @return this builder + */ + MarkupBuilder monospaced(String text, CharSequence... textStyle); + + /** + * 单引号 + * + * @param text text + * @return this builder + */ + MarkupBuilder quoted(String text, CharSequence... textStyle); + + /** + * 双引号 + * + * @param text text + * @return this builder + */ + MarkupBuilder doubleQuoted(String text, CharSequence... textStyle); + + /** + * 正常的引用文字 + * + * @param text text + * @return this builder + */ + MarkupBuilder unquoted(String text, CharSequence... textStyle); + + + /** + * 换行 + * + * @return this builder + */ + MarkupBuilder br(); + + /** + * 强制换行 + * + * @return this builder + */ + MarkupBuilder hbr(); + + /** + * 另起一行 + * + * @return this builder + */ + MarkupBuilder newLine(); + + /** + * 横线 + * + * @return this builder + */ + MarkupBuilder pageBreak(); + + /** + * 获取文件内容 + * @return + */ + String getContent(); + + /** + * 清空content + */ + void clean(); + +} + diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/AsciiDoc.java b/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/AsciiDoc.java new file mode 100644 index 0000000000000000000000000000000000000000..db39dae5eab48a7eb49d6c88607ef8b7e417bcd9 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/AsciiDoc.java @@ -0,0 +1,92 @@ +package com.apidoc.core.common.markup.asciidoc; + +public enum AsciiDoc implements CharSequence { + EXTENSION(".adoc"), + /** + * 各种关键字 + */ + HEADER("= "), + TABLE("|==="), + TABLE_CELL("|"), + TITLE("="), + EMPHASIZED("_"), + STRONG("*"), + MONOSPACED("+"), + QUOTED("`"), + DOUBLE_QUOTED("``"), + UNQUOTED("#"), + LIST_FLAG("1. "), + LIST_FLAG_LETTER("a. "), + LIST_FLAG_LETTER_UPPER("A. "), + LISTING("----"), + LITERAL("...."), + SIDEBAR("****"), + COMMENT("////"), + PASSTHROUGH("++++"), + QUOTE("____"), + EXAMPLE("===="), + NOTE("NOTE"), + TIP("TIP"), + IMPORTANT("IMPORTANT"), + WARNING("WARNING"), + CAUTION("CAUTION"), + PAGEBREAKS("<<<"), + HARDBREAKS("[%hardbreaks]"), + WHITESPACE(" "), + BR("\r\n"), + NEW_LINE("\r\n\r\n"), + HBR(" +"), + /** + * 文档属性 + */ + TOC(":toc:"), + LEFT("left"), + TOC_LEVEL(":toclevels:"), + TOC_TITLE(":toc-title:"), + DOCTYPE(":doctype:"), + BOOK("book"), + SOURCE_HIGHLIGHTER(":source-highlighter:"), + PRETTIFY("prettify"), + HIGHLIGHTJS("highlightjs"), + CODERAY("coderay"), + /** + * 文字样式 + */ + STYLE_BIG("big"), + STYLE_SMALL("small"), + STYLE_UNDERLINE("underline"), + STYLE_OVERLINE("overline"), + STYLE_LINE_THROUGH("line-through"), + ; + + private final String markup; + + AsciiDoc(final String markup) { + this.markup = markup; + } + + @Override + public int length() { + return markup.length(); + } + + @Override + public char charAt(int index) { + return markup.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return markup.subSequence(start, end); + } + + @Override + public String toString() { + return markup; + } + + public static CharSequence attr(AsciiDoc key, Object value){ + return key.toString() + " " + String.valueOf(value); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/AsciiDocBuilder.java b/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/AsciiDocBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..0ba3cd61efc12d6482d8b79fd756c1d70a193f63 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/AsciiDocBuilder.java @@ -0,0 +1,335 @@ +package com.apidoc.core.common.markup.asciidoc; + +import com.apidoc.core.common.Assert; +import com.apidoc.core.common.markup.MarkupBuilder; +import com.google.common.base.Strings; + +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.regex.Matcher; + +public class AsciiDocBuilder implements MarkupBuilder { + + public static final int MAX_TITLE = 6; + + private StringBuilder content = new StringBuilder(); + + @Override + public MarkupBuilder header(String text, CharSequence... attrs) { + Assert.notBlank(text, "header must not be blank"); + content.append(AsciiDoc.HEADER); + content.append(nobr(text.trim())); + br(); + for (CharSequence attr : attrs) { + content.append(attr); + br(); + } + br(); + return this; + } + + @Override + public MarkupBuilder title(int level, String text) { + Assert.notBlank(text, "header must not be blank"); + Assert.between(level, 1, MAX_TITLE, "title level can not be " + level); + br(); + content.append(Strings.repeat(AsciiDoc.TITLE.toString(), level + 1)).append(AsciiDoc.WHITESPACE) + .append(nobr(text.trim())); + br(); + return this; + } + + @Override + public MarkupBuilder text(String text) { + if (Assert.isBlank(text)) { + return this; + } + content.append(text.trim()); + return this; + } + + @Override + public MarkupBuilder textLine(String text) { + if (Assert.isBlank(text)) { + return this; + } + text(nobr(text)); + br(); + return this; + } + + @Override + public MarkupBuilder paragraph(String text, CharSequence... attrs) { + if (Assert.isBlank(text)) { + return this; + } + content.append(AsciiDoc.HARDBREAKS); + br(); + if (attrs.length > 0) { + content.append("["); + for (CharSequence attr : attrs) { + content.append(attr).append(AsciiDoc.WHITESPACE); + } + content.append("]"); + br(); + } + text(text); + newLine(); + return this; + } + + @Override + public MarkupBuilder note(String text) { + paragraph(text, AsciiDoc.NOTE); + return this; + } + + @Override + public MarkupBuilder tip(String text) { + paragraph(text, AsciiDoc.TIP); + return this; + } + + @Override + public MarkupBuilder important(String text) { + paragraph(text, AsciiDoc.IMPORTANT); + return this; + } + + @Override + public MarkupBuilder warning(String text) { + paragraph(text, AsciiDoc.WARNING); + return this; + } + + @Override + public MarkupBuilder caution(String text) { + paragraph(text, AsciiDoc.CAUTION); + return this; + } + + @Override + public MarkupBuilder block(Consumer consumer, CharSequence flag, CharSequence... attrs) { + if (attrs.length > 0) { + content.append("["); + for (CharSequence attr : attrs) { + content.append(attr).append(" "); + } + content.append("]"); + br(); + } + content.append(flag); + br(); + consumer.accept(this); + br(); + content.append(flag); + newLine(); + return this; + + } + + @Override + public MarkupBuilder listing(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.LISTING, attrs); + } + + @Override + public MarkupBuilder literal(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.LITERAL, attrs); + } + + @Override + public MarkupBuilder sidebar(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.SIDEBAR, attrs); + } + + @Override + public MarkupBuilder comment(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.COMMENT, attrs); + } + + @Override + public MarkupBuilder passthrough(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.PASSTHROUGH, attrs); + } + + @Override + public MarkupBuilder quote(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.QUOTE, attrs); + } + + @Override + public MarkupBuilder example(Consumer consumer, CharSequence... attrs) { + return block(consumer, AsciiDoc.EXAMPLE, attrs); + } + + @Override + public MarkupBuilder list(String text) { + return list(text, AsciiDoc.LIST_FLAG); + } + + @Override + public MarkupBuilder list(String text, CharSequence flag) { + if (!Assert.isBlank(text)) { + content.append(flag).append(nobr(text)); + } + return this; + } + + @Override + public MarkupBuilder url(String text, String url) { + if (!Assert.isBlank(text) && !Assert.isBlank(url)) { + content.append(url).append("[").append(nobr(text)).append("]"); + br(); + } + return this; + } + + @Override + public MarkupBuilder image(String text, String url) { + if (!Assert.isBlank(text) && !Assert.isBlank(url)) { + text("image:"); + url(text, url); + } + return this; + } + + @Override + public MarkupBuilder table(List> data) { + return table(data, true, false); + } + + @Override + public MarkupBuilder table(List> data, boolean header, boolean footer) { + int min = 1; + if (header) { + min++; + } + if (footer) { + min++; + } + if (data.size() < min) { + return this; + } + content.append("[stripes=even,options=\""); + if (header) { + content.append("header"); + } + if (header && footer) { + content.append(","); + } + if (footer) { + content.append("footer"); + } + content.append("\"]"); + br(); + content.append(AsciiDoc.TABLE); + br(); + for (List rows : data) { + for (String cell : rows) { + content.append(AsciiDoc.TABLE_CELL); + if(cell!=null){ + monospaced(cell.replace(AsciiDoc.TABLE_CELL, "\\" + AsciiDoc.TABLE_CELL)); + } + } + br(); + } + content.append(AsciiDoc.TABLE); + newLine(); + return this; + } + + + public MarkupBuilder style(CharSequence flag, String text, CharSequence... textStyle) { + if (Assert.isBlank(text)) { + return this; + } + if (Objects.nonNull(textStyle) && textStyle.length > 0) { + content.append("["); + for (CharSequence style : textStyle) { + content.append(style).append(" "); + } + content.append("]"); + } + content.append(flag); + text(text); + content.append(flag); + return this; + + } + + @Override + public MarkupBuilder emphasized(String text, CharSequence... textStyle) { + return style(AsciiDoc.EMPHASIZED, text, textStyle); + } + + @Override + public MarkupBuilder strong(String text, CharSequence... textStyle) { + return style(AsciiDoc.STRONG, text, textStyle); + } + + @Override + public MarkupBuilder monospaced(String text, CharSequence... textStyle) { + return style(AsciiDoc.MONOSPACED, text, textStyle); + } + + @Override + public MarkupBuilder quoted(String text, CharSequence... textStyle) { + return style(AsciiDoc.QUOTE, text, textStyle); + } + + @Override + public MarkupBuilder doubleQuoted(String text, CharSequence... textStyle) { + return style(AsciiDoc.DOUBLE_QUOTED, text, textStyle); + } + + @Override + public MarkupBuilder unquoted(String text, CharSequence... textStyle) { + return style(AsciiDoc.UNQUOTED, text, textStyle); + } + + @Override + public MarkupBuilder br() { + content.append(AsciiDoc.BR); + return this; + } + + @Override + public MarkupBuilder hbr() { + content.append(AsciiDoc.HBR); + return this; + } + + @Override + public MarkupBuilder newLine() { + content.append(AsciiDoc.NEW_LINE); + return this; + } + + @Override + public MarkupBuilder pageBreak() { + content.append(AsciiDoc.PAGEBREAKS); + br(); + return this; + } + + @Override + public String getContent() { + return content.toString(); + } + + @Override + public void clean() { + content = new StringBuilder(); + } + + String nobr(String content) { + if (Assert.isBlank(content)) { + return content; + } + return content.replaceAll(AsciiDoc.BR.toString(), + Matcher.quoteReplacement(AsciiDoc.WHITESPACE.toString())); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/Color.java b/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/Color.java new file mode 100644 index 0000000000000000000000000000000000000000..836e8db53b1e95d00897e207e5c2fbe5d65f30ae --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/markup/asciidoc/Color.java @@ -0,0 +1,53 @@ +package com.apidoc.core.common.markup.asciidoc; + +/** + * https://en.wikipedia.org/wiki/Web_colors#HTML_color_names + */ +public enum Color implements CharSequence{ + WHITE("white"), + SILVER("silver"), + GRAY("gray"), + BLACK("black"), + RED("red"), + MAROON("maroon"), + YELLOW("yellow"), + OLIVE("olive"), + LIME("lime"), + GREEN("green"), + AQUA("aqua"), + TEAL("teal"), + BLUE("blue"), + NAVY("navy"), + FUCHSIA("fuchsia"), + PURPLE("purple"); + + private String text; + + Color(String text) { + this.text = text; + } + + @Override + public int length() { + return text.length(); + } + + @Override + public char charAt(int index) { + return text.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return text.subSequence(start, end); + } + + @Override + public String toString() { + return text; + } + + public String bg() { + return text + "-background"; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/markup/markdown/Markdown.java b/apidoc-core/src/main/java/com/apidoc/core/common/markup/markdown/Markdown.java new file mode 100644 index 0000000000000000000000000000000000000000..f31b1612fedc5491165efeef67464279e81d6976 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/markup/markdown/Markdown.java @@ -0,0 +1,69 @@ +package com.apidoc.core.common.markup.markdown; + +public enum Markdown implements CharSequence { + EXTENSION(".md"), + /** + * 各种关键字 + */ + HEADER("# "), + TABLE_CELL("|"), + TABLE_ROW("-"), + TABLE_Header("---|"), + TITLE("#"), + EMPHASIZED("**"), + STRONG("**"), + MONOSPACED("`"), + QUOTED("**"), + DOUBLE_QUOTED("**"), + UNQUOTED("**"), + LIST_FLAG("* "), + LISTING("```"), + LITERAL("```"), + SIDEBAR("```"), + COMMENT("```"), + PASSTHROUGH("```"), + QUOTE("> "), + EXAMPLE("===="), + NOTE("NOTE"), + TIP("TIP"), + IMPORTANT("IMPORTANT"), + WARNING("WARNING"), + CAUTION("CAUTION"), + PAGEBREAKS(" "), + WHITESPACE(" "), + BR("\r\n"), + NEW_LINE("\r\n\r\n"), + HBR(" +"), + ; + + private final String markup; + + Markdown(final String markup) { + this.markup = markup; + } + + @Override + public int length() { + return markup.length(); + } + + @Override + public char charAt(int index) { + return markup.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return markup.subSequence(start, end); + } + + @Override + public String toString() { + return markup; + } + + public static CharSequence attr(Markdown key, Object value){ + return key.toString() + " " + String.valueOf(value); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/markup/markdown/MarkdownBuilder.java b/apidoc-core/src/main/java/com/apidoc/core/common/markup/markdown/MarkdownBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..d31fc0504eb51646cfadfb527e8cef2cf4279009 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/markup/markdown/MarkdownBuilder.java @@ -0,0 +1,305 @@ +package com.apidoc.core.common.markup.markdown; + +import com.apidoc.core.common.Assert; +import com.apidoc.core.common.markup.MarkupBuilder; +import com.google.common.base.Strings; + +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Matcher; + +import static com.apidoc.core.common.markup.markdown.Markdown.*; + +public class MarkdownBuilder implements MarkupBuilder { + + public static final int MAX_TITLE = 6; + + private StringBuilder content = new StringBuilder(); + + @Override + public MarkupBuilder header(String text, CharSequence... attrs) { + Assert.notBlank(text, "header must not be blank"); + content.append(HEADER); + content.append(nobr(text.trim())); + br(); + return this; + } + + @Override + public MarkupBuilder title(int level, String text) { + Assert.notBlank(text, "header must not be blank"); + Assert.between(level, 1, MAX_TITLE, "title level can not be " + level); + br(); + content.append(Strings.repeat(TITLE.toString(), level + 1)).append(WHITESPACE) + .append(nobr(text.trim())); + br(); + return this; + } + + @Override + public MarkupBuilder text(String text) { + if (Assert.isBlank(text)) { + return this; + } + content.append(text.trim()); + return this; + } + + @Override + public MarkupBuilder textLine(String text) { + if (Assert.isBlank(text)) { + return this; + } + text(nobr(text)); + br(); + return this; + } + + @Override + public MarkupBuilder paragraph(String text, CharSequence... attrs) { + if (Assert.isBlank(text)) { + return this; + } + text(text); + newLine(); + return this; + } + + @Override + public MarkupBuilder note(String text) { + content.append(QUOTE); + paragraph(text); + return this; + } + + @Override + public MarkupBuilder tip(String text) { + content.append(QUOTE); + paragraph(text); + return this; + } + + @Override + public MarkupBuilder important(String text) { + content.append(QUOTE); + paragraph(text); + return this; + } + + @Override + public MarkupBuilder warning(String text) { + content.append(QUOTE); + paragraph(text); + return this; + } + + @Override + public MarkupBuilder caution(String text) { + content.append(QUOTE); + paragraph(text); + return this; + } + + @Override + public MarkupBuilder block(Consumer consumer, CharSequence flag, CharSequence... attrs) { + content.append(flag); + br(); + consumer.accept(this); + br(); + content.append(flag); + newLine(); + return this; + + } + + @Override + public MarkupBuilder listing(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder literal(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder sidebar(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder comment(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder passthrough(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder quote(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder example(Consumer consumer, CharSequence... attrs) { + return block(consumer, LISTING, attrs); + } + + @Override + public MarkupBuilder list(String text) { + return list(text, LIST_FLAG); + } + + @Override + public MarkupBuilder list(String text, CharSequence flag) { + if (!Assert.isBlank(text)) { + content.append(flag).append(nobr(text)); + } + return this; + } + + @Override + public MarkupBuilder url(String text, String url) { + if (!Assert.isBlank(text) && !Assert.isBlank(url)) { + content.append("[").append(nobr(text)).append("](").append(url).append(")"); + br(); + } + return this; + } + + @Override + public MarkupBuilder image(String text, String url) { + if (!Assert.isBlank(text) && !Assert.isBlank(url)) { + text("!"); + url(text, url); + } + return this; + } + + @Override + public MarkupBuilder table(List> data) { + return table(data, true, false); + } + + @Override + public MarkupBuilder table(List> data, boolean header, boolean footer) { + + for (int i = 0; i < data.size(); i++) { + content.append(TABLE_CELL); + for (int j = 0; j < data.get(i).size(); j++) { + String value = data.get(i).get(j); + if(value!=null){ + content.append(data.get(i).get(j).replace(TABLE_CELL, "\\" + TABLE_CELL)); + }else{ + content.append(" "); + } + content.append(TABLE_CELL); + } + br(); + if(i==0 && header){ + content.append(TABLE_CELL); + for (int j = 0; j < data.get(i).size(); j++) { + content.append(TABLE_Header); + } + br(); + } + if(i==data.size()-2 && footer){ + content.append(TABLE_CELL); + for (int j = 0; j < data.get(i).size(); j++) { + content.append(TABLE_Header); + } + br(); + } + } + newLine(); + return this; + } + + + public MarkupBuilder style(CharSequence flag, String text, CharSequence... textStyle) { + if (Assert.isBlank(text)) { + return this; + } + content.append(flag); + text(text); + content.append(flag); + return this; + + } + + @Override + public MarkupBuilder emphasized(String text, CharSequence... textStyle) { + return style(EMPHASIZED, text, textStyle); + } + + @Override + public MarkupBuilder strong(String text, CharSequence... textStyle) { + return style(STRONG, text, textStyle); + } + + @Override + public MarkupBuilder monospaced(String text, CharSequence... textStyle) { + return style(MONOSPACED, text, textStyle); + } + + @Override + public MarkupBuilder quoted(String text, CharSequence... textStyle) { + return style(QUOTE, text, textStyle); + } + + @Override + public MarkupBuilder doubleQuoted(String text, CharSequence... textStyle) { + return style(DOUBLE_QUOTED, text, textStyle); + } + + @Override + public MarkupBuilder unquoted(String text, CharSequence... textStyle) { + return style(UNQUOTED, text, textStyle); + } + + @Override + public MarkupBuilder br() { + content.append(BR); + return this; + } + + @Override + public MarkupBuilder hbr() { + content.append(HBR); + return this; + } + + @Override + public MarkupBuilder newLine() { + content.append(NEW_LINE); + return this; + } + + @Override + public MarkupBuilder pageBreak() { + content.append(PAGEBREAKS); + br(); + return this; + } + + @Override + public String getContent() { + return content.toString(); + } + + @Override + public void clean() { + content = new StringBuilder(); + } + + String nobr(String content) { + if (Assert.isBlank(content)) { + return content; + } + return content.replaceAll(BR.toString(), + Matcher.quoteReplacement(WHITESPACE.toString())); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Body.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Body.java new file mode 100644 index 0000000000000000000000000000000000000000..810f97136466c588a663f1c889ca2d7a0d238c50 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Body.java @@ -0,0 +1,18 @@ +package com.apidoc.core.common.postman; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +public class Body{ + + BodyMode mode; + String raw; + List urlencoded = new ArrayList<>(); + List formdata = new ArrayList<>(); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/BodyMode.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/BodyMode.java new file mode 100644 index 0000000000000000000000000000000000000000..19bbd0eceb99bbf7cffe8a6ce5fbc194707daf49 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/BodyMode.java @@ -0,0 +1,10 @@ +package com.apidoc.core.common.postman; + +/** + * 对应postman定义的mode + */ +public enum BodyMode { + + raw,urlencoded,formdata,file + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Folder.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Folder.java new file mode 100644 index 0000000000000000000000000000000000000000..2634775bba232f847e6cca4af7b7b5750422e63a --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Folder.java @@ -0,0 +1,19 @@ +package com.apidoc.core.common.postman; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Folder { + + String name; + String description; + List item = new ArrayList<>(); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Info.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Info.java new file mode 100644 index 0000000000000000000000000000000000000000..f53cbc88cd11f3e27988782b91e5b6a41dccc09e --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Info.java @@ -0,0 +1,16 @@ +package com.apidoc.core.common.postman; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Info { + + String name; + String _postman_id; + String description; + String version; + String schema = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"; + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Item.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..6473b404c6fffa6fd01b4fe434dfc369bacdae68 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Item.java @@ -0,0 +1,21 @@ +package com.apidoc.core.common.postman; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Setter +@Getter +public class Item extends Folder { + + String id = UUID.randomUUID().toString(); + Request request = new Request(); + List response = new ArrayList<>(); + + public Item() { + setItem(null); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Parameter.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Parameter.java new file mode 100644 index 0000000000000000000000000000000000000000..adc2cfb2e06ebd0f3e3e69fd4b2c6f15a4deee1c --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Parameter.java @@ -0,0 +1,26 @@ +package com.apidoc.core.common.postman; + + +import com.apidoc.core.schema.Row; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Parameter{ + + String key; + String type; + Object value; + String description; + boolean disabled = false; + + public static Parameter of(Row row) { + Parameter parameter = new Parameter(); + parameter.setKey(row.getKey()); + parameter.setType(row.getType()); + parameter.setValue(row.getDef()); + parameter.setDescription(row.getRemark()); + return parameter; + } +} \ No newline at end of file diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Postman.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Postman.java new file mode 100644 index 0000000000000000000000000000000000000000..3fac52aaf5e40ffac8b4f8b41b68cc3c77324676 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Postman.java @@ -0,0 +1,20 @@ +package com.apidoc.core.common.postman; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Rest api schema from postman schema + * https://schema.getpostman.com/json/collection/v2.1.0/collection.json + */ +@Setter +@Getter +public class Postman { + + Info info = new Info(); + List item = new ArrayList<>(); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Request.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..8aa7db8fdab7251c335526c51c3bc2286ffa75b5 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Request.java @@ -0,0 +1,21 @@ +package com.apidoc.core.common.postman; + +import com.apidoc.core.schema.Header; +import com.apidoc.core.schema.Method; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Setter +@Getter +public class Request{ + + Url url = new Url(); + Method method; + String description; + List
headers = new ArrayList<>(); + Body body = new Body(); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Response.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..bec12b1747cca92e7feba24cc1f3d0dbd72cea4f --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Response.java @@ -0,0 +1,22 @@ +package com.apidoc.core.common.postman; + +import com.apidoc.core.schema.Header; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Setter +@Getter +public class Response{ + + String name; + Request originalRequest ; + List
headers = new ArrayList<>(); + String body; + String status; + Integer code; + String _postman_previewlanguage = "json"; + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/common/postman/Url.java b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Url.java new file mode 100644 index 0000000000000000000000000000000000000000..b1925f682d1a63991a4fe1882f4a67943b8e4b23 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/common/postman/Url.java @@ -0,0 +1,20 @@ +package com.apidoc.core.common.postman; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Setter +@Getter +public class Url{ + + String raw; + String protocol = "http"; + String host = "{{host}}"; + String path; + String port; + List query = new ArrayList<>(); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/parser/ParserStrategy.java b/apidoc-core/src/main/java/com/apidoc/core/parser/ParserStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..4f88c3cd85146aada0bcf63aa616c3d52378d0a9 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/parser/ParserStrategy.java @@ -0,0 +1,59 @@ +package com.apidoc.core.parser; + +import com.apidoc.core.schema.Chapter; +import com.apidoc.core.schema.Section; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; + + +/** + * 解析策略接口 + * @author fengyuchenglun + */ +public interface ParserStrategy { + + /** + * Name string. + * + * @return the string + */ + String name(); + + /** + * On load. + */ + void onLoad(); + + /** + * 判断是否为需要解析的类 + * + * @param n the n + * @return boolean boolean + */ + boolean accept(ClassOrInterfaceDeclaration n); + + /** + * 判断是否为需要解析的方法 + * + * @param n the n + * @return boolean boolean + */ + boolean accept(MethodDeclaration n); + + /** + * Visit. + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + void visit(MethodDeclaration n, Chapter chapter, Section section); + + /** + * Visit. + * + * @param n the n + * @param chapter the chapter + */ + void visit(ClassOrInterfaceDeclaration n, Chapter chapter); +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/parser/VisitorParser.java b/apidoc-core/src/main/java/com/apidoc/core/parser/VisitorParser.java new file mode 100644 index 0000000000000000000000000000000000000000..0fb25788577678f1c3902c251fd45fb79e8277f2 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/parser/VisitorParser.java @@ -0,0 +1,55 @@ +package com.apidoc.core.parser; + +import com.apidoc.core.common.helper.OptionalHelper; +import com.apidoc.core.schema.Chapter; +import com.apidoc.core.schema.Node; +import com.apidoc.core.schema.Project; +import com.apidoc.core.schema.Section; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +public class VisitorParser extends VoidVisitorAdapter { + + private ParserStrategy parserStrategy; + + public void setParserStrategy(ParserStrategy parserStrategy) { + this.parserStrategy = parserStrategy; + } + + @Override + public void visit(final ClassOrInterfaceDeclaration n, final Node arg) { + + if (arg instanceof Project && parserStrategy.accept(n)) { + Project project = (Project) arg; + Chapter chapter = new Chapter(); + n.getFullyQualifiedName().ifPresent(chapter::setId); + chapter.setName(n.getNameAsString()); + n.getComment().ifPresent(chapter::accept); + + OptionalHelper.any(chapter.getTag("book"),chapter.getTag("group")) + .ifPresent(tag -> chapter.setBookName(tag.getContent())); + + parserStrategy.visit(n, chapter); + project.addChapter(chapter); + super.visit(n, chapter); + } + } + + @Override + public void visit(final MethodDeclaration n, final Node arg) { + if (arg instanceof Chapter && parserStrategy.accept(n)) { + Chapter chapter = (Chapter) arg; + Section section = new Section(); + section.setId(n.getNameAsString()); + section.setName(n.getNameAsString()); + section.setIndex(chapter.getSections().size()); + n.getComment().ifPresent(section::accept); + + parserStrategy.visit(n, chapter, section); + chapter.getSections().add(section); + super.visit(n, section); + } + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/render/AsciiDocRender.java b/apidoc-core/src/main/java/com/apidoc/core/render/AsciiDocRender.java new file mode 100644 index 0000000000000000000000000000000000000000..61f75df2f596110e2c3e0c8bd8a7b82ba5957cda --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/render/AsciiDocRender.java @@ -0,0 +1,142 @@ +package com.apidoc.core.render; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.helper.FileHelper; +import com.apidoc.core.common.helper.StringHelper; +import com.apidoc.core.common.markup.MarkupBuilder; +import com.apidoc.core.common.markup.asciidoc.AsciiDoc; +import com.apidoc.core.schema.*; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.AttributesBuilder; +import org.asciidoctor.OptionsBuilder; +import org.asciidoctor.SafeMode; +import org.asciidoctor.jruby.AsciiDocDirectoryWalker; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/** + * 构建并渲染adoc文档 + * + * @author fengyuchenglun + * @version 1.0.0 + */ +@Slf4j +public class AsciiDocRender implements ProjectRender { + + /** + * The constant attrs. + */ + public static final CharSequence[] attrs = Lists.newArrayList( + AsciiDoc.attr(AsciiDoc.DOCTYPE, AsciiDoc.BOOK), + AsciiDoc.attr(AsciiDoc.TOC, AsciiDoc.LEFT), AsciiDoc.attr(AsciiDoc.TOC_LEVEL, 2), AsciiDoc.attr(AsciiDoc.TOC_TITLE, "TOC"), + AsciiDoc.attr(AsciiDoc.SOURCE_HIGHLIGHTER, AsciiDoc.HIGHLIGHTJS)).toArray(new CharSequence[0]); + + @Override + public void render(Project project) { + + Path buildPath = ApiDoc.getInstance().getContext().getBuildPath(); + Path projectBuildPath = buildPath.resolve(project.getId()); + + project.getBooks().forEach((name, book) -> { + MarkupBuilder builder = MarkupBuilder.getInstance(); + String displayName = project.getName(); + if(!Objects.equals(Book.DEFAULT, name)){ + displayName += " - " + name; + } + builder.header(displayName, attrs); + if (Objects.nonNull(project.getVersion())) { + builder.paragraph("version:" + project.getVersion()); + } + builder.paragraph(project.getDescription()); + for (Chapter chapter : book.getChapters()) { + if (chapter.isIgnore() || chapter.getSections().isEmpty()) { + continue; + } + builder.title(1, chapter.getName()); + builder.paragraph(chapter.getDescription()); + for (Section section : chapter.getSections()) { + if(section.isIgnore()){ + continue; + } + builder.title(2, section.getName()); + builder.paragraph(section.getDescription()); + + + builder.title(4, "request"); + builder.listing(b -> { + b.textLine(section.getRequestLine()); + section.getInHeaders().values().forEach(header->builder.textLine(header.toString())); + if (section.hasRequestBody()) { + b.br(); + b.text(section.getParameterString()); + } + }, "source,HTTP"); + + table(builder, section.getRequestRows().values()); + + builder.title(4, "response"); + + builder.listing(b -> { + section.getOutHeaders().values().forEach(header -> builder.textLine(header.toString())); + if (section.hasResponseBody()) { + b.br(); + b.text(section.getResponseString()); + }else{ + b.text("N/A"); + } + }, "source,JSON"); + + table(builder, section.getResponseRows().values()); + + } + } + + Path adocFile = projectBuildPath.resolve(name + AsciiDoc.EXTENSION); + FileHelper.write(adocFile, builder.getContent()); + log.info("Build AsciiDoc {}", adocFile); + }); + + //渲染adoc文件 + AttributesBuilder attributes = AttributesBuilder.attributes(); + attributes.sectionNumbers(true); + attributes.noFooter(true); + String css = ApiDoc.getInstance().getContext().getCss(); + if (StringHelper.nonBlank(css)) { + attributes.linkCss(true); + attributes.styleSheetName(css); + } + //asciidoctorj 的 options + OptionsBuilder builder = OptionsBuilder.options() + .mkDirs(true) + .inPlace(true) + .toDir(projectBuildPath.toFile()) + .safe(SafeMode.SAFE) + .attributes(attributes); + Asciidoctor.Factory.create() + .convertDirectory(new AsciiDocDirectoryWalker(projectBuildPath.toString()), builder.get()); + + log.info("Render AsciiDoc to html {}", projectBuildPath); + } + + /** + * Table. + * + * @param builder the builder + * @param rows the rows + */ + private void table(MarkupBuilder builder, Collection rows) { + if (rows.size() > 0) { + List> responseTable = new ArrayList<>(); + responseTable.add(Lists.newArrayList("Field", "Type", "Condition", "Default", "Description")); + rows.forEach(row -> responseTable.add(Lists.newArrayList(row.getKey(), row.getType(), row.getCondition(), row.getDef(), row.getRemark()))); + builder.table(responseTable, true, false); + } + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/render/PostmanRender.java b/apidoc-core/src/main/java/com/apidoc/core/render/PostmanRender.java new file mode 100644 index 0000000000000000000000000000000000000000..64a5b6347f0e3456023e0e94cd464c8fa101ff62 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/render/PostmanRender.java @@ -0,0 +1,115 @@ +package com.apidoc.core.render; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.ObjectMappers; +import com.apidoc.core.common.helper.FileHelper; +import com.apidoc.core.common.postman.*; +import com.apidoc.core.schema.*; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; + +import java.nio.file.Path; + +/** + * Postman v2.1 json文件构建 + */ +@Slf4j +public class PostmanRender implements ProjectRender { + + @Override + public void render(Project project) { + Postman postman = build(project); + String id = ApiDoc.getInstance().getContext().getId(); + Path buildPath = ApiDoc.getInstance().getContext().getBuildPath(); + Path file = buildPath.resolve(id).resolve("postman_v2_1.json"); + FileHelper.write(file, ObjectMappers.pretty(postman)); + log.info("Build Postman {}", file); + } + + private Postman build(Project project) { + Postman postman = new Postman(); + Info info = new Info(); + info.set_postman_id(project.getId()); + info.setName(project.getName()); + info.setDescription(project.getDescription()); + postman.setInfo(info); + + for (Book book : project.getBooks().values()) { + Folder folder = new Folder(); + folder.setName(book.getId()); + for (Chapter chapter : book.getChapters()) { + if(chapter.isIgnore() || chapter.getSections().isEmpty()){ + continue; + } + Folder chapterFolder = new Folder(); + chapterFolder.setName(chapter.getName()); + chapterFolder.setDescription(chapter.getDescription()); + for (Section section : chapter.getSections()) { + if(section.isIgnore()){ + continue; + } + chapterFolder.getItem().add(build(section)); + } + folder.getItem().add(chapterFolder); + } + postman.getItem().add(folder); + } + + if(postman.getItem().size()==1){ + Folder folder = postman.getItem().get(0); + postman.setItem(folder.getItem()); + } + + return postman; + } + + private Item build(Section section) { + Item item = new Item(); + item.setName(section.getName()); + item.setDescription(section.getDescription()); + + Request request = new Request(); + request.setDescription(section.getDescription()); + request.getUrl().setPath(section.getUri()); + request.setMethod(section.getMethod()); + request.getHeaders().addAll(section.getInHeaders().values()); + + if(section.isQueryParameter()){ + if(Method.GET.equals(request.getMethod())){ + ObjectNode objectNode = (ObjectNode)section.getParameter(); + for (String key : section.getRequestRows().keySet()) { + if (objectNode.has(key)) { + Row row = section.getRequestRows().get(key); + request.getUrl().getQuery().add(Parameter.of(row)); + } + } + }else{ + request.getBody().setMode(BodyMode.urlencoded); + ObjectNode objectNode = (ObjectNode)section.getParameter(); + for (String key : section.getRequestRows().keySet()) { + if (objectNode.has(key)) { + Row row = section.getRequestRows().get(key); + request.getBody().getUrlencoded().add(Parameter.of(row)); + } + } + } + }else{ + request.getBody().setMode(BodyMode.raw); + request.getBody().setRaw(section.getParameterString()); + } + item.setRequest(request); + + Response response = new Response(); + response.setName("success"); + response.setOriginalRequest(request); + response.getHeaders().addAll(section.getOutHeaders().values()); + response.setBody(section.getResponseString()); + response.setCode(200); + response.setStatus("OK"); + item.getResponse().add(response); + + return item; + } + + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/render/ProjectRender.java b/apidoc-core/src/main/java/com/apidoc/core/render/ProjectRender.java new file mode 100644 index 0000000000000000000000000000000000000000..b1541f6fd6ad8eac1dc6e6eb9d02385b4665abb7 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/render/ProjectRender.java @@ -0,0 +1,18 @@ +package com.apidoc.core.render; + +import com.apidoc.core.schema.Project; + +/** + * The interface Project render. + * @author fengyuchenglun + */ +public interface ProjectRender { + + /** + * Render. + * + * @param project the project + */ + void render(Project project); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/ArrayTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/ArrayTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..55c4ba8b4b12c9b7237df73f6777037fd8168253 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/ArrayTypeResolver.java @@ -0,0 +1,19 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.ArrayTypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; + +public class ArrayTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return type.isArray(); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + return new ArrayTypeDescription(ApiDoc.getInstance().getTypeResolvers().resolve(type.asArrayType().getComponentType())); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/CollectionTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/CollectionTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..2723e0d1c747643d0b56e4ec058b5de731460c33 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/CollectionTypeResolver.java @@ -0,0 +1,46 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.helper.TypeParameterHelper; +import com.apidoc.core.common.description.ArrayTypeDescription; +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.UnAvailableTypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; +import com.google.common.collect.ImmutableList; + +import java.util.Optional; + +public class CollectionTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return isCollection(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + Optional optional = TypeParameterHelper.getTypeParameter(type.asReferenceType(), 0); + return new ArrayTypeDescription(optional + .map(ApiDoc.getInstance().getTypeResolvers()::resolve) + .orElseGet(UnAvailableTypeDescription::new)); + } + + private static boolean isCollection(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + return isCollection(type.asReferenceType().getId()); + } + + private static boolean isCollection(String id){ + return ImmutableList.of("java.util.List", + "java.util.Collection", + "java.util.ArrayList", + "java.util.LinkedList", + "java.util.Set", + "java.util.HashSet", + "java.util.Iterator", + "java.lang.Iterable" + ).contains(id); + + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/DateTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/DateTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..93cfad10c3d8eb6d700ba9e9f546b06ee1c79bc1 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/DateTypeResolver.java @@ -0,0 +1,33 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.StringTypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; +import com.google.common.collect.ImmutableList; + +public class DateTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return isDate(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + return new StringTypeDescription(type.asReferenceType().getTypeDeclaration().getName(),""); + } + + private static boolean isDate(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + return isDate(type.asReferenceType().getId()); + } + + private static boolean isDate(String id){ + return ImmutableList.of("java.util.Date", + "java.time.LocalDateTime", + "java.time.LocalDate", + "java.time.LocalTime" + ).contains(id); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/EnumTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/EnumTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..3863c131e6e38dc0b0aa5a577773e10303cbcd49 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/EnumTypeResolver.java @@ -0,0 +1,24 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.StringTypeDescription; +import com.apidoc.core.common.description.TypeDescription; +import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedEnumDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +public class EnumTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return type.isReferenceType() && type.asReferenceType().getTypeDeclaration().isEnum(); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + ResolvedEnumDeclaration enumDeclaration = type.asReferenceType().getTypeDeclaration().asEnum(); + TypeDescription description = new StringTypeDescription(enumDeclaration.getName(),""); + for (ResolvedEnumConstantDeclaration enumConstant : enumDeclaration.getEnumConstants()) { + description.addRemark(enumConstant.getName()); + } + return description; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/MapTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/MapTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..2855dcda870d7a8edc95b4f1bb25072de5bd4e37 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/MapTypeResolver.java @@ -0,0 +1,38 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.UnAvailableTypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; +import com.google.common.collect.ImmutableList; + +/** + * 不支持直接使用Map,建议使用DTO + */ +public class MapTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return isMap(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + return new UnAvailableTypeDescription(); + } + + private static boolean isMap(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + return isMap(type.asReferenceType().getId()); + } + + private static boolean isMap(String id){ + return ImmutableList.of("java.util.Map", + "java.util.HashMap", + "java.util.Hashtable", + "java.util.SortedMap", + "java.util.LinkedHashMap", + "java.lang.TreeMap" + ).contains(id); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/ObjectTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/ObjectTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..8117ea2d67d4fb5187207ad22cdba141663390fb --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/ObjectTypeResolver.java @@ -0,0 +1,83 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.description.ObjectTypeDescription; +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.helper.*; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.Optional; + +/** + * java bean解析 + */ +public class ObjectTypeResolver implements TypeResolver { + + @Override + public boolean accept(ResolvedType type) { + return type.isReferenceType(); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + + ResolvedReferenceType referenceType = type.asReferenceType(); + ObjectTypeDescription typeDescription = new ObjectTypeDescription(); + + typeDescription.setType(referenceType.getTypeDeclaration().getName()); + //类型解析缓冲池,防止循环引用 + if(!ReferenceContext.getInstance().push(referenceType.describe())){ + return typeDescription; + } + + //解析父类属性,并合并至当前 + for (ResolvedReferenceType directAncestor : referenceType.getDirectAncestors()) { + TypeDescription ancestorDescription = ApiDoc.getInstance().getTypeResolvers().resolve(directAncestor); + if(ancestorDescription.isAvailable() && ancestorDescription.isObject()){ + typeDescription.merge(ancestorDescription.asObject()); + } + } + + + //TODO fix use access method + for (ResolvedFieldDeclaration declaredField : referenceType.getTypeDeclaration().getDeclaredFields()) { + if(declaredField.isStatic()){ + continue; + } + ResolvedType fieldType = declaredField.getType(); + + if(fieldType.isReferenceType()){ + //将父类的T,传递给 属性的T + fieldType = TypeParameterHelper.useClassTypeParameter(referenceType,fieldType.asReferenceType()); + } + if(declaredField.getType().isTypeVariable()){ + //类型为T,这种泛型 + Optional optional = TypeParameterHelper.getTypeParameter(referenceType, declaredField.getType().asTypeParameter().getName()); + if(optional.isPresent()){ + fieldType = optional.get(); + } + } + + TypeDescription fieldDescription = ApiDoc.getInstance().getTypeResolvers().resolve(fieldType); + fieldDescription.setKey(declaredField.getName()); + //查找json别名 + JsonPropertyHelper.getJsonName(declaredField).ifPresent(fieldDescription::setKey); + //解析注释 + fieldDescription.addRemark(CommentHelper.getComment(declaredField)); + //查找Validation注解 + for (String validation : ValidationHelper.getValidations(declaredField)) { + fieldDescription.getCondition().append(validation).append(" "); + } + //查找字段初始化值 + FieldHelper.getInitializer(declaredField).ifPresent(expr-> fieldDescription.setDefaultValue(ExpressionHelper.getValue(expr))); + + typeDescription.add(fieldDescription); + } + + ReferenceContext.getInstance().remove(referenceType.describe()); + return typeDescription; + } + +} \ No newline at end of file diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/PrimitiveTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/PrimitiveTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..790bedaa9de6addf647c07f9613730084783a600 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/PrimitiveTypeResolver.java @@ -0,0 +1,40 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.PrimitiveTypeDescription; +import com.apidoc.core.common.description.TypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; +import com.google.common.collect.ImmutableList; + +public class PrimitiveTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return type.isPrimitive() || isBoxing(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + if(type.isPrimitive()){ + return new PrimitiveTypeDescription(type.asPrimitive()); + }else{ + return new PrimitiveTypeDescription(type.asReferenceType()); + } + } + + private static boolean isBoxing(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + String id = type.asReferenceType().getId(); + return ImmutableList.of( + "java.lang.Boolean", + "java.lang.Character", + "java.lang.Double", + "java.lang.Float", + "java.lang.Long", + "java.lang.Integer", + "java.lang.Short", + "java.lang.Byte" + ).contains(id); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/StringTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/StringTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..4557b44907f9f00284cb2c7f8b06d32cfd5176bc --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/StringTypeResolver.java @@ -0,0 +1,31 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.StringTypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; +import com.google.common.collect.ImmutableList; + +public class StringTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return isString(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + return new StringTypeDescription("String",""); + } + + private static boolean isString(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + return isString(type.asReferenceType().getId()); + } + + private static boolean isString(String id){ + return ImmutableList.of("java.lang.String", + "java.lang.CharSequence" + ).contains(id); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/SystemObjectTypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/SystemObjectTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..fabfa4066ccd4f9d41c4ee0c88d8b024891696f6 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/SystemObjectTypeResolver.java @@ -0,0 +1,28 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.UnAvailableTypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; + +public class SystemObjectTypeResolver implements TypeResolver { + @Override + public boolean accept(ResolvedType type) { + return isSystem(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + return new UnAvailableTypeDescription(); + } + + private static boolean isSystem(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + return isSystem(type.asReferenceType().getId()); + } + + private static boolean isSystem(String id){ + return id!=null && (id.startsWith("java") ||id.startsWith("sun")); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeNameResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeNameResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..ae5c4e0960051d51ee0027bae15db1e8325c4676 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeNameResolver.java @@ -0,0 +1,12 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.github.javaparser.ast.type.Type; + +public interface TypeNameResolver { + + boolean accept(String id); + + TypeDescription resolve(Type type); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeResolver.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..ce68789cd8e485cf65385d83852fc0526438bb75 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeResolver.java @@ -0,0 +1,12 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.github.javaparser.resolution.types.ResolvedType; + +public interface TypeResolver { + + boolean accept(ResolvedType type); + + TypeDescription resolve(ResolvedType type); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeResolvers.java b/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeResolvers.java new file mode 100644 index 0000000000000000000000000000000000000000..4b5ca96f26d7ebd48bc58313857bd876154218bd --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/resolver/TypeResolvers.java @@ -0,0 +1,90 @@ +package com.apidoc.core.resolver; + +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.helper.TypeNameHelper; +import com.apidoc.core.common.description.UnAvailableTypeDescription; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.types.ResolvedType; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class TypeResolvers { + + private ObjectTypeResolver objectTypeResolver = new ObjectTypeResolver(); + + /** + * 类型解析器 + */ + private List resolvers = Lists.newArrayList( + new PrimitiveTypeResolver(), + new ArrayTypeResolver(), + new StringTypeResolver(), + new CollectionTypeResolver(), + new DateTypeResolver(), + new MapTypeResolver(), + new EnumTypeResolver(), + new SystemObjectTypeResolver() + ); + + private List nameResolvers = Lists.newArrayList(); + + /** + * 获取类型信息 + * @param type + * @return + */ + public TypeDescription resolve(Type type){ + try{ + ResolvedType resolvedType = type.resolve(); + return resolve(resolvedType); + } catch (UnsolvedSymbolException e){ + //解析失败时,尝试降级,使用名称解析 + return nameResolve(type); + } catch (Exception e){ + log.error(e.getMessage(),e); + } + return new UnAvailableTypeDescription(); + } + + /** + * 解析类型信息 + * @param type + * @return + */ + public TypeDescription resolve(ResolvedType type){ + for (TypeResolver typeResolver : resolvers) { + if(typeResolver.accept(type)){ + return typeResolver.resolve(type); + } + } + if(objectTypeResolver.accept(type)){ + return objectTypeResolver.resolve(type); + } + return new UnAvailableTypeDescription(); + } + + public TypeDescription nameResolve(Type type){ + String id = TypeNameHelper.getName(type); + for (TypeNameResolver nameResolver : nameResolvers) { + if (nameResolver.accept(id)) { + return nameResolver.resolve(type); + } + } + log.warn("type({}) resolve failed",id); + return new UnAvailableTypeDescription(); + } + + public void addResolver(TypeResolver typeResolver){ + resolvers.add(typeResolver); + } + + public void addNameResolver(TypeNameResolver nameResolver){ + nameResolvers.add(nameResolver); + } + + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Book.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Book.java new file mode 100644 index 0000000000000000000000000000000000000000..02f01510e0a33fd1cebf8d944acf5494b87c14d2 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Book.java @@ -0,0 +1,20 @@ +package com.apidoc.core.schema; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Set; +import java.util.TreeSet; + +@Setter +@Getter +public class Book extends Node { + + public static final String DEFAULT = "index"; + + Set chapters = new TreeSet<>(); + + public Book(String id) { + this.id = id; + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Chapter.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Chapter.java new file mode 100644 index 0000000000000000000000000000000000000000..102097926bbc2e4ea2c080bc48077bb4768f5f4c --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Chapter.java @@ -0,0 +1,20 @@ +package com.apidoc.core.schema; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Set; +import java.util.TreeSet; + +/** + * 章,一个类解析为一章 + */ +@Setter +@Getter +public class Chapter extends Node { + + String bookName; + + Set
sections = new TreeSet<>(); + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Header.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Header.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f6df416fb3311d576ce3bf726ce58bdc22e9b4 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Header.java @@ -0,0 +1,39 @@ +package com.apidoc.core.schema; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@NoArgsConstructor +public class Header{ + + public static final Header APPLICATION_JSON = new Header("Content-Type","application/json"); + public static final Header X_FORM_WWW_URLENCODED = new Header("Content-Type","x-www-form-urlencoded"); + + String key; + String value; + String description; + + public Header(String key, String value) { + this.key = key; + this.value = value; + } + + public static Header valueOf(String text){ + String[] arr = text.split(":"); + Header header = new Header(); + header.setKey(arr[0]); + if(arr.length>1){ + header.setValue(arr[1]); + } + return header; + } + + @Override + public String toString(){ + return key + ": " + value; + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Method.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Method.java new file mode 100644 index 0000000000000000000000000000000000000000..a4f32260076004e020b6dab04ea3f6614326e263 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Method.java @@ -0,0 +1,19 @@ +package com.apidoc.core.schema; + +import lombok.extern.slf4j.Slf4j; + +/** + * 支持的http method + */ +@Slf4j +public enum Method { + + + GET,POST,PUT,DELETE, + OPTIONS,PATCH,COPY,HEAD,LINK,UNLINK,PURGE,LOCK,UNLOCK,PROPFIND,VIEW; + + public static Method of(String name) { + return valueOf(name); + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Node.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Node.java new file mode 100644 index 0000000000000000000000000000000000000000..8ebcefe11e91eaa96004476473c1ea4462f0cb82 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Node.java @@ -0,0 +1,147 @@ +package com.apidoc.core.schema; + +import com.apidoc.core.Context; +import com.apidoc.core.common.helper.CommentHelper; +import com.apidoc.core.common.helper.StringHelper; +import com.github.javaparser.ast.comments.Comment; +import com.github.javaparser.javadoc.Javadoc; +import lombok.Getter; +import lombok.Setter; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * The type Node. + * + * @author fengyuchenglun + * @version 1.0.0 + */ +@Setter +@Getter +public class Node implements Comparable { + + /** + * 节点编号 + */ + String id; + /** + * 节点名称 + */ + String name; + /** + * 节点描述 + */ + String description; + /** + * 节点索引 + */ + int index = Context.DEFAULT_NODE_INDEX; + /** + * 扩展属性 + * 如:Spring在Controller的RequestMapping,可以存在扩展属性中 + */ + Map ext = new HashMap<>(); + + /** + * javadoc 中的tag + */ + Map tags = new HashMap<>(); + + @Override + public int compareTo(@Nonnull Node other) { + if (this.index != other.index) { + return this.index - other.index; + } + if (this.id != null && other.id != null) { + return this.id.compareTo(other.id); + } + if (this.name != null && other.name != null) { + return this.name.compareTo(other.name); + } + return 0; + } + + /** + * Accept. + * + * @param comment the comment + */ + public void accept(Comment comment) { + if (!comment.isJavadocComment()) { + setNameAndDescription(comment.getContent()); + return; + } + Javadoc javadoc = comment.asJavadocComment().parse(); + setNameAndDescription(CommentHelper.getDescription(javadoc.getDescription())); + + javadoc.getBlockTags().forEach(blockTag -> { + Tag tag = new Tag(); + tag.id = blockTag.getTagName(); + tag.key = blockTag.getName().isPresent() ? blockTag.getName().get() : ""; + tag.content = CommentHelper.getDescription(blockTag.getContent()); + putTag(tag); + }); + + getTag("index").ifPresent(tag -> setIndex(tag.getIntContent(Context.DEFAULT_NODE_INDEX))); + } + + /** + * Sets name and description. + * + * @param content the content + */ + public void setNameAndDescription(String content) { + String[] arr = content.split("(\\r\\n)|(\\r)|(\\n)+", 2); + if (arr.length >= 1 && StringHelper.nonBlank(arr[0])) { + name = arr[0]; + } + if (arr.length >= 2 && StringHelper.nonBlank(arr[1])) { + description = arr[1]; + } + } + + /** + * Gets tag. + * + * @param id the id + * @return the tag + */ + public Optional getTag(String id) { + return Optional.ofNullable(tags.get(id)); + } + + /** + * Gets param tag. + * + * @param id the id + * @return the param tag + */ + public Optional getParamTag(String id) { + return Optional.ofNullable(tags.get("param:"+id)); + } + + /** + * Put tag. + * + * @param tag the tag + */ + public void putTag(Tag tag) { + String id = tag.id; + if(StringHelper.nonBlank(tag.getKey())){ + id += ":" + tag.getKey(); + } + tags.put(id, tag); + } + + /** + * Is ignore boolean. + * + * @return the boolean + */ + public boolean isIgnore() { + return getTag("ignore").isPresent(); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Project.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Project.java new file mode 100644 index 0000000000000000000000000000000000000000..5935f6869fa45b98b00e71108f67242e56d7b5c7 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Project.java @@ -0,0 +1,42 @@ +package com.apidoc.core.schema; + +import lombok.Getter; +import lombok.Setter; + +import java.util.*; + +/** + * 项目 + * + * @author fengyuchenglun + * @version 1.0.0 + */ +@Setter +@Getter +public class Project extends Node { + + /** + * 版本 + */ + String version; + + /** + * The Books. + */ + Map books = new TreeMap<>(); + + /** + * Add chapter. + * + * @param chapter the chapter + */ + public void addChapter(Chapter chapter) { + if(Objects.isNull(chapter.getBookName())){ + chapter.setBookName(Book.DEFAULT); + } + if (!books.containsKey(chapter.getBookName())) { + books.put(chapter.getBookName(), new Book(chapter.getBookName())); + } + books.get(chapter.getBookName()).getChapters().add(chapter); + } +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Row.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Row.java new file mode 100644 index 0000000000000000000000000000000000000000..d4fc4048709172640731201ee6d23a915d5a12da --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Row.java @@ -0,0 +1,22 @@ +package com.apidoc.core.schema; + +import lombok.*; + +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +@Setter +@Getter +public class Row { + + String key; + String type; + String condition; + String def; + String remark; + + public Row(String type) { + this.type = type; + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Section.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Section.java new file mode 100644 index 0000000000000000000000000000000000000000..d6be636389095d19d0b52185325ce0fc9fdb7e76 --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Section.java @@ -0,0 +1,107 @@ +package com.apidoc.core.schema; + +import com.apidoc.core.common.ObjectMappers; +import com.apidoc.core.common.QueryStringBuilder; +import com.apidoc.core.common.helper.StringHelper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import lombok.Setter; + +import java.util.*; + +/** + * 小节,一个请求,封装为一个小节 + */ +@Setter +@Getter +public class Section extends Node { + + Method method; + String uri; + Map inHeaders = new LinkedHashMap<>(); + ObjectNode pathVariable = ObjectMappers.instance.createObjectNode(); + JsonNode parameter; + boolean queryParameter = true; + Map requestRows = new LinkedHashMap<>(); + + Map outHeaders = new LinkedHashMap<>(); + JsonNode response; + Map responseRows = new LinkedHashMap<>(); + Object rawResponse; + + public void addRequestRow(Row row){ + requestRows.put(row.getKey(), row); + } + + public void addRequestRows(Collection rows){ + for (Row row : rows) { + if(row.getKey()!=null && !requestRows.containsKey(row.getKey())){ + requestRows.put(row.getKey(),row); + } + } + } + + public String getRequestLine(){ + StringBuilder builder = new StringBuilder(this.method.toString()); + builder.append(" ").append(this.uri); + if(this.queryParameter && Objects.equals("GET", this.method)){ + String parameterString = getParameterString(); + if(StringHelper.nonBlank(parameterString)){ + builder.append("?").append(parameterString); + } + } + builder.append(" HTTP/1.1"); + return builder.toString(); + } + + public String getParameterString(){ + if(queryParameter && parameter instanceof ObjectNode){ + return new QueryStringBuilder().append((ObjectNode)parameter).toString(); + } + return ObjectMappers.pretty(parameter); + } + + public boolean hasRequestBody(){ + if(Objects.equals("GET",this.method)){ + return false; + } + return parameter!=null && parameter.size()>0; + } + + public void addResponseRow(Row row){ + responseRows.put(row.getKey(), row); + } + + public void addResponseRows(Collection rows){ + for (Row row : rows) { + if(row.getKey()!=null && !responseRows.containsKey(row.getKey())) { + responseRows.put(row.getKey(), row); + } + } + } + + public boolean hasResponseBody(){ + return response!=null || rawResponse!=null; + } + + public String getResponseString(){ + if(response!=null){ + return ObjectMappers.pretty(response); + } + return String.valueOf(rawResponse); + } + + public void addInHeader(Header header){ + if (!inHeaders.containsKey(header.getKey())) { + inHeaders.put(header.getKey(),header); + } + } + + public void addOutHeader(Header header){ + if (!outHeaders.containsKey(header.getKey())) { + outHeaders.put(header.getKey(),header); + } + } + +} diff --git a/apidoc-core/src/main/java/com/apidoc/core/schema/Tag.java b/apidoc-core/src/main/java/com/apidoc/core/schema/Tag.java new file mode 100644 index 0000000000000000000000000000000000000000..81964c9d9cc98193267d0e35463db614466f736d --- /dev/null +++ b/apidoc-core/src/main/java/com/apidoc/core/schema/Tag.java @@ -0,0 +1,33 @@ +package com.apidoc.core.schema; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +/** + * //@param username 用户名 + * id key content + */ +@Slf4j +@Setter +@Getter +public class Tag { + + String id; + String key; + String content; + + public int getIntContent(int def){ + if (Objects.nonNull(content)) { + try{ + return Integer.parseInt(content); + }catch (Exception e){ + log.warn(content+" parse error"); + } + } + return def; + } + +} diff --git a/apidoc-springmvc/build.gradle b/apidoc-springmvc/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b2f870b211505b91c8cf95ee344b9c9b8d8a5adb --- /dev/null +++ b/apidoc-springmvc/build.gradle @@ -0,0 +1,9 @@ +dependencies { + compile project(":apidoc-core") + + testCompile group: 'org.springframework', name: 'spring-webmvc', version: '5.1.0.RELEASE' + testCompile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final' + testCompile group: 'com.alibaba', name: 'fastjson', version: '1.2.51' + testCompile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + testCompile fileTree(dir: "src/test/resources/lib/", includes: ["*.jar"]) +} diff --git a/apidoc-springmvc/src/main/java/com/apigcc/springmvc/ParameterHelper.java b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/ParameterHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..c45f3a822bb782a309f9f4200911979a0a6df954 --- /dev/null +++ b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/ParameterHelper.java @@ -0,0 +1,60 @@ +package com.apigcc.springmvc; + +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.Parameter; + +public class ParameterHelper { + + public static final String ANNOTATION_REQUEST_PARAM = "RequestParam"; + public static final String ANNOTATION_REQUEST_HEADER = "RequestHeader"; + public static final String ANNOTATION_REQUEST_ATTRIBUTE = "RequestAttribute"; + public static final String ANNOTATION_REQUEST_PART = "RequestPart"; + public static final String ANNOTATION_COOKIE_VALUE = "CookieValue"; + public static final String ANNOTATION_PATH_VARIABLE = "PathVariable"; + public static final String ANNOTATION_REQUEST_BODY = "RequestBody"; + public static final String ANNOTATION_MULTIPART_FILE = "MultipartFile"; + + public static boolean hasRequestBody(NodeList parameters) { + for (Parameter parameter : parameters) { + if (isRequestBody(parameter)) { + return true; + } + } + return false; + } + + public static boolean isRequestParam(Parameter parameter) { + if (!parameter.isAnnotationPresent(ANNOTATION_PATH_VARIABLE) && + !parameter.isAnnotationPresent(ANNOTATION_REQUEST_BODY) && + !parameter.isAnnotationPresent(ANNOTATION_REQUEST_HEADER) && + !parameter.isAnnotationPresent(ANNOTATION_COOKIE_VALUE) && + !parameter.isAnnotationPresent(ANNOTATION_REQUEST_PART) && + !parameter.isAnnotationPresent(ANNOTATION_MULTIPART_FILE) && + !parameter.isAnnotationPresent(ANNOTATION_REQUEST_ATTRIBUTE)) { + return true; + } + return false; + } + + public static boolean isPathVariable(Parameter parameter) { + if (parameter.isAnnotationPresent(ANNOTATION_PATH_VARIABLE)) { + return true; + } + return false; + } + + public static boolean isRequestBody(Parameter parameter) { + if (parameter.isAnnotationPresent(ANNOTATION_REQUEST_BODY)) { + return true; + } + return false; + } + + public static boolean isRequestHeader(Parameter parameter) { + if (parameter.isAnnotationPresent(ANNOTATION_REQUEST_HEADER)) { + return true; + } + return false; + } + +} diff --git a/apidoc-springmvc/src/main/java/com/apigcc/springmvc/RequestMappingHelper.java b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/RequestMappingHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..02c6afee4dbf114e794586414e923f12c4db0ae0 --- /dev/null +++ b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/RequestMappingHelper.java @@ -0,0 +1,228 @@ +package com.apigcc.springmvc; + +import com.apidoc.core.common.URI; +import com.apidoc.core.common.helper.AnnotationHelper; +import com.apidoc.core.common.helper.ClassDeclarationHelper; +import com.apidoc.core.common.helper.ExpressionHelper; +import com.apidoc.core.schema.Method; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.expr.Expression; +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Optional; + +/** + * The type Request mapping helper. + * + * @author fengyuchenglun + * @version 1.0.0 + */ +public class RequestMappingHelper { + + + /** + * 常量-GetMapping + */ + public static final String ANNOTATION_GET_MAPPING = "GetMapping"; + /** + * 常量-PostMapping + */ + public static final String ANNOTATION_POST_MAPPING = "PostMapping"; + /** + * 常量-PutMapping + */ + public static final String ANNOTATION_PUT_MAPPING = "PutMapping"; + /** + * 常量-PatchMapping + */ + public static final String ANNOTATION_PATCH_MAPPING = "PatchMapping"; + /** + * 常量-DeleteMapping + */ + public static final String ANNOTATION_DELETE_MAPPING = "DeleteMapping"; + /** + * 常量-RequestMapping + */ + public static final String ANNOTATION_REQUEST_MAPPING = "RequestMapping"; + + /** + * 常量-ResponseBody + */ + public static final String ANNOTATION_RESPONSE_BODY = "ResponseBody"; + + /** + * The constant ANNOTATION_REQUEST_MAPPINGS. + */ + public static final List ANNOTATION_REQUEST_MAPPINGS = Lists.newArrayList( + ANNOTATION_GET_MAPPING, + ANNOTATION_POST_MAPPING, + ANNOTATION_PUT_MAPPING, + ANNOTATION_PATCH_MAPPING, + ANNOTATION_DELETE_MAPPING, + ANNOTATION_REQUEST_MAPPING); + + /** + * Is rest boolean. + * + * @param n the n + * @return the boolean + */ + public static boolean isRest(MethodDeclaration n) { + if (n.isAnnotationPresent(ANNOTATION_RESPONSE_BODY)) { + return true; + } + Optional parentOptional = n.getParentNode(); + if (parentOptional.isPresent()) { + Node parentNode = parentOptional.get(); + if (parentNode instanceof ClassOrInterfaceDeclaration) { + if (AnnotationHelper.isAnnotationPresent(((ClassOrInterfaceDeclaration) parentNode), SpringMVCContext.getInstance().getRestControllers())) { + return true; + } + } + } + return false; + } + + + /** + * Pick method method. + * + * @param methodDeclaration methodDeclaration + * @return the method + */ + public static Method pickMethod(MethodDeclaration methodDeclaration) { + if (methodDeclaration.isAnnotationPresent(ANNOTATION_GET_MAPPING)) { + return Method.GET; + } + if (methodDeclaration.isAnnotationPresent(ANNOTATION_POST_MAPPING)) { + return Method.POST; + } + if (methodDeclaration.isAnnotationPresent(ANNOTATION_PUT_MAPPING)) { + return Method.PUT; + } + if (methodDeclaration.isAnnotationPresent(ANNOTATION_PATCH_MAPPING)) { + return Method.PATCH; + } + if (methodDeclaration.isAnnotationPresent(ANNOTATION_DELETE_MAPPING)) { + return Method.DELETE; + } + if (methodDeclaration.isAnnotationPresent(ANNOTATION_REQUEST_MAPPING)) { + AnnotationExpr annotationExpr = methodDeclaration.getAnnotationByName(ANNOTATION_REQUEST_MAPPING).get(); + Optional expressionOptional = AnnotationHelper.getAttribute(annotationExpr, "method"); + if (expressionOptional.isPresent()) { + Expression expression = expressionOptional.get(); + if (expression.isArrayInitializerExpr()) { + NodeList values = expression.asArrayInitializerExpr().getValues(); + if (values != null && values.size() > 0) { + return Method.valueOf(values.get(0).toString().replaceAll("RequestMethod.", "")); + } + } + return Method.of(expression.toString().replaceAll("RequestMethod.", "")); + } + } + return Method.GET; + } + + + /** + * 获取uri数据 + * + * @param n the n + * @return uri + */ + public static URI pickUriToParent(ClassOrInterfaceDeclaration n) { + URI parentUri = null; + Optional parentOptional = ClassDeclarationHelper.getParent(n); + if (parentOptional.isPresent()) { + parentUri = pickUriToParent(parentOptional.get()); + } + URI uri = new URI(pickUri(n.getAnnotations())); + if (parentUri != null) { + parentUri.add(uri); + return parentUri; + } + return uri; + } + + /** + * 获取uri数据,有多个时,暂时只取第一个 + * + * @param nodeList the node list + * @return string + */ + public static String pickUri(NodeList nodeList) { + for (AnnotationExpr annotationExpr : nodeList) { + if (ANNOTATION_REQUEST_MAPPINGS.contains(annotationExpr.getNameAsString())) { + Optional expressionOptional = AnnotationHelper.getAnyAttribute(annotationExpr, "value", "path"); + if (expressionOptional.isPresent()) { + Expression expression = expressionOptional.get(); + return ExpressionHelper.getStringValue(expression); + } + } + } + return ""; + } + + /** + * 获取headers + * + * @param nodeList the node list + * @return list + */ + public static List pickHeaders(NodeList nodeList) { + for (AnnotationExpr annotationExpr : nodeList) { + if (ANNOTATION_REQUEST_MAPPINGS.contains(annotationExpr.getNameAsString())) { + Optional expressionOptional = AnnotationHelper.getAttribute(annotationExpr, "headers"); + if (expressionOptional.isPresent()) { + Expression expression = expressionOptional.get(); + return ExpressionHelper.getStringValues(expression); + } + } + } + return Lists.newArrayList(); + } + + /** + * 获取headers + * + * @param nodeList the node list + * @return list + */ + public static List pickConsumers(NodeList nodeList) { + for (AnnotationExpr annotationExpr : nodeList) { + if (ANNOTATION_REQUEST_MAPPINGS.contains(annotationExpr.getNameAsString())) { + Optional expressionOptional = AnnotationHelper.getAttribute(annotationExpr, "consumes"); + if (expressionOptional.isPresent()) { + Expression expression = expressionOptional.get(); + return ExpressionHelper.getStringValues(expression); + } + } + } + return Lists.newArrayList(); + } + + /** + * 获取headers + * + * @param nodeList the node list + * @return list + */ + public static List pickProduces(NodeList nodeList) { + for (AnnotationExpr annotationExpr : nodeList) { + if (ANNOTATION_REQUEST_MAPPINGS.contains(annotationExpr.getNameAsString())) { + Optional expressionOptional = AnnotationHelper.getAttribute(annotationExpr, "produces"); + if (expressionOptional.isPresent()) { + Expression expression = expressionOptional.get(); + return ExpressionHelper.getStringValues(expression); + } + } + } + return Lists.newArrayList(); + } + +} diff --git a/apidoc-springmvc/src/main/java/com/apigcc/springmvc/SpringMVCContext.java b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/SpringMVCContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0b21ceb15136be30d13769670ab5f6ac8abe1407 --- /dev/null +++ b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/SpringMVCContext.java @@ -0,0 +1,80 @@ +package com.apigcc.springmvc; + +import com.apidoc.core.ApiDoc; +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * springMVC 参数上下文 + * + * @author duanledexianxianxian + * @version 1.0.0 + * @date 2020 /3/25 0:21 + * @since 1.0.0 + */ +@Data +public class SpringMVCContext { + + /** + * 常量-Controller + */ + public static final String ANNOTATION_CONTROLLER = "Controller"; + /** + * 常量-RestController + */ + public static final String ANNOTATION_REST_CONTROLLER = "RestController"; + + /** + * 常量-KimController + */ + public static final String ANNOTATION_KIM_CONTROLLER = "KimController"; + + /** + * The Controllers. + */ + public List controllers = Lists.newArrayList(ANNOTATION_CONTROLLER, ANNOTATION_REST_CONTROLLER, ANNOTATION_KIM_CONTROLLER); + + /** + * The Rest controllers. + */ + public List restControllers = Lists.newArrayList(ANNOTATION_REST_CONTROLLER, ANNOTATION_KIM_CONTROLLER); + + + /** + * 获取springMVC 参数上下文实例 + * + * @return the instance + */ + public static SpringMVCContext getInstance() { + Map ext = ApiDoc.getInstance().getContext().getExt(); + if (null == ext) { + throw new IllegalArgumentException("Context ext is null"); + } + SpringMVCContext context = (SpringMVCContext) ext.get(SpringParser.FRAMEWORK); + if (context == null) { + context = new SpringMVCContext(); + } + return context; + } + + /** + * Add controller. + * + * @param controller controller + */ + public void addController(String controller) { + this.controllers.add(controller); + } + + /** + * Add restControllers. + * + * @param restController restController + */ + public void addRestController(String restController) { + this.restControllers.add(restController); + } +} diff --git a/apidoc-springmvc/src/main/java/com/apigcc/springmvc/SpringParser.java b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/SpringParser.java new file mode 100644 index 0000000000000000000000000000000000000000..d5e04fe0c8d5107ab54650b5a7aaa990b97b7c7c --- /dev/null +++ b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/SpringParser.java @@ -0,0 +1,324 @@ +package com.apigcc.springmvc; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.URI; +import com.apidoc.core.common.description.ObjectTypeDescription; +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.helper.AnnotationHelper; +import com.apidoc.core.common.helper.ExpressionHelper; +import com.apidoc.core.common.helper.StringHelper; +import com.apidoc.core.parser.ParserStrategy; +import com.apidoc.core.schema.Chapter; +import com.apidoc.core.schema.Header; +import com.apidoc.core.schema.Row; +import com.apidoc.core.schema.Section; +import com.apigcc.springmvc.resovler.SpringComponentTypeResolver; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.expr.Expression; + +import java.util.List; +import java.util.Optional; + +import static com.apigcc.springmvc.ParameterHelper.ANNOTATION_REQUEST_HEADER; +import static com.apigcc.springmvc.ParameterHelper.ANNOTATION_REQUEST_PARAM; + + +/** + * spring 解析 + * + * @author fengyuchenglun + * @version 1.0.0 + */ +public class SpringParser implements ParserStrategy { + + /** + * The constant FRAMEWORK. + */ + public static final String FRAMEWORK = "springmvc"; + + + /** + * The constant EXT_URI. + */ + public static final String EXT_URI = "uri"; + + + + @Override + public String name() { + return FRAMEWORK; + } + + @Override + public void onLoad() { + // 添加类型解析器 + ApiDoc.getInstance().getTypeResolvers().addResolver(new SpringComponentTypeResolver()); + // 类型名称解析器 + ApiDoc.getInstance().getTypeResolvers().addNameResolver(new SpringComponentTypeResolver()); + } + + /** + * 处理被@RestController和@Controller标记的类 + * + * @return classOrInterfaceDeclaration + */ + @Override + public boolean accept(ClassOrInterfaceDeclaration classOrInterfaceDeclaration) { + return AnnotationHelper.isAnnotationPresent(classOrInterfaceDeclaration, SpringMVCContext.getInstance().getControllers()); + } + + /** + * 类被@RestController标记,或方法被@ResponseBody标记 + * + * @param methodDeclaration methodDeclaration + * @return boolean + */ + @Override + public boolean accept(MethodDeclaration methodDeclaration) { + //类被@RestController标记,或方法被@ResponseBody标记 + return RequestMappingHelper.isRest(methodDeclaration) && AnnotationHelper.isAnnotationPresent(methodDeclaration, RequestMappingHelper.ANNOTATION_REQUEST_MAPPINGS); + } + + /** + * 解析类定义 + * + * @param n + * @param chapter + */ + @Override + public void visit(ClassOrInterfaceDeclaration n, Chapter chapter) { + chapter.getExt().put(EXT_URI, RequestMappingHelper.pickUriToParent(n)); + } + + /** + * 解析方法定义 + * + * @param n + * @param chapter + * @param section + */ + @Override + public void visit(MethodDeclaration n, Chapter chapter, Section section) { + visitMethod(n, chapter, section); + visitUri(n, chapter, section); + visitPathVariable(n, chapter, section); + visitHeaders(n, chapter, section); + visitParameters(n, chapter, section); + visitReturn(n, chapter, section); + } + + /** + * 解析请求方法 + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitMethod(MethodDeclaration n, Chapter chapter, Section section) { + section.setMethod(RequestMappingHelper.pickMethod(n)); + } + + /** + * 解析请求URI,与父类URI拼接 + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitUri(MethodDeclaration n, Chapter chapter, Section section) { + URI uri = (URI) chapter.getExt().get(EXT_URI); + section.setUri(new URI(uri.toString()).add(RequestMappingHelper.pickUri(n.getAnnotations())).toString()); + } + + /** + * 解析方法参数 + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitParameters(MethodDeclaration n, Chapter chapter, Section section) { + if (ParameterHelper.hasRequestBody(n.getParameters())) { + visitRequestBody(n, chapter, section); + } else { + visitParameter(n, chapter, section); + } + } + + /** + * 解析PathVariable + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitPathVariable(MethodDeclaration n, Chapter chapter, Section section) { + for (Parameter parameter : n.getParameters()) { + if (ParameterHelper.isPathVariable(parameter)) { + section.getPathVariable().put(parameter.getNameAsString(), ""); + Row row = new Row(); + row.setKey(parameter.getNameAsString()); + row.setType(parameter.getType().toString()); + section.getParamTag(row.getKey()).ifPresent(tag -> row.setRemark(tag.getContent())); + section.addRequestRow(row); + } + } + } + + /** + * 解析RequestHeader + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitHeaders(MethodDeclaration n, Chapter chapter, Section section) { + + List headers = RequestMappingHelper.pickHeaders(n.getAnnotations()); + for (String text : headers) { + section.addInHeader(Header.valueOf(text)); + } + + List consumers = RequestMappingHelper.pickConsumers(n.getAnnotations()); + if (!consumers.isEmpty()) { + section.addInHeader(new Header("Content-Type", String.join(",", consumers))); + } + + List produces = RequestMappingHelper.pickProduces(n.getAnnotations()); + if (!produces.isEmpty()) { + section.addOutHeader(new Header("Content-Type", String.join(",", produces))); + } + + for (Parameter parameter : n.getParameters()) { + if (ParameterHelper.isRequestHeader(parameter)) { + String key = parameter.getNameAsString(); + String defaultValue = "{value}"; + AnnotationExpr annotationExpr = parameter.getAnnotationByName(ANNOTATION_REQUEST_HEADER).get(); + Optional valueOptional = AnnotationHelper.getAnyAttribute(annotationExpr, "value", "name"); + if (valueOptional.isPresent()) { + key = String.valueOf(ExpressionHelper.getValue(valueOptional.get())); + } + Optional defaultValueOptional = AnnotationHelper.getAttribute(annotationExpr, "defaultValue"); + if (defaultValueOptional.isPresent()) { + defaultValue = String.valueOf(ExpressionHelper.getValue(defaultValueOptional.get())); + } + TypeDescription description = ApiDoc.getInstance().getTypeResolvers().resolve(parameter.getType()); + if (description.isAvailable()) { + Object value = description.getValue(); + if (StringHelper.isBlank(defaultValue) && StringHelper.nonBlank(value)) { + defaultValue = String.valueOf(value); + } + section.addInHeader(new Header(key, defaultValue)); + } + } + } + } + + /** + * 解析RequestBody + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitRequestBody(MethodDeclaration n, Chapter chapter, Section section) { + section.setQueryParameter(false); + section.addInHeader(Header.APPLICATION_JSON); + for (Parameter parameter : n.getParameters()) { + if (ParameterHelper.isRequestBody(parameter)) { + TypeDescription description = ApiDoc.getInstance().getTypeResolvers().resolve(parameter.getType()); + if (description.isAvailable()) { + if (description.isArray()) { + section.setParameter(description.asArray().getValue()); + } else if (description.isObject()) { + section.setParameter(description.asObject().getValue()); + } + section.addRequestRows(description.rows()); + } + break; + } + } + + } + + /** + * 解析RequestParameter + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitParameter(MethodDeclaration n, Chapter chapter, Section section) { + ObjectTypeDescription objectTypeDescription = new ObjectTypeDescription(); + for (Parameter parameter : n.getParameters()) { + if (ParameterHelper.isRequestParam(parameter)) { + String key = parameter.getNameAsString(); + + Object defaultValue = null; + Boolean required = null; + + Optional optional = parameter.getAnnotationByName(ANNOTATION_REQUEST_PARAM); + if (optional.isPresent()) { + Optional valueOptional = AnnotationHelper.getAnyAttribute(optional.get(), "value", "name"); + if (valueOptional.isPresent()) { + key = String.valueOf(ExpressionHelper.getValue(valueOptional.get())); + } + Optional defaultValueOptional = AnnotationHelper.getAttribute(optional.get(), "defaultValue"); + if (defaultValueOptional.isPresent()) { + defaultValue = ExpressionHelper.getValue(defaultValueOptional.get()); + } + Optional requiredOptional = AnnotationHelper.getAttribute(optional.get(), "required"); + if (requiredOptional.isPresent() && requiredOptional.get().isBooleanLiteralExpr()) { + required = requiredOptional.get().asBooleanLiteralExpr().getValue(); + } + } + + TypeDescription description = ApiDoc.getInstance().getTypeResolvers().resolve(parameter.getType()); + if (description.isAvailable()) { + section.getParamTag(key).ifPresent(tag -> description.addRemark(tag.getContent())); + if (required != null) { + description.setRequired(required); + } + if (description.isObject()) { + objectTypeDescription.merge(description.asObject()); + } else { + description.setKey(key); + if (defaultValue != null && (description.isPrimitive() || description.isString())) { + description.setDefaultValue(defaultValue); + } + objectTypeDescription.add(description); + } + } + } + } + section.setParameter(objectTypeDescription.getValue()); + section.addRequestRows(objectTypeDescription.rows()); + } + + /** + * 解析方法返回参数 + * + * @param n the n + * @param chapter the chapter + * @param section the section + */ + private void visitReturn(MethodDeclaration n, Chapter chapter, Section section) { + TypeDescription description = ApiDoc.getInstance().getTypeResolvers().resolve(n.getType()); + if (description.isAvailable()) { + if (description.isPrimitive()) { + section.setRawResponse(description.getValue()); + } else if (description.isString()) { + section.setRawResponse(description.getValue()); + } else if (description.isArray()) { + section.setResponse(description.asArray().getValue()); + } else if (description.isObject()) { + section.setResponse(description.asObject().getValue()); + } + section.addResponseRows(description.rows()); + } + } + +} diff --git a/apidoc-springmvc/src/main/java/com/apigcc/springmvc/resovler/SpringComponentTypeResolver.java b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/resovler/SpringComponentTypeResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..a589003811eec84409b923992815c7d6f3847445 --- /dev/null +++ b/apidoc-springmvc/src/main/java/com/apigcc/springmvc/resovler/SpringComponentTypeResolver.java @@ -0,0 +1,57 @@ +package com.apigcc.springmvc.resovler; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.common.description.TypeDescription; +import com.apidoc.core.common.description.UnAvailableTypeDescription; +import com.apidoc.core.common.helper.TypeParameterHelper; +import com.apidoc.core.resolver.TypeNameResolver; +import com.apidoc.core.resolver.TypeResolver; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.Optional; + +public class SpringComponentTypeResolver implements TypeResolver, TypeNameResolver { + @Override + public boolean accept(ResolvedType type) { + return isSpringComponent(type); + } + + @Override + public TypeDescription resolve(ResolvedType type) { + Optional optional = TypeParameterHelper.getTypeParameter(type.asReferenceType(), 0); + return optional + .map(ApiDoc.getInstance().getTypeResolvers()::resolve) + .orElseGet(UnAvailableTypeDescription::new); + } + + @Override + public boolean accept(String id) { + return isSpringComponent(id); + } + + @Override + public TypeDescription resolve(Type type) { + if (type.isClassOrInterfaceType()) { + Optional> optional = type.asClassOrInterfaceType().getTypeArguments(); + if (optional.isPresent() && optional.get().size()>0) { + Type typeArgument = optional.get().get(0); + return ApiDoc.getInstance().getTypeResolvers().resolve(typeArgument); + } + + } + return new UnAvailableTypeDescription(); + } + + private static boolean isSpringComponent(ResolvedType type){ + if(!type.isReferenceType()){ + return false; + } + return isSpringComponent(type.asReferenceType().getId()); + } + + private static boolean isSpringComponent(String id){ + return id!=null && (id.startsWith("org.springframework")); + } +} diff --git a/apidoc-springmvc/src/main/resources/META-INF/services/com.apidoc.core.parser.ParserStrategy b/apidoc-springmvc/src/main/resources/META-INF/services/com.apidoc.core.parser.ParserStrategy new file mode 100644 index 0000000000000000000000000000000000000000..8c48b3a65529d35361c6211e3663d24de23843a7 --- /dev/null +++ b/apidoc-springmvc/src/main/resources/META-INF/services/com.apidoc.core.parser.ParserStrategy @@ -0,0 +1 @@ +com.apigcc.springmvc.SpringParser \ No newline at end of file diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/ApigccTest.java b/apidoc-springmvc/src/test/java/com/apigcc/example/ApigccTest.java new file mode 100644 index 0000000000000000000000000000000000000000..751dc720453d44b54888412767e4d1af86927d9c --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/ApigccTest.java @@ -0,0 +1,33 @@ +package com.apigcc.example; + + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.Context; +import org.junit.Test; + +import java.nio.file.Paths; + +/** + * @title Apigcc示例文档 + * @description 通过javadoc设置文档描述信息 + * 优先级大于通过Environment.description()设置的值 + * @readme 所有接口均使用Https调用 + * /app路径下的接口为app专用 + * /mini路径下的接口为小程序专用 + */ +public class ApigccTest { + + @Test + public void testApigcc() { + Context context = new Context(); + context.setId("test"); + context.setName("测试项目"); + context.addSource(Paths.get("D:/apigcc/apigcc-demo-spring")); + //context.setCss("https://darshandsoni.com/asciidoctor-skins/css/monospace.css"); + + ApiDoc apigcc = new ApiDoc(context); + apigcc.parse(); + apigcc.render(); + } + +} \ No newline at end of file diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/annotation/KimController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/annotation/KimController.java new file mode 100644 index 0000000000000000000000000000000000000000..94d0e6e5ba7e593a91ceb3d8681d735d367354cf --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/annotation/KimController.java @@ -0,0 +1,28 @@ +package com.apigcc.example.annotation; +import org.springframework.stereotype.Controller; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义controller注解 + * 正常执行流程统一结果返回 + * + * @author duanledexianxianxian + * @date 2018 /11/6 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Controller +@KimResponseBody +public @interface KimController { + /** + * The value may indicate a suggestion for a logical component name, + * to be turned into a Spring bean in case of an autodetected component. + * + * @return the suggested component name, if any (or empty String otherwise) + */ + String value() default ""; +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/annotation/KimResponseBody.java b/apidoc-springmvc/src/test/java/com/apigcc/example/annotation/KimResponseBody.java new file mode 100644 index 0000000000000000000000000000000000000000..b1eef0125bcb0bcaa44e0bd6e5b3f674fd852dbd --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/annotation/KimResponseBody.java @@ -0,0 +1,18 @@ +package com.apigcc.example.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义response body注解 + * 正常请求统一结果返回 + * + * @author duanledexianxianxian + * @date 2018 /11/6 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface KimResponseBody { +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/Code.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Code.java new file mode 100644 index 0000000000000000000000000000000000000000..8001de8f43728bc8755b2a4ea5fb5f1ed941fd7a --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Code.java @@ -0,0 +1,19 @@ +package com.apigcc.example.common; + +/** + * @code + */ +public enum Code{ + + OK(0,"ok"), + ERROR(-1,"error"), + NoAuth(1,"no auth"); + + private int code; + private String text; + + Code(int code, String text) { + this.code = code; + this.text = text; + } +} \ No newline at end of file diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/Menu.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Menu.java new file mode 100644 index 0000000000000000000000000000000000000000..746d2bae4bfc2452b5464384abe2803e80711b27 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Menu.java @@ -0,0 +1,16 @@ +package com.apigcc.example.common; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class Menu { + + int id; + String name; + List menus; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/Page.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Page.java new file mode 100644 index 0000000000000000000000000000000000000000..66f421fd9b547af1f1b651c814f3cd67cd1c6a26 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Page.java @@ -0,0 +1,26 @@ +package com.apigcc.example.common; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Page extends Query { + + /** + * 第几页 + */ + @JsonProperty + int page = 1; + /* 每页条数 */ + @JSONField(name = "limit") + int sizs = 20; + @SerializedName("totalPage") + int total = 0; + @JsonProperty("max") + int maxPage = 0; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/Query.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..92ae713b71b213aa0329e289838f1dae0ca8ccd5 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Query.java @@ -0,0 +1,20 @@ +package com.apigcc.example.common; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Query { + + /** + * static will be ignore + */ + public static final String CONSTANS = ""; + + /** + * 查询关键字 + */ + String q = ""; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/ResultData.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/ResultData.java new file mode 100644 index 0000000000000000000000000000000000000000..f7ee9784edbb01bd70f64b7c4ac4b4326b45575c --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/ResultData.java @@ -0,0 +1,29 @@ +package com.apigcc.example.common; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class ResultData { + + /** + * 返回码 + */ + int code; + //返回信息 + String msg; + T data; + + public static ResultData ok(){ + return ok(null); + } + public static ResultData ok(T data){ + ResultData resultData = new ResultData<>(); + resultData.code = 0; + resultData.msg = "ok"; + resultData.data = data; + return resultData; + } + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/Role.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..6ef902b53b0ac86dd095d65c2d550eba0a4dd785 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Role.java @@ -0,0 +1,16 @@ +package com.apigcc.example.common; + +/** + * 用户角色 + * @code + */ +public enum Role { + + ADMIN("管理员"),USER("用户"),VIP("会员"); + + String text; + + Role(String text) { + this.text = text; + } +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/StaticFinalCode.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/StaticFinalCode.java new file mode 100644 index 0000000000000000000000000000000000000000..3cef9c557706c63aec2fc34cfb676721ec58b3e3 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/StaticFinalCode.java @@ -0,0 +1,17 @@ +package com.apigcc.example.common; + +/** + * @code + */ +public class StaticFinalCode { + + /** + * 成功 + */ + public static final int SUCCESS = 1; + /** + * 失败 + */ + public static final int ERROR = -1; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/User.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/User.java new file mode 100644 index 0000000000000000000000000000000000000000..dfaf63afc0ed11992711ea0f7059237bb42b92b4 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/User.java @@ -0,0 +1,28 @@ +package com.apigcc.example.common; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.List; + +@Setter +@Getter +public class User { + + int id; + @NotBlank + String name; + @Min(1) + @NotNull + Integer age; + Date createAt; + @NotBlank + @JsonProperty("Sex") + String sex; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserDTO.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..dcd96a290bce0a5eedf4a4d7c96b93cd84f568ee --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserDTO.java @@ -0,0 +1,49 @@ +package com.apigcc.example.common; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Setter +@Getter +public class UserDTO { + + /** + * 编号 + */ + Integer id; + /*姓名*/ + @Size(min = 5, max = 20) + @NotEmpty + String name; + //年龄 + @Min(1) + int age; + /** + * 生日,还是推荐使用javadoc + */ + @NotNull + Date birthday; + /** + * 用户标签 + */ + @Size(min = 1, max = 2) + List tags; + + List data; + /** + * 用户图标 + */ + String[] icons; + + Map attrs; + + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserDetails.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserDetails.java new file mode 100644 index 0000000000000000000000000000000000000000..c37c65f812d2ed1df184acb9fa8422e50c833175 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserDetails.java @@ -0,0 +1,8 @@ +package com.apigcc.example.common; + +/** + * 某些用户信息框架,如spring security的用户登录信息 + * 此处只是模拟,真实的将调用security中的类 + */ +public class UserDetails { +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserQuery.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..baf4e7b6ded6e650781a9f86c532e8431a6f3df2 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/UserQuery.java @@ -0,0 +1,16 @@ +package com.apigcc.example.common; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class UserQuery extends Page { + + /** + * 名称 + * @value apigcc + */ + String name; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/common/Wrapper.java b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Wrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..4f74b94237d6f29d5e6ca46644c2d5f267fa0c0f --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/common/Wrapper.java @@ -0,0 +1,14 @@ +package com.apigcc.example.common; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Wrapper { + + String wrapper; + + T data; + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/BaseController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/BaseController.java new file mode 100644 index 0000000000000000000000000000000000000000..3f52b1c4b561e00e287848b3384353b6472ce371 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/BaseController.java @@ -0,0 +1,11 @@ +package com.apigcc.example.spring; + +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * 示例controller继承的情况 + * 子类继承父类的路径 + */ +@RequestMapping("/restdoc") +public class BaseController { +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/AuthController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/AuthController.java new file mode 100644 index 0000000000000000000000000000000000000000..b30d526e469ca8b2cf065db197bb562b7a5e650c --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/AuthController.java @@ -0,0 +1,25 @@ +package com.apigcc.example.spring.advanced; + +import com.apigcc.example.common.ResultData; +import com.apigcc.example.spring.BaseController; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +/** + * @index 3 + */ +@Controller +@RequestMapping("/auth") +public class AuthController extends BaseController{ + + /** + * + * @param token 上报的身份验证token,jwt + * @return + */ + @PostMapping + public ResultData auth(@RequestHeader() String token){ + return ResultData.ok(); + } + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/EmptyController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/EmptyController.java new file mode 100644 index 0000000000000000000000000000000000000000..4b84a0b84e3b73d7b95b05d00d683d9ca988d68d --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/EmptyController.java @@ -0,0 +1,15 @@ +package com.apigcc.example.spring.advanced; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * will be ignore + */ +@RestController +@RequestMapping("/empty") +public class EmptyController { + + + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/IgnoreController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/IgnoreController.java new file mode 100644 index 0000000000000000000000000000000000000000..f2e611fccc24d71ed97f71a36a608a3097a43143 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/IgnoreController.java @@ -0,0 +1,20 @@ +package com.apigcc.example.spring.advanced; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * will be ignore + * @ignore + */ +@RestController +@RequestMapping("/ignore") +public class IgnoreController { + + + @RequestMapping + public void ignoreThis(){ + } + + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/KimUserController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/KimUserController.java new file mode 100644 index 0000000000000000000000000000000000000000..bfba70230e274f5f32d5bb46a9246812370746f7 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/KimUserController.java @@ -0,0 +1,47 @@ +package com.apigcc.example.spring.advanced; + +import com.apigcc.example.annotation.KimController; +import com.apigcc.example.common.ResultData; +import com.apigcc.example.common.User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * kim接口 + * + * @author duanledexianxianxian + * @version 1.0.0 + * @date 2020 /3/25 1:00 + * @index 20 + * @since 1.0.0 + */ +@KimController +@RequestMapping("/api/v1") +public class KimUserController { + + /** + * 用户详情信息 + * 主动根据id获取用户的信息 + * + * @param id 用户编号 + * @return result data + */ + @GetMapping(value = "/{id}") + public ResultData detail(@PathVariable String id) { + User user = new User(); + return ResultData.ok(user); + } + + + /** + * Add integer. + * + * @return the integer + */ + @PostMapping + public Integer add() { + return null; + } +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/PageController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/PageController.java new file mode 100644 index 0000000000000000000000000000000000000000..7b8b4363847616125f1b1676d9e9e1204e1db07a --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/PageController.java @@ -0,0 +1,140 @@ +package com.apigcc.example.spring.advanced; + +import com.apigcc.example.common.*; +import com.apigcc.example.spring.BaseController; +import com.apigcc.example.spring.hello.Greeting; +import com.apigcc.model.Info; +import com.apigcc.model.InfoQuery; +import org.jruby.ir.Tuple; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; + +import java.util.List; +import java.util.Map; + +/** + * @index 4 + */ +@Controller +@RequestMapping("/page") +public class PageController extends BaseController { + + /** + * 默认页面,由于不是restful的,restdoc将忽略该Endpoint + * + * @return + */ + @GetMapping + public ModelAndView index() { + return new ModelAndView(); + } + + /** + * Hello with ResponseBody + * ********* + * 由于带有@ResponseBody,restdoc将解析该Endpoint + *

+ * hhh + * \********* + * ********* + * hhhh + * ********* + *

+ * class ************** { + *

+ * } + * + * @return + */ + @GetMapping("/hello") + @ResponseBody + public Greeting hello() { + return new Greeting(1, "hello world"); + } + + + /** + * 未知的多泛型的tuple 演示 + * + * @return + */ + @GetMapping("/tuple") + @ResponseBody + public Tuple tuple() { + return null; + } + + /** + * 多个RequestMethod + * + * @return + */ + @RequestMapping(value = "/multiMethod", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT}) + @ResponseBody + public ResultData multiMethod() { + return null; + } + + @PostMapping("/multi") + @ResponseBody + public ResultData> multi(@RequestBody ResultData>> resultData) { + return null; + } + + + /** + * 引用二方Jar + * 使用二方Jar的类时,代码解析器无法获取类上的注释,注解 + * 只能获取属性的名称和类型 + * @param infoQuery + * @return + */ + @PostMapping("/jar") + @ResponseBody + public Info jar(@RequestBody InfoQuery infoQuery){ + return null; + } + + + /** + * 一个复杂的类型 List> + * @return + */ + @GetMapping("/map") + @ResponseBody + public List> map(){ + return null; + } + + /** + * 一个更复杂的类型 List>>> + * @return + */ + @GetMapping("/map") + @ResponseBody + public List>>> maps(){ + return null; + } + + /** + * 一个问号类型 List>> + * @return + */ + @GetMapping("/map") + @ResponseBody + public List>> maps1(){ + return null; + } + + + /** + * 多级菜单 + * @return + */ + @GetMapping("/menus") + @ResponseBody + public List

menus(){ + return null; + } +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/UserController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/UserController.java new file mode 100644 index 0000000000000000000000000000000000000000..897f44bc4450c0558a286dd09072eb72d7b4fe70 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/advanced/UserController.java @@ -0,0 +1,139 @@ +package com.apigcc.example.spring.advanced; + +import com.apigcc.example.common.*; +import com.apigcc.example.spring.BaseController; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户模块(标题) + * 用户示例模块文字描述(详情) + * 支持多行文字 + * @index 2 + */ +@RestController +@RequestMapping("/users") +public class UserController extends BaseController { + + /** + * 用户详情信息 + * 主动根据id获取用户的信息 + * + * @param id 用户编号 + * @return + */ + @GetMapping(value = "/{id}") + public ResultData detail(@PathVariable String id) { + User user = new User(); + return ResultData.ok(user); + } + + /** + * 用户详情信息(根据email或电话号码) + * 多路径适配 + * + * @param email + * @param phone + * @return + */ + @GetMapping(value = {"/detail", "/info"}) + public ResultData detailOrInfo(String email, String phone) { + return ResultData.ok(new User()); + } + + /** + * 用户信息新增 + * + * @param user 用户信息 + * @return + */ + @PostMapping + public ResultData add(@RequestBody UserDTO user) { + return ResultData.ok(); + } + + /** + * 用户信息更新 + * + * @param user 用户信息 + * @return + */ + @PatchMapping + public ResultData update(@RequestBody UserDTO user) { + return ResultData.ok(); + } + + /** + * 用户列表信息查询 + * 默认展示GET方法查询 + * 返回集合类的结果 + * + * @param page 页码 + * @param size 每页条数 + * @return + */ + @RequestMapping("/list") + public ResultData> list(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size) { + return ResultData.ok(); + } + + /** + * 用户列表信息搜索 + * POST搜索时,请求参数将放在请求体中 + * + * @param userQuery 查询参数 + * @return + */ + @PostMapping("/search") + public ResultData> search(UserQuery userQuery) { + return ResultData.ok(); + } + + /** + * 用户信息删除 + * ResponseEntity、Model以及未知类型将忽略 + * + * @param id + * @return + */ + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + public ResponseEntity delete(@PathVariable String id, Model model) { + return ResponseEntity.ok(ResultData.ok()); + } + + /** + * 用户禁用 + * 某些项目使用自定义的ArgumentResolver,让spring自动注入一些信息 + * restdoc在解析时,可通过env.ignoreTypes("UserDtails")来忽略这些 + * + * @param userDetails 当前登录用户的信息 + * @return + */ + @RequestMapping(value = "/{id}/disable", method = RequestMethod.PUT) + public ResultData disable(UserDetails userDetails) { + return ResultData.ok(); + } + + /** + * 查询角色下的用户总数 + * @param role 枚举类型{@link Role} + * @return + */ + @GetMapping("/role") + public ResultData listFromRole(Role role){ + return ResultData.ok(); + } + + /** + * 批量上传用户信息 + * @param list + * @return com.apigcc.example.common.UserDTO + */ + @PostMapping("/batch") + public void batch(@RequestBody List list){ + } + +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/hello/Greeting.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/hello/Greeting.java new file mode 100644 index 0000000000000000000000000000000000000000..2fda48e7ea65acf35e53badcfc1faebd9bc7be80 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/hello/Greeting.java @@ -0,0 +1,26 @@ +package com.apigcc.example.spring.hello; + +public class Greeting { + + /** + * 编号 + */ + private final long id; + /** + * 内容 + */ + private final String content; + + public Greeting(long id, String content) { + this.id = id; + this.content = content; + } + + public long getId() { + return id; + } + + public String getContent() { + return content; + } +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/example/spring/hello/GreetingController.java b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/hello/GreetingController.java new file mode 100644 index 0000000000000000000000000000000000000000..c61c008b4f666a89b13ce391564a39a05fe0b8c6 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/example/spring/hello/GreetingController.java @@ -0,0 +1,44 @@ +package com.apigcc.example.spring.hello; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * 欢迎使用Apiggs + * + * @author fengyuchenglun + * @version 1.0.0 + * @index 1 + */ +@RestController +public class GreetingController { + + /** + * The constant template. + */ + private static final String template = "Hello, %s!"; + /** + * The Counter. + */ + private final AtomicLong counter = new AtomicLong(); + + /** + * 示例接口 + * 自定义错误编码 + * + * @param name 名称 + * @return greeting greeting + * @errorCode ERROR_CODE_1 错误编码1 很长很长的描述 + * @errorCode ERROR_CODE_2 错误编码2 很长很长的描述 + * @errorCode ERROR_CODE_3 错误编码3 很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述 + * @errorCode ERROR_CODE_4 错误编码4 很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述很长很长的描述 + */ + @RequestMapping("/greeting") + public Greeting greeting(@RequestParam(value = "name", defaultValue = "apigcc") String name) { + return new Greeting(counter.incrementAndGet(), + String.format(template, name)); + } +} diff --git a/apidoc-springmvc/src/test/java/com/apigcc/springmvc/SpringTest.java b/apidoc-springmvc/src/test/java/com/apigcc/springmvc/SpringTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c7a9288c39d9a5c9262e3a9c5c620d1989171538 --- /dev/null +++ b/apidoc-springmvc/src/test/java/com/apigcc/springmvc/SpringTest.java @@ -0,0 +1,85 @@ +package com.apigcc.springmvc; + +import com.apidoc.core.ApiDoc; +import com.apidoc.core.Context; +import com.apidoc.core.common.diff.FileMatcher; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class SpringTest { + + @Test + public void test() throws IOException { + + Context context = new Context(); + context.setId("test"); + context.setName("测试项目"); + context.addSource(Paths.get("K:\\@project-dianli@\\apigcc\\apigcc-springmvc\\src\\test\\java")); +// context.setCss("https://darshandsoni.com/asciidoctor-skins/css/monospace.css"); + + ApiDoc apigcc = new ApiDoc(context); + apigcc.parse(); + apigcc.render(); + } + + + @Test + public void testTestToolls() throws IOException { + + Context context = new Context(); + context.setId("test-tools"); + context.setName("测试工具"); + context.addSource(Paths.get("D:/workspaces/ubisor-test-tools/backend/")); +// context.setCss("https://darshandsoni.com/asciidoctor-skins/css/monospace.css"); + + ApiDoc apigcc = new ApiDoc(context); + apigcc.parse(); + apigcc.render(); + + Path buildAdoc = Paths.get("build/test-tools/index.adoc"); + Path template = Paths.get("src/test/resources/test-tools.adoc"); + Path templateHtml = Paths.get("src/test/resources/template.html"); + Path resultHtml = Paths.get("build/test-tools/diff.html"); + + FileMatcher fileMatcher = new FileMatcher(); + int changed = fileMatcher.compare(template, buildAdoc); + if(changed>0){ + fileMatcher.rederHtml(templateHtml, resultHtml); + } + + System.out.println("BUILD SUCCESS"); + } + + + @Test + public void testUbcloud() throws IOException { + + Context context = new Context(); + context.setId("ubcloud"); + context.setName("优碧云1"); + context.addSource(Paths.get("D:/workspaces/ubisor-backend/ubcloud-front-web/")); + context.addDependency(Paths.get("D:/workspaces/ubisor-backend/")); + context.setCss("https://darshandsoni.com/asciidoctor-skins/css/monospace.css"); + + ApiDoc apigcc = new ApiDoc(context); + apigcc.parse(); + apigcc.render(); + + Path buildAdoc = Paths.get("build/ubcloud/index.adoc"); + Path template = Paths.get("src/test/resources/ubcloud-front-web.adoc"); + Path templateHtml = Paths.get("src/test/resources/template.html"); + Path resultHtml = Paths.get("build/ubcloud/diff.html"); + + FileMatcher fileMatcher = new FileMatcher(); + int changed = fileMatcher.compare(template, buildAdoc); + if(changed>0){ + fileMatcher.rederHtml(templateHtml, resultHtml); + } + + System.out.println("BUILD SUCCESS"); + } + +} \ No newline at end of file diff --git a/apidoc-springmvc/src/test/resources/lib/apigcc-model-1.0-SNAPSHOT.jar b/apidoc-springmvc/src/test/resources/lib/apigcc-model-1.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..8ac356da44ca4d9293916a01669a739a0d63e0fc Binary files /dev/null and b/apidoc-springmvc/src/test/resources/lib/apigcc-model-1.0-SNAPSHOT.jar differ diff --git a/apidoc-springmvc/src/test/resources/template.adoc b/apidoc-springmvc/src/test/resources/template.adoc new file mode 100644 index 0000000000000000000000000000000000000000..de6294b5380032eb3012bd980583aff3b19d2f0a --- /dev/null +++ b/apidoc-springmvc/src/test/resources/template.adoc @@ -0,0 +1,936 @@ += Apigcc示例文档 +:doctype: book +:toc: left +:toclevels: 3 +:toc-title: 目录 +:source-highlighter: highlightjs + +[%hardbreaks] +通过javadoc设置文档描述信息 +优先级大于通过Environment.description()设置的值 + + +== 文档说明 +[%hardbreaks] +所有接口均使用Https调用 +/app路径下的接口为app专用 +/mini路径下的接口为小程序专用 + + +== 欢迎使用Apiggs + +=== 示例接口 +请求 +[source,HTTP ] +---- +GET /greeting?name=apigcc HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+name+|+String+||+apigcc+|+名称+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "content" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+long+||+0+|+编号+ +|+content+|+String+|||+内容+ +|=== + + +== 用户模块(标题) +[%hardbreaks] +用户示例模块文字描述(详情) +支持多行文字 + + +=== 用户详情信息 +[%hardbreaks] +主动根据id获取用户的信息 + +请求 +[source,HTTP ] +---- +GET /users/{id} HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+String+|||+用户编号+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "", + "data" : { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+User+||| +|+data.id+|+int+||+0+| +|+data.name+|+String+|+NotBlank+|| +|+data.age+|+Integer+|+Min(1) NotNull+|+0+| +|+data.createAt+|+Date+||| +|+data.Sex+|+String+|+NotBlank+|| +|=== + + +=== 用户详情信息(根据email或电话号码) +[%hardbreaks] +多路径适配 + +请求 +[source,HTTP ] +---- +GET /users/detail?email=&phone= HTTP/1.1 +GET /users/info?email=&phone= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+email+|+String+||| +|+phone+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "", + "data" : { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+User+||| +|+data.id+|+int+||+0+| +|+data.name+|+String+|+NotBlank+|| +|+data.age+|+Integer+|+Min(1) NotNull+|+0+| +|+data.createAt+|+Date+||| +|+data.Sex+|+String+|+NotBlank+|| +|=== + + +=== 用户信息新增 +请求 +[source,HTTP ] +---- +POST /users HTTP/1.1 +Content-Type: application/json + +{ + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+|+编号+ +|+name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+age+|+int+|+Min(1)+|+0+|+年龄+ +|+birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+data+|+List<>+||| +|+icons+|+java.lang.String[]+|||+用户图标+ +|+attrs+|+Map+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+T+||| +|=== + + +=== 用户信息更新 +请求 +[source,HTTP ] +---- +PATCH /users HTTP/1.1 +Content-Type: application/json + +{ + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+|+编号+ +|+name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+age+|+int+|+Min(1)+|+0+|+年龄+ +|+birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+data+|+List<>+||| +|+icons+|+java.lang.String[]+|||+用户图标+ +|+attrs+|+Map+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+T+||| +|=== + + +=== 用户列表信息查询 +[%hardbreaks] +默认展示GET方法查询 +返回集合类的结果 + +请求 +[source,HTTP ] +---- +GET /users/list?page=1&size=20 HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+page+|+int+||+1+|+页码+ +|+size+|+int+||+20+|+每页条数+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "", + "data" : [ { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+List+||| +|+data.[].id+|+int+||+0+| +|+data.[].name+|+String+|+NotBlank+|| +|+data.[].age+|+Integer+|+Min(1) NotNull+|+0+| +|+data.[].createAt+|+Date+||| +|+data.[].Sex+|+String+|+NotBlank+|| +|=== + + +=== 用户列表信息搜索 +[%hardbreaks] +POST搜索时,请求参数将放在请求体中 + +请求 +[source,HTTP ] +---- +POST /users/search HTTP/1.1 + +q=&page=1&limit=20&totalPage=0&max=0&name=apigcc +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+q+|+String+|||+查询关键字+ +|+page+|+int+||+1+|+第几页+ +|+limit+|+int+||+20+|+每页条数+ +|+totalPage+|+int+||+0+| +|+max+|+int+||+0+| +|+name+|+String+||+apigcc+|+名称+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "", + "data" : [ { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+List+||| +|+data.[].id+|+int+||+0+| +|+data.[].name+|+String+|+NotBlank+|| +|+data.[].age+|+Integer+|+Min(1) NotNull+|+0+| +|+data.[].createAt+|+Date+||| +|+data.[].Sex+|+String+|+NotBlank+|| +|=== + + +=== 用户信息删除 +[%hardbreaks] +ResponseEntity、Model以及未知类型将忽略 + +请求 +[source,HTTP ] +---- +DELETE /users/{id} HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+T+||| +|=== + + +=== 用户禁用 +[%hardbreaks] +某些项目使用自定义的ArgumentResolver,让spring自动注入一些信息 +restdoc在解析时,可通过env.ignoreTypes("UserDtails")来忽略这些 + +请求 +[source,HTTP ] +---- +PUT /users/{id}/disable HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+T+||| +|=== + + +=== 查询角色下的用户总数 +请求 +[source,HTTP ] +---- +GET /users/role?role= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+role+|+Role+|||+枚举类型+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "", + "data" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+Integer+||+0+| +|=== + + +=== 批量上传用户信息 +请求 +[source,HTTP ] +---- +POST /users/batch HTTP/1.1 +Content-Type: application/json + +[ { + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+Integer+||+0+|+编号+ +|+[].name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+[].age+|+int+|+Min(1)+|+0+|+年龄+ +|+[].birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+[].tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+[].data+|+List<>+||| +|+[].icons+|+java.lang.String[]+|||+用户图标+ +|+[].attrs+|+Map+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+|+编号+ +|+name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+age+|+int+|+Min(1)+|+0+|+年龄+ +|+birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+data+|+List<>+||| +|+icons+|+java.lang.String[]+|||+用户图标+ +|+attrs+|+Map+||| +|=== + + +== PageController + +=== Hello with ResponseBody +[%hardbreaks] +********* +由于带有@ResponseBody,restdoc将解析该Endpoint +

+hhh +\********* + ********* +hhhh +********* +

+class ************** { +

+} + +请求 +[source,HTTP ] +---- +GET /page/hello HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "content" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+long+||+0+|+编号+ +|+content+|+String+|||+内容+ +|=== + + +=== 未知的多泛型的tuple 演示 +请求 +[source,HTTP ] +---- +GET /page/tuple HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } +}, { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+?0.id+|+Integer+||+0+|+编号+ +|+?0.name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+?0.age+|+int+|+Min(1)+|+0+|+年龄+ +|+?0.birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+?0.tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+?0.data+|+List<>+||| +|+?0.icons+|+java.lang.String[]+|||+用户图标+ +|+?0.attrs+|+Map+||| +|+?1.id+|+int+||+0+| +|+?1.name+|+String+|+NotBlank+|| +|+?1.age+|+Integer+|+Min(1) NotNull+|+0+| +|+?1.createAt+|+Date+||| +|+?1.Sex+|+String+|+NotBlank+|| +|=== + + +=== 多个RequestMethod +请求 +[source,HTTP ] +---- +GET /page/multiMethod HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+T+||| +|=== + + +=== multi +请求 +[source,HTTP ] +---- +POST /page/multi HTTP/1.1 +Content-Type: application/json + +{ + "code" : 0, + "msg" : "", + "data" : { + "wrapper" : "", + "data" : [ { + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } + } ] + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+Wrapper+||| +|+data.wrapper+|+String+||| +|+data.data+|+List+||| +|+data.data.[].id+|+Integer+||+0+|+编号+ +|+data.data.[].name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+data.data.[].age+|+int+|+Min(1)+|+0+|+年龄+ +|+data.data.[].birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+data.data.[].tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+data.data.[].data+|+List<>+||| +|+data.data.[].icons+|+java.lang.String[]+|||+用户图标+ +|+data.data.[].attrs+|+Map+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "code" : 0, + "msg" : "", + "data" : { + "wrapper" : "", + "data" : { + "id" : 0, + "name" : "", + "age" : 0, + "birthday" : "", + "tags" : [ "" ], + "data" : [ ], + "icons" : [ "" ], + "attrs" : { + "" : "" + } + } + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+code+|+int+||+0+|+返回码+ +|+msg+|+String+|||+返回信息+ +|+data+|+Wrapper+||| +|+data.wrapper+|+String+||| +|+data.data+|+UserDTO+||| +|+data.data.id+|+Integer+||+0+|+编号+ +|+data.data.name+|+String+|+Size(min=5,max=20) NotEmpty+||+姓名+ +|+data.data.age+|+int+|+Min(1)+|+0+|+年龄+ +|+data.data.birthday+|+Date+|+NotNull+||+生日,还是推荐使用javadoc+ +|+data.data.tags+|+List+|+Size(min=1,max=2)+||+用户标签+ +|+data.data.data+|+List<>+||| +|+data.data.icons+|+java.lang.String[]+|||+用户图标+ +|+data.data.attrs+|+Map+||| +|=== + + +=== 引用二方Jar +[%hardbreaks] +使用二方Jar的类时,代码解析器无法获取类上的注释,注解 +只能获取属性的名称和类型 + +请求 +[source,HTTP ] +---- +POST /page/jar HTTP/1.1 +Content-Type: application/json + +{ + "page" : 0, + "size" : 0, + "name" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+page+|+int+||+0+| +|+size+|+int+||+0+| +|+name+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "man" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+int+||+0+| +|+name+|+String+||| +|+man+|+String+||| +|=== + + +=== 一个复杂的类型 List> +请求 +[source,HTTP ] +---- +GET /page/map HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "" : { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" + } +} ] +---- + + +=== 一个更复杂的类型 List>>> +请求 +[source,HTTP ] +---- +GET /page/map HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "" : { + "code" : 0, + "msg" : "", + "data" : { + "0" : { + "id" : 0, + "name" : "", + "age" : 0, + "createAt" : "", + "Sex" : "" + } + } + } +} ] +---- + + +=== 一个问号类型 List>> +请求 +[source,HTTP ] +---- +GET /page/map HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "" : [ ] +} ] +---- + + +=== 多级菜单 +请求 +[source,HTTP ] +---- +GET /page/menus HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "name" : "" +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+int+||+0+| +|+[].name+|+String+||| +|+[].menus+|+List

+||| +|=== + + +== 附录 + +=== Code +[options=""] +|=== +|+OK+|+0+|+ok+ +|+ERROR+|+-1+|+error+ +|+NoAuth+|+1+|+no auth+ +|=== + + +=== StaticFinalCode +[options=""] +|=== +|+SUCCESS+|+1+|+成功+ +|+ERROR+|+-1+|+失败+ +|=== + + +=== 用户角色 +[options=""] +|=== +|+ADMIN+|+管理员+ +|+USER+|+用户+ +|+VIP+|+会员+ +|=== + diff --git a/apidoc-springmvc/src/test/resources/template.html b/apidoc-springmvc/src/test/resources/template.html new file mode 100644 index 0000000000000000000000000000000000000000..7e7f23d899dafa2a841d03fa6620a3013e3af7df --- /dev/null +++ b/apidoc-springmvc/src/test/resources/template.html @@ -0,0 +1,51 @@ + + + + + +
+ + ${content} +
+
+ + \ No newline at end of file diff --git a/apidoc-springmvc/src/test/resources/test-tools.adoc b/apidoc-springmvc/src/test/resources/test-tools.adoc new file mode 100644 index 0000000000000000000000000000000000000000..61ea2e0cc712256507dc434fe32c31027b656275 --- /dev/null +++ b/apidoc-springmvc/src/test/resources/test-tools.adoc @@ -0,0 +1,1377 @@ += 硬件测试辅助工具TEST-TOOLS +:doctype: book +:toc: left +:toclevels: 3 +:toc-title: 目录 +:source-highlighter: highlightjs + +[%hardbreaks] +version:1.0-SNAPSHOT + +[%hardbreaks] +web前后端交互接口文档 + + +== 终端服务 + +=== 查询在线设备编号 +[%hardbreaks] +返回sn数组 + +请求 +[source,HTTP ] +---- +GET /terminals HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +=== 查询波形数据(分页) +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/waves?type=&page=1&size=10 HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|+type+|+String+|||+波类型,如:0101,0103+ +|+page+|+int+||+1+| +|+size+|+int+||+10+| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "terminalTime" : "", + "terminalClock" : "", + "length" : 0, + "type" : "", + "lightening" : 0, + "waveMax" : "", + "headTime" : "", + "headClock" : "", + "maxTime" : "", + "maxClock" : "", + "createTime" : "", + "maxAd" : 0, + "minAd" : 0, + "scopePoint" : "", + "dataMax" : 0, + "dataMaxIndex" : 0, + "data1" : 0, + "data1Index" : 0, + "data2" : 0, + "data2Index" : 0, + "data3" : 0, + "data3Index" : 0, + "highPassMax" : 0, + "rate" : 0, + "headIndex" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+| +|+sn+|+String+||| +|+terminalTime+|+String+||| +|+terminalClock+|+String+||| +|+length+|+Integer+||+0+| +|+type+|+String+||| +|+lowScope+|+Boolean+||| +|+lightening+|+Integer+||+0+| +|+waveMax+|+String+||| +|+headTime+|+String+||| +|+headClock+|+String+||| +|+maxTime+|+String+||| +|+maxClock+|+String+||| +|+createTime+|+Date+||| +|+maxAd+|+Integer+||+0+| +|+minAd+|+Integer+||+0+| +|+scopePoint+|+String+||| +|+dataMax+|+Float+||+0+| +|+dataMaxIndex+|+Integer+||+0+|+最大值得索引+ +|+data1+|+Float+||+0+|+幅值往前150个点的值+ +|+data1Index+|+Integer+||+0+| +|+data2+|+Float+||+0+|+幅值往前100个点的值+ +|+data2Index+|+Integer+||+0+| +|+data3+|+Float+||+0+|+幅值往前50个点的值+ +|+data3Index+|+Integer+||+0+| +|+highPassMax+|+Float+||+0+| +|+rate+|+Integer+||+0+| +|+flattop+|+Boolean+||| +|+useful+|+Boolean+||| +|+headIndex+|+Integer+||+0+| +|=== + + +=== 查询所有波形 +请求 +[source,HTTP ] +---- +GET /terminals/all/waves?type=&page=1&size=10 HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+type+|+String+||| +|+page+|+int+||+1+| +|+size+|+int+||+10+| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "terminalTime" : "", + "terminalClock" : "", + "length" : 0, + "type" : "", + "lightening" : 0, + "waveMax" : "", + "headTime" : "", + "headClock" : "", + "maxTime" : "", + "maxClock" : "", + "createTime" : "", + "maxAd" : 0, + "minAd" : 0, + "scopePoint" : "", + "dataMax" : 0, + "dataMaxIndex" : 0, + "data1" : 0, + "data1Index" : 0, + "data2" : 0, + "data2Index" : 0, + "data3" : 0, + "data3Index" : 0, + "highPassMax" : 0, + "rate" : 0, + "headIndex" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+| +|+sn+|+String+||| +|+terminalTime+|+String+||| +|+terminalClock+|+String+||| +|+length+|+Integer+||+0+| +|+type+|+String+||| +|+lowScope+|+Boolean+||| +|+lightening+|+Integer+||+0+| +|+waveMax+|+String+||| +|+headTime+|+String+||| +|+headClock+|+String+||| +|+maxTime+|+String+||| +|+maxClock+|+String+||| +|+createTime+|+Date+||| +|+maxAd+|+Integer+||+0+| +|+minAd+|+Integer+||+0+| +|+scopePoint+|+String+||| +|+dataMax+|+Float+||+0+| +|+dataMaxIndex+|+Integer+||+0+|+最大值得索引+ +|+data1+|+Float+||+0+|+幅值往前150个点的值+ +|+data1Index+|+Integer+||+0+| +|+data2+|+Float+||+0+|+幅值往前100个点的值+ +|+data2Index+|+Integer+||+0+| +|+data3+|+Float+||+0+|+幅值往前50个点的值+ +|+data3Index+|+Integer+||+0+| +|+highPassMax+|+Float+||+0+| +|+rate+|+Integer+||+0+| +|+flattop+|+Boolean+||| +|+useful+|+Boolean+||| +|+headIndex+|+Integer+||+0+| +|=== + + +=== 查询历史波形数据sn号 +请求 +[source,HTTP ] +---- +GET /terminals/waves HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +=== 查询历史波形数据详情 +请求 +[source,HTTP ] +---- +GET /terminals/waves/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "terminalTime" : "", + "terminalClock" : "", + "data" : [ 0 ], + "dataMax" : 0, + "dataMaxIndex" : 0, + "data1" : 0, + "data1Index" : 0, + "data2" : 0, + "data2Index" : 0, + "data3" : 0, + "data3Index" : 0, + "highPassBlock" : [ 0 ], + "highPassMax" : 0, + "rate" : 0, + "length" : 0, + "type" : "", + "lightening" : 0, + "waveMax" : "", + "headTime" : "", + "headClock" : "", + "maxTime" : "", + "maxClock" : "", + "createTime" : "", + "maxAd" : 0, + "minAd" : 0, + "scopePoint" : "", + "headIndex" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+| +|+sn+|+String+||| +|+terminalTime+|+String+||| +|+terminalClock+|+String+||| +|+data+|+float[]+||| +|+dataMax+|+Float+||+0+| +|+dataMaxIndex+|+Integer+||+0+|+最大值得索引+ +|+data1+|+Float+||+0+|+幅值往前150个点的值+ +|+data1Index+|+Integer+||+0+| +|+data2+|+Float+||+0+|+幅值往前100个点的值+ +|+data2Index+|+Integer+||+0+| +|+data3+|+Float+||+0+|+幅值往前50个点的值+ +|+data3Index+|+Integer+||+0+| +|+highPassBlock+|+float[]+||| +|+highPassMax+|+Float+||+0+| +|+rate+|+Integer+||+0+| +|+flattop+|+Boolean+||| +|+length+|+Integer+||+0+| +|+type+|+String+||| +|+lowScope+|+Boolean+||| +|+lightening+|+Integer+||+0+| +|+waveMax+|+String+||| +|+headTime+|+String+||| +|+headClock+|+String+||| +|+maxTime+|+String+||| +|+maxClock+|+String+||| +|+createTime+|+Date+||| +|+maxAd+|+Integer+||+0+| +|+minAd+|+Integer+||+0+| +|+scopePoint+|+String+||| +|+useful+|+Boolean+||| +|+headIndex+|+Integer+||+0+| +|=== + + +=== 获取txt格式 波形数据 +请求 +[source,HTTP ] +---- +GET /terminals/waves/{id}/txt? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ 0 ] +---- + + +=== 查询波形数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/wave? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "id" : "", + "terminalClock" : "", + "headClock" : "", + "maxClock" : "", + "block" : [ 0 ], + "highPassBlock" : [ 0 ], + "headIndex" : 0, + "algotithmResp" : { + "lightening" : 0, + "waveMax" : "", + "headTime" : "", + "headMaxTime" : "" + } + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+WaveDataDTO+||| +|+[].data.id+|+String+||| +|+[].data.terminalClock+|+LocalDateTime+||| +|+[].data.headClock+|+LocalDateTime+||| +|+[].data.maxClock+|+LocalDateTime+||| +|+[].data.block+|+float[]+||| +|+[].data.highPassBlock+|+float[]+||| +|+[].data.headIndex+|+int+||+0+| +|+[].data.algotithmResp+|+AlgotithmResp+||| +|+[].data.algotithmResp.lightening+|+Integer+||+0+|+1:雷击,2:非雷击+ +|+[].data.algotithmResp.waveMax+|+String+|||+幅值+ +|+[].data.algotithmResp.headTime+|+String+|||+波头+ +|+[].data.algotithmResp.headMaxTime+|+String+|||+半峰+ +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询故障数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/wave/gz? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "id" : "", + "terminalClock" : "", + "headClock" : "", + "maxClock" : "", + "block" : [ 0 ], + "highPassBlock" : [ 0 ], + "headIndex" : 0, + "algotithmResp" : { + "lightening" : 0, + "waveMax" : "", + "headTime" : "", + "headMaxTime" : "" + } + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+WaveDataDTO+||| +|+[].data.id+|+String+||| +|+[].data.terminalClock+|+LocalDateTime+||| +|+[].data.headClock+|+LocalDateTime+||| +|+[].data.maxClock+|+LocalDateTime+||| +|+[].data.block+|+float[]+||| +|+[].data.highPassBlock+|+float[]+||| +|+[].data.headIndex+|+int+||+0+| +|+[].data.algotithmResp+|+AlgotithmResp+||| +|+[].data.algotithmResp.lightening+|+Integer+||+0+|+1:雷击,2:非雷击+ +|+[].data.algotithmResp.waveMax+|+String+|||+幅值+ +|+[].data.algotithmResp.headTime+|+String+|||+波头+ +|+[].data.algotithmResp.headMaxTime+|+String+|||+半峰+ +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 清理半波 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/wave/clear? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + + +=== 查询行波电流数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/xb? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "h" : "0x5566", + "id" : "", + "type" : 0, + "length" : 0, + "sum" : 0, + "blockLen" : 0, + "block" : [ 0 ], + "clock" : { + "year" : 0, + "month" : 0, + "day" : 0, + "hour" : 0, + "minute" : 0, + "second" : 0, + "mill" : 0, + "micro" : 0, + "nano" : 0 + }, + "totalBytes" : 0, + "n" : 0, + "p" : 0, + "packages" : 0, + "num" : 0, + "scope" : 0 + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+DataPktType+||| +|+[].data.h+|+short+||+0x5566+|+帧头+ +|+[].data.id+|+String+|||+设备编号+ +|+[].data.type+|+short+||+0+|+帧类型+ +|+[].data.length+|+int+||+0+| +|+[].data.sum+|+short+||+0+|+帧尾校验位+ +|+[].data.blockLen+|+int+||+0+|+波形数据字节数+ +|+[].data.block+|+byte[]+|||+波形数据+ +|+[].data.clock+|+Clock+|||+时间+ +|+[].data.clock.year+|+byte+||+0+| +|+[].data.clock.month+|+byte+||+0+| +|+[].data.clock.day+|+byte+||+0+| +|+[].data.clock.hour+|+byte+||+0+| +|+[].data.clock.minute+|+byte+||+0+| +|+[].data.clock.second+|+byte+||+0+| +|+[].data.clock.mill+|+int+||+0+|+毫秒+ +|+[].data.clock.micro+|+int+||+0+|+微秒+ +|+[].data.clock.nano+|+int+||+0+|+纳秒+ +|+[].data.totalBytes+|+int+||+0+|+本次波形字节总数+ +|+[].data.n+|+short+||+0+|+当前报文数据包序号+ +|+[].data.p+|+short+||+0+|+本次波形数据包数量+ +|+[].data.packages+|+long+||+0+|+扩展的 +本次波形数据包数量+ +|+[].data.num+|+long+||+0+|+扩展的 +当前报文数据包序号+ +|+[].data.scope+|+byte+||+0+| +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询故障电流数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/gz? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "h" : "0x5566", + "id" : "", + "type" : 0, + "length" : 0, + "sum" : 0, + "blockLen" : 0, + "block" : [ 0 ], + "clock" : { + "year" : 0, + "month" : 0, + "day" : 0, + "hour" : 0, + "minute" : 0, + "second" : 0, + "mill" : 0, + "micro" : 0, + "nano" : 0 + }, + "totalBytes" : 0, + "n" : 0, + "p" : 0, + "packages" : 0, + "num" : 0, + "scope" : 0 + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+DataPktType+||| +|+[].data.h+|+short+||+0x5566+|+帧头+ +|+[].data.id+|+String+|||+设备编号+ +|+[].data.type+|+short+||+0+|+帧类型+ +|+[].data.length+|+int+||+0+| +|+[].data.sum+|+short+||+0+|+帧尾校验位+ +|+[].data.blockLen+|+int+||+0+|+波形数据字节数+ +|+[].data.block+|+byte[]+|||+波形数据+ +|+[].data.clock+|+Clock+|||+时间+ +|+[].data.clock.year+|+byte+||+0+| +|+[].data.clock.month+|+byte+||+0+| +|+[].data.clock.day+|+byte+||+0+| +|+[].data.clock.hour+|+byte+||+0+| +|+[].data.clock.minute+|+byte+||+0+| +|+[].data.clock.second+|+byte+||+0+| +|+[].data.clock.mill+|+int+||+0+|+毫秒+ +|+[].data.clock.micro+|+int+||+0+|+微秒+ +|+[].data.clock.nano+|+int+||+0+|+纳秒+ +|+[].data.totalBytes+|+int+||+0+|+本次波形字节总数+ +|+[].data.n+|+short+||+0+|+当前报文数据包序号+ +|+[].data.p+|+short+||+0+|+本次波形数据包数量+ +|+[].data.packages+|+long+||+0+|+扩展的 +本次波形数据包数量+ +|+[].data.num+|+long+||+0+|+扩展的 +当前报文数据包序号+ +|+[].data.scope+|+byte+||+0+| +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询设备故障数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/fault? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "h" : "0x5566", + "id" : "", + "type" : 0, + "length" : 0, + "sum" : 0, + "clock" : { + "year" : 0, + "month" : 0, + "day" : 0, + "hour" : 0, + "minute" : 0, + "second" : 0, + "mill" : 0, + "micro" : 0, + "nano" : 0 + }, + "info" : "" + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+FaultDTO+||| +|+[].data.h+|+short+||+0x5566+|+帧头+ +|+[].data.id+|+String+|||+设备编号+ +|+[].data.type+|+short+||+0+|+帧类型+ +|+[].data.length+|+int+||+0+| +|+[].data.sum+|+short+||+0+|+帧尾校验位+ +|+[].data.clock+|+Clock+||| +|+[].data.clock.year+|+byte+||+0+| +|+[].data.clock.month+|+byte+||+0+| +|+[].data.clock.day+|+byte+||+0+| +|+[].data.clock.hour+|+byte+||+0+| +|+[].data.clock.minute+|+byte+||+0+| +|+[].data.clock.second+|+byte+||+0+| +|+[].data.clock.mill+|+int+||+0+|+毫秒+ +|+[].data.clock.micro+|+int+||+0+|+微秒+ +|+[].data.clock.nano+|+int+||+0+|+纳秒+ +|+[].data.info+|+String+|||+故障信息+ +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询心跳数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/heart? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "h" : "0x5566", + "id" : "", + "type" : 0, + "length" : 0, + "sum" : 0 + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+HeartPktType+||| +|+[].data.h+|+short+||+0x5566+|+帧头+ +|+[].data.id+|+String+|||+设备编号+ +|+[].data.type+|+short+||+0+|+帧类型+ +|+[].data.length+|+int+||+0+| +|+[].data.sum+|+short+||+0+|+帧尾校验位+ +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询设备基本信息数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/info? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "h" : "0x5566", + "id" : "", + "type" : 0, + "length" : 0, + "sum" : 0, + "name" : "", + "model" : "", + "verion" : "", + "infoPara1" : { + "ad" : 0, + "phyInt" : 0 + }, + "infoPara2" : { + "ad" : 0, + "phyInt" : 0 + }, + "total" : 0, + "infoParas" : [ { + "ad" : 0, + "phyInt" : 0 + } ] + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+InfoPktType+||| +|+[].data.h+|+short+||+0x5566+|+帧头+ +|+[].data.id+|+String+|||+设备编号+ +|+[].data.type+|+short+||+0+|+帧类型+ +|+[].data.length+|+int+||+0+| +|+[].data.sum+|+short+||+0+|+帧尾校验位+ +|+[].data.name+|+String+|||+监测终端名称+ +|+[].data.model+|+String+|||+监测终端型号+ +|+[].data.verion+|+String+|||+监测终端基本信息版本 +号+ +|+[].data.infoPara1+|+InfoPara+||| +|+[].data.infoPara1.ad+|+int+||+0+| +|+[].data.infoPara1.phyInt+|+int+||+0+| +|+[].data.infoPara2+|+InfoPara+||| +|+[].data.infoPara2.ad+|+int+||+0+| +|+[].data.infoPara2.phyInt+|+int+||+0+| +|+[].data.total+|+int+||+0+| +|+[].data.infoParas+|+List+||| +|+[].data.infoParas.[].ad+|+int+||+0+| +|+[].data.infoParas.[].phyInt+|+int+||+0+| +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询工况数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/work? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "createTime" : "System.currentTimeMillis()", + "data" : { + "h" : "0x5566", + "id" : "", + "type" : 0, + "length" : 0, + "sum" : 0, + "clock" : "", + "src" : 0, + "voltage" : 0, + "temp" : 0, + "i" : 0, + "remark" : "" + }, + "frame" : "", + "count" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+[].data+|+WorkPktType+||| +|+[].data.h+|+short+||+0x5566+|+帧头+ +|+[].data.id+|+String+|||+设备编号+ +|+[].data.type+|+short+||+0+|+帧类型+ +|+[].data.length+|+int+||+0+| +|+[].data.sum+|+short+||+0+|+帧尾校验位+ +|+[].data.clock+|+String+||| +|+[].data.src+|+byte+||+0+|+电池供电状态:0 表示感应电源供电;1 表示 +电池供电;2.表示太阳能供电+ +|+[].data.voltage+|+int+||+0+|+电池电压 单位 mV+ +|+[].data.temp+|+int+||+0+|+设备温度 单位℃+ +|+[].data.i+|+int+||+0+|+电流有效值 单位 A+ +|+[].data.remark+|+String+|||+备用+ +|+[].frame+|+String+||| +|+[].count+|+Integer+||+0+| +|=== + + +=== 查询参数信息 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/param? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "createTime" : "System.currentTimeMillis()", + "data" : { + "xbUpTime" : 0, + "xbUpThreshold" : 0, + "xbUpAdcDeep" : 0, + "xbUpAdcHz" : 0, + "gzUpTime" : 0, + "gzUpThreshold" : 0, + "gzUpAdcDeep" : 0, + "gzUpAdcHz" : 0, + "workInfoUpTime" : 0, + "workInfoAcQGAP" : 0 + }, + "frame" : "", + "count" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+data+|+ParamDTO+||| +|+data.xbUpTime+|+Integer+||+0+|+行波电流召回时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+data.xbUpThreshold+|+Integer+||+0+|+行波电流阈值 单位 安倍+ +|+data.xbUpAdcDeep+|+Integer+||+0+|+行波电流采集时长 单位微秒+ +|+data.xbUpAdcHz+|+Integer+||+0+|+行波电流采样频率 单位Hz+ +|+data.gzUpTime+|+Integer+||+0+|+工频电流召回时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+data.gzUpThreshold+|+Integer+||+0+|+工频电流阈值 单位 安倍+ +|+data.gzUpAdcDeep+|+Integer+||+0+|+工频电流采集时长 单位 毫秒+ +|+data.gzUpAdcHz+|+Integer+||+0+|+工频电流采样频率 单位 Hz+ +|+data.workInfoUpTime+|+Integer+||+0+|+工作状态上报时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+data.workInfoAcQGAP+|+Integer+||+0+|+工况数据采集间隔 单位分钟+ +|+frame+|+String+||| +|+count+|+Integer+||+0+| +|=== + + +=== 设置参数信息 +请求 +[source,HTTP ] +---- +POST /terminals/{sn}/param HTTP/1.1 +Content-Type: application/json + +{ + "xbUpTime" : 0, + "xbUpThreshold" : 0, + "xbUpAdcDeep" : 0, + "xbUpAdcHz" : 0, + "gzUpTime" : 0, + "gzUpThreshold" : 0, + "gzUpAdcDeep" : 0, + "gzUpAdcHz" : 0, + "workInfoUpTime" : 0, + "workInfoAcQGAP" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|+xbUpTime+|+Integer+||+0+|+行波电流召回时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+xbUpThreshold+|+Integer+||+0+|+行波电流阈值 单位 安倍+ +|+xbUpAdcDeep+|+Integer+||+0+|+行波电流采集时长 单位微秒+ +|+xbUpAdcHz+|+Integer+||+0+|+行波电流采样频率 单位Hz+ +|+gzUpTime+|+Integer+||+0+|+工频电流召回时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+gzUpThreshold+|+Integer+||+0+|+工频电流阈值 单位 安倍+ +|+gzUpAdcDeep+|+Integer+||+0+|+工频电流采集时长 单位 毫秒+ +|+gzUpAdcHz+|+Integer+||+0+|+工频电流采样频率 单位 Hz+ +|+workInfoUpTime+|+Integer+||+0+|+工作状态上报时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+workInfoAcQGAP+|+Integer+||+0+|+工况数据采集间隔 单位分钟+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "createTime" : "System.currentTimeMillis()", + "data" : { + "xbUpTime" : 0, + "xbUpThreshold" : 0, + "xbUpAdcDeep" : 0, + "xbUpAdcHz" : 0, + "gzUpTime" : 0, + "gzUpThreshold" : 0, + "gzUpAdcDeep" : 0, + "gzUpAdcHz" : 0, + "workInfoUpTime" : 0, + "workInfoAcQGAP" : 0 + }, + "frame" : "", + "count" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+createTime+|+long+||+System.currentTimeMillis()+|+接收时间+ +|+data+|+ParamDTO+||| +|+data.xbUpTime+|+Integer+||+0+|+行波电流召回时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+data.xbUpThreshold+|+Integer+||+0+|+行波电流阈值 单位 安倍+ +|+data.xbUpAdcDeep+|+Integer+||+0+|+行波电流采集时长 单位微秒+ +|+data.xbUpAdcHz+|+Integer+||+0+|+行波电流采样频率 单位Hz+ +|+data.gzUpTime+|+Integer+||+0+|+工频电流召回时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+data.gzUpThreshold+|+Integer+||+0+|+工频电流阈值 单位 安倍+ +|+data.gzUpAdcDeep+|+Integer+||+0+|+工频电流采集时长 单位 毫秒+ +|+data.gzUpAdcHz+|+Integer+||+0+|+工频电流采样频率 单位 Hz+ +|+data.workInfoUpTime+|+Integer+||+0+|+工作状态上报时间 +至当天0点的秒数 +显示格式 HH:mm:ss+ +|+data.workInfoAcQGAP+|+Integer+||+0+|+工况数据采集间隔 单位分钟+ +|+frame+|+String+||| +|+count+|+Integer+||+0+| +|=== + + +=== 终端重置 +请求 +[source,HTTP ] +---- +POST /terminals/{sn}/reset HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + + +---- + + +=== 获取最近重置成功的数据 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/reset? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + + +=== 终端升级 +请求 +[source,HTTP ] +---- +POST /terminals/{sn}/upgrade HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + + +=== 当前终端的升级进度 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/upgrade/progress? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "total" : 0, + "index" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+total+|+int+||+0+|+升级包总数+ +|+index+|+int+||+0+|+当前进行中的包索引+ +|=== + + +=== 上传升级文件 +请求 +[source,HTTP ] +---- +POST /terminals/upgrade/file HTTP/1.1 + +version=0 +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+version+|+short+||+0+| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + + +---- + + +=== 获取升级文件信息 +请求 +[source,HTTP ] +---- +GET /terminals/upgrade/file HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "fileName" : "", + "version" : 0, + "total" : 0, + "bytes" : 0, + "crc" : 0 +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+fileName+|+String+|||+升级文件名+ +|+version+|+short+||+0+|+版本号+ +|+total+|+int+||+0+|+升级包总数+ +|+bytes+|+int+||+0+|+文件大小+ +|+crc+|+int+||+0+|+文件校验码+ +|=== + + +=== 清除升级文件 +请求 +[source,HTTP ] +---- +DELETE /terminals/upgrade/file HTTP/1.1 + +---- + + +=== 切换量程 +请求 +[source,HTTP ] +---- +GET /terminals/{sn}/range?enable=0 HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+||| +|+enable+|+boolean+||+0+|+0禁用大量程 1使能大量程+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + + +---- + + +== 终端时间标定 + +=== 第一步,开始标定 +请求 +[source,HTTP ] +---- +GET /terminals/calibration/start HTTP/1.1 + +---- + + +=== 第二步,锁定标定终端 +请求 +[source,HTTP ] +---- +GET /terminals/calibration/lock HTTP/1.1 + +---- + + +=== 第三步,结束标定 +请求 +[source,HTTP ] +---- +GET /terminals/calibration/finish HTTP/1.1 + +---- + + +=== 设置标定偏差阈值 +请求 +[source,HTTP ] +---- +PUT /terminals/calibration/limit HTTP/1.1 + +limit=0 +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+limit+|+long+||+0+| +|=== + + +=== 当前标定状态 +[%hardbreaks] +状态 0-初始化 1-开始标定,允许新增标定设备 2-锁定设备 3-结束标定,保存标定结果 + +请求 +[source,HTTP ] +---- +GET /terminals/calibration/state HTTP/1.1 + +---- + + +=== 查询当前标定信息 +请求 +[source,HTTP ] +---- +GET /terminals/calibration HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "terminalCount" : 0, + "batchCount" : 0, + "createTime" : "", + "terminals" : "new LinkedHashMap<>()" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+| +|+terminalCount+|+Integer+||+0+|+终端数量+ +|+batchCount+|+Integer+||+0+| +|+createTime+|+Date+||| +|+terminals+|+Map+||| +|=== + + +=== 获取标定详情 +请求 +[source,HTTP ] +---- +GET /terminals/calibration/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+int+||+0+| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "terminalCount" : 0, + "batchCount" : 0, + "createTime" : "", + "terminals" : "new LinkedHashMap<>()" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Integer+||+0+| +|+terminalCount+|+Integer+||+0+|+终端数量+ +|+batchCount+|+Integer+||+0+| +|+createTime+|+Date+||| +|+terminals+|+Map+||| +|=== + diff --git a/apidoc-springmvc/src/test/resources/ubcloud-front-web.adoc b/apidoc-springmvc/src/test/resources/ubcloud-front-web.adoc new file mode 100644 index 0000000000000000000000000000000000000000..4ba88c454b31a9d3b8f92c8155a6dde381c2a436 --- /dev/null +++ b/apidoc-springmvc/src/test/resources/ubcloud-front-web.adoc @@ -0,0 +1,2809 @@ += 优碧云接口文档 +:doctype: book +:toc: left +:toclevels: 3 +:toc-title: 目录 +:source-highlighter: highlightjs + +[%hardbreaks] +version:1.0 + +[%hardbreaks] +使用http或https访问服务 + 使用http code标识服务处理状态 + 处理成功使用200+ + 错误信息使用400+ + 服务端错误使用500+ + + +== 文档说明 +[%hardbreaks] +系统使用cookie跟踪会话SESSION,如 +SESSION=ZDczYzVhYjItOGExZC00ZWE3LWI5MzUtZmZiOTc0NDEyZDk0; path=/; domain=127.0.0.1; HttpOnly; Expires=Tue, 19 Jan 2038 03:14:07 GMT; +分页使用 page,size;size为-1时返回不分页的数据 + + +== 登录、注册等 + +=== 登录 +[%hardbreaks] +支持手机号、验证码 和 用户名、密码 +登录成功结果参考 /users/my + +请求 +[source,HTTP ] +---- +POST /login HTTP/1.1 +Content-Type: application/json + +{ + "account" : "", + "code" : "", + "codeType" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+account+|+String+|+NotBlank+||+登录账户,用户名或手机号+ +|+code+|+String+|+NotBlank+||+登录码,密码或短信验证码+ +|+codeType+|+CodeType+|+NotNull+||+登录码类型,支持 PASSWORD、VERIFYCODE+ +|=== + + +=== 用户注册 +请求 +[source,HTTP ] +---- +POST /register HTTP/1.1 +Content-Type: application/json + +{ + "account" : "", + "code" : "", + "password" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+account+|+String+|+NotBlank+||+手机号+ +|+code+|+String+|+NotBlank+||+短信校验码+ +|+password+|+String+|+NotBlank+||+密码+ +|=== + + +=== 获取验证码 +请求 +[source,HTTP ] +---- +POST /code HTTP/1.1 +Content-Type: application/json + +{ + "account" : "", + "module" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+account+|+String+|+NotBlank+||+手机号+ +|+module+|+VerifyCodeModule+|+NotNull+||+校验码用途,支持 REGISTER LOGIN RESETPASSWORD+ +|=== + + +=== 验证码校验 +[%hardbreaks] +单纯校验,后端不会作废该验证码 + +请求 +[source,HTTP ] +---- +POST /code/check HTTP/1.1 +Content-Type: application/json + +{ + "account" : "", + "module" : "", + "code" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+account+|+String+|+NotBlank+||+手机号+ +|+module+|+VerifyCodeModule+|+NotNull+||+校验码用途,支持 REGISTER LOGIN RESETPASSWORD+ +|+code+|+String+|+NotBlank+||+短信校验码+ +|=== + + +=== 退出登录 +[%hardbreaks] +spring security 的拦截器处理,这里什么都不做 +用于apigcc生成文档 + +请求 +[source,HTTP ] +---- +GET /logout HTTP/1.1 + +---- + + +=== 用户重置密码 +请求 +[source,HTTP ] +---- +PUT /password/reset HTTP/1.1 +Content-Type: application/json + +{ + "account" : "", + "code" : "", + "password" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+account+|+String+|+NotBlank+||+手机号+ +|+code+|+String+|+NotBlank+||+验证码+ +|+password+|+String+|+NotBlank+||+新密码+ +|=== + + +== 用户管理 + +=== 获取当前登录用户信息 +[%hardbreaks] +包含员工信息 +获取自己对哪个组织具有权限 +organizationId 为null时,表示系统权限:sys_admin或 sys_visit + +请求 +[source,HTTP ] +---- +GET /users/my HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "username" : "", + "phone" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "", + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "roleCode" : "", + "staffs" : [ { + "userId" : 0, + "roleCode" : "", + "organizationId" : 0, + "duty" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+name+|+String+|||+姓名+ +|+username+|+String+|||+用户名+ +|+phone+|+String+|||+注册手机号+ +|+avatarUrl+|+String+|||+用户头像+ +|+email+|+String+|||+注册邮箱+ +|+location+|+String+|||+详细地址+ +|+country+|+String+|||+国家+ +|+remark+|+String+|||+个人备注+ +|+fixedPhone+|+String+|||+固定手机号+ +|+createTime+|+Date+|||+设备创建时间+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+roleCode+|+String+|||+系统角色+ +|+staffs+|+List+||| +|+staffs.[].userId+|+Long+||+0+| +|+staffs.[].roleCode+|+String+||| +|+staffs.[].organizationId+|+Long+||+0+| +|+staffs.[].duty+|+String+|||+组织内职位(不涉及到权限)+ +|+staffs.[].createTime+|+Date+|||+设备创建时间+ +|+staffs.[].createUserId+|+Long+||+0+|+创建人id+ +|+staffs.[].createUserName+|+String+|||+创建人用户名+ +|+staffs.[].updateTime+|+Date+|||+设备修改时间+ +|+staffs.[].updateUserId+|+Long+||+0+|+更新人id+ +|+staffs.[].updateUserName+|+String+|||+操作人员+ +|+staffs.[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 获取某个用户信息 +请求 +[source,HTTP ] +---- +GET /users/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "username" : "", + "phone" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "", + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "roleCode" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+name+|+String+|||+姓名+ +|+username+|+String+|||+用户名+ +|+phone+|+String+|||+注册手机号+ +|+avatarUrl+|+String+|||+用户头像+ +|+email+|+String+|||+注册邮箱+ +|+location+|+String+|||+详细地址+ +|+country+|+String+|||+国家+ +|+remark+|+String+|||+个人备注+ +|+fixedPhone+|+String+|||+固定手机号+ +|+createTime+|+Date+|||+设备创建时间+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+roleCode+|+String+|||+系统角色+ +|=== + + +=== 通过电话号码查找用户,全匹配 +请求 +[source,HTTP ] +---- +GET /users/phone?phone= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+phone+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "username" : "", + "phone" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "", + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "roleCode" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+name+|+String+|||+姓名+ +|+username+|+String+|||+用户名+ +|+phone+|+String+|||+注册手机号+ +|+avatarUrl+|+String+|||+用户头像+ +|+email+|+String+|||+注册邮箱+ +|+location+|+String+|||+详细地址+ +|+country+|+String+|||+国家+ +|+remark+|+String+|||+个人备注+ +|+fixedPhone+|+String+|||+固定手机号+ +|+createTime+|+Date+|||+设备创建时间+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+roleCode+|+String+|||+系统角色+ +|=== + + +=== 更新个人信息 +请求 +[source,HTTP ] +---- +PUT /users/my HTTP/1.1 +Content-Type: application/json + +{ + "id" : 0, + "name" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+name+|+String+|||+姓名+ +|+avatarUrl+|+String+|||+用户头像+ +|+email+|+String+|||+注册邮箱+ +|+location+|+String+|||+详细地址+ +|+country+|+String+|||+国家+ +|+remark+|+String+|||+个人备注+ +|+fixedPhone+|+String+|||+固定手机号+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "username" : "", + "phone" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "", + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "roleCode" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+name+|+String+|||+姓名+ +|+username+|+String+|||+用户名+ +|+phone+|+String+|||+注册手机号+ +|+avatarUrl+|+String+|||+用户头像+ +|+email+|+String+|||+注册邮箱+ +|+location+|+String+|||+详细地址+ +|+country+|+String+|||+国家+ +|+remark+|+String+|||+个人备注+ +|+fixedPhone+|+String+|||+固定手机号+ +|+createTime+|+Date+|||+设备创建时间+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+roleCode+|+String+|||+系统角色+ +|=== + + +=== 获取用户头像上传签名 +[%hardbreaks] +不设置callback,上传完成后,拿到完整图片路径,调用更新个人信息接口 + +请求 +[source,HTTP ] +---- +GET /users/avatar/sign HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "accessid" : "", + "policy" : "", + "signature" : "", + "dir" : "", + "host" : "", + "expire" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+accessid+|+String+||| +|+policy+|+String+||| +|+signature+|+String+||| +|+dir+|+String+||| +|+host+|+String+||| +|+expire+|+String+||| +|=== + + +=== 用户修改密码 +请求 +[source,HTTP ] +---- +PUT /users/my/password HTTP/1.1 +Content-Type: application/json + +{ + "password" : "", + "newPassword" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+password+|+String+|+NotBlank+||+原密码+ +|+newPassword+|+String+|+NotBlank+||+新密码+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + + +---- + + +=== 获取用户列表 +请求 +[source,HTTP ] +---- +GET /users HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "page" : 0, + "size" : 0, + "totalPage" : 0, + "totalElement" : 0, + "data" : [ ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+page+|+int+||+0+|+页码+ +|+size+|+int+||+0+|+每页数据量+ +|+totalPage+|+int+||+0+|+总页数+ +|+totalElement+|+long+||+0+|+总记录数+ +|+data+|+Iterable+|||+数据+ +|=== + + +== 组织管理 + +=== 创建组织 +请求 +[source,HTTP ] +---- +POST /organizations HTTP/1.1 +Content-Type: application/json + +{ + "name" : "", + "managerId" : 0, + "remarks" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+name+|+String+|+NotBlank+||+组织名称+ +|+managerId+|+Long+|+NotNull+|+0+|+组织管理员用户 id+ +|+remarks+|+String+|+NotBlank+||+组织备注信息+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "managerId" : 0, + "remarks" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+|+ID-主键+ +|+name+|+String+|||+组织名称+ +|+managerId+|+Long+||+0+|+组织管理员用户 id+ +|+remarks+|+String+|||+组织备注信息+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 修改组织信息 +请求 +[source,HTTP ] +---- +PUT /organizations/{id} HTTP/1.1 +Content-Type: application/json + +{ + "name" : "", + "remarks" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||| +|+name+|+String+|+NotBlank+||+组织名称+ +|+remarks+|+String+|+NotBlank+||+组织备注信息+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "managerId" : 0, + "remarks" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+|+ID-主键+ +|+name+|+String+|||+组织名称+ +|+managerId+|+Long+||+0+|+组织管理员用户 id+ +|+remarks+|+String+|||+组织备注信息+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 删除组织 +请求 +[source,HTTP ] +---- +DELETE /organizations/{id} HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||| +|=== + + +=== 查询组织列表 +请求 +[source,HTTP ] +---- +GET /organizations HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "name" : "", + "managerId" : 0, + "remarks" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+Long+||+0+|+ID-主键+ +|+[].name+|+String+|||+组织名称+ +|+[].managerId+|+Long+||+0+|+组织管理员用户 id+ +|+[].remarks+|+String+|||+组织备注信息+ +|+[].createTime+|+Date+|||+设备创建时间+ +|+[].createUserId+|+Long+||+0+|+创建人id+ +|+[].createUserName+|+String+|||+创建人用户名+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 查询组织详情 +请求 +[source,HTTP ] +---- +GET /organizations/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "managerId" : 0, + "remarks" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+|+ID-主键+ +|+name+|+String+|||+组织名称+ +|+managerId+|+Long+||+0+|+组织管理员用户 id+ +|+remarks+|+String+|||+组织备注信息+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +== 角色、权限管理 + +=== 全部角色列表 +[%hardbreaks] +接口返回数据格式: [{"name":"管理员","code":"sys_admin"},...] + +请求 +[source,HTTP ] +---- +GET /roles HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +=== 组织内可分配的角色 +[%hardbreaks] +接口返回数据格式: [{"name":"管理员","code":"sys_admin"},...] + +请求 +[source,HTTP ] +---- +GET /roles/assignable HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +=== 获取该角色具有的权限 +[%hardbreaks] +接口返回数据是权限数组 如 ["org_read","org_write"] + +请求 +[source,HTTP ] +---- +GET /roles/{role}/authorities? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+role+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +=== 修改角色权限 +请求 +[source,HTTP ] +---- +PUT /roles/{role}/authorities HTTP/1.1 +Content-Type: application/json + +[ "" ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+role+|+String+||| +|=== + + +=== 获取系统预置权限列表 +[%hardbreaks] +接口返回数据是权限数组 如 ["org_read","org_write"] + +请求 +[source,HTTP ] +---- +GET /authorities HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +== 员工管理 + +=== 我的员工信息 +[%hardbreaks] +获取自己对哪个组织具有权限 +organizationId 为null时,表示系统权限:sys_admin或 sys_visit + +请求 +[source,HTTP ] +---- +GET /staffs/my HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "userId" : 0, + "roleCode" : "", + "organizationId" : 0, + "duty" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].userId+|+Long+||+0+| +|+[].roleCode+|+String+||| +|+[].organizationId+|+Long+||+0+| +|+[].duty+|+String+|||+组织内职位(不涉及到权限)+ +|+[].createTime+|+Date+|||+设备创建时间+ +|+[].createUserId+|+Long+||+0+|+创建人id+ +|+[].createUserName+|+String+|||+创建人用户名+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 我的默认员工信息 +请求 +[source,HTTP ] +---- +GET /staffs/my/default HTTP/1.1 + +---- + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "userId" : 0, + "roleCode" : "", + "organizationId" : 0, + "duty" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+userId+|+Long+||+0+| +|+roleCode+|+String+||| +|+organizationId+|+Long+||+0+| +|+duty+|+String+|||+组织内职位(不涉及到权限)+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 根据电话号码查询用户与组织的关系 +[%hardbreaks] +roleCode为null时表示在组织无角色 + +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/staffs/phone?phone= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+phone+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "userId" : 0, + "roleCode" : "", + "organizationId" : 0, + "duty" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "user" : { + "id" : 0, + "name" : "", + "username" : "", + "phone" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "", + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "roleCode" : "" + } +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+userId+|+Long+||+0+| +|+roleCode+|+String+||| +|+organizationId+|+Long+||+0+| +|+duty+|+String+|||+组织内职位(不涉及到权限)+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+user+|+UserDTO+|||+用户信息+ +|+user.id+|+Long+||+0+| +|+user.name+|+String+|||+姓名+ +|+user.username+|+String+|||+用户名+ +|+user.phone+|+String+|||+注册手机号+ +|+user.avatarUrl+|+String+|||+用户头像+ +|+user.email+|+String+|||+注册邮箱+ +|+user.location+|+String+|||+详细地址+ +|+user.country+|+String+|||+国家+ +|+user.remark+|+String+|||+个人备注+ +|+user.fixedPhone+|+String+|||+固定手机号+ +|+user.createTime+|+Date+|||+设备创建时间+ +|+user.updateTime+|+Date+|||+设备修改时间+ +|+user.updateUserId+|+Long+||+0+|+更新人id+ +|+user.updateUserName+|+String+|||+操作人员+ +|+user.disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+user.roleCode+|+String+|||+系统角色+ +|=== + + +=== 查询员工信息 +[%hardbreaks] +

+包含角色和用户信息 +返回的数组内的结构 参考上一个接口 + +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/staffs? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "userId" : 0, + "roleCode" : "", + "organizationId" : 0, + "duty" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "user" : { + "id" : 0, + "name" : "", + "username" : "", + "phone" : "", + "avatarUrl" : "", + "email" : "", + "location" : "", + "country" : "", + "remark" : "", + "fixedPhone" : "", + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "roleCode" : "" + } +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].userId+|+Long+||+0+| +|+[].roleCode+|+String+||| +|+[].organizationId+|+Long+||+0+| +|+[].duty+|+String+|||+组织内职位(不涉及到权限)+ +|+[].createTime+|+Date+|||+设备创建时间+ +|+[].createUserId+|+Long+||+0+|+创建人id+ +|+[].createUserName+|+String+|||+创建人用户名+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+[].user+|+UserDTO+|||+用户信息+ +|+[].user.id+|+Long+||+0+| +|+[].user.name+|+String+|||+姓名+ +|+[].user.username+|+String+|||+用户名+ +|+[].user.phone+|+String+|||+注册手机号+ +|+[].user.avatarUrl+|+String+|||+用户头像+ +|+[].user.email+|+String+|||+注册邮箱+ +|+[].user.location+|+String+|||+详细地址+ +|+[].user.country+|+String+|||+国家+ +|+[].user.remark+|+String+|||+个人备注+ +|+[].user.fixedPhone+|+String+|||+固定手机号+ +|+[].user.createTime+|+Date+|||+设备创建时间+ +|+[].user.updateTime+|+Date+|||+设备修改时间+ +|+[].user.updateUserId+|+Long+||+0+|+更新人id+ +|+[].user.updateUserName+|+String+|||+操作人员+ +|+[].user.disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+[].user.roleCode+|+String+|||+系统角色+ +|=== + + +=== 设置为员工(角色) +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/staffs HTTP/1.1 +Content-Type: application/json + +{ + "userId" : 0, + "roleCode" : "", + "duty" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+userId+|+Long+|+NotNull+|+0+| +|+roleCode+|+String+|+NotBlank+|| +|+duty+|+String+|+NotBlank+||+组织内职位(不涉及到权限)+ +|=== + + +=== 移除员工 +请求 +[source,HTTP ] +---- +DELETE /organizations/{organizationId}/staffs/{userId} HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+userId+|+Long+||| +|=== + + +== 线路管理 + +=== 添加线路 +请求 +[source,HTTP ] +---- +POST /organizations/{organizationId}/lines HTTP/1.1 +Content-Type: application/json + +{ + "name" : "", + "start" : "", + "end" : "", + "currentType" : "", + "towers" : 0, + "length" : 0, + "voltLevel" : "", + "faultyPushPhone" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+name+|+String+|+NotBlank+||+线路名称+ +|+start+|+String+|+NotBlank+||+线路的起点+ +|+end+|+String+|+NotBlank+||+线路的终点+ +|+currentType+|+CurrentType+|+NotNull+||+电流类型 AC DC+ +|+towers+|+Integer+|+NotNull+|+0+|+塔杆基数+ +|+length+|+Integer+|+NotNull+|+0+|+线路长度km+ +|+voltLevel+|+String+|+NotBlank+||+电压等级+ +|+faultyPushPhone+|+String+|+NotBlank+||+故障推送手机+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "start" : "", + "end" : "", + "currentType" : "", + "deviceGroups" : 0, + "towers" : 0, + "length" : 0, + "voltLevel" : "", + "organizationId" : 0, + "faultyPushPhone" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+|+线路ID-主键+ +|+name+|+String+|||+线路名称+ +|+start+|+String+|||+线路的起点+ +|+end+|+String+|||+线路的终点+ +|+currentType+|+CurrentType+|||+电流类型+ +|+deviceGroups+|+Integer+||+0+|+当前设备组数+ +|+towers+|+Integer+||+0+|+塔杆基数+ +|+length+|+Integer+||+0+|+线路长度km+ +|+voltLevel+|+String+|||+电压等级+ +|+organizationId+|+Long+||+0+|+线路管理单位+ +|+faultyPushPhone+|+String+|||+故障推送手机+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 更新线路信息 +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/lines/{id} HTTP/1.1 +Content-Type: application/json + +{ + "name" : "", + "start" : "", + "end" : "", + "towers" : 0, + "length" : 0, + "voltLevel" : "", + "faultyPushPhone" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+id+|+Long+||| +|+name+|+String+|+NotBlank+||+线路名称+ +|+start+|+String+|+NotBlank+||+线路的起点+ +|+end+|+String+|+NotBlank+||+线路的终点+ +|+towers+|+Integer+|+NotNull+|+0+|+塔杆基数+ +|+length+|+Integer+|+NotNull+|+0+|+线路长度km+ +|+voltLevel+|+String+|+NotBlank+||+电压等级+ +|+faultyPushPhone+|+String+|+NotBlank+||+故障推送手机+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "start" : "", + "end" : "", + "currentType" : "", + "deviceGroups" : 0, + "towers" : 0, + "length" : 0, + "voltLevel" : "", + "organizationId" : 0, + "faultyPushPhone" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+|+线路ID-主键+ +|+name+|+String+|||+线路名称+ +|+start+|+String+|||+线路的起点+ +|+end+|+String+|||+线路的终点+ +|+currentType+|+CurrentType+|||+电流类型+ +|+deviceGroups+|+Integer+||+0+|+当前设备组数+ +|+towers+|+Integer+||+0+|+塔杆基数+ +|+length+|+Integer+||+0+|+线路长度km+ +|+voltLevel+|+String+|||+电压等级+ +|+organizationId+|+Long+||+0+|+线路管理单位+ +|+faultyPushPhone+|+String+|||+故障推送手机+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 查询线路详情 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+id+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "name" : "", + "start" : "", + "end" : "", + "currentType" : "", + "deviceGroups" : 0, + "towers" : 0, + "length" : 0, + "voltLevel" : "", + "organizationId" : 0, + "faultyPushPhone" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+|+线路ID-主键+ +|+name+|+String+|||+线路名称+ +|+start+|+String+|||+线路的起点+ +|+end+|+String+|||+线路的终点+ +|+currentType+|+CurrentType+|||+电流类型+ +|+deviceGroups+|+Integer+||+0+|+当前设备组数+ +|+towers+|+Integer+||+0+|+塔杆基数+ +|+length+|+Integer+||+0+|+线路长度km+ +|+voltLevel+|+String+|||+电压等级+ +|+organizationId+|+Long+||+0+|+线路管理单位+ +|+faultyPushPhone+|+String+|||+故障推送手机+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 查询线路 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines?key= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+key+|+String+|||+查询的关键字+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "name" : "", + "start" : "", + "end" : "", + "currentType" : "", + "deviceGroups" : 0, + "towers" : 0, + "length" : 0, + "voltLevel" : "", + "organizationId" : 0, + "faultyPushPhone" : "", + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+Long+||+0+|+线路ID-主键+ +|+[].name+|+String+|||+线路名称+ +|+[].start+|+String+|||+线路的起点+ +|+[].end+|+String+|||+线路的终点+ +|+[].currentType+|+CurrentType+|||+电流类型+ +|+[].deviceGroups+|+Integer+||+0+|+当前设备组数+ +|+[].towers+|+Integer+||+0+|+塔杆基数+ +|+[].length+|+Integer+||+0+|+线路长度km+ +|+[].voltLevel+|+String+|||+电压等级+ +|+[].organizationId+|+Long+||+0+|+线路管理单位+ +|+[].faultyPushPhone+|+String+|||+故障推送手机+ +|+[].createTime+|+Date+|||+设备创建时间+ +|+[].createUserId+|+Long+||+0+|+创建人id+ +|+[].createUserName+|+String+|||+创建人用户名+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 删除线路 +请求 +[source,HTTP ] +---- +DELETE /organizations/{organizationId}/lines/{id} HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+id+|+Long+||| +|=== + + +== 设备组管理 + +=== 新增设备组 +[%hardbreaks] +以组为单位添加设备 + +请求 +[source,HTTP ] +---- +POST /organizations/{organizationId}/lines/{lineId}/devices/groups HTTP/1.1 +Content-Type: application/json + +{ + "address" : "", + "towerNumber" : "", + "milepost" : 0, + "devices" : [ { + "sn" : "", + "phase" : "", + "name" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+address+|+String+|+NotBlank+||+设备地址+ +|+towerNumber+|+String+|+NotBlank+||+塔杆编号+ +|+milepost+|+Integer+|+NotNull+|+0+|+里程标,安装在线路多少米处,单位:米+ +|+devices+|+List+|+NotNull+|| +|+devices.[].sn+|+String+|+NotBlank+||+设备SN+ +|+devices.[].phase+|+Phase+|+NotBlank+||+设备相别+ +|+devices.[].name+|+String+|+NotBlank+||+设备名称+ +|=== + + +=== 更新设备组信息 +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/lines/{lineId}/devices/groups/{number} HTTP/1.1 +Content-Type: application/json + +{ + "address" : "", + "towerNumber" : "", + "milepost" : 0, + "devices" : [ { + "id" : 0, + "name" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+number+|+Integer+||| +|+address+|+String+|+NotBlank+||+设备地址+ +|+towerNumber+|+String+|+NotBlank+||+塔杆编号+ +|+milepost+|+Integer+|+NotNull+|+0+|+里程标,安装在线路多少米处,单位:米+ +|+devices+|+List+||| +|+devices.[].id+|+Long+|+NotNull+|+0+| +|+devices.[].name+|+String+|+NotBlank+||+设备名称+ +|=== + + +=== 查询设备组详情,包含设备信息 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/devices/groups/{number}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+number+|+Integer+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "organizationId" : 0, + "lineId" : 0, + "number" : 0, + "address" : "", + "towerNumber" : "", + "milepost" : 0, + "longitude" : 0, + "latitude" : 0, + "devices" : [ { + "id" : 0, + "sn" : "", + "name" : "", + "phase" : "", + "groupNumber" : 0, + "organizationId" : 0, + "lineId" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "online" : 0, + "longitude" : 0, + "latitude" : 0 + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||+0+| +|+lineId+|+Long+||+0+|+关联线路+ +|+number+|+Integer+||+0+|+组编号+ +|+address+|+String+|||+设备地址+ +|+towerNumber+|+String+|||+塔杆编号+ +|+milepost+|+Integer+||+0+|+里程标,安装在线路多少米处,单位:米+ +|+longitude+|+Double+||+0+|+经度+ +|+latitude+|+Double+||+0+|+纬度+ +|+devices+|+List+||| +|+devices.[].id+|+Long+||+0+| +|+devices.[].sn+|+String+|||+设备SN+ +|+devices.[].name+|+String+|||+设备名称+ +|+devices.[].phase+|+Phase+|||+设备相别+ +|+devices.[].groupNumber+|+Integer+||+0+|+组编号+ +|+devices.[].organizationId+|+Long+||+0+|+组织id+ +|+devices.[].lineId+|+Long+||+0+|+关联线路+ +|+devices.[].createTime+|+Date+|||+设备创建时间+ +|+devices.[].createUserId+|+Long+||+0+|+创建人id+ +|+devices.[].createUserName+|+String+|||+创建人用户名+ +|+devices.[].updateTime+|+Date+|||+设备修改时间+ +|+devices.[].updateUserId+|+Long+||+0+|+更新人id+ +|+devices.[].updateUserName+|+String+|||+操作人员+ +|+devices.[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+devices.[].online+|+boolean+||+0+|+是否在线+ +|+devices.[].longitude+|+Double+||+0+|+经度+ +|+devices.[].latitude+|+Double+||+0+|+纬度+ +|=== + + +=== 查询设备组 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/devices/groups? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "organizationId" : 0, + "lineId" : 0, + "number" : 0, + "address" : "", + "towerNumber" : "", + "milepost" : 0, + "longitude" : 0, + "latitude" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].organizationId+|+Long+||+0+| +|+[].lineId+|+Long+||+0+|+关联线路+ +|+[].number+|+Integer+||+0+|+组编号+ +|+[].address+|+String+|||+设备地址+ +|+[].towerNumber+|+String+|||+塔杆编号+ +|+[].milepost+|+Integer+||+0+|+里程标,安装在线路多少米处,单位:米+ +|+[].longitude+|+Double+||+0+|+经度+ +|+[].latitude+|+Double+||+0+|+纬度+ +|=== + + +=== 删除设备组 +请求 +[source,HTTP ] +---- +DELETE /organizations/{organizationId}/lines/{lineId}/devices/groups/{number} HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+number+|+Integer+||| +|=== + + +== 设备管理 + +=== 更新设备信息 +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/lines/{lineId}/devices/{id} HTTP/1.1 +Content-Type: application/json + +{ + "name" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+id+|+Long+||| +|+name+|+String+|+NotBlank+||+设备名称+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "name" : "", + "phase" : "", + "groupNumber" : 0, + "organizationId" : 0, + "lineId" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+sn+|+String+|||+设备SN+ +|+name+|+String+|||+设备名称+ +|+phase+|+Phase+|||+设备相别+ +|+groupNumber+|+Integer+||+0+|+组编号+ +|+organizationId+|+Long+||+0+|+组织id+ +|+lineId+|+Long+||+0+|+关联线路+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 查询设备详情 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/devices/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+id+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "name" : "", + "phase" : "", + "groupNumber" : 0, + "organizationId" : 0, + "lineId" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+sn+|+String+|||+设备SN+ +|+name+|+String+|||+设备名称+ +|+phase+|+Phase+|||+设备相别+ +|+groupNumber+|+Integer+||+0+|+组编号+ +|+organizationId+|+Long+||+0+|+组织id+ +|+lineId+|+Long+||+0+|+关联线路+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 根据终端sn查询设备 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/devices/sn/{sn}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|+sn+|+String+|||+终端sn+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "name" : "", + "phase" : "", + "groupNumber" : 0, + "organizationId" : 0, + "lineId" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+sn+|+String+|||+设备SN+ +|+name+|+String+|||+设备名称+ +|+phase+|+Phase+|||+设备相别+ +|+groupNumber+|+Integer+||+0+|+组编号+ +|+organizationId+|+Long+||+0+|+组织id+ +|+lineId+|+Long+||+0+|+关联线路+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 查询设备 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/devices? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "sn" : "", + "name" : "", + "phase" : "", + "groupNumber" : 0, + "organizationId" : 0, + "lineId" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "" +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+Long+||+0+| +|+[].sn+|+String+|||+设备SN+ +|+[].name+|+String+|||+设备名称+ +|+[].phase+|+Phase+|||+设备相别+ +|+[].groupNumber+|+Integer+||+0+|+组编号+ +|+[].organizationId+|+Long+||+0+|+组织id+ +|+[].lineId+|+Long+||+0+|+关联线路+ +|+[].createTime+|+Date+|||+设备创建时间+ +|+[].createUserId+|+Long+||+0+|+创建人id+ +|+[].createUserName+|+String+|||+创建人用户名+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|=== + + +=== 查询线路下的设备,包含工况数据 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/devices/work? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "sn" : "", + "name" : "", + "phase" : "", + "groupNumber" : 0, + "organizationId" : 0, + "lineId" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "towerNumber" : "", + "temp" : 0, + "src" : 0, + "batVoltage" : 0, + "rmsI" : 0 +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+Long+||+0+| +|+[].sn+|+String+|||+设备SN+ +|+[].name+|+String+|||+设备名称+ +|+[].phase+|+Phase+|||+设备相别+ +|+[].groupNumber+|+Integer+||+0+|+组编号+ +|+[].organizationId+|+Long+||+0+|+组织id+ +|+[].lineId+|+Long+||+0+|+关联线路+ +|+[].createTime+|+Date+|||+设备创建时间+ +|+[].createUserId+|+Long+||+0+|+创建人id+ +|+[].createUserName+|+String+|||+创建人用户名+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+[].towerNumber+|+String+|||+塔杆编号+ +|+[].temp+|+Float+||+0+|+设备温度+ +|+[].src+|+Integer+||+0+|+供电方式+ +|+[].batVoltage+|+Float+||+0+|+电池电压+ +|+[].rmsI+|+Float+||+0+|+电流有效值+ +|+[].updateTime+|+Date+|||+设备修改时间+ +|=== + + +== 终端管理 + +=== 查询终端详情 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/terminals/{sn}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "sn" : "", + "organizationId" : 0, + "name" : "", + "model" : "", + "manufacturer" : "", + "versionHw" : 0, + "versionSw" : 0, + "productionDate" : "", + "xbdlUpTime" : "", + "xbdlThreshold" : 0, + "xbdlAdcDeep" : 0, + "xbdlAdcHz" : 0, + "gzdlUpTime" : "", + "gzdlThreshold" : 0, + "gzdlAdcDeep" : 0, + "gzdlAdcHz" : 0, + "workInfoUpTime" : "", + "workInfoAcQGAP" : 0, + "loraAddr" : 0, + "ip" : "", + "port" : 0, + "lteRssi" : 0, + "angleX" : 0, + "angleY" : 0, + "angleZ" : 0, + "power" : 0, + "longitude" : 0, + "latitude" : 0, + "temp" : 0, + "src" : 0, + "batVoltage" : 0, + "rmsI" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+|||+终端编号+ +|+organizationId+|+Long+||+0+| +|+name+|+String+|||+设备名称+ +|+model+|+String+|||+设备型号+ +|+manufacturer+|+String+|||+生产厂家+ +|+versionHw+|+Integer+||+0+|+硬件版本+ +|+versionSw+|+Integer+||+0+|+软件版本+ +|+productionDate+|+String+|||+生产日期+ +|+xbdlUpTime+|+String+|||+行波电流波形数据召回时间+ +|+xbdlThreshold+|+Long+||+0+|+行波电流阈值+ +|+xbdlAdcDeep+|+Long+||+0+|+行波电流采集深度+ +|+xbdlAdcHz+|+Long+||+0+|+行波电流采样频率+ +|+gzdlUpTime+|+String+|||+故障电流波形数据召回时间+ +|+gzdlThreshold+|+Long+||+0+|+故障电流阈值+ +|+gzdlAdcDeep+|+Long+||+0+|+故障电流采集深度+ +|+gzdlAdcHz+|+Long+||+0+|+故障电流采集频率+ +|+workInfoUpTime+|+String+|||+工况数据上报时间+ +|+workInfoAcQGAP+|+Long+||+0+|+工况数据采集间隔+ +|+loraAddr+|+Long+||+0+|+lora地址+ +|+ip+|+String+||| +|+port+|+Integer+||+0+| +|+lteRssi+|+Long+||+0+|+4g信号强度+ +|+angleX+|+Float+||+0+|+设备倾角+ +|+angleY+|+Float+||+0+| +|+angleZ+|+Float+||+0+| +|+power+|+Float+||+0+|+设备功率+ +|+longitude+|+Double+||+0+| +|+latitude+|+Double+||+0+| +|+temp+|+Float+||+0+|+设备温度+ +|+src+|+Integer+||+0+|+供电方式+ +|+batVoltage+|+Float+||+0+|+电池电压+ +|+rmsI+|+Float+||+0+|+电流有效值+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+used+|+Boolean+|||+终端是否已被绑定+ +|+online+|+Boolean+||| +|=== + + +=== 根据关键字查询sn +[%hardbreaks] +关键字模糊搜索 + +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/terminals/search?key= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+key+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ "" ] +---- + + +=== 发送重置命令 +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/terminals/{sn}/reset HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+sn+|+String+||| +|=== + + +=== 从终端同步参数到云平台 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/terminals/{sn}/sync? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+sn+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "sn" : "", + "organizationId" : 0, + "name" : "", + "model" : "", + "manufacturer" : "", + "versionHw" : 0, + "versionSw" : 0, + "productionDate" : "", + "xbdlUpTime" : "", + "xbdlThreshold" : 0, + "xbdlAdcDeep" : 0, + "xbdlAdcHz" : 0, + "gzdlUpTime" : "", + "gzdlThreshold" : 0, + "gzdlAdcDeep" : 0, + "gzdlAdcHz" : 0, + "workInfoUpTime" : "", + "workInfoAcQGAP" : 0, + "loraAddr" : 0, + "ip" : "", + "port" : 0, + "lteRssi" : 0, + "angleX" : 0, + "angleY" : 0, + "angleZ" : 0, + "power" : 0, + "longitude" : 0, + "latitude" : 0, + "temp" : 0, + "src" : 0, + "batVoltage" : 0, + "rmsI" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+|||+终端编号+ +|+organizationId+|+Long+||+0+| +|+name+|+String+|||+设备名称+ +|+model+|+String+|||+设备型号+ +|+manufacturer+|+String+|||+生产厂家+ +|+versionHw+|+Integer+||+0+|+硬件版本+ +|+versionSw+|+Integer+||+0+|+软件版本+ +|+productionDate+|+String+|||+生产日期+ +|+xbdlUpTime+|+String+|||+行波电流波形数据召回时间+ +|+xbdlThreshold+|+Long+||+0+|+行波电流阈值+ +|+xbdlAdcDeep+|+Long+||+0+|+行波电流采集深度+ +|+xbdlAdcHz+|+Long+||+0+|+行波电流采样频率+ +|+gzdlUpTime+|+String+|||+故障电流波形数据召回时间+ +|+gzdlThreshold+|+Long+||+0+|+故障电流阈值+ +|+gzdlAdcDeep+|+Long+||+0+|+故障电流采集深度+ +|+gzdlAdcHz+|+Long+||+0+|+故障电流采集频率+ +|+workInfoUpTime+|+String+|||+工况数据上报时间+ +|+workInfoAcQGAP+|+Long+||+0+|+工况数据采集间隔+ +|+loraAddr+|+Long+||+0+|+lora地址+ +|+ip+|+String+||| +|+port+|+Integer+||+0+| +|+lteRssi+|+Long+||+0+|+4g信号强度+ +|+angleX+|+Float+||+0+|+设备倾角+ +|+angleY+|+Float+||+0+| +|+angleZ+|+Float+||+0+| +|+power+|+Float+||+0+|+设备功率+ +|+longitude+|+Double+||+0+| +|+latitude+|+Double+||+0+| +|+temp+|+Float+||+0+|+设备温度+ +|+src+|+Integer+||+0+|+供电方式+ +|+batVoltage+|+Float+||+0+|+电池电压+ +|+rmsI+|+Float+||+0+|+电流有效值+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+used+|+Boolean+|||+终端是否已被绑定+ +|+online+|+Boolean+||| +|=== + + +=== 修改参数,并同步至终端 +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/terminals/{sn} HTTP/1.1 +Content-Type: application/json + +{ + "sn" : "", + "organizationId" : 0, + "name" : "", + "model" : "", + "manufacturer" : "", + "versionHw" : 0, + "versionSw" : 0, + "productionDate" : "", + "xbdlUpTime" : "", + "xbdlThreshold" : 0, + "xbdlAdcDeep" : 0, + "xbdlAdcHz" : 0, + "gzdlUpTime" : "", + "gzdlThreshold" : 0, + "gzdlAdcDeep" : 0, + "gzdlAdcHz" : 0, + "workInfoUpTime" : "", + "workInfoAcQGAP" : 0, + "loraAddr" : 0, + "ip" : "", + "port" : 0, + "lteRssi" : 0, + "angleX" : 0, + "angleY" : 0, + "angleZ" : 0, + "power" : 0, + "longitude" : 0, + "latitude" : 0, + "temp" : 0, + "src" : 0, + "batVoltage" : 0, + "rmsI" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+sn+|+String+||| +|+sn+|+String+|||+终端编号+ +|+organizationId+|+Long+||+0+| +|+name+|+String+|||+设备名称+ +|+model+|+String+|||+设备型号+ +|+manufacturer+|+String+|||+生产厂家+ +|+versionHw+|+Integer+||+0+|+硬件版本+ +|+versionSw+|+Integer+||+0+|+软件版本+ +|+productionDate+|+String+|||+生产日期+ +|+xbdlUpTime+|+String+|||+行波电流波形数据召回时间+ +|+xbdlThreshold+|+Long+||+0+|+行波电流阈值+ +|+xbdlAdcDeep+|+Long+||+0+|+行波电流采集深度+ +|+xbdlAdcHz+|+Long+||+0+|+行波电流采样频率+ +|+gzdlUpTime+|+String+|||+故障电流波形数据召回时间+ +|+gzdlThreshold+|+Long+||+0+|+故障电流阈值+ +|+gzdlAdcDeep+|+Long+||+0+|+故障电流采集深度+ +|+gzdlAdcHz+|+Long+||+0+|+故障电流采集频率+ +|+workInfoUpTime+|+String+|||+工况数据上报时间+ +|+workInfoAcQGAP+|+Long+||+0+|+工况数据采集间隔+ +|+loraAddr+|+Long+||+0+|+lora地址+ +|+ip+|+String+||| +|+port+|+Integer+||+0+| +|+lteRssi+|+Long+||+0+|+4g信号强度+ +|+angleX+|+Float+||+0+|+设备倾角+ +|+angleY+|+Float+||+0+| +|+angleZ+|+Float+||+0+| +|+power+|+Float+||+0+|+设备功率+ +|+longitude+|+Double+||+0+| +|+latitude+|+Double+||+0+| +|+temp+|+Float+||+0+|+设备温度+ +|+src+|+Integer+||+0+|+供电方式+ +|+batVoltage+|+Float+||+0+|+电池电压+ +|+rmsI+|+Float+||+0+|+电流有效值+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+used+|+Boolean+|||+终端是否已被绑定+ +|+online+|+Boolean+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "sn" : "", + "organizationId" : 0, + "name" : "", + "model" : "", + "manufacturer" : "", + "versionHw" : 0, + "versionSw" : 0, + "productionDate" : "", + "xbdlUpTime" : "", + "xbdlThreshold" : 0, + "xbdlAdcDeep" : 0, + "xbdlAdcHz" : 0, + "gzdlUpTime" : "", + "gzdlThreshold" : 0, + "gzdlAdcDeep" : 0, + "gzdlAdcHz" : 0, + "workInfoUpTime" : "", + "workInfoAcQGAP" : 0, + "loraAddr" : 0, + "ip" : "", + "port" : 0, + "lteRssi" : 0, + "angleX" : 0, + "angleY" : 0, + "angleZ" : 0, + "power" : 0, + "longitude" : 0, + "latitude" : 0, + "temp" : 0, + "src" : 0, + "batVoltage" : 0, + "rmsI" : 0, + "createTime" : "", + "createUserId" : 0, + "createUserName" : "", + "updateTime" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+sn+|+String+|||+终端编号+ +|+organizationId+|+Long+||+0+| +|+name+|+String+|||+设备名称+ +|+model+|+String+|||+设备型号+ +|+manufacturer+|+String+|||+生产厂家+ +|+versionHw+|+Integer+||+0+|+硬件版本+ +|+versionSw+|+Integer+||+0+|+软件版本+ +|+productionDate+|+String+|||+生产日期+ +|+xbdlUpTime+|+String+|||+行波电流波形数据召回时间+ +|+xbdlThreshold+|+Long+||+0+|+行波电流阈值+ +|+xbdlAdcDeep+|+Long+||+0+|+行波电流采集深度+ +|+xbdlAdcHz+|+Long+||+0+|+行波电流采样频率+ +|+gzdlUpTime+|+String+|||+故障电流波形数据召回时间+ +|+gzdlThreshold+|+Long+||+0+|+故障电流阈值+ +|+gzdlAdcDeep+|+Long+||+0+|+故障电流采集深度+ +|+gzdlAdcHz+|+Long+||+0+|+故障电流采集频率+ +|+workInfoUpTime+|+String+|||+工况数据上报时间+ +|+workInfoAcQGAP+|+Long+||+0+|+工况数据采集间隔+ +|+loraAddr+|+Long+||+0+|+lora地址+ +|+ip+|+String+||| +|+port+|+Integer+||+0+| +|+lteRssi+|+Long+||+0+|+4g信号强度+ +|+angleX+|+Float+||+0+|+设备倾角+ +|+angleY+|+Float+||+0+| +|+angleZ+|+Float+||+0+| +|+power+|+Float+||+0+|+设备功率+ +|+longitude+|+Double+||+0+| +|+latitude+|+Double+||+0+| +|+temp+|+Float+||+0+|+设备温度+ +|+src+|+Integer+||+0+|+供电方式+ +|+batVoltage+|+Float+||+0+|+电池电压+ +|+rmsI+|+Float+||+0+|+电流有效值+ +|+createTime+|+Date+|||+设备创建时间+ +|+createUserId+|+Long+||+0+|+创建人id+ +|+createUserName+|+String+|||+创建人用户名+ +|+updateTime+|+Date+|||+设备修改时间+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+used+|+Boolean+|||+终端是否已被绑定+ +|+online+|+Boolean+||| +|=== + + +== 告警管理 + +=== 线路下的告警信息,设备组信息 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/lines/{lineId}/alarms? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +[ { + "id" : 0, + "sn" : "", + "faultType" : "", + "faultName" : "", + "faultTime" : "", + "groupNumber1" : 0, + "groupNumber2" : 0, + "distance1" : 0, + "distance2" : 0, + "milepost" : 0, + "lineId" : 0, + "organizationId" : 0, + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "groups" : [ 0 ] +} ] +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+[].id+|+Long+||+0+| +|+[].sn+|+String+|||+故障编号+ +|+[].faultType+|+FaultType+|||+故障类型+ +|+[].faultName+|+String+|||+故障名称+ +|+[].faultTime+|+Date+|||+故障时间+ +|+[].groupNumber1+|+Integer+||+0+|+故障区间小端设备组号+ +|+[].groupNumber2+|+Integer+||+0+|+故障区间大端设备组号+ +|+[].inner+|+Boolean+|||+故障是否在区间内+ +|+[].distance1+|+Double+||+0+|+故障到小端距离 米+ +|+[].distance2+|+Double+||+0+|+故障到大端距离 米+ +|+[].milepost+|+Long+||+0+|+故障位置,里程碑,在线路多少米处+ +|+[].lineId+|+Long+||+0+|+线路+ +|+[].organizationId+|+Long+||+0+|+组织+ +|+[].createTime+|+Date+|||+创建时间+ +|+[].updateTime+|+Date+|||+修改时间+ +|+[].updateUserId+|+Long+||+0+|+更新人id+ +|+[].updateUserName+|+String+|||+操作人员+ +|+[].disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+[].cleared+|+Boolean+|||+报警是否已解除 1已解除,0未解除、报警中+ +|+[].groups+|+Set+|||+发送故障的设备组编号+ +|=== + + +=== 告警信息查询 +[%hardbreaks] +lineId 不传时,查询该组织下的告警信息 + +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/alarms?lineId=0&cleared= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+lineId+|+Long+||+0+|+关联的线路id+ +|+cleared+|+Boolean+|||+告警是否已解除,不传查所有,未解除报警传false+ +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "page" : 0, + "size" : 0, + "totalPage" : 0, + "totalElement" : 0, + "data" : [ ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+page+|+int+||+0+|+页码+ +|+size+|+int+||+0+|+每页数据量+ +|+totalPage+|+int+||+0+|+总页数+ +|+totalElement+|+long+||+0+|+总记录数+ +|+data+|+Iterable+|||+数据+ +|=== + + +=== 告警详情查询 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/alarms/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+id+|+Long+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : 0, + "sn" : "", + "faultType" : "", + "faultName" : "", + "faultTime" : "", + "groupNumber1" : 0, + "groupNumber2" : 0, + "distance1" : 0, + "distance2" : 0, + "milepost" : 0, + "lineId" : 0, + "organizationId" : 0, + "createTime" : "", + "updateTime" : "", + "updateUserId" : 0, + "updateUserName" : "", + "waves" : [ { + "id" : 0, + "groupNumber" : 0, + "phase" : "", + "waveDataId" : "", + "alarmId" : 0, + "lineId" : 0, + "createTime" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+Long+||+0+| +|+sn+|+String+|||+故障编号+ +|+faultType+|+FaultType+|||+故障类型+ +|+faultName+|+String+|||+故障名称+ +|+faultTime+|+Date+|||+故障时间+ +|+groupNumber1+|+Integer+||+0+|+故障区间小端设备组号+ +|+groupNumber2+|+Integer+||+0+|+故障区间大端设备组号+ +|+inner+|+Boolean+|||+故障是否在区间内+ +|+distance1+|+Double+||+0+|+故障到小端距离 米+ +|+distance2+|+Double+||+0+|+故障到大端距离 米+ +|+milepost+|+Long+||+0+|+故障位置,里程碑,在线路多少米处+ +|+lineId+|+Long+||+0+|+线路+ +|+organizationId+|+Long+||+0+|+组织+ +|+createTime+|+Date+|||+创建时间+ +|+updateTime+|+Date+|||+修改时间+ +|+updateUserId+|+Long+||+0+|+更新人id+ +|+updateUserName+|+String+|||+操作人员+ +|+disabled+|+Boolean+|||+使能状态:0正常(默认), 1删除+ +|+cleared+|+Boolean+|||+报警是否已解除 1已解除,0未解除、报警中+ +|+waves+|+List+||| +|+waves.[].id+|+Long+||+0+| +|+waves.[].groupNumber+|+Integer+||+0+|+告警组编号+ +|+waves.[].phase+|+Phase+|||+告警相别+ +|+waves.[].waveDataId+|+String+|||+故障波id+ +|+waves.[].alarmId+|+Long+||+0+|+告警id+ +|+waves.[].lineId+|+Long+||+0+|+线路id+ +|+waves.[].createTime+|+Date+|||+设备创建时间+ +|=== + + +=== 查询波信息 +请求 +[source,HTTP ] +---- +GET /organizations/{organizationId}/alarms/wave/{id}? HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+id+|+String+||| +|=== + +响应 +[source,HTTP ] +---- +HTTP/1.1 200 OK + +{ + "id" : "", + "sn" : "", + "lineId" : 0, + "groupNumber" : 0, + "phase" : "", + "terminalTime" : "", + "type" : 0, + "block" : [ 0 ], + "createTime" : "", + "analysed" : false +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+id+|+String+||| +|+sn+|+String+|||+终端编号+ +|+lineId+|+Long+||+0+|+线路id+ +|+groupNumber+|+Integer+||+0+|+设备组编号+ +|+phase+|+Phase+|||+相别+ +|+terminalTime+|+String+|||+终端时间+ +|+type+|+Integer+||+0+|+波形类型(0故障,1行波)+ +|+block+|+float[]+|||+波形数据+ +|+createTime+|+Date+|||+记录时间+ +|+analysed+|+boolean+||+false+|+已分析过的+ +|=== + + +=== 告警解除 +请求 +[source,HTTP ] +---- +PUT /organizations/{organizationId}/alarms/{id}/clear HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+id+|+Long+||| +|=== + + +=== 创建告警信息(测试使用) +[%hardbreaks] +波形数据不校验,测试时请传入正确波形 + +请求 +[source,HTTP ] +---- +POST /organizations/{organizationId}/alarms HTTP/1.1 +Content-Type: application/json + +{ + "faultType" : "", + "faultTime" : "", + "groupNumber1" : 0, + "groupNumber2" : 0, + "distance1" : 0, + "distance2" : 0, + "lineId" : 0, + "waves" : [ { + "groupNumber" : 0, + "phase" : "", + "waveDataId" : "" + } ] +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+organizationId+|+Long+||| +|+faultType+|+FaultType+|||+故障类型 +BACK_FLASHOVER SHIELDING_FAILURE NOTLIGHTNING+ +|+faultTime+|+Date+|||+故障时间+ +|+groupNumber1+|+Integer+||+0+|+故障区间小端设备组号+ +|+groupNumber2+|+Integer+||+0+|+故障区间大端设备组号+ +|+inner+|+Boolean+|||+故障是否在区间内+ +|+distance1+|+Double+||+0+|+故障到小端距离 米+ +|+distance2+|+Double+||+0+|+故障到大端距离 米+ +|+lineId+|+Long+||+0+|+线路+ +|+waves+|+List+|||+电流波+ +|+waves.[].groupNumber+|+int+||+0+|+组编号+ +|+waves.[].phase+|+Phase+|||+相别+ +|+waves.[].waveDataId+|+String+|||+波的id+ +|=== + + +== 开放服务 + +=== 开放的邮件服务 +请求 +[source,HTTP ] +---- +POST /open/mail HTTP/1.1 +Content-Type: application/json + +{ + "to" : "", + "subject" : "", + "text" : "" +} +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+to+|+String+|+NotBlank Email+||+邮件接收人+ +|+subject+|+String+|+NotBlank+||+邮件主题+ +|+text+|+String+|+NotBlank+||+邮件内容,文本格式+ +|=== + + +=== echo服务 +[%hardbreaks] +用于测试 + +请求 +[source,HTTP ] +---- +GET /open/echo?msg= HTTP/1.1 + +---- + +[options="header"] +|=== +|+名称+|+类型+|+校验+|+默认+|+描述+ +|+msg+|+String+||| +|=== + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ece9c30264c61d53aa76070bb6bc229caf2b0568 --- /dev/null +++ b/build.gradle @@ -0,0 +1,102 @@ +allprojects { + + apply plugin: 'java' + apply plugin: 'maven' + apply plugin: 'com.novoda.bintray-release' + + group 'com.apidoc' + version '1.7.0' + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + + repositories { + maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' } + } + + dependencies { + compile 'ch.qos.logback:logback-classic:1.2.3' + + compileOnly 'org.projectlombok:lombok:1.18.4' + annotationProcessor 'org.projectlombok:lombok:1.18.6' + + testCompileOnly 'org.projectlombok:lombok:1.18.4' + testCompile group: 'junit', name: 'junit', version: '4.12' + } + + ext { + DESCRIPTION = 'A rest document generator with parse the source code.' + SITE_URL = 'https://github.com/apigcc/apigcc' + GIT_URL = 'https://github.com/apigcc/apigcc.git' + + LICENSE = 'MIT License' + + DEVELOPER_ID = 'ayz6uem' + DEVELOPER_NAME = 'ayz6uem' + DEVELOPER_EMAIL = '360188606@qq.com' + + IS_UPLOADING = project.getGradle().startParameter.taskNames.any { it.contains('bintrayUpload') } + } + + publish { + userOrg = 'apidoc' + groupId = group + artifactId = name + publishVersion = version + desc = project.DESCRIPTION + website = project.SITE_URL +// bintrayUser = project.BINTRAYUSER +// bintrayKey = project.BINTRAYKEY + dryRun = false + } +} + +buildscript { + repositories { + maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' } + } + dependencies { + classpath 'com.novoda:bintray-release:0.8.1' + } +} + +// +//gradle.taskGraph.whenReady { taskGraph -> +// taskGraph.getAllTasks().find { it.path == ":generatePomFileForMavenPublication" }?.doLast { +// file("build/publications/maven/pom-default.xml").delete() +// println 'Overriding pom-file to make sure we can sync to maven!' +// pom { +// //noinspection GroovyAssignabilityCheck +// project { +// name "$project.name" +// artifactId name +// packaging 'jar' +// description DESCRIPTION +// url SITE_URL +// version version +// +// scm { +// url GIT_URL +// connection GIT_URL +// developerConnection GIT_URL +// } +// +// licenses { +// license { +// name LICENSE +// } +// } +// +// developers { +// developer { +// id DEVELOPER_ID +// name DEVELOPER_NAME +// email DEVELOPER_EMAIL +// } +// } +// } +// }.writeTo("build/publications/maven/pom-default.xml") +// } +//} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..d457437d538939c28ade0463f0964ed85845ffbc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Mar 26 00:50:33 CST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git "a/markdown\346\216\245\345\217\243\346\250\241\347\211\210.md" "b/markdown\346\216\245\345\217\243\346\250\241\347\211\210.md" new file mode 100644 index 0000000000000000000000000000000000000000..ead46e3b3944de27c95613b2eba76844a4e922ed --- /dev/null +++ "b/markdown\346\216\245\345\217\243\346\250\241\347\211\210.md" @@ -0,0 +1,64 @@ +[TOC] + + + +## 1. 用户接口 + +与用户相关接口。 + +### 1.1 添加用户 + +添加用户 + +#### request + +```HTTP +POST /api/v1/users HTTP/1.1 + +{ + "loginId":"admin", + "userName":"123456", + "passwords":"123456" +} +``` + +##### header + +| Field | Type | Condition | Default | Description | +| :------- | :----- | :-------- | :------ | :---------- | +| loginId | String | | | 用户编号 | +| userName | String | | | 用户名 | +| password | String | | | 密码 | + +##### query + +| Field | Type | Condition | Default | Description | +| :------- | :----- | :-------- | :------ | :---------- | +| loginId | String | | | 用户编号 | +| userName | String | | | 用户名 | +| password | String | | | 密码 | + +##### body + +| Field | Type | Condition | Default | Description | +| :------- | :----- | :-------- | :------ | :---------- | +| loginId | String | | | 用户编号 | +| userName | String | | | 用户名 | +| password | String | | | 密码 | + +#### response + +``` +{ + "loginId":"admin", + "userName":"123456", + "passwords":"123456" +} +``` + +| Field | Type | Condition | Default | Description | +| :------- | :----- | :-------- | :------ | :---------- | +| loginId | String | | | 用户编号 | +| userName | String | | | 用户名 | +| password | String | | | 密码 | + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..187caa2be79a3dff8225d40f1e9e3d412059a30e --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'apidoc' +include 'apidoc-core' +include 'apidoc-springmvc' +