diff --git a/CHANGELOG.md b/CHANGELOG.md index c75776b..7fa2bde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,4 +154,6 @@ 2. 修改支持上传文件参数不列举到文档的问题。 3. 新增ApiDataBuilder用于获取smart-doc生成的文档数据,包含header、字典、错误码等。 4. 合并fork分支的github book html5模板,新增搜索和锚点。 - 5. 重点:smart-doc的maven插件smart-doc-maven-plugin增强对maven标准项目的支持。 \ No newline at end of file + 5. 新增自定义@mock tag用于指定生成文档的字段值,@param 的参数注释增加mock值的功能(@param name 姓名|张三) + 6. 重点:smart-doc的maven插件smart-doc-maven-plugin增强对maven标准项目的支持。 + 7. 全面支持表单。 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 14c6bca..252e6f6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 smart-doc jar - 1.7.9 + 1.8.0 smart-doc https://github.com/shalousun/smart-doc.git diff --git a/src/main/java/com/power/doc/builder/AdocDocBuilder.java b/src/main/java/com/power/doc/builder/AdocDocBuilder.java index e1f5a29..0655dc9 100644 --- a/src/main/java/com/power/doc/builder/AdocDocBuilder.java +++ b/src/main/java/com/power/doc/builder/AdocDocBuilder.java @@ -2,6 +2,8 @@ package com.power.doc.builder; import com.power.doc.model.ApiConfig; import com.power.doc.model.ApiDoc; +import com.power.doc.template.IDocBuildTemplate; +import com.power.doc.template.SpringBootDocBuildTemplate; import com.thoughtworks.qdox.JavaProjectBuilder; import java.util.List; @@ -29,8 +31,9 @@ public class AdocDocBuilder { DocBuilderTemplate builderTemplate = new DocBuilderTemplate(); builderTemplate.checkAndInit(config); JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder(); - SourceBuilder sourceBuilder = new SourceBuilder(config,javaProjectBuilder); - List apiDocList = sourceBuilder.getControllerApiData(); + ProjectDocConfigBuilder configBuilder = new ProjectDocConfigBuilder(config,javaProjectBuilder); + IDocBuildTemplate docBuildTemplate = new SpringBootDocBuildTemplate(); + List apiDocList = docBuildTemplate.getApiData(configBuilder); if (config.isAllInOne()) { builderTemplate.buildAllInOne(apiDocList, config, javaProjectBuilder,ALL_IN_ONE_ADOC_TPL, INDEX_DOC); } else { diff --git a/src/main/java/com/power/doc/builder/ApiDocBuilder.java b/src/main/java/com/power/doc/builder/ApiDocBuilder.java index a63bb27..924d5dc 100644 --- a/src/main/java/com/power/doc/builder/ApiDocBuilder.java +++ b/src/main/java/com/power/doc/builder/ApiDocBuilder.java @@ -3,6 +3,8 @@ package com.power.doc.builder; import com.power.common.util.DateTimeUtil; import com.power.doc.model.ApiConfig; import com.power.doc.model.ApiDoc; +import com.power.doc.template.IDocBuildTemplate; +import com.power.doc.template.SpringBootDocBuildTemplate; import com.thoughtworks.qdox.JavaProjectBuilder; import java.util.List; @@ -28,8 +30,9 @@ public class ApiDocBuilder { JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder(); DocBuilderTemplate builderTemplate = new DocBuilderTemplate(); builderTemplate.checkAndInit(config); - SourceBuilder sourceBuilder = new SourceBuilder(config,javaProjectBuilder); - List apiDocList = sourceBuilder.getControllerApiData(); + ProjectDocConfigBuilder configBuilder = new ProjectDocConfigBuilder(config,javaProjectBuilder); + IDocBuildTemplate docBuildTemplate = new SpringBootDocBuildTemplate(); + List apiDocList = docBuildTemplate.getApiData(configBuilder); if (config.isAllInOne()) { String version = config.isCoverOld() ? "" : "-V" + DateTimeUtil.long2Str(System.currentTimeMillis(), DATE_FORMAT); builderTemplate.buildAllInOne(apiDocList, config, javaProjectBuilder,ALL_IN_ONE_MD_TPL, "AllInOne" + version + ".md"); diff --git a/src/main/java/com/power/doc/builder/DocBuilderTemplate.java b/src/main/java/com/power/doc/builder/DocBuilderTemplate.java index 8d36517..dd719d7 100644 --- a/src/main/java/com/power/doc/builder/DocBuilderTemplate.java +++ b/src/main/java/com/power/doc/builder/DocBuilderTemplate.java @@ -5,6 +5,8 @@ import com.power.doc.constants.DocGlobalConstants; import com.power.doc.constants.DocLanguage; import com.power.doc.constants.TemplateVariable; import com.power.doc.model.*; +import com.power.doc.template.IDocBuildTemplate; +import com.power.doc.template.SpringBootDocBuildTemplate; import com.power.doc.utils.BeetlTemplateUtil; import com.power.doc.utils.DocUtil; import com.thoughtworks.qdox.JavaProjectBuilder; @@ -61,15 +63,15 @@ public class DocBuilderTemplate { * @param javaProjectBuilder JavaProjectBuilder * @return */ - public ApiAllData getApiData(ApiConfig config,JavaProjectBuilder javaProjectBuilder) { + public ApiAllData getApiData(ApiConfig config, JavaProjectBuilder javaProjectBuilder) { ApiAllData apiAllData = new ApiAllData(); apiAllData.setProjectName(config.getProjectName()); apiAllData.setProjectId(DocUtil.handleId(config.getProjectName())); apiAllData.setLanguage(config.getLanguage().getCode()); - apiAllData.setApiDocList(listOfApiData(config,javaProjectBuilder)); + apiAllData.setApiDocList(listOfApiData(config, javaProjectBuilder)); apiAllData.setErrorCodeList(errorCodeDictToList(config)); apiAllData.setRevisionLogs(config.getRevisionLogs()); - apiAllData.setApiDocDictList(buildDictionary(config,javaProjectBuilder)); + apiAllData.setApiDocDictList(buildDictionary(config, javaProjectBuilder)); return apiAllData; } @@ -101,13 +103,13 @@ public class DocBuilderTemplate { /** * Merge all api doc into one document * - * @param apiDocList list data of Api doc - * @param config api config + * @param apiDocList list data of Api doc + * @param config api config * @param javaProjectBuilder JavaProjectBuilder - * @param template template - * @param outPutFileName output file + * @param template template + * @param outPutFileName output file */ - public void buildAllInOne(List apiDocList, ApiConfig config,JavaProjectBuilder javaProjectBuilder, String template, String outPutFileName) { + public void buildAllInOne(List apiDocList, ApiConfig config, JavaProjectBuilder javaProjectBuilder, String template, String outPutFileName) { String outPath = config.getOutPath(); String strTime = DateTimeUtil.long2Str(now, DateTimeUtil.DATE_FORMAT_SECOND); FileUtil.mkdirs(outPath); @@ -137,7 +139,7 @@ public class DocBuilderTemplate { tpl.binding(TemplateVariable.ERROR_LIST_TITLE.getVariable(), DocGlobalConstants.ERROR_CODE_LIST_CN_TITLE); tpl.binding(TemplateVariable.DICT_LIST_TITLE.getVariable(), DocGlobalConstants.DICT_CN_TITLE); } - List apiDocDictList = buildDictionary(config,javaProjectBuilder); + List apiDocDictList = buildDictionary(config, javaProjectBuilder); tpl.binding(TemplateVariable.DICT_LIST.getVariable(), apiDocDictList); FileUtil.nioWriteFile(tpl.render(), outPath + FILE_SEPARATOR + outPutFileName); } @@ -167,7 +169,7 @@ public class DocBuilderTemplate { */ public void buildSingleControllerApi(String outPath, String controllerName, String template, String fileExtension) { FileUtil.mkdirs(outPath); - SourceBuilder sourceBuilder = new SourceBuilder(Boolean.TRUE,new JavaProjectBuilder()); + SourceBuilder sourceBuilder = new SourceBuilder(Boolean.TRUE, new JavaProjectBuilder()); ApiDoc doc = sourceBuilder.getSingleControllerApiData(controllerName); Template mapper = BeetlTemplateUtil.getByName(template); mapper.binding(TemplateVariable.DESC.getVariable(), doc.getDesc()); @@ -179,7 +181,7 @@ public class DocBuilderTemplate { /** * Build dictionary * - * @param config api config + * @param config api config * @param javaProjectBuilder JavaProjectBuilder * @return list of ApiDocDict */ @@ -239,21 +241,22 @@ public class DocBuilderTemplate { } clzz = Class.forName(dictionary.getEnumClassName()); } - List enumDictionaryList = EnumUtil.getEnumInformation(clzz,dictionary.getCodeField(), + List enumDictionaryList = EnumUtil.getEnumInformation(clzz, dictionary.getCodeField(), dictionary.getDescField()); errorCodeList.addAll(enumDictionaryList); } - } catch ( ClassNotFoundException e) { + } catch (ClassNotFoundException e) { e.printStackTrace(); } return errorCodeList; } } - private List listOfApiData(ApiConfig config,JavaProjectBuilder javaProjectBuilder) { + private List listOfApiData(ApiConfig config, JavaProjectBuilder javaProjectBuilder) { this.checkAndInitForGetApiData(config); config.setMd5EncryptedHtmlName(true); - SourceBuilder sourceBuilder = new SourceBuilder(config,javaProjectBuilder); - return sourceBuilder.getControllerApiData(); + ProjectDocConfigBuilder configBuilder = new ProjectDocConfigBuilder(config, javaProjectBuilder); + IDocBuildTemplate docBuildTemplate = new SpringBootDocBuildTemplate(); + return docBuildTemplate.getApiData(configBuilder); } } diff --git a/src/main/java/com/power/doc/builder/HtmlApiDocBuilder.java b/src/main/java/com/power/doc/builder/HtmlApiDocBuilder.java index 80cccfb..359bf0d 100644 --- a/src/main/java/com/power/doc/builder/HtmlApiDocBuilder.java +++ b/src/main/java/com/power/doc/builder/HtmlApiDocBuilder.java @@ -10,6 +10,8 @@ import com.power.doc.model.ApiConfig; import com.power.doc.model.ApiDoc; import com.power.doc.model.ApiDocDict; import com.power.doc.model.ApiErrorCode; +import com.power.doc.template.IDocBuildTemplate; +import com.power.doc.template.SpringBootDocBuildTemplate; import com.power.doc.utils.BeetlTemplateUtil; import com.power.doc.utils.MarkDownUtil; import com.thoughtworks.qdox.JavaProjectBuilder; @@ -39,8 +41,9 @@ public class HtmlApiDocBuilder { DocBuilderTemplate builderTemplate = new DocBuilderTemplate(); builderTemplate.checkAndInit(config); JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder(); - SourceBuilder sourceBuilder = new SourceBuilder(config,javaProjectBuilder); - List apiDocList = sourceBuilder.getControllerApiData(); + ProjectDocConfigBuilder configBuilder = new ProjectDocConfigBuilder(config,javaProjectBuilder); + IDocBuildTemplate docBuildTemplate = new SpringBootDocBuildTemplate(); + List apiDocList = docBuildTemplate.getApiData(configBuilder); if (config.isAllInOne()) { Template indexCssTemplate = BeetlTemplateUtil.getByName(ALL_IN_ONE_CSS); FileUtil.nioWriteFile(indexCssTemplate.render(), config.getOutPath() + FILE_SEPARATOR + ALL_IN_ONE_CSS); @@ -52,7 +55,6 @@ public class HtmlApiDocBuilder { buildApiDoc(apiDocList, config.getOutPath()); buildErrorCodeDoc(config.getErrorCodes(), config.getOutPath()); buildDictionary(apiDocDictList, config.getOutPath()); - } } diff --git a/src/main/java/com/power/doc/builder/PostmanJsonBuilder.java b/src/main/java/com/power/doc/builder/PostmanJsonBuilder.java index 4a9b865..8d2586e 100644 --- a/src/main/java/com/power/doc/builder/PostmanJsonBuilder.java +++ b/src/main/java/com/power/doc/builder/PostmanJsonBuilder.java @@ -15,7 +15,7 @@ import com.power.doc.model.postman.ItemBean; import com.power.doc.model.postman.RequestItem; import com.power.doc.model.postman.request.RequestBean; import com.power.doc.model.postman.request.body.BodyBean; -import com.power.doc.model.postman.request.body.FormData; +import com.power.doc.model.FormData; import com.power.doc.model.postman.request.header.HeaderBean; import com.thoughtworks.qdox.JavaProjectBuilder; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/com/power/doc/builder/ProjectDocConfigBuilder.java b/src/main/java/com/power/doc/builder/ProjectDocConfigBuilder.java index d0bee30..1331ece 100644 --- a/src/main/java/com/power/doc/builder/ProjectDocConfigBuilder.java +++ b/src/main/java/com/power/doc/builder/ProjectDocConfigBuilder.java @@ -52,7 +52,7 @@ public class ProjectDocConfigBuilder { } this.javaProjectBuilder = javaProjectBuilder; this.loadJavaSource(apiConfig.getSourceCodePaths(),this.javaProjectBuilder); - this.initClassFilesMap(apiConfig); + this.initClassFilesMap(); this.initCustomResponseFieldsMap(apiConfig); } @@ -76,7 +76,7 @@ public class ProjectDocConfigBuilder { } } - private void initClassFilesMap(ApiConfig config) { + private void initClassFilesMap() { Collection javaClasses = javaProjectBuilder.getClasses(); for (JavaClass cls : javaClasses) { classFilesMap.put(cls.getFullyQualifiedName(), cls); diff --git a/src/main/java/com/power/doc/builder/SourceBuilders.java b/src/main/java/com/power/doc/builder/SourceBuilders.java index 5eae8f3..c2941e4 100644 --- a/src/main/java/com/power/doc/builder/SourceBuilders.java +++ b/src/main/java/com/power/doc/builder/SourceBuilders.java @@ -6,9 +6,8 @@ import com.power.common.util.JsonFormatUtil; import com.power.common.util.StringUtil; import com.power.common.util.UrlUtil; import com.power.doc.constants.*; -import com.power.doc.helper.JsonBuildHelper; import com.power.doc.model.*; -import com.power.doc.model.postman.request.body.FormData; +import com.power.doc.model.FormData; import com.power.doc.utils.DocClassUtil; import com.power.doc.utils.DocUtil; import com.power.doc.utils.ReqJsonUtil; diff --git a/src/main/java/com/power/doc/handler/SpringMVCRequestMappingHandler.java b/src/main/java/com/power/doc/handler/SpringMVCRequestMappingHandler.java index 621fe31..6b2fb91 100644 --- a/src/main/java/com/power/doc/handler/SpringMVCRequestMappingHandler.java +++ b/src/main/java/com/power/doc/handler/SpringMVCRequestMappingHandler.java @@ -5,7 +5,7 @@ import com.power.common.util.UrlUtil; import com.power.doc.constants.DocGlobalConstants; import com.power.doc.constants.Methods; import com.power.doc.constants.SpringMvcAnnotations; -import com.power.doc.model.RequestMapping; +import com.power.doc.model.request.RequestMapping; import com.power.doc.utils.DocUrlUtil; import com.power.doc.utils.DocUtil; import com.thoughtworks.qdox.model.JavaAnnotation; @@ -86,7 +86,7 @@ public class SpringMVCRequestMappingHandler { shortUrl = DocUrlUtil.getMvcUrls("", controllerBaseUrl, Arrays.asList(urls)); } else { url = UrlUtil.simplifyUrl(serverUrl + "/" + controllerBaseUrl + "/" + shortUrl); - shortUrl = UrlUtil.simplifyUrl("/" + controllerBaseUrl + "/" + url); + shortUrl = UrlUtil.simplifyUrl("/" + controllerBaseUrl + "/" + shortUrl); } RequestMapping requestMapping = RequestMapping.builder(). setMediaType(mediaType).setMethodType(methodType).setUrl(url).setShortUrl(shortUrl) diff --git a/src/main/java/com/power/doc/helper/FormDataBuildHelper.java b/src/main/java/com/power/doc/helper/FormDataBuildHelper.java new file mode 100644 index 0000000..4692de8 --- /dev/null +++ b/src/main/java/com/power/doc/helper/FormDataBuildHelper.java @@ -0,0 +1,128 @@ +package com.power.doc.helper; + +import com.power.common.util.RandomUtil; +import com.power.common.util.StringUtil; +import com.power.doc.builder.ProjectDocConfigBuilder; +import com.power.doc.model.FormData; +import com.power.doc.utils.DocClassUtil; +import com.power.doc.utils.DocUtil; +import com.power.doc.utils.JavaClassUtil; +import com.thoughtworks.qdox.model.JavaClass; +import com.thoughtworks.qdox.model.JavaField; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author yu 2019/12/25. + */ +public class FormDataBuildHelper { + + /** + * build form data + * + * @param className class name + * @param registryClasses Class container + * @param counter invoked counter + * @param builder ProjectDocConfigBuilder + * @param pre pre + * @return list of FormData + */ + public static List getFormData(String className, Map registryClasses, int counter, ProjectDocConfigBuilder builder, String pre) { + if (StringUtil.isEmpty(className)) { + throw new RuntimeException("Class name can't be null or empty."); + } + // Check circular reference + List formDataList = new ArrayList<>(); + if (registryClasses.containsKey(className) && counter > registryClasses.size()) { + return formDataList; + } + // Registry class + registryClasses.put(className, className); + counter++; + String simpleName = DocClassUtil.getSimpleName(className); + String[] globGicName = DocClassUtil.getSimpleGicName(className); + JavaClass cls = builder.getJavaProjectBuilder().getClassByName(simpleName); + List fields = JavaClassUtil.getFields(cls, 0); + + if (DocClassUtil.isPrimitive(simpleName)) { + FormData formData = new FormData(); + formData.setKey(pre); + formData.setType("text"); + formData.setValue(RandomUtil.randomValueByType(className)); + formDataList.add(formData); + return formDataList; + } + if (DocClassUtil.isCollection(simpleName) || DocClassUtil.isArray(simpleName)) { + String gicName = globGicName[0]; + if (DocClassUtil.isArray(gicName)) { + gicName = gicName.substring(0, gicName.indexOf("[")); + } + formDataList.addAll(getFormData(gicName, registryClasses, counter, builder, pre + "[]")); + } + int n = 0; + out: + for (JavaField field : fields) { + String fieldName = field.getName(); + String subTypeName = field.getType().getFullyQualifiedName(); + String fieldGicName = field.getType().getGenericCanonicalName(); + JavaClass javaClass = builder.getJavaProjectBuilder().getClassByName(subTypeName); + if ("this$0".equals(fieldName) || + "serialVersionUID".equals(fieldName) || + DocClassUtil.isIgnoreFieldTypes(subTypeName)) { + continue; + } + String typeSimpleName = field.getType().getSimpleName(); + if (DocClassUtil.isMap(subTypeName)) { + continue; + } + String comment = field.getComment(); + if (StringUtil.isNotEmpty(comment)) { + comment = DocUtil.replaceNewLineToHtmlBr(comment); + } + if (DocClassUtil.isPrimitive(subTypeName)) { + String fieldValue = DocUtil.getValByTypeAndFieldName(typeSimpleName, field.getName()); + FormData formData = new FormData(); + formData.setKey(pre + fieldName); + formData.setType("text"); + formData.setValue(fieldValue); + formData.setDesc(comment); + formDataList.add(formData); + } else if (javaClass.isEnum()) { + Object value = JavaClassUtil.getEnumValue(javaClass, Boolean.FALSE); + FormData formData = new FormData(); + formData.setKey(pre + fieldName); + formData.setType("text"); + formData.setValue(String.valueOf(value)); + formData.setDesc(comment); + formDataList.add(formData); + } else if (DocClassUtil.isCollection(subTypeName)) { + String gNameTemp = field.getType().getGenericCanonicalName(); + String[] gNameArr = DocClassUtil.getSimpleGicName(gNameTemp); + if (gNameArr.length == 0) { + continue out; + } + String gName = DocClassUtil.getSimpleGicName(gNameTemp)[0]; + if (!DocClassUtil.isPrimitive(gName)) { + if (!simpleName.equals(gName) && !gName.equals(simpleName)) { + if (gName.length() == 1) { + int len = globGicName.length; + if (len > 0) { + String gicName = (n < len) ? globGicName[n] : globGicName[len - 1]; + if (!DocClassUtil.isPrimitive(gicName) && !simpleName.equals(gicName)) { + formDataList.addAll(getFormData(gicName, registryClasses, counter, builder, pre + fieldName + "[0].")); + } + } + } else { + formDataList.addAll(getFormData(gName, registryClasses, counter, builder, pre + fieldName + "[0].")); + } + } + } + } else { + formDataList.addAll(getFormData(fieldGicName, registryClasses,counter, builder, pre + fieldName + ".")); + } + } + return formDataList; + } +} diff --git a/src/main/java/com/power/doc/helper/JsonBuildHelper.java b/src/main/java/com/power/doc/helper/JsonBuildHelper.java index 83b494a..a1d8584 100644 --- a/src/main/java/com/power/doc/helper/JsonBuildHelper.java +++ b/src/main/java/com/power/doc/helper/JsonBuildHelper.java @@ -10,7 +10,7 @@ import com.power.doc.constants.DocTags; import com.power.doc.model.ApiMethodDoc; import com.power.doc.model.ApiReturn; import com.power.doc.model.CustomRespField; -import com.power.doc.model.postman.request.body.FormData; +import com.power.doc.model.FormData; import com.power.doc.utils.*; import com.thoughtworks.qdox.model.*; diff --git a/src/main/java/com/power/doc/model/ApiMethodDoc.java b/src/main/java/com/power/doc/model/ApiMethodDoc.java index 1a2eb74..b70ce13 100644 --- a/src/main/java/com/power/doc/model/ApiMethodDoc.java +++ b/src/main/java/com/power/doc/model/ApiMethodDoc.java @@ -1,5 +1,7 @@ package com.power.doc.model; +import com.power.doc.model.request.ApiRequestExample; + import java.io.Serializable; import java.util.List; @@ -43,10 +45,20 @@ public class ApiMethodDoc implements Serializable { private String detail; /** - * controller method url + * server url + */ + private String serverUrl; + + /** + * controller method url contains server */ private String url; + /** + * controller path + */ + private String path; + /** * http request type */ @@ -84,6 +96,11 @@ public class ApiMethodDoc implements Serializable { */ private String requestUsage; + /** + * request example detail + */ + private ApiRequestExample requestExample; + /** * http request-example requestUrlParam */ @@ -239,4 +256,28 @@ public class ApiMethodDoc implements Serializable { public void setAuthor(String author) { this.author = author; } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getServerUrl() { + return serverUrl; + } + + public void setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + } + + public ApiRequestExample getRequestExample() { + return requestExample; + } + + public void setRequestExample(ApiRequestExample requestExample) { + this.requestExample = requestExample; + } } diff --git a/src/main/java/com/power/doc/model/postman/request/body/FormData.java b/src/main/java/com/power/doc/model/FormData.java similarity index 67% rename from src/main/java/com/power/doc/model/postman/request/body/FormData.java rename to src/main/java/com/power/doc/model/FormData.java index 4698af0..4d05ef4 100644 --- a/src/main/java/com/power/doc/model/postman/request/body/FormData.java +++ b/src/main/java/com/power/doc/model/FormData.java @@ -1,13 +1,14 @@ -package com.power.doc.model.postman.request.body; +package com.power.doc.model; /** * @author xingzi 2019/12/21 20:20 */ public class FormData { - private String key; - private String type; - private String src; - private String value; + private String key; + private String type; + private String desc; + private String src; + private String value; public String getKey() { return key; @@ -40,4 +41,12 @@ public class FormData { public void setValue(String value) { this.value = value; } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } } diff --git a/src/main/java/com/power/doc/model/postman/request/body/BodyBean.java b/src/main/java/com/power/doc/model/postman/request/body/BodyBean.java index ac6f60e..394fbb2 100644 --- a/src/main/java/com/power/doc/model/postman/request/body/BodyBean.java +++ b/src/main/java/com/power/doc/model/postman/request/body/BodyBean.java @@ -1,6 +1,8 @@ package com.power.doc.model.postman.request.body; +import com.power.doc.model.FormData; + import java.util.List; /** diff --git a/src/main/java/com/power/doc/model/ApiRequestExample.java b/src/main/java/com/power/doc/model/request/ApiRequestExample.java similarity index 85% rename from src/main/java/com/power/doc/model/ApiRequestExample.java rename to src/main/java/com/power/doc/model/request/ApiRequestExample.java index c27a627..fa18a1a 100644 --- a/src/main/java/com/power/doc/model/ApiRequestExample.java +++ b/src/main/java/com/power/doc/model/request/ApiRequestExample.java @@ -1,6 +1,6 @@ -package com.power.doc.model; +package com.power.doc.model.request; -import com.power.doc.model.postman.request.body.FormData; +import com.power.doc.model.FormData; import java.util.List; @@ -10,12 +10,24 @@ import java.util.List; public class ApiRequestExample { + /** + * json body + */ private String jsonBody; + /** + * example body + */ private String exampleBody; + /** + * url + */ private String url; + /** + * list of form data + */ private List formDataList; private boolean json; diff --git a/src/main/java/com/power/doc/model/RequestMapping.java b/src/main/java/com/power/doc/model/request/RequestMapping.java similarity index 89% rename from src/main/java/com/power/doc/model/RequestMapping.java rename to src/main/java/com/power/doc/model/request/RequestMapping.java index 7dd2276..e531d10 100644 --- a/src/main/java/com/power/doc/model/RequestMapping.java +++ b/src/main/java/com/power/doc/model/request/RequestMapping.java @@ -1,16 +1,23 @@ -package com.power.doc.model; +package com.power.doc.model.request; /** * @author yu 2019/12/22. */ public class RequestMapping { + /** + * url + */ private String url; + + /** + * path + */ private String shortUrl; private String methodType; private String mediaType; private boolean postMethod; - public static RequestMapping builder(){ + public static RequestMapping builder() { return new RequestMapping(); } diff --git a/src/main/java/com/power/doc/template/IDocBuildTemplate.java b/src/main/java/com/power/doc/template/IDocBuildTemplate.java index 5a197e5..8b103ae 100644 --- a/src/main/java/com/power/doc/template/IDocBuildTemplate.java +++ b/src/main/java/com/power/doc/template/IDocBuildTemplate.java @@ -14,6 +14,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import static com.power.doc.constants.DocGlobalConstants.NO_COMMENTS_FOUND; + /** * @author yu 2019/12/21. */ @@ -38,6 +40,17 @@ public interface IDocBuildTemplate { return builder.toString(); } + default String paramCommentResolve(String comment) { + if (StringUtil.isEmpty(comment)) { + comment = NO_COMMENTS_FOUND; + } else { + if (comment.contains("|")) { + comment = comment.substring(0, comment.indexOf("|")); + } + } + return comment; + } + default void handleApiDoc(JavaClass cls, List apiDocList, List apiMethodDocs, int order, boolean isUseMD5) { String controllerName = cls.getName(); diff --git a/src/main/java/com/power/doc/template/SpringBootDocBuildTemplate.java b/src/main/java/com/power/doc/template/SpringBootDocBuildTemplate.java index 2e69855..9f0d7a9 100644 --- a/src/main/java/com/power/doc/template/SpringBootDocBuildTemplate.java +++ b/src/main/java/com/power/doc/template/SpringBootDocBuildTemplate.java @@ -1,29 +1,32 @@ package com.power.doc.template; +import com.power.common.util.JsonFormatUtil; +import com.power.common.util.RandomUtil; import com.power.common.util.StringUtil; +import com.power.common.util.UrlUtil; import com.power.doc.builder.ProjectDocConfigBuilder; import com.power.doc.constants.*; import com.power.doc.handler.SpringMVCRequestHeaderHandler; import com.power.doc.handler.SpringMVCRequestMappingHandler; +import com.power.doc.helper.FormDataBuildHelper; import com.power.doc.helper.JsonBuildHelper; import com.power.doc.helper.ParamsBuildHelper; import com.power.doc.model.*; +import com.power.doc.model.request.ApiRequestExample; +import com.power.doc.model.request.RequestMapping; import com.power.doc.utils.DocClassUtil; import com.power.doc.utils.DocUtil; -import com.thoughtworks.qdox.model.JavaAnnotation; -import com.thoughtworks.qdox.model.JavaClass; -import com.thoughtworks.qdox.model.JavaMethod; -import com.thoughtworks.qdox.model.JavaParameter; +import com.power.doc.utils.JavaClassUtil; +import com.thoughtworks.qdox.model.*; import com.thoughtworks.qdox.model.expression.AnnotationValue; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.power.doc.constants.DocGlobalConstants.NO_COMMENTS_FOUND; +import static com.power.doc.constants.DocGlobalConstants.FILE_CONTENT_TYPE; +import static com.power.doc.constants.DocGlobalConstants.JSON_CONTENT_TYPE; import static com.power.doc.constants.DocTags.IGNORE; -import static com.power.doc.helper.JsonBuildHelper.JSON_GET_PARAMS; -import static com.power.doc.helper.JsonBuildHelper.JSON_REQUEST_BODY; /** * @author yu 2019/12/21. @@ -114,23 +117,17 @@ public class SpringBootDocBuildTemplate implements IDocBuildTemplate { } apiMethodDoc.setType(requestMapping.getMethodType()); apiMethodDoc.setUrl(requestMapping.getUrl()); + apiMethodDoc.setServerUrl(projectBuilder.getServerUrl()); + apiMethodDoc.setPath(requestMapping.getShortUrl()); // build request params - List requestParams = requestParams(method, DocTags.PARAM, cls.getCanonicalName(), projectBuilder); + List requestParams = requestParams(method, DocTags.PARAM, projectBuilder); apiMethodDoc.setRequestParams(requestParams); // build request json - HashMap requestJson = JsonBuildHelper.buildReqJson(method, apiMethodDoc, requestMapping.isPostMethod(), projectBuilder); - - //build request urlParams json - apiMethodDoc.setRequestUrlParam(requestJson.get(JsonBuildHelper.JSON_GET_PARAMS)); - //build request body json - apiMethodDoc.setRequestBody(requestJson.get(JsonBuildHelper.JSON_REQUEST_BODY)); - //build request-example json - if (requestJson.get(JSON_REQUEST_BODY) != null) { - apiMethodDoc.setRequestUsage(requestJson.get(JSON_GET_PARAMS) + "\n\n" + requestJson.get(JSON_REQUEST_BODY)); - } else { - apiMethodDoc.setRequestUsage(requestJson.get(JSON_GET_PARAMS)); - } - + ApiRequestExample requestExample = buildReqJson(method, apiMethodDoc, requestMapping.isPostMethod(), projectBuilder); + String requestJson = requestExample.getExampleBody(); + // set request example detail + apiMethodDoc.setRequestExample(requestExample); + apiMethodDoc.setRequestUsage(requestJson); // build response usage apiMethodDoc.setResponseUsage(JsonBuildHelper.buildReturnJson(method, projectBuilder)); // build response params @@ -139,9 +136,7 @@ public class SpringBootDocBuildTemplate implements IDocBuildTemplate { List allApiReqHeaders; if (this.headers != null) { allApiReqHeaders = Stream.of(this.headers, apiReqHeaders) - .flatMap(Collection::stream) - .distinct() - .collect(Collectors.toList()); + .flatMap(Collection::stream).distinct().collect(Collectors.toList()); } else { allApiReqHeaders = apiReqHeaders; } @@ -154,17 +149,186 @@ public class SpringBootDocBuildTemplate implements IDocBuildTemplate { return methodDocList; } - /** - * Get tag - * - * @param javaMethod The JavaMethod method - * @param tagName The doc tag name - * @param className The class name - * @return String - */ - private List requestParams(final JavaMethod javaMethod, final String tagName, final String className, ProjectDocConfigBuilder builder) { + private ApiRequestExample buildReqJson(JavaMethod method, ApiMethodDoc apiMethodDoc, Boolean isPostMethod, ProjectDocConfigBuilder configBuilder) { + List parameterList = method.getParameters(); + if (parameterList.size() < 1) { + return ApiRequestExample.builder().setJsonBody(apiMethodDoc.getUrl()); + } + Map pathParamsMap = new LinkedHashMap<>(); + Map paramsComments = DocUtil.getParamsComments(method, DocTags.PARAM, null); + List springMvcRequestAnnotations = SpringMvcRequestAnnotationsEnum.listSpringMvcRequestAnnotations(); + List formDataList = new ArrayList<>(); + ApiRequestExample requestExample = ApiRequestExample.builder(); + for (JavaParameter parameter : parameterList) { + JavaType javaType = parameter.getType(); + String simpleTypeName = javaType.getValue(); + String gicTypeName = javaType.getGenericCanonicalName(); + String typeName = javaType.getFullyQualifiedName(); + JavaClass javaClass = configBuilder.getJavaProjectBuilder().getClassByName(typeName); + String[] globGicName = DocClassUtil.getSimpleGicName(gicTypeName); + String paramName = parameter.getName(); + if (DocClassUtil.isMvcIgnoreParams(typeName)) { + continue; + } + String comment = this.paramCommentResolve(paramsComments.get(paramName)); + String mockValue = ""; + if (DocClassUtil.isPrimitive(simpleTypeName)) { + mockValue = paramsComments.get(paramName); + if (Objects.nonNull(mockValue) && mockValue.contains("|")) { + mockValue = mockValue.substring(mockValue.lastIndexOf("|") + 1, mockValue.length()); + } else { + mockValue = ""; + } + if (StringUtil.isEmpty(mockValue)) { + mockValue = DocUtil.getValByTypeAndFieldName(simpleTypeName, paramName, true); + } + } + List annotations = parameter.getAnnotations(); + boolean paramAdded = false; + for (JavaAnnotation annotation : annotations) { + String annotationName = annotation.getType().getSimpleName(); + String fullName = annotation.getType().getSimpleName(); + if (!springMvcRequestAnnotations.contains(fullName) || paramAdded) { + continue; + } + if (SpringMvcAnnotations.REQUEST_HERDER.equals(annotationName)) { + continue; + } + AnnotationValue annotationDefaultVal = annotation.getProperty(DocAnnotationConstants.DEFAULT_VALUE_PROP); + if (null != annotationDefaultVal) { + mockValue = StringUtil.removeQuotes(annotationDefaultVal.toString()); + } + AnnotationValue annotationValue = annotation.getProperty(DocAnnotationConstants.VALUE_PROP); + if (null != annotationValue) { + paramName = StringUtil.removeQuotes(annotationValue.toString()); + } + AnnotationValue annotationOfName = annotation.getProperty(DocAnnotationConstants.NAME_PROP); + if (null != annotationOfName) { + paramName = StringUtil.removeQuotes(annotationOfName.toString()); + } + if (SpringMvcAnnotations.REQUEST_BODY.equals(annotationName) || DocGlobalConstants.REQUEST_BODY_FULLY.equals(annotationName)) { + apiMethodDoc.setContentType(JSON_CONTENT_TYPE); + if (DocClassUtil.isPrimitive(simpleTypeName)) { + StringBuilder builder = new StringBuilder(); + builder.append("{\"") + .append(paramName) + .append("\":") + .append(DocUtil.handleJsonStr(mockValue)) + .append("}"); + requestExample.setJsonBody(builder.toString()).setJson(true); + paramAdded = true; + } else { + String json = JsonBuildHelper.buildJson(typeName, gicTypeName, false, 0, new HashMap<>(), configBuilder); + requestExample.setJsonBody(json).setJson(true); + paramAdded = true; + } + } else if (SpringMvcAnnotations.PATH_VARIABLE.contains(annotationName)) { + if (javaClass.isEnum()) { + Object value = JavaClassUtil.getEnumValue(javaClass, Boolean.TRUE); + mockValue = StringUtil.removeQuotes(String.valueOf(value)); + } + pathParamsMap.put(paramName, mockValue); + paramAdded = true; + } + } + if (paramAdded) { + continue; + } + //file upload + if (gicTypeName.contains(DocGlobalConstants.MULTIPART_FILE_FULLY)) { + apiMethodDoc.setContentType(FILE_CONTENT_TYPE); + FormData formData = new FormData(); + formData.setKey(paramName); + formData.setType("file"); + formData.setDesc(comment); + formData.setValue(mockValue); + formDataList.add(formData); + } else if (DocClassUtil.isPrimitive(typeName)) { + FormData formData = new FormData(); + formData.setKey(paramName); + formData.setDesc(comment); + formData.setType("text"); + formData.setValue(mockValue); + formDataList.add(formData); + } else if (DocClassUtil.isArray(typeName) || DocClassUtil.isCollection(typeName)) { + String gicName = globGicName[0]; + if (DocClassUtil.isArray(gicName)) { + gicName = gicName.substring(0, gicName.indexOf("[")); + } + if (!DocClassUtil.isPrimitive(gicName)) { + throw new RuntimeException("FormData can't support binding Collection on method " + + method.getName() + "Check it in " + method.getDeclaringClass().getCanonicalName()); + } + FormData formData = new FormData(); + formData.setKey(paramName); + if (!paramName.contains("[]")) { + formData.setKey(paramName + "[]"); + } + formData.setDesc(comment); + formData.setType("text"); + formData.setValue(RandomUtil.randomValueByType(gicName)); + formDataList.add(formData); + } else if (javaClass.isEnum()) { + // do nothing + Object value = JavaClassUtil.getEnumValue(javaClass, Boolean.TRUE); + String strVal = StringUtil.removeQuotes(String.valueOf(value)); + FormData formData = new FormData(); + formData.setKey(paramName); + formData.setType("text"); + formData.setDesc(comment); + formData.setValue(strVal); + formDataList.add(formData); + } else { + formDataList.addAll(FormDataBuildHelper.getFormData(gicTypeName, new HashMap<>(), 0, configBuilder, "")); + } + } + requestExample.setFormDataList(formDataList); + String[] paths = apiMethodDoc.getPath().split(";"); + String path = paths[0]; + String body; + String exampleBody; + String url; + + if (isPostMethod) { + //for post + path = DocUtil.formatAndRemove(path, pathParamsMap); + body = UrlUtil.urlJoin("", DocUtil.formDataToMap(formDataList)).replace("?", ""); + body = StringUtil.removeQuotes(body); + url = apiMethodDoc.getServerUrl() + "/" + path; + url = UrlUtil.simplifyUrl(url); + if (requestExample.isJson()) { + if (StringUtil.isNotEmpty(requestExample.getJsonBody())) { + exampleBody = "curl -X POST -H 'Content-Type: application/json; charset=utf-8' -i " + url + " --data \'" + JsonFormatUtil.formatJson(requestExample.getJsonBody()) + "\n'"; + } else { + exampleBody = "curl -X POST -i " + url; + } + } else { + if (StringUtil.isNotEmpty(body)) { + exampleBody = "curl -X POST -i " + url + " --data \'" + body + "'"; + } else { + exampleBody = "curl -X POST -i " + url; + } + } + requestExample.setExampleBody(exampleBody).setJsonBody(body).setUrl(url); + } else { + // for get + pathParamsMap.putAll(DocUtil.formDataToMap(formDataList)); + path = DocUtil.formatAndRemove(path, pathParamsMap); + url = UrlUtil.urlJoin(path, pathParamsMap); + url = StringUtil.removeQuotes(url); + url = apiMethodDoc.getServerUrl() + "/" + url; + url = UrlUtil.simplifyUrl(url); + exampleBody = "curl -X GET -i \'" + url + "\'"; + requestExample.setExampleBody(exampleBody).setJsonBody("").setUrl(url); + } + return requestExample; + } + + private List requestParams(final JavaMethod javaMethod, final String tagName, ProjectDocConfigBuilder builder) { + boolean isStrict = builder.getApiConfig().isStrict(); Map responseFieldMap = new HashMap<>(); + String className = javaMethod.getDeclaringClass().getCanonicalName(); Map paramTagMap = DocUtil.getParamsComments(javaMethod, tagName, className); List parameterList = javaMethod.getParameters(); if (parameterList.size() < 1) { @@ -183,16 +347,14 @@ public class SpringBootDocBuildTemplate implements IDocBuildTemplate { if (DocClassUtil.isMvcIgnoreParams(typeName)) { continue out; } + JavaClass javaClass = builder.getJavaProjectBuilder().getClassByName(fullTypeName); if (!paramTagMap.containsKey(paramName) && DocClassUtil.isPrimitive(fullTypeName) && isStrict) { throw new RuntimeException("ERROR: Unable to find javadoc @param for actual param \"" + paramName + "\" in method " + javaMethod.getName() + " from " + className); } - String comment = paramTagMap.get(paramName); - if (StringUtil.isEmpty(comment)) { - comment = NO_COMMENTS_FOUND; - } + String comment = this.paramCommentResolve(paramTagMap.get(paramName)); //file upload if (typeName.contains(DocGlobalConstants.MULTIPART_FILE_FULLY)) { ApiParam param = ApiParam.of().setField(paramName).setType("file") @@ -327,12 +489,6 @@ public class SpringBootDocBuildTemplate implements IDocBuildTemplate { return paramList; } - /** - * check controller - * - * @param cls - * @return - */ private boolean checkController(JavaClass cls) { List classAnnotations = cls.getAnnotations(); for (JavaAnnotation annotation : classAnnotations) { diff --git a/src/main/java/com/power/doc/utils/DocUtil.java b/src/main/java/com/power/doc/utils/DocUtil.java index 918acfb..bd16080 100644 --- a/src/main/java/com/power/doc/utils/DocUtil.java +++ b/src/main/java/com/power/doc/utils/DocUtil.java @@ -7,7 +7,7 @@ import com.power.common.util.RandomUtil; import com.power.common.util.StringUtil; import com.power.doc.constants.DocAnnotationConstants; import com.power.doc.constants.DocGlobalConstants; -import com.power.doc.model.postman.request.body.FormData; +import com.power.doc.model.FormData; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaField; @@ -293,7 +293,7 @@ public class DocUtil { String value = docletTag.getValue(); if (StringUtil.isEmpty(value) && StringUtil.isNotEmpty(className)) { throw new RuntimeException("ERROR: #" + javaMethod.getName() - + "() - bad @" + tagName + " javadoc from " + className + ", must be add comment if you use it."); + + "() - bad @" + tagName + " javadoc from " + javaMethod.getDeclaringClass().getCanonicalName() + ", must be add comment if you use it."); } String pName; String pValue; @@ -390,15 +390,13 @@ public class DocUtil { } public static Map formDataToMap(List formDataList) { - Map map = formDataList.stream() - .collect(Collectors.toMap( - FormData::getKey, - FormData::getValue, - (u, v) -> { - throw new IllegalStateException(String.format("Duplicate key %s", u)); - }, - LinkedHashMap::new - )); - return map; + Map formDataMap = new LinkedHashMap<>(); + for (FormData formData : formDataList) { + if ("file".equals(formData.getType())) { + continue; + } + formDataMap.put(formData.getKey(), formData.getValue()); + } + return formDataMap; } } diff --git a/src/main/java/com/power/doc/utils/JavaFieldUtil.java b/src/main/java/com/power/doc/utils/JavaFieldUtil.java index 905884b..5dae858 100644 --- a/src/main/java/com/power/doc/utils/JavaFieldUtil.java +++ b/src/main/java/com/power/doc/utils/JavaFieldUtil.java @@ -2,7 +2,7 @@ package com.power.doc.utils; import com.power.common.util.RandomUtil; import com.power.doc.builder.ProjectDocConfigBuilder; -import com.power.doc.model.postman.request.body.FormData; +import com.power.doc.model.FormData; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaField; @@ -27,85 +27,4 @@ public class JavaFieldUtil { } return false; } - - public static List getFormData(String className, ProjectDocConfigBuilder builder, String pre) { - String simpleName = DocClassUtil.getSimpleName(className); - String[] globGicName = DocClassUtil.getSimpleGicName(className); - JavaClass cls = builder.getJavaProjectBuilder().getClassByName(simpleName); - List fields = JavaClassUtil.getFields(cls, 0); - List formDataList = new ArrayList<>(); - if(DocClassUtil.isPrimitive(simpleName)){ - FormData formData = new FormData(); - formData.setKey(pre); - formData.setType("text"); - formData.setValue(RandomUtil.randomValueByType(className)); - formDataList.add(formData); - return formDataList; - } - if (DocClassUtil.isCollection(simpleName) || DocClassUtil.isArray(simpleName)) { - String gicName = globGicName[0]; - if (DocClassUtil.isArray(gicName)) { - gicName = gicName.substring(0, gicName.indexOf("[")); - } - formDataList.addAll(getFormData(gicName, builder, pre+"[]")); - } - int n = 0; - out: - for (JavaField field : fields) { - String fieldName = field.getName(); - String subTypeName = field.getType().getFullyQualifiedName(); - String fieldGicName = field.getType().getGenericCanonicalName(); - JavaClass javaClass = builder.getJavaProjectBuilder().getClassByName(subTypeName); - if ("this$0".equals(fieldName) || - "serialVersionUID".equals(fieldName) || - DocClassUtil.isIgnoreFieldTypes(subTypeName)) { - continue; - } - String typeSimpleName = field.getType().getSimpleName(); - if(DocClassUtil.isMap(subTypeName)){ - continue; - } - if (DocClassUtil.isPrimitive(subTypeName)) { - String fieldValue = DocUtil.getValByTypeAndFieldName(typeSimpleName, field.getName()); - FormData formData = new FormData(); - formData.setKey(pre+fieldName); - formData.setType("text"); - formData.setValue(fieldValue); - formDataList.add(formData); - } else if (javaClass.isEnum()) { - Object value = JavaClassUtil.getEnumValue(javaClass, Boolean.FALSE); - FormData formData = new FormData(); - formData.setKey(pre+fieldName); - formData.setType("text"); - formData.setValue(String.valueOf(value)); - formDataList.add(formData); - } else if (DocClassUtil.isCollection(subTypeName)) { - String gNameTemp = field.getType().getGenericCanonicalName(); - String[] gNameArr = DocClassUtil.getSimpleGicName(gNameTemp); - if (gNameArr.length == 0) { - continue out; - } - String gName = DocClassUtil.getSimpleGicName(gNameTemp)[0]; - if (!DocClassUtil.isPrimitive(gName)) { - if (!simpleName.equals(gName) && !gName.equals(simpleName)) { - if (gName.length() == 1) { - int len = globGicName.length; - if (len > 0) { - String gicName = (n < len) ? globGicName[n] : globGicName[len - 1]; - if (!DocClassUtil.isPrimitive(gicName) && !simpleName.equals(gicName)) { - formDataList.addAll(getFormData(gicName, builder,pre+fieldName+"[0].")); - } - } - } else { - formDataList.addAll(getFormData(gName,builder, pre+fieldName+"[0].")); - } - } - } - } else { - formDataList.addAll(getFormData(fieldGicName,builder, pre+fieldName+".")); - } - } - return formDataList; - - } }