├── src └── main │ ├── resources │ ├── application.properties │ └── static │ │ ├── image │ │ └── 2.ico │ │ ├── css │ │ └── jquery.jsonview.css │ │ ├── js │ │ └── jquery.jsonview.js │ │ └── index.html │ └── java │ └── com │ └── lerry │ └── swaggershowdoc │ ├── config │ ├── GitlabConfig.java │ ├── CrossConfig.java │ └── OkHttpConfiguration.java │ ├── SwaggerShowDocApplication.java │ ├── util │ ├── GitUtils.java │ ├── OkHttpUtil.java │ └── SwaggerUtils.java │ ├── swagger │ └── XforcceSwagger2MarkupConverter.java │ └── web │ └── ApidocController.java ├── .gitignore ├── README.md ├── pom.xml └── LICENSE /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/static/image/2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lerry903/swagger-showdoc/HEAD/src/main/resources/static/image/2.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/config/GitlabConfig.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.config; 2 | 3 | import org.gitlab4j.api.GitLabApi; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class GitlabConfig { 9 | 10 | @Bean 11 | public GitLabApi gitLabApi (){ 12 | return new GitLabApi("http://121.40.242.195", "Hgyedp4XZYV7Bppx3sR3"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/SwaggerShowDocApplication.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * Description: 9 | * 10 | * @author LErry.li 11 | * Date: 2018-07-14 12 | * Time: 下午11:00:00 13 | */ 14 | @SpringBootApplication 15 | public class SwaggerShowDocApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(SwaggerShowDocApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/config/CrossConfig.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.config; 2 | 3 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.cors.CorsConfiguration; 7 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 8 | import org.springframework.web.filter.CorsFilter; 9 | 10 | @Configuration 11 | public class CrossConfig { 12 | @Bean 13 | public FilterRegistrationBean corsFilter() { 14 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 15 | CorsConfiguration config = new CorsConfiguration(); 16 | config.setAllowCredentials(true); 17 | // 设置你要允许的网站域名,如果全允许则设为 * 18 | config.addAllowedOrigin("*"); 19 | // 如果要限制 HEADER 或 METHOD 请自行更改 20 | config.addAllowedHeader("*"); 21 | config.addAllowedMethod("*"); 22 | source.registerCorsConfiguration("/**", config); 23 | FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); 24 | // 这个顺序很重要哦,为避免麻烦请设置在最前 25 | bean.setOrder(0); 26 | return bean; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swagger-showdoc 2 | 3 | [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![GitHub Release](https://img.shields.io/github/release/lerry903/swagger-showdoc.svg)](https://github.com/lerry903/swagger-showdoc/releases) 5 | [![SpringBoot](https://img.shields.io/badge/SpringBoot-2.0.3.RELEASE-brightgreen.svg)](https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/) 6 | 7 | #### 项目介绍 8 | 很多在使用swagger的公司都有一个问题,文档真的可读性不高,在有些情况下,甚至只是开发人员自己能玩,完全不能作为开放文档使用。本项目旨在帮助使用swagger项目的团队,利用现有的swagger.json格式文件来实现文档的自动生成,文档的展示使用了showDoc 9 | 10 | #### 软件架构 11 | 12 | Spring Boot 2.0.3 13 | 14 | JDK 1.8 15 | 16 | swagger2markup 1.3.3 17 | 18 | okhttp 3.11.0 19 | 20 | 21 | #### 使用说明 22 | 23 | 1. 克隆项目 24 | 2. 直接运行SwaggerShowDocApplication 25 | 3. 访问http://localhost:8080/index.html 26 | 4. 输入各种参数,点击"开始文档同步"按钮 27 | 28 | #### 源码地址 29 | - GitHub:https://github.com/lerry903/swagger-showdoc 30 | - 码云:https://gitee.com/lerry903/swagger-showdoc 31 | 32 | #### 参与贡献 33 | 34 | 1. Fork 本项目 35 | 2. 新建分支 36 | 3. 提交代码 37 | 4. 新建 Pull Request 38 | 39 | 40 | ## License 41 | 42 | 用户在遵循本项目协议的同时,如果用户下载、安装、使用本项目中所提供的软件,软件作者对任何原因在使用本项目中提供的软件时可能对用户自己或他人造成的任何形式的损失和伤害不承担任何责任。作者有权根据有关法律、法规的变化修改本项目协议。修改后的协议会随附于本项目的新版本中。当发生有关争议时,以最新的协议文本为准。如果用户不同意改动的内容,用户可以自行删除本项目。如果用户继续使用本项目,则视为您接受本协议的变动。 43 | 44 | 感谢大家 [Star](https://github.com/lerry903/swagger-showdoc/stargazers) & [Fork](https://github.com/lerry903/swagger-showdoc/network/members) 的支持。 -------------------------------------------------------------------------------- /src/main/resources/static/css/jquery.jsonview.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .jsonview { 3 | font-family: monospace; 4 | font-size: 1.1em; 5 | white-space: pre-wrap; } 6 | .jsonview .prop { 7 | font-weight: bold; 8 | text-decoration: none; 9 | color: #000; } 10 | .jsonview .null { 11 | color: red; } 12 | .jsonview .undefined { 13 | color: red; } 14 | .jsonview .bool { 15 | color: blue; } 16 | .jsonview .num { 17 | color: blue; } 18 | .jsonview .string { 19 | color: green; 20 | white-space: pre-wrap; } 21 | .jsonview .string.multiline { 22 | display: inline-block; 23 | vertical-align: text-top; } 24 | .jsonview .collapser { 25 | position: absolute; 26 | left: -1em; 27 | cursor: pointer; } 28 | .jsonview .collapsible { 29 | transition: height 1.2s; 30 | transition: width 1.2s; } 31 | .jsonview .collapsible.collapsed { 32 | height: .8em; 33 | width: 1em; 34 | display: inline-block; 35 | overflow: hidden; 36 | margin: 0; } 37 | .jsonview .collapsible.collapsed:before { 38 | content: "…"; 39 | width: 1em; 40 | margin-left: .2em; } 41 | .jsonview .collapser.collapsed { 42 | transform: rotate(0deg); } 43 | .jsonview .q { 44 | display: inline-block; 45 | width: 0px; 46 | color: transparent; } 47 | .jsonview li { 48 | position: relative; } 49 | .jsonview ul { 50 | list-style: none; 51 | margin: 0 0 0 2em; 52 | padding: 0; } 53 | .jsonview h1 { 54 | font-size: 1.2em; } 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/util/GitUtils.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.util; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | import org.gitlab4j.api.GitLabApi; 5 | import org.gitlab4j.api.GitLabApiException; 6 | import org.gitlab4j.api.models.Project; 7 | import org.gitlab4j.api.models.RepositoryFile; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.List; 12 | 13 | @Component 14 | public class GitUtils { 15 | 16 | 17 | private static GitLabApi gitLabApi; 18 | 19 | @Autowired 20 | public void setGitLabApi(GitLabApi gitLabApi){ 21 | GitUtils.gitLabApi = gitLabApi; 22 | } 23 | 24 | /** 25 | * 从Git中下载文件 26 | * @param filePath 27 | * @param projectName 28 | * @param ref 29 | * @return 30 | */ 31 | public static String downLoadFile(String filePath, String projectName, String ref){ 32 | RepositoryFile file; 33 | byte[] bytes; 34 | try { 35 | Integer projectId = getProjectId(projectName); 36 | file = gitLabApi.getRepositoryFileApi().getFile(filePath, projectId, ref); 37 | String content = file.getContent(); 38 | bytes = Base64.decodeBase64(content.getBytes()); 39 | return new String(bytes,"UTF-8"); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | return ""; 44 | } 45 | 46 | /** 47 | * 获取项目id 48 | * @param projectName 49 | * @return 50 | */ 51 | private static Integer getProjectId(String projectName){ 52 | try { 53 | List projects = gitLabApi.getProjectApi().getProjects(projectName); 54 | if(projects.size() == 1){ 55 | return projects.get(0).getId(); 56 | } 57 | } catch (GitLabApiException e) { 58 | e.printStackTrace(); 59 | } 60 | return null; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/config/OkHttpConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.config; 2 | 3 | import okhttp3.ConnectionPool; 4 | import okhttp3.OkHttpClient; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import javax.net.ssl.SSLContext; 9 | import javax.net.ssl.SSLSocketFactory; 10 | import javax.net.ssl.TrustManager; 11 | import javax.net.ssl.X509TrustManager; 12 | import java.security.KeyManagementException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.SecureRandom; 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.X509Certificate; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | @Configuration 20 | public class OkHttpConfiguration { 21 | @Bean 22 | public OkHttpClient okHttpClient() { 23 | return new OkHttpClient.Builder() 24 | .retryOnConnectionFailure(Boolean.TRUE) 25 | .connectionPool(pool()) 26 | .connectTimeout(30, TimeUnit.SECONDS) 27 | .readTimeout(30, TimeUnit.SECONDS) 28 | .writeTimeout(30,TimeUnit.SECONDS) 29 | .build(); 30 | } 31 | 32 | @Bean 33 | public X509TrustManager x509TrustManager() { 34 | return new X509TrustManager() { 35 | @Override 36 | public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { 37 | } 38 | @Override 39 | public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { 40 | } 41 | @Override 42 | public X509Certificate[] getAcceptedIssuers() { 43 | return new X509Certificate[0]; 44 | } 45 | }; 46 | } 47 | 48 | @Bean 49 | public SSLSocketFactory sslSocketFactory() { 50 | try { 51 | //信任任何链接 52 | SSLContext sslContext = SSLContext.getInstance("TLS"); 53 | sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom()); 54 | return sslContext.getSocketFactory(); 55 | } catch (NoSuchAlgorithmException e) { 56 | e.printStackTrace(); 57 | } catch (KeyManagementException e) { 58 | e.printStackTrace(); 59 | } 60 | return null; 61 | } 62 | 63 | /** 64 | * Create a new connection pool with tuning parameters appropriate for a single-user application. 65 | * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently 66 | */ 67 | @Bean 68 | public ConnectionPool pool() { 69 | return new ConnectionPool(200, 5, TimeUnit.MINUTES); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/swagger/XforcceSwagger2MarkupConverter.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.swagger; 2 | 3 | 4 | import io.github.swagger2markup.Swagger2MarkupConverter; 5 | import io.github.swagger2markup.internal.document.DefinitionsDocument; 6 | import io.github.swagger2markup.internal.document.OverviewDocument; 7 | import io.github.swagger2markup.internal.document.PathsDocument; 8 | import io.github.swagger2markup.internal.document.SecurityDocument; 9 | import io.github.swagger2markup.markup.builder.MarkupDocBuilder; 10 | 11 | public class XforcceSwagger2MarkupConverter extends Swagger2MarkupConverter { 12 | 13 | 14 | private final Context context; 15 | private final OverviewDocument overviewDocument; 16 | private final PathsDocument pathsDocument; 17 | private final DefinitionsDocument definitionsDocument; 18 | private final SecurityDocument securityDocument; 19 | 20 | public XforcceSwagger2MarkupConverter(Context context) { 21 | super(context); 22 | this.context = context; 23 | this.overviewDocument = new OverviewDocument(context); 24 | this.pathsDocument = new PathsDocument(context); 25 | this.definitionsDocument = new DefinitionsDocument(context); 26 | this.securityDocument = new SecurityDocument(context); 27 | } 28 | 29 | 30 | @Override 31 | public String toString() { 32 | StringBuilder sb = new StringBuilder(); 33 | sb.append(applyPathsDocument().toString()); 34 | return sb.toString(); 35 | } 36 | 37 | public String overviewDocumenttoString() { 38 | StringBuilder sb = new StringBuilder(); 39 | sb.append(applyOverviewDocument().toString()); 40 | return sb.toString(); 41 | } 42 | 43 | public String definitionsDocumenttoString() { 44 | StringBuilder sb = new StringBuilder(); 45 | sb.append(applyDefinitionsDocument().toString()); 46 | return sb.toString(); 47 | } 48 | 49 | public String securityDocumenttoString() { 50 | StringBuilder sb = new StringBuilder(); 51 | sb.append(applySecurityDocument().toString()); 52 | return sb.toString(); 53 | } 54 | 55 | 56 | 57 | private MarkupDocBuilder applyOverviewDocument() { 58 | return overviewDocument.apply( 59 | context.createMarkupDocBuilder(), 60 | OverviewDocument.parameters(context.getSwagger())); 61 | } 62 | 63 | private MarkupDocBuilder applyPathsDocument() { 64 | return pathsDocument.apply( 65 | context.createMarkupDocBuilder(), 66 | PathsDocument.parameters(context.getSwagger().getPaths())); 67 | } 68 | 69 | private MarkupDocBuilder applyDefinitionsDocument() { 70 | return definitionsDocument.apply( 71 | context.createMarkupDocBuilder(), 72 | DefinitionsDocument.parameters(context.getSwagger().getDefinitions())); 73 | } 74 | 75 | private MarkupDocBuilder applySecurityDocument() { 76 | return securityDocument.apply( 77 | context.createMarkupDocBuilder(), 78 | SecurityDocument.parameters(context.getSwagger().getSecurityDefinitions())); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.lerry 7 | swagger-showdoc 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | swagger-showdoc 12 | Swagger Json 文件转 ShowDoc 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | org.projectlombok 34 | lombok 35 | true 36 | 37 | 38 | io.github.swagger2markup 39 | swagger2markup 40 | 1.3.3 41 | 42 | 43 | com.squareup.okhttp3 44 | okhttp 45 | 3.11.0 46 | 47 | 48 | org.gitlab4j 49 | gitlab4j-api 50 | 4.8.32 51 | 52 | 53 | commons-codec 54 | commons-codec 55 | 1.11 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | 67 | 68 | 69 | 70 | aliyun-repos 71 | http://maven.aliyun.com/nexus/content/groups/public/ 72 | 73 | false 74 | 75 | 76 | 77 | 78 | 79 | 80 | aliyun-plugin 81 | http://maven.aliyun.com/nexus/content/groups/public/ 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/web/ApidocController.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.web; 2 | 3 | import com.lerry.swaggershowdoc.util.GitUtils; 4 | import com.lerry.swaggershowdoc.util.SwaggerUtils; 5 | import io.swagger.models.Swagger; 6 | import io.swagger.parser.Swagger20Parser; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.util.AntPathMatcher; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.multipart.MultipartFile; 17 | import org.springframework.web.servlet.HandlerMapping; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import java.io.IOException; 21 | 22 | @Slf4j 23 | @Controller 24 | public class ApidocController { 25 | 26 | 27 | @RequestMapping("/") 28 | public String index() { 29 | return "all.html"; 30 | } 31 | 32 | /** 33 | * 用于手动上传文档原始文件并且生成文档 34 | * @param apiKey showDoc中文档项目的 api_key 35 | * @param apiToken showDoc中文档项目的 api_token 36 | * @param multipartFile 用于生成文档的原始文件 37 | * @return 38 | */ 39 | @PostMapping("/updateShowDoc/{showDoc_url}/{api_key}/{api_token}/{swaggerUiUrl}") 40 | public ResponseEntity updateShowDoc(@PathVariable("showDoc_url") String showDocUrl, 41 | @PathVariable("api_key") String apiKey, 42 | @PathVariable("api_token") String apiToken, 43 | @PathVariable("swaggerUiUrl") String swaggerUiUrl, 44 | @RequestParam("files") MultipartFile multipartFile) { 45 | try { 46 | byte[] bytes = multipartFile.getBytes(); 47 | Swagger swagger = new Swagger20Parser().parse(new String(bytes,"UTF-8")); 48 | SwaggerUtils.updateToShowDoc(showDocUrl,apiKey,apiToken,swagger,swaggerUiUrl); 49 | return ResponseEntity.ok("同步成功!"); 50 | } catch (IOException e) { 51 | log.info(e.getMessage()); 52 | } 53 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("同步失败"); 54 | } 55 | 56 | 57 | /** 58 | * 提供给Git仓库用于 webHook 触发文档自动同步更新 59 | * @param projectName Git仓库中的项目名称 60 | * @param ref 分支 61 | * @param apiKey showDoc中文档项目的 api_key 62 | * @param apiToken showDoc中文档项目的 api_token 63 | * @param request 用于接收Git仓库中文档生成文件的原始下载地址 64 | * @return 65 | */ 66 | @PostMapping("/gitWebHookUpdateShowDoc/{projectName}/{ref}/{api_key}/{api_token}/**") 67 | public ResponseEntity gitWebHookUpdateShowDoc(@PathVariable("projectName") String projectName, 68 | @PathVariable("ref") String ref, 69 | @PathVariable("showDoc_url") String showDocUrl, 70 | @PathVariable("api_key")String apiKey, 71 | @PathVariable("api_token")String apiToken, 72 | @PathVariable("swaggerUiUrl") String swaggerUiUrl, 73 | HttpServletRequest request){ 74 | try { 75 | 76 | 77 | String filePath = extractPathFromPattern(request); 78 | 79 | String file = GitUtils.downLoadFile(filePath, projectName, ref); 80 | 81 | Swagger swagger = new Swagger20Parser().parse(file); 82 | SwaggerUtils.updateToShowDoc(showDocUrl,apiKey,apiToken,swagger,swaggerUiUrl); 83 | return ResponseEntity.ok("gitWebHook触发更新文档成功"); 84 | } catch (Exception e) { 85 | log.info(e.getMessage()); 86 | } 87 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("gitWebHook触发更新文档失败"); 88 | 89 | } 90 | 91 | /** 92 | * 用于截取请求地址中的特殊参数 93 | * @param request 94 | * @return 95 | */ 96 | private String extractPathFromPattern(final HttpServletRequest request) { 97 | String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); 98 | String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); 99 | return new AntPathMatcher().extractPathWithinPattern(bestMatchPattern, path); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/util/OkHttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import okhttp3.*; 5 | import org.apache.commons.lang3.exception.ExceptionUtils; 6 | import org.apache.http.util.TextUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.util.Iterator; 13 | import java.util.Map; 14 | 15 | @Slf4j 16 | @Component 17 | public class OkHttpUtil { 18 | 19 | @Autowired 20 | private OkHttpClient okHttpClient; 21 | 22 | /** 23 | * get 24 | * 25 | * @param url 请求的url 26 | * @param queries 请求的参数,在浏览器?后面的数据,没有可以传null 27 | * @return 28 | */ 29 | public String get(String url, Map queries) { 30 | StringBuffer sb = new StringBuffer(url); 31 | this.doQueries(sb, queries); 32 | Request request = new Request.Builder() 33 | .url(sb.toString()) 34 | .build(); 35 | Response response = null; 36 | return this.doHtpp(request, response); 37 | } 38 | 39 | public Response downLoadfile(String url, Map queries) throws IOException { 40 | 41 | StringBuffer sb = new StringBuffer(url); 42 | this.doQueries(sb, queries); 43 | Request request = new Request.Builder() 44 | .url(sb.toString()) 45 | .build(); 46 | Response response = null; 47 | 48 | response = okHttpClient.newCall(request).execute(); 49 | if (response.isSuccessful()) { 50 | return response; 51 | } 52 | return response; 53 | } 54 | 55 | private static String getHeaderFileName(Response response) { 56 | String dispositionHeader = response.header("Content-Disposition"); 57 | if (!TextUtils.isEmpty(dispositionHeader)) { 58 | dispositionHeader.replace("attachment;filename=", ""); 59 | dispositionHeader.replace("filename*=utf-8", ""); 60 | String[] strings = dispositionHeader.split("; "); 61 | if (strings.length > 1) { 62 | dispositionHeader = strings[1].replace("filename=", ""); 63 | dispositionHeader = dispositionHeader.replace("\"", ""); 64 | return dispositionHeader; 65 | } 66 | return ""; 67 | } 68 | return ""; 69 | } 70 | 71 | /** 72 | * post 73 | * 74 | * @param url 请求的url 75 | * @param params post form 提交的参数 76 | * @return 77 | */ 78 | public String post(String url, Map params) { 79 | FormBody.Builder builder = new FormBody.Builder(); 80 | //添加参数 81 | if (params != null && !params.keySet().isEmpty()) { 82 | for (String key : params.keySet()) { 83 | builder.add(key, params.get(key)); 84 | } 85 | } 86 | Request request = new Request.Builder() 87 | .url(url) 88 | .post(builder.build()) 89 | .build(); 90 | Response response = null; 91 | return this.doHtpp(request, response); 92 | } 93 | 94 | /** 95 | * get 96 | * 97 | * @param url 请求的url 98 | * @param queries 请求的参数,在浏览器?后面的数据,没有可以传null 99 | * @return 100 | */ 101 | public String getForHeader(String url, Map queries) { 102 | StringBuffer sb = new StringBuffer(url); 103 | this.doQueries(sb, queries); 104 | Request request = new Request.Builder() 105 | .addHeader("key", "value") 106 | .url(sb.toString()) 107 | .build(); 108 | Response response = null; 109 | return this.doHtpp(request, response); 110 | } 111 | 112 | /** 113 | * Post请求发送JSON数据....{"name":"zhangsan","pwd":"123456"} 114 | * 参数一:请求Url 115 | * 参数二:请求的JSON 116 | * 参数三:请求回调 117 | */ 118 | public String postJsonParams(String url, String jsonParams) { 119 | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams); 120 | Request request = new Request.Builder() 121 | .url(url) 122 | .post(requestBody) 123 | .build(); 124 | Response response = null; 125 | return this.doHtpp(request, response); 126 | } 127 | 128 | /** 129 | * Post请求发送xml数据.... 130 | * 参数一:请求Url 131 | * 参数二:请求的xmlString 132 | * 参数三:请求回调 133 | */ 134 | public String postXmlParams(String url, String xml) { 135 | RequestBody requestBody = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), xml); 136 | Request request = new Request.Builder() 137 | .url(url) 138 | .post(requestBody) 139 | .build(); 140 | Response response = null; 141 | return this.doHtpp(request, response); 142 | } 143 | 144 | 145 | private String doHtpp(Request request, Response response) { 146 | try { 147 | response = okHttpClient.newCall(request).execute(); 148 | if (response.isSuccessful()) { 149 | return response.body().string(); 150 | } 151 | } catch (Exception e) { 152 | log.error("okhttp3 post error >> ex = {}", ExceptionUtils.getStackTrace(e)); 153 | } finally { 154 | httpClientClose(response); 155 | } 156 | return ""; 157 | } 158 | 159 | public void httpClientClose(Response response){ 160 | if (response != null) { 161 | ResponseBody body =response.body(); 162 | if(body == null || body.contentLength() == 0){ 163 | if(body != null){ 164 | body.close(); 165 | } 166 | } 167 | response.close(); 168 | } 169 | } 170 | 171 | private void doQueries(StringBuffer sb, Map queries) { 172 | if (queries != null && !queries.keySet().isEmpty()) { 173 | boolean firstFlag = true; 174 | Iterator> iterator = queries.entrySet().iterator(); 175 | while (iterator.hasNext()) { 176 | Map.Entry entry = iterator.next(); 177 | if (firstFlag) { 178 | sb.append("?").append(entry.getKey()).append("=").append(entry.getValue()); 179 | firstFlag = false; 180 | } else { 181 | sb.append("&").append(entry.getKey()).append("=").append(entry.getValue()); 182 | } 183 | } 184 | } 185 | } 186 | 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery.jsonview.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | jQuery JSONView. 4 | Licensed under the MIT License. 5 | */ 6 | (function(jQuery) { 7 | var $, Collapser, JSONFormatter, JSONView, JSON_VALUE_TYPES; 8 | JSON_VALUE_TYPES = ['object', 'array', 'number', 'string', 'boolean', 'null']; 9 | JSONFormatter = (function() { 10 | function JSONFormatter(options) { 11 | if (options == null) { 12 | options = {}; 13 | } 14 | this.options = options; 15 | } 16 | 17 | JSONFormatter.prototype.htmlEncode = function(html) { 18 | if (html !== null) { 19 | return html.toString().replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); 20 | } else { 21 | return ''; 22 | } 23 | }; 24 | 25 | JSONFormatter.prototype.jsString = function(s) { 26 | s = JSON.stringify(s).slice(1, -1); 27 | return this.htmlEncode(s); 28 | }; 29 | 30 | JSONFormatter.prototype.decorateWithSpan = function(value, className) { 31 | return "" + (this.htmlEncode(value)) + ""; 32 | }; 33 | 34 | JSONFormatter.prototype.valueToHTML = function(value, level) { 35 | var valueType; 36 | if (level == null) { 37 | level = 0; 38 | } 39 | valueType = Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase(); 40 | if (this.options.strict && !jQuery.inArray(valueType, JSON_VALUE_TYPES)) { 41 | throw new Error("" + valueType + " is not a valid JSON value type"); 42 | } 43 | return this["" + valueType + "ToHTML"].call(this, value, level); 44 | }; 45 | 46 | JSONFormatter.prototype.nullToHTML = function(value) { 47 | return this.decorateWithSpan('null', 'null'); 48 | }; 49 | 50 | JSONFormatter.prototype.undefinedToHTML = function() { 51 | return this.decorateWithSpan('undefined', 'undefined'); 52 | }; 53 | 54 | JSONFormatter.prototype.numberToHTML = function(value) { 55 | return this.decorateWithSpan(value, 'num'); 56 | }; 57 | 58 | JSONFormatter.prototype.stringToHTML = function(value) { 59 | var multilineClass, newLinePattern; 60 | if (/^(http|https|file):\/\/[^\s]+$/i.test(value)) { 61 | return "\"" + (this.jsString(value)) + "\""; 62 | } else { 63 | multilineClass = ''; 64 | value = this.jsString(value); 65 | if (this.options.nl2br) { 66 | newLinePattern = /([^>\\r\\n]?)(\\r\\n|\\n\\r|\\r|\\n)/g; 67 | if (newLinePattern.test(value)) { 68 | multilineClass = ' multiline'; 69 | value = (value + '').replace(newLinePattern, '$1' + '
'); 70 | } 71 | } 72 | return "\"" + value + "\""; 73 | } 74 | }; 75 | 76 | JSONFormatter.prototype.booleanToHTML = function(value) { 77 | return this.decorateWithSpan(value, 'bool'); 78 | }; 79 | 80 | JSONFormatter.prototype.arrayToHTML = function(array, level) { 81 | var collapsible, hasContents, index, numProps, output, value, _i, _len; 82 | if (level == null) { 83 | level = 0; 84 | } 85 | hasContents = false; 86 | output = ''; 87 | numProps = array.length; 88 | for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) { 89 | value = array[index]; 90 | hasContents = true; 91 | output += '
  • ' + this.valueToHTML(value, level + 1); 92 | if (numProps > 1) { 93 | output += ','; 94 | } 95 | output += '
  • '; 96 | numProps--; 97 | } 98 | if (hasContents) { 99 | collapsible = level === 0 ? '' : ' collapsible'; 100 | return "[
      " + output + "
    ]"; 101 | } else { 102 | return '[ ]'; 103 | } 104 | }; 105 | 106 | JSONFormatter.prototype.objectToHTML = function(object, level) { 107 | var collapsible, hasContents, key, numProps, output, prop, value; 108 | if (level == null) { 109 | level = 0; 110 | } 111 | hasContents = false; 112 | output = ''; 113 | numProps = 0; 114 | for (prop in object) { 115 | numProps++; 116 | } 117 | for (prop in object) { 118 | value = object[prop]; 119 | hasContents = true; 120 | key = this.options.escape ? this.jsString(prop) : prop; 121 | output += "
  • \"" + key + "\": " + (this.valueToHTML(value, level + 1)); 122 | if (numProps > 1) { 123 | output += ','; 124 | } 125 | output += '
  • '; 126 | numProps--; 127 | } 128 | if (hasContents) { 129 | collapsible = level === 0 ? '' : ' collapsible'; 130 | return "{
      " + output + "
    }"; 131 | } else { 132 | return '{ }'; 133 | } 134 | }; 135 | 136 | JSONFormatter.prototype.jsonToHTML = function(json) { 137 | return "
    " + (this.valueToHTML(json)) + "
    "; 138 | }; 139 | 140 | return JSONFormatter; 141 | 142 | })(); 143 | (typeof module !== "undefined" && module !== null) && (module.exports = JSONFormatter); 144 | Collapser = (function() { 145 | function Collapser() {} 146 | 147 | Collapser.bindEvent = function(item, options) { 148 | var collapser; 149 | item.firstChild.addEventListener('click', (function(_this) { 150 | return function(event) { 151 | return _this.toggle(event.target.parentNode.firstChild, options); 152 | }; 153 | })(this)); 154 | collapser = document.createElement('div'); 155 | collapser.className = 'collapser'; 156 | collapser.innerHTML = options.collapsed ? '+' : '-'; 157 | collapser.addEventListener('click', (function(_this) { 158 | return function(event) { 159 | return _this.toggle(event.target, options); 160 | }; 161 | })(this)); 162 | item.insertBefore(collapser, item.firstChild); 163 | if (options.collapsed) { 164 | return this.collapse(collapser); 165 | } 166 | }; 167 | 168 | Collapser.expand = function(collapser) { 169 | var ellipsis, target; 170 | target = this.collapseTarget(collapser); 171 | if (target.style.display === '') { 172 | return; 173 | } 174 | ellipsis = target.parentNode.getElementsByClassName('ellipsis')[0]; 175 | target.parentNode.removeChild(ellipsis); 176 | target.style.display = ''; 177 | return collapser.innerHTML = '-'; 178 | }; 179 | 180 | Collapser.collapse = function(collapser) { 181 | var ellipsis, target; 182 | target = this.collapseTarget(collapser); 183 | if (target.style.display === 'none') { 184 | return; 185 | } 186 | target.style.display = 'none'; 187 | ellipsis = document.createElement('span'); 188 | ellipsis.className = 'ellipsis'; 189 | ellipsis.innerHTML = ' … '; 190 | target.parentNode.insertBefore(ellipsis, target); 191 | return collapser.innerHTML = '+'; 192 | }; 193 | 194 | Collapser.toggle = function(collapser, options) { 195 | var action, collapsers, target, _i, _len, _results; 196 | if (options == null) { 197 | options = {}; 198 | } 199 | target = this.collapseTarget(collapser); 200 | action = target.style.display === 'none' ? 'expand' : 'collapse'; 201 | if (options.recursive_collapser) { 202 | collapsers = collapser.parentNode.getElementsByClassName('collapser'); 203 | _results = []; 204 | for (_i = 0, _len = collapsers.length; _i < _len; _i++) { 205 | collapser = collapsers[_i]; 206 | _results.push(this[action](collapser)); 207 | } 208 | return _results; 209 | } else { 210 | return this[action](collapser); 211 | } 212 | }; 213 | 214 | Collapser.collapseTarget = function(collapser) { 215 | var target, targets; 216 | targets = collapser.parentNode.getElementsByClassName('collapsible'); 217 | if (!targets.length) { 218 | return; 219 | } 220 | return target = targets[0]; 221 | }; 222 | 223 | return Collapser; 224 | 225 | })(); 226 | $ = jQuery; 227 | JSONView = { 228 | collapse: function(el) { 229 | if (el.innerHTML === '-') { 230 | return Collapser.collapse(el); 231 | } 232 | }, 233 | expand: function(el) { 234 | if (el.innerHTML === '+') { 235 | return Collapser.expand(el); 236 | } 237 | }, 238 | toggle: function(el) { 239 | return Collapser.toggle(el); 240 | } 241 | }; 242 | return $.fn.JSONView = function() { 243 | var args, defaultOptions, formatter, json, method, options, outputDoc; 244 | args = arguments; 245 | if (JSONView[args[0]] != null) { 246 | method = args[0]; 247 | return this.each(function() { 248 | var $this, level; 249 | $this = $(this); 250 | if (args[1] != null) { 251 | level = args[1]; 252 | return $this.find(".jsonview .collapsible.level" + level).siblings('.collapser').each(function() { 253 | return JSONView[method](this); 254 | }); 255 | } else { 256 | return $this.find('.jsonview > ul > li .collapsible').siblings('.collapser').each(function() { 257 | return JSONView[method](this); 258 | }); 259 | } 260 | }); 261 | } else { 262 | json = args[0]; 263 | options = args[1] || {}; 264 | defaultOptions = { 265 | collapsed: false, 266 | nl2br: false, 267 | recursive_collapser: false, 268 | escape: true, 269 | strict: false 270 | }; 271 | options = $.extend(defaultOptions, options); 272 | formatter = new JSONFormatter(options); 273 | if (Object.prototype.toString.call(json) === '[object String]') { 274 | json = JSON.parse(json); 275 | } 276 | outputDoc = formatter.jsonToHTML(json); 277 | return this.each(function() { 278 | var $this, item, items, _i, _len, _results; 279 | $this = $(this); 280 | $this.html(outputDoc); 281 | items = $this[0].getElementsByClassName('collapsible'); 282 | _results = []; 283 | for (_i = 0, _len = items.length; _i < _len; _i++) { 284 | item = items[_i]; 285 | if (item.parentNode.nodeName === 'LI') { 286 | _results.push(Collapser.bindEvent(item.parentNode, options)); 287 | } else { 288 | _results.push(void 0); 289 | } 290 | } 291 | return _results; 292 | }); 293 | } 294 | }; 295 | })(jQuery); 296 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ShowDoc文档生成 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 71 | 72 | 73 | 74 |
    75 | 76 |
    77 |
    78 | 同步中,请等待.... 79 | 80 |
    81 |
    82 |
    83 |
    84 |

    文档生成器

    85 |
    86 |
    87 |
    88 |
    89 |
    90 |
    91 |
    92 |
    93 |
    94 |

    showDoc地址:

    95 |
    96 |
    97 |
    98 |

    showDoc api_key:

    99 |
    100 |
    101 |
    102 |

    showDoc api_token:

    103 | 104 |
    105 |
    106 |

    SwaggerUi地址:

    107 | 108 |
    109 |
    110 | 111 |
    112 | 113 |

    文档阅览

    114 |
    115 |
    116 |

    拖拽上传接口文件,当前支持swagger格式的json文件

    117 |
    118 | 119 | 120 | 121 | 122 |
    123 |
    124 |

    接口数据

    125 | 126 | 127 | 128 | 129 | 130 |
    131 |
    132 |
    133 | 134 |
    135 |
    136 | 137 | 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main/java/com/lerry/swaggershowdoc/util/SwaggerUtils.java: -------------------------------------------------------------------------------- 1 | package com.lerry.swaggershowdoc.util; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.lerry.swaggershowdoc.swagger.XforcceSwagger2MarkupConverter; 6 | import io.github.swagger2markup.Language; 7 | import io.github.swagger2markup.Swagger2MarkupConfig; 8 | import io.github.swagger2markup.Swagger2MarkupConverter; 9 | import io.github.swagger2markup.builder.Swagger2MarkupConfigBuilder; 10 | import io.github.swagger2markup.markup.builder.MarkupLanguage; 11 | import io.swagger.models.*; 12 | import io.swagger.models.parameters.BodyParameter; 13 | import io.swagger.models.parameters.Parameter; 14 | import io.swagger.models.properties.ArrayProperty; 15 | import io.swagger.models.properties.ObjectProperty; 16 | import io.swagger.models.properties.Property; 17 | import io.swagger.models.properties.RefProperty; 18 | import io.swagger.parser.SwaggerParser; 19 | import io.swagger.util.Json; 20 | import io.swagger.util.Yaml; 21 | import lombok.extern.slf4j.Slf4j; 22 | import org.apache.commons.lang3.StringUtils; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.stereotype.Component; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.net.URL; 29 | import java.util.*; 30 | import java.util.concurrent.atomic.AtomicReference; 31 | 32 | @Slf4j 33 | @Component 34 | public class SwaggerUtils { 35 | 36 | 37 | private static OkHttpUtil okHttpUtil; 38 | 39 | @Autowired 40 | public void setOkHttpUtil(OkHttpUtil okHttpUtil) { 41 | SwaggerUtils.okHttpUtil = okHttpUtil; 42 | } 43 | 44 | private static Swagger2MarkupConfig config; 45 | 46 | static { 47 | config = new Swagger2MarkupConfigBuilder() 48 | .withMarkupLanguage(MarkupLanguage.MARKDOWN) 49 | .withOutputLanguage(Language.ZH) 50 | .withFlatBody() 51 | .withGeneratedExamples() 52 | .build(); 53 | } 54 | 55 | private static String getExternalDocs(Path path){ 56 | if(null!= path.getPost()){ 57 | return path.getPost().getTags().get(0)+"/"+path.getPost().getOperationId(); 58 | } 59 | if(null!= path.getGet()){ 60 | return path.getGet().getTags().get(0)+"/"+path.getGet().getOperationId(); 61 | } 62 | if(null!= path.getHead()){ 63 | return path.getHead().getTags().get(0)+"/"+path.getHead().getOperationId(); 64 | } 65 | if(null!= path.getDelete()){ 66 | return path.getDelete().getTags().get(0)+"/"+path.getDelete().getOperationId(); 67 | } 68 | if(null!= path.getOptions()){ 69 | return path.getOptions().getTags().get(0)+"/"+path.getOptions().getOperationId(); 70 | } 71 | if(null!= path.getPut()){ 72 | return path.getPut().getTags().get(0)+"/"+path.getPut().getOperationId(); 73 | } 74 | if(null!= path.getPatch()){ 75 | return path.getPatch().getTags().get(0)+"/"+path.getPatch().getOperationId(); 76 | } 77 | return ""; 78 | } 79 | 80 | private static Map definitions = new HashMap<>(); 81 | 82 | 83 | /** 84 | * 将模型与接口文档组合 85 | * @param swagger 86 | * @param path 87 | * @return 88 | */ 89 | private static String doPathWithParameter(Swagger swagger,Path path){ 90 | 91 | StringBuilder builder = new StringBuilder(); 92 | 93 | // 请求报文模型处理 94 | if(path.getPost() != null){ 95 | // POST 请求报文处理 96 | List postParameters = path.getPost().getParameters(); 97 | postAndPutModelDoc(swagger, builder, postParameters); 98 | getParAndChangeId(swagger, builder, path.getPost().getResponses()); 99 | } 100 | 101 | if( null != path.getPut() ){ 102 | List putParameters = path.getPut().getParameters(); 103 | postAndPutModelDoc(swagger, builder, putParameters); 104 | getParAndChangeId(swagger, builder, path.getPut().getResponses()); 105 | } 106 | 107 | // 响应报文处理 108 | if(null != path.getGet()){ 109 | getParAndChangeId(swagger, builder, path.getGet().getResponses()); 110 | } 111 | if(null!= path.getHead()){ 112 | getParAndChangeId(swagger, builder, path.getHead().getResponses()); 113 | } 114 | if(null!= path.getDelete()){ 115 | getParAndChangeId(swagger, builder, path.getDelete().getResponses()); 116 | } 117 | if(null!= path.getOptions()){ 118 | getParAndChangeId(swagger, builder, path.getOptions().getResponses()); 119 | } 120 | if(null!= path.getPatch()){ 121 | getParAndChangeId(swagger, builder, path.getPatch().getResponses()); 122 | } 123 | 124 | return builder.toString(); 125 | } 126 | 127 | private static void postAndPutModelDoc(Swagger swagger, StringBuilder builder, List parameters) { 128 | AtomicReference backModel = new AtomicReference<>(); 129 | 130 | if(parameters != null && !parameters.isEmpty()){ 131 | parameters.forEach(parameter -> { 132 | if(parameter instanceof BodyParameter){ 133 | BodyParameter bop = (BodyParameter)parameter; 134 | 135 | if(bop.getSchema() instanceof RefModel){ 136 | RefModel refModel = (RefModel)bop.getSchema(); 137 | String simpleRef = refModel.getSimpleRef(); 138 | log.info(simpleRef); 139 | 140 | Model model1 = definitions.get(simpleRef); 141 | String s = SwaggerUtils.definitionsDocumentGenerateMd(swagger, simpleRef, model1); 142 | builder.append(StringUtils.replace(s, "name=", "id=")); 143 | modelDoc(swagger,builder,model1); 144 | } 145 | 146 | if(bop.getSchema() instanceof ModelImpl){ 147 | ModelImpl model = (ModelImpl)bop.getSchema(); 148 | modelDocAddJdd(swagger,builder,backModel,model.getProperties()); 149 | } 150 | 151 | 152 | if(bop.getSchema() instanceof ArrayModel){ 153 | ArrayModel arrayModel = (ArrayModel)bop.getSchema(); 154 | modelDocAdd(swagger,builder,backModel,arrayModel.getItems()); 155 | 156 | if(null != backModel.get()){ 157 | modelDoc(swagger,builder,backModel.get()); 158 | } 159 | } 160 | 161 | } 162 | }); 163 | } 164 | } 165 | 166 | private static void getParAndChangeId(Swagger swagger, StringBuilder builder, Map responses) { 167 | if(null!= responses){ 168 | responses.forEach((k,response)-> modelDoc(swagger, builder, response.getResponseSchema())); 169 | } 170 | } 171 | 172 | 173 | /** 174 | * 递归处理参数模型 175 | * @param swagger 176 | * @param builder 177 | * @param model 178 | */ 179 | private static void modelDoc(Swagger swagger, StringBuilder builder , Model model){ 180 | AtomicReference backModel = new AtomicReference<>(); 181 | 182 | if (null != model){ 183 | if(model instanceof RefModel){ 184 | RefModel refModel = (RefModel)model; 185 | 186 | Map properties = refModel.getProperties(); 187 | String simpleRef = refModel.getSimpleRef(); 188 | 189 | if( null == properties && StringUtils.isNotBlank(simpleRef)){ 190 | 191 | backModel.set(SwaggerUtils.definitions.get(simpleRef)); 192 | 193 | String s = SwaggerUtils.definitionsDocumentGenerateMd(swagger, simpleRef, backModel.get()); 194 | builder.append(StringUtils.replace(s, "name=", "id=")); 195 | 196 | if(null != backModel.get()){ 197 | modelDoc(swagger,builder,backModel.get()); 198 | } 199 | } 200 | 201 | } 202 | 203 | if(model instanceof ComposedModel){ 204 | ComposedModel composedModel = (ComposedModel)model; 205 | List allOfModel = composedModel.getAllOf(); 206 | allOfModel.forEach(oneOfModel -> modelDoc(swagger,builder,oneOfModel)); 207 | 208 | } 209 | 210 | if(model instanceof ArrayModel){ 211 | ArrayModel arrayModel = (ArrayModel)model; 212 | modelDocAdd(swagger,builder,backModel,arrayModel.getItems()); 213 | } 214 | 215 | Map properties = model.getProperties(); 216 | 217 | modelDocAddJdd(swagger,builder,backModel,properties); 218 | } 219 | } 220 | 221 | private static void modelDocAdd(Swagger swagger, StringBuilder builder, AtomicReference backModel, Property property) { 222 | if(property instanceof RefProperty){ 223 | RefProperty refProperty = (RefProperty)property; 224 | String simpleRef = refProperty.getSimpleRef(); 225 | 226 | backModel.set(SwaggerUtils.definitions.get(simpleRef)); 227 | String modelDoc = SwaggerUtils.definitionsDocumentGenerateMd(swagger, simpleRef, backModel.get()); 228 | builder.append(StringUtils.replace(modelDoc, "name=", "id=")); 229 | } 230 | } 231 | 232 | private static void modelDocAddJdd(Swagger swagger, StringBuilder builder, AtomicReference backModel, Map properties){ 233 | if(null != properties){ 234 | properties.forEach((zd,property)->{ 235 | 236 | modelDocAdd(swagger, builder, backModel, property); 237 | if(null != backModel.get()){ 238 | modelDoc(swagger,builder,backModel.get()); 239 | } 240 | 241 | if(property instanceof ObjectProperty){ 242 | ObjectProperty objectProperty = (ObjectProperty)property; 243 | Map objectProperties = objectProperty.getProperties(); 244 | modelDocAddJdd(swagger,builder,backModel,objectProperties); 245 | } 246 | 247 | if(property instanceof ArrayProperty){ 248 | ArrayProperty arrayProperty = (ArrayProperty)property; 249 | Property items = arrayProperty.getItems(); 250 | modelDocAdd(swagger, builder, backModel, items); 251 | 252 | if(null != backModel.get()){ 253 | modelDoc(swagger,builder,backModel.get()); 254 | } 255 | } 256 | 257 | 258 | 259 | }); 260 | } 261 | } 262 | 263 | 264 | private static String generateMd(Swagger swagger, String k, Path swaggerPath,String swaggerUiUrl){ 265 | StringBuilder builder = new StringBuilder(); 266 | 267 | Map paths = new HashMap<>(); 268 | paths.put(k,swaggerPath); 269 | swagger.setPaths(paths); 270 | 271 | Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build(); 272 | Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext(); 273 | XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context); 274 | 275 | builder.append(xforcceSwagger2MarkupConverter.toString()).append("\n").append("#### 在线调试").append("\n").append("[模拟调用](").append(swaggerUiUrl).append(getExternalDocs(swaggerPath)).append(")"); 276 | 277 | String s = SwaggerUtils.doPathWithParameter(swagger, swaggerPath); 278 | builder.append(s); 279 | return builder.toString(); 280 | } 281 | 282 | private static String overviewDocumentGenerateMd(Swagger swagger){ 283 | Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build(); 284 | Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext(); 285 | XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context); 286 | return xforcceSwagger2MarkupConverter.overviewDocumenttoString(); 287 | } 288 | 289 | private static String securityDocumentGenerateMd(Swagger swagger){ 290 | Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build(); 291 | Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext(); 292 | XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context); 293 | return xforcceSwagger2MarkupConverter.securityDocumenttoString(); 294 | } 295 | 296 | private static String definitionsDocumentGenerateMd(Swagger swagger,String modelKey,Model model){ 297 | Map definitions = new HashMap<>(); 298 | definitions.put(modelKey,model); 299 | swagger.setDefinitions(definitions); 300 | 301 | 302 | Swagger2MarkupConverter swagger2MarkupConverter = XforcceSwagger2MarkupConverter.from(swagger).withConfig(config).build(); 303 | Swagger2MarkupConverter.Context context = swagger2MarkupConverter.getContext(); 304 | XforcceSwagger2MarkupConverter xforcceSwagger2MarkupConverter = new XforcceSwagger2MarkupConverter(context); 305 | return xforcceSwagger2MarkupConverter.definitionsDocumenttoString(); 306 | } 307 | 308 | 309 | private static void findApiByTag(Path path, Tag tag, List showDocPath){ 310 | addShowDocByTag(path.getPost(),path,tag,showDocPath); 311 | addShowDocByTag(path.getGet(),path,tag,showDocPath); 312 | addShowDocByTag(path.getDelete(),path,tag,showDocPath); 313 | addShowDocByTag(path.getPut(),path,tag,showDocPath); 314 | addShowDocByTag(path.getOptions(),path,tag,showDocPath); 315 | addShowDocByTag(path.getHead(),path,tag,showDocPath); 316 | addShowDocByTag(path.getPatch(),path,tag,showDocPath); 317 | } 318 | 319 | private static void addShowDocByTag(Operation operation, Path path, Tag tag, List showDocPath){ 320 | if(null!= operation && null != operation.getTags()){ 321 | List tagList = operation.getTags(); 322 | 323 | tagList.forEach(tags ->{ 324 | if(StringUtils.equals(tag.getName(),tags)){ 325 | showDocPath.add(path); 326 | } 327 | }); 328 | } 329 | } 330 | 331 | private static String getApiDescription(Path path){ 332 | if(null!= path.getPost()){ 333 | return path.getPost().getSummary(); 334 | } 335 | if(null!= path.getGet()){ 336 | return path.getGet().getSummary(); 337 | } 338 | if(null!= path.getHead()){ 339 | return path.getHead().getSummary(); 340 | } 341 | if(null!= path.getDelete()){ 342 | return path.getDelete().getSummary(); 343 | } 344 | if(null!= path.getOptions()){ 345 | return path.getOptions().getSummary(); 346 | } 347 | if(null!= path.getPut()){ 348 | return path.getPut().getSummary(); 349 | } 350 | if(null!= path.getPatch()){ 351 | return path.getPatch().getSummary(); 352 | } 353 | return ""; 354 | } 355 | 356 | 357 | private static void doShowDoc(String showDocApiUrl,Path path, Tag tag, Swagger swagger, String apiPath,String apiKey,String apiToken,String swaggerUiUrl){ 358 | if(null!= path.getPost() && path.getPost().getTags().contains(tag.getName())){ 359 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 360 | } 361 | if(null!= path.getGet() && path.getGet().getTags().contains(tag.getName())){ 362 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 363 | } 364 | if(null!= path.getPut() && path.getPut().getTags().contains(tag.getName())){ 365 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 366 | } 367 | if(null!= path.getOptions() && path.getOptions().getTags().contains(tag.getName())){ 368 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 369 | } 370 | if(null!= path.getDelete() && path.getDelete().getTags().contains(tag.getName())){ 371 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 372 | } 373 | if(null!= path.getPatch() && path.getPatch().getTags().contains(tag.getName())){ 374 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 375 | } 376 | if(null!= path.getHead() && path.getHead().getTags().contains(tag.getName())){ 377 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"接口列表",tag.getDescription(),getApiDescription(path),SwaggerUtils.generateMd(swagger,apiPath,path,swaggerUiUrl),""); 378 | } 379 | } 380 | 381 | /** 382 | * 将数据发送到ShowDoc 383 | * url:https://www.showdoc.cc/web/#/page/102098 384 | * @param showDocApiUrl showDoc文档地址(必填) 385 | * @param apiKey 认证凭证(必填) 386 | * @param apiToken 认证凭证(必填) 387 | * @param catName 当页面文档处于目录下时,请传递目录名(可空) 388 | * @param catNameSub 当页面文档处于更细分的子目录下时,请传递子目录名。(可空) 389 | * @param pageTitle 页面标题(必填)page_title存在则用内容更新,不存在则创建 390 | * @param pageContent 页面内容,可传递markdown格式的文本或者html源码(必填) 391 | * @param sNumber 页面序号。数字越小,该页面越靠前(可空) 392 | */ 393 | private static void sendToShowDoc(String showDocApiUrl, String apiKey , String apiToken,String catName, String catNameSub, String pageTitle, String pageContent, String sNumber){ 394 | Map parMap = new HashMap<>(); 395 | parMap.put("api_key",apiKey); 396 | parMap.put("api_token",apiToken); 397 | parMap.put("cat_name",catName); 398 | parMap.put("cat_name_sub",catNameSub); 399 | parMap.put("page_title",pageTitle); 400 | parMap.put("page_content",pageContent); 401 | parMap.put("s_number",sNumber); 402 | okHttpUtil.post(showDocApiUrl,parMap); 403 | } 404 | 405 | public static void updateToShowDoc(List ignoreApis) throws IOException { 406 | //TODO 增加排除特定接口后导入showDoc 407 | } 408 | 409 | 410 | /** 411 | * 更新文档到ShowDoc 412 | * @param showDocUrl showDoc地址 413 | * @param apiKey 414 | * @param apiToken 415 | * @param swagger 416 | */ 417 | public static void updateToShowDoc(String showDocUrl,String apiKey, String apiToken, Swagger swagger,String swaggerUiUrl) { 418 | if(StringUtils.isEmpty(showDocUrl)){ 419 | throw new RuntimeException("showDoc地址不能为空"); 420 | } 421 | /** 422 | * showDoc更新文档api地址 423 | * 如果你使用开源版的showdoc ,则请求url为 424 | * http://你的域名/server/index.php?s=/api/item/updateByApi 425 | */ 426 | if(!showDocUrl.contains("www.showdoc.cc")){ 427 | showDocUrl = showDocUrl + "/server/index.php?s=/api/item/updateByApi"; 428 | }else{ 429 | showDocUrl="https://www.showdoc.cc/server/api/item/updateByApi"; 430 | } 431 | String showDocApiUrl = showDocUrl; 432 | SwaggerUtils.definitions = swagger.getDefinitions(); 433 | Map> showDocMap = new HashMap<>(); 434 | 435 | 436 | List tags = swagger.getTags(); 437 | Map paths = swagger.getPaths(); 438 | 439 | tags.forEach( tag -> { 440 | List showDocPath = new ArrayList<>(); 441 | paths.forEach((k,v) ->{ 442 | findApiByTag(v, tag, showDocPath); 443 | doShowDoc(showDocApiUrl,v,tag,swagger,k,apiKey,apiToken,swaggerUiUrl); 444 | }); 445 | showDocMap.put(tag.getDescription(),showDocPath); 446 | }); 447 | 448 | /** 449 | * 数据模型 450 | */ 451 | if(null != SwaggerUtils.definitions){ 452 | SwaggerUtils.definitions.forEach((k,v) -> sendToShowDoc(showDocApiUrl,apiKey,apiToken,"数据模型","",k,SwaggerUtils.definitionsDocumentGenerateMd(swagger,k,v),"")); 453 | } 454 | 455 | /** 456 | * 概览 457 | */ 458 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"","","概览",SwaggerUtils.overviewDocumentGenerateMd(swagger),""); 459 | 460 | /** 461 | * 安全 462 | */ 463 | sendToShowDoc(showDocApiUrl,apiKey,apiToken,"","","安全",SwaggerUtils.securityDocumentGenerateMd(swagger),""); 464 | 465 | } 466 | } 467 | --------------------------------------------------------------------------------