基于swagger插件的方式推送接口文档至torna

目录

  • 一、前言
  • 二、登录torna
  • 三、创建/选择空间
  • 四、创建/选择项目
  • 五、创建/选择应用
  • 六、获取应用的token
  • 七、服务推送
    • 7.1 引入maven依赖
    • 7.2 test下面按照如下方式新建文件

一、前言

Torna作为一款企业级文档管理系统,支持了很多种接口文档的推送方式。官方比较推荐的一种方式,就是使用smart-doc插件推送,该插件需要完善接口代码中的javadoc,相对来说,代码规范性要求较高。
使用方式如下:
接口文档管理解决方案调研及Torna+Smart-doc的使用

这里,由于某些老项目,javadoc并不规范,而且某些接口连swagger注解都没有。所以,在这里提供了一种基于swagger插件的方式,利用main方法推送文档至torna的方式。

二、登录torna

在这里插入图片描述

三、创建/选择空间

这里空间可以配置为某个具体的环境,例如:开发环境、测试环境。
在这里插入图片描述

四、创建/选择项目

在这里插入图片描述

五、创建/选择应用

在这里插入图片描述

六、获取应用的token

在这里插入图片描述

七、服务推送

说明:

由于默认的swagger插件只支持扫描带有@Api的Controller以及只带有@ApiOperation的接口方法,这里兼容了无swagger注解的接口推送。

7.1 引入maven依赖

  <dependency><groupId>cn.torna</groupId><artifactId>swagger-plugin</artifactId><version>1.2.14</version><scope>test</scope></dependency>

7.2 test下面按照如下方式新建文件

在这里插入图片描述

  • torna.json
{// 开启推送"enable": true,// 扫描package,多个用;隔开"basePackage": "com.product",// 推送URL,IP端口对应Torna服务器"url": "http://test.xxx.com:7700/torna/api",// 模块token,复制应用的token"token": "xxxxxxxxxxxxxxxxxxxxxxxxxx","debugEnv": "test,https://test.xxx.com/product",// 推送人"author": "author",// 打开调试:true/false"debug": true,// 是否替换文档,true:替换,false:不替换(追加)。默认:true"isReplace": false
}
  • DocPushTest.java
import cn.torna.swaggerplugin.TmlySwaggerPlugin;public class DocPushTest {public static void main(String[] args) {TmlySwaggerPlugin.pushDoc();}
}
  • TmlySwaggerPlugin.java
package cn.torna.swaggerplugin;import cn.torna.swaggerplugin.bean.TornaConfig;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;public class TmlySwaggerPlugin {/*** 推送文档,前提:把<code>torna.json</code>文件复制到resources下*/public static void pushDoc() {pushDoc("torna.json");}/*** 推送swagger文档** @param configFile 配置文件*/public static void pushDoc(String configFile) {pushDoc(configFile, TmlySwaggerPluginService.class);}public static void pushDoc(String configFile, Class<? extends SwaggerPluginService> swaggerPluginServiceClazz) {ClassPathResource classPathResource = new ClassPathResource(configFile);if (!classPathResource.exists()) {throw new IllegalArgumentException("找不到文件:" + configFile + ",请确保resources下有torna.json");}System.out.println("加载Torna配置文件:" + configFile);try {InputStream inputStream = classPathResource.getInputStream();String json = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);JSONObject jsonObject = JSON.parseObject(json);TornaConfig tornaConfig = jsonObject.toJavaObject(TornaConfig.class);Constructor<? extends SwaggerPluginService> constructor = swaggerPluginServiceClazz.getConstructor(TornaConfig.class);SwaggerPluginService swaggerPluginService = constructor.newInstance(tornaConfig);swaggerPluginService.pushDoc();} catch (IOException | InstantiationException | IllegalAccessException | NoSuchMethodException |InvocationTargetException e) {e.printStackTrace();throw new RuntimeException("推送文档出错", e);}}
}
  • TmlySwaggerPluginService.java
package cn.torna.swaggerplugin;import cn.torna.sdk.param.DocItem;
import cn.torna.swaggerplugin.bean.Booleans;
import cn.torna.swaggerplugin.bean.ControllerInfo;
import cn.torna.swaggerplugin.bean.PluginConstants;
import cn.torna.swaggerplugin.bean.TornaConfig;
import cn.torna.swaggerplugin.builder.MvcRequestInfoBuilder;
import cn.torna.swaggerplugin.builder.RequestInfoBuilder;
import cn.torna.swaggerplugin.exception.HiddenException;
import cn.torna.swaggerplugin.exception.IgnoreException;
import cn.torna.swaggerplugin.util.ClassUtil;
import cn.torna.swaggerplugin.util.PluginUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;public class TmlySwaggerPluginService extends SwaggerPluginService {private final TornaConfig tornaConfig;public TmlySwaggerPluginService(TornaConfig tornaConfig) {super(tornaConfig);this.tornaConfig = tornaConfig;}public void pushDoc() {if (!tornaConfig.getEnable()) {return;}String basePackage = tornaConfig.getBasePackage();if (StringUtils.isEmpty(basePackage)) {throw new IllegalArgumentException("basePackage can not empty.");}this.doPush();this.pushCode();}protected void doPush() {String packageConfig = tornaConfig.getBasePackage();String[] pkgs = packageConfig.split(";");Set<Class<?>> classes = new HashSet<>();for (String basePackage : pkgs) {
//            Set<Class<?>> clazzs = ClassUtil.getClasses(basePackage, Api.class);// 把带有RestController的控制层抽取出来Set<Class<?>> clazzs = ClassUtil.getClasses(basePackage, RestController.class);classes.addAll(clazzs);}Map<ControllerInfo, List<DocItem>> controllerDocMap = new HashMap<>(32);for (Class<?> clazz : classes) {ControllerInfo controllerInfo;try {controllerInfo = buildControllerInfo(clazz);} catch (HiddenException | IgnoreException e) {System.out.println(e.getMessage());continue;}List<DocItem> docItems = controllerDocMap.computeIfAbsent(controllerInfo, k -> new ArrayList<>());ReflectionUtils.doWithMethods(clazz, method -> {try {DocItem apiInfo = this.buildDocItem(new MvcRequestInfoBuilder(method, tornaConfig));docItems.add(apiInfo);} catch (HiddenException | IgnoreException e) {System.out.println(e.getMessage());} catch (Exception e) {System.out.printf("Create doc error, method:%s%n", method);throw new RuntimeException(e.getMessage(), e);}}, this::match);}List<DocItem> docItems = mergeSameFolder(controllerDocMap);this.push(docItems);}private ControllerInfo buildControllerInfo(Class<?> controllerClass) throws HiddenException, IgnoreException {Api api = AnnotationUtils.findAnnotation(controllerClass, Api.class);ApiIgnore apiIgnore = AnnotationUtils.findAnnotation(controllerClass, ApiIgnore.class);if (api != null && api.hidden()) {throw new HiddenException("Hidden doc(@Api.hidden=true):" + api.value());}if (apiIgnore != null) {throw new IgnoreException("Ignore doc(@ApiIgnore):" + controllerClass.getName());}String name, description;int position = 0;if (api == null) {name = controllerClass.getSimpleName();description = "";} else {name = api.value();if (StringUtils.isEmpty(name) && api.tags().length > 0) {name = api.tags()[0];}description = api.description();position = api.position();}ControllerInfo controllerInfo = new ControllerInfo();controllerInfo.setName(name);controllerInfo.setDescription(description);controllerInfo.setPosition(position);return controllerInfo;}/*** 合并控制层文档* 按照控制层类的顺序及名称(@Api为value,否则类的getSimpleName),合并为一个有序的文档数组** @param controllerDocMap 控制层->文档集合* @return*/private List<DocItem> mergeSameFolder(Map<ControllerInfo, List<DocItem>> controllerDocMap) {// key:文件夹,value:文档Map<String, List<DocItem>> folderDocMap = new HashMap<>();controllerDocMap.forEach((key, value) -> {List<DocItem> docItems = folderDocMap.computeIfAbsent(key.getName(), k -> new ArrayList<>());docItems.addAll(value);});List<ControllerInfo> controllerInfoList = controllerDocMap.keySet().stream().sorted(Comparator.comparing(ControllerInfo::getPosition)).collect(Collectors.toList());List<DocItem> folders = new ArrayList<>(controllerDocMap.size());for (Map.Entry<String, List<DocItem>> entry : folderDocMap.entrySet()) {String name = entry.getKey();ControllerInfo info = controllerInfoList.stream().filter(controllerInfo -> name.equals(controllerInfo.getName())).findFirst().orElse(null);if (info == null) {continue;}DocItem docItem = new DocItem();docItem.setName(name);docItem.setDefinition(info.getDescription());docItem.setOrderIndex(info.getPosition());docItem.setIsFolder(Booleans.TRUE);List<DocItem> items = entry.getValue();items.sort(Comparator.comparing(DocItem::getOrderIndex));docItem.setItems(items);folders.add(docItem);}return folders;}protected DocItem buildDocItem(RequestInfoBuilder requestInfoBuilder) throws HiddenException, IgnoreException {Method method = requestInfoBuilder.getMethod();ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);ApiIgnore apiIgnore = method.getAnnotation(ApiIgnore.class);if (apiOperation != null && apiOperation.hidden()) {throw new HiddenException("Hidden API(@ApiOperation.hidden=true):" + apiOperation.value());}if (apiIgnore != null) {throw new IgnoreException("Ignore API(@ApiIgnore):" + apiOperation.value());}return this.doBuildDocItem(requestInfoBuilder);}/*** 兼容方法名上@ApiOperation为空的情况** @param requestInfoBuilder* @return*/protected DocItem doBuildDocItem(RequestInfoBuilder requestInfoBuilder) {ApiOperation apiOperation = requestInfoBuilder.getApiOperation();Method method = requestInfoBuilder.getMethod();DocItem docItem = new DocItem();String httpMethod = getHttpMethod(requestInfoBuilder);docItem.setAuthor(apiOperation != null ? buildAuthor(apiOperation) : "");docItem.setName(apiOperation != null ? apiOperation.value() : method.getName());docItem.setDescription(apiOperation != null ? apiOperation.notes() : "");docItem.setOrderIndex(apiOperation != null ? buildOrder(apiOperation, method) : 0);docItem.setUrl(requestInfoBuilder.buildUrl());String contentType = buildContentType(requestInfoBuilder);docItem.setHttpMethod(httpMethod);docItem.setContentType(contentType);docItem.setIsFolder(PluginConstants.FALSE);docItem.setPathParams(buildPathParams(method));docItem.setHeaderParams(buildHeaderParams(method));docItem.setQueryParams(buildQueryParams(method, httpMethod));TmlyDocParamWrapper reqWrapper = new TmlyDocParamWrapper();BeanUtils.copyProperties(buildRequestParams(method, httpMethod), reqWrapper);TmlyDocParamWrapper respWrapper = new TmlyDocParamWrapper();BeanUtils.copyProperties(buildResponseParams(method), respWrapper);docItem.setRequestParams(reqWrapper.getData());docItem.setResponseParams(respWrapper.getData());docItem.setIsRequestArray(reqWrapper.getIsArray());docItem.setRequestArrayType(reqWrapper.getArrayType());docItem.setIsResponseArray(respWrapper.getIsArray());docItem.setResponseArrayType(respWrapper.getArrayType());docItem.setErrorCodeParams(apiOperation != null ? buildErrorCodes(apiOperation) : new ArrayList<>(0));return docItem;}private String getHttpMethod(RequestInfoBuilder requestInfoBuilder) {ApiOperation apiOperation = requestInfoBuilder.getApiOperation();Method method = requestInfoBuilder.getMethod();if (apiOperation != null && StringUtils.hasText(apiOperation.httpMethod())) {return apiOperation.httpMethod();}RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);if (requestMapping != null) {RequestMethod[] methods = requestMapping.method();if (methods.length == 0) {return this.tornaConfig.getMethodWhenMulti();} else {return methods[0].name();}}return tornaConfig.getDefaultHttpMethod();}private String buildContentType(RequestInfoBuilder requestInfoBuilder) {ApiOperation apiOperation = requestInfoBuilder.getApiOperation();Method method = requestInfoBuilder.getMethod();if (apiOperation != null && StringUtils.hasText(apiOperation.consumes())) {return apiOperation.consumes();}String[] consumeArr = getConsumes(method);if (consumeArr != null && consumeArr.length > 0) {return consumeArr[0];}Parameter[] methodParameters = method.getParameters();if (methodParameters.length == 0) {return "";}for (Parameter methodParameter : methodParameters) {RequestBody requestBody = methodParameter.getAnnotation(RequestBody.class);if (requestBody != null) {return MediaType.APPLICATION_JSON_VALUE;}if (PluginUtil.isFileParameter(methodParameter)) {return MediaType.MULTIPART_FORM_DATA_VALUE;}}return getTornaConfig().getGlobalContentType();}private String[] getConsumes(Method method) {RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);if (requestMapping != null) {return requestMapping.consumes();}return null;}public boolean match(Method method) {List<String> scanApis = this.tornaConfig.getScanApis();if (CollectionUtils.isEmpty(scanApis)) {
//            return method.getAnnotation(ApiOperation.class) != null;return AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class);}for (String scanApi : scanApis) {String methodName = method.toString();if (methodName.contains(scanApi)) {return true;}}return false;}@Data@AllArgsConstructor@NoArgsConstructorprivate static class TmlyDocParamWrapper<T> {/*** 是否数组*/private Byte isArray;/*** 数组元素类型*/private String arrayType;private List<T> data;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/373475.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝牙人员定位精准吗?是否会对人体有伤害?

不知道大家现在使用的蓝牙人员定位系统都是什么样的呢&#xff1f;其实就出行而言&#xff0c;使用GPS定位也就是足够了的&#xff0c;而且目前的定位相对也比较精准了&#xff0c;效果还是很不错的。但是如果说是室内定位&#xff0c;很显然常规的定位系统是无法满足使用需求的…

[数据结构] 基于插入的排序 插入排序希尔排序

标题&#xff1a;[数据结构] 排序#插入排序&希尔排序 水墨不写bug 目录 &#xff08;一&#xff09;插入排序 实现思路&#xff1a; 插入排序实现&#xff1a; &#xff08;二&#xff09;希尔排序 希尔排序的基本思想&#xff1a; 希尔排序的实现&#xff1a; 正…

PHP同城多商户多行业系统小程序源码

同城新生态&#xff0c;解锁多商户多行业系统的无限魅力&#x1f306;&#x1f680; &#x1f308; 开篇&#xff1a;同城新纪元&#xff0c;多商户多行业系统引领潮流&#xff01; 想象一下&#xff0c;在同一个城市内&#xff0c;无论是美食探索、购物狂欢&#xff0c;还是…

Python在量化交易中的应用

量化交易近年来越来越受到投资者的青睐。Python因其简洁的语法和丰富的库&#xff0c;成为量化交易的首选编程语言。本文将从Python量化交易的基础知识、主要技术及其在实际交易中的应用三个方面进行介绍。 一、Python量化交易的基础知识 1. 量化交易的概念 量化交易是指利用…

东方通Tongweb发布vue前端

一、前端包中添加文件 1、解压vue打包文件 以dist.zip为例&#xff0c;解压之后得到dist文件夹&#xff0c;进入dist文件夹&#xff0c;新建WEB-INF文件夹&#xff0c;进入WEB-INF文件夹&#xff0c;新建web.xml文件&#xff0c; 打开web.xml文件&#xff0c;输入以下内容 …

sdwan是硬件还是网络协议?

SD-WAN&#xff08;Software-Defined Wide Area Network&#xff0c;软件定义广域网&#xff09;并不是一个硬件产品或单一的网络协议&#xff0c;而是结合了软件、硬件和网络技术的一种解决方案。SD-WAN的核心在于其软件定义的特性&#xff0c;它通过软件来控制和管理广域网的…

【BUG】RestTemplate发送Post请求后,响应中编码为gzip而导致的报错

BUG描述 20240613-09:59:59.062|INFO|null|810184|xxx|xxx||8|http-nio-xxx-exec-1|com.xxx.jim.xxx.XXXController.?.?|MSG接收到来自xxx的文件请求 headers:[host:"xxx", accept:"text/html,application/json,application/xhtmlxml,application/xml;q0.9,*…

Apache Seata分布式事务原理解析探秘

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 前言 fescar发布已有时日&#xff0c;分布式事务一直是业界备受关注的领域&#xff0c;fesca…

如何探索高效知识管理:FlowUs知识库体验很好

在当今信息爆炸的时代&#xff0c;有效的知识管理对于个人和团队的发展至关重要。FlowUs 知识库作为一款创新的知识管理工具&#xff0c;正逐渐成为众多用户的首选&#xff0c;为他们带来了高效、便捷和有条理的知识管理体验。 FlowUs 知识库的一大特色在于其简洁直观的界面设计…

雷池WAF动态防护功能初体验

一、 介绍 大名鼎鼎的雷池WAF最近新上了个名为 动态防护 的功能 所谓动态防护&#xff0c;是在用户浏览到的网页内容不变的情况下&#xff0c;将网页赋予动态特性&#xff0c;即使是静态页面&#xff0c;也会具有动态的随机性。 说白了就是给你网站的 html 和 js 代码加上加密…

前端与嵌入式开发通信之QWebChannel(Qt)

前端与嵌入式开发通信之QWebChannel 最近开发中需要用到和c开发的操作台进行通信的的需求&#xff0c;就找到了这个技术&#xff0c;记录一下 首先需要安装导入 qwebchannel npm i qwebchannel import { QWebChannel } from "qwebchannel"; 初始化qwebchannel并封…

哈喽GPT-4o,程序员如何通过GPT-4o提高办公效率

目录 一、编写工作汇报Prompt&#xff1a;我是一名Java开发工程师&#xff0c;请写一份工作总结&#xff0c;工作内容是一个SpringBootVue实现的图书管理系统&#xff0c;按下面的结构来撰写&#xff1a;1. 工作背景&#xff1b;2. 工作内容&#xff1b;3. 工作建议&#xff1b…

springboot中@bean注解的创建和使用

bean的创建顺序 在Spring Boot中&#xff0c;当一个配置类&#xff08;使用Configuration注解的类&#xff09;中定义了多个bean时&#xff0c;这些bean的创建顺序并不完全由它们在类中的声明顺序决定。Spring框架在创建和管理bean时&#xff0c;遵循了复杂的依赖注入和生命周…

简单仿写SpringIOC

gitee地址&#xff08;需要自取&#xff09;ioc_Imitation: 简单仿写IOC (gitee.com) 项目目录结构 Autowired Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface Autowired { }Component Target(ElementType.TYPE) Retention(RetentionPoli…

文献笔记|综述|When Large Language Model Meets Optimization

When Large Language Model Meets Optimization 题目&#xff1a;当大型语言模型遇到优化时 作者&#xff1a;Sen Huang , Kaixiang Yang , Sheng Qi and Rui Wang 来源&#xff1a;arXiv 单位&#xff1a;华南理工大学 文章目录 When Large Language Model Meets Optimization…

Redis主从部署

文章目录 Redis主从部署1.下载安装Redis2.单点双副本主从配置1.修改配置信息2.修改配置文件redis.conf3.拷贝配置文件到每一个实例文件夹里4.修改每一个实例的端口和工作目录5.配置主从关系6.检查效果 3.哨兵模式监控主从1.创建实例目录2.复制配置文件并进行修改3.启动并测试 4…

Java增加线程后kafka仍然消费很慢

文章目录 一、问题分析二、控制kafka消费速度属性三、案例描述 一、问题分析 Java增加线程通常是为了提高程序的并发处理能力&#xff0c;但如果Kafka仍然消费很慢&#xff0c;可能的原因有&#xff1a; 网络延迟较大&#xff1a;如果网络延迟较大&#xff0c;即使开启了多线…

使用redis进行短信登录验证(验证码打印在控制台)

使用redis进行短信登录验证 一、流程1. 总体流程图2. 流程文字讲解&#xff1a;3.代码3.1 UserServiceImpl&#xff1a;&#xff08;难点&#xff09;3.2 拦截器LoginInterceptor&#xff1a;3.3 拦截器配置类&#xff1a; 4 功能实现&#xff0c;成功存入redis &#xff08;黑…

悠律凝声环Ringbuds Pro耳机:素皮纹理质感独一档,音质也拉满

悠律&#xff08;UMELODY&#xff09;推出的这款新品——凝声环开放式耳机&#xff0c;以其独特的设计风格和出色的音质表现赢得了众多消费者的喜爱。 在外观上&#xff0c;凝声环采用了时尚潮酷的设计理念&#xff0c;并且采用简约典雅素皮工艺&#xff0c;首次将“素皮”材料…

QT文件生成可执行的exe程序

将qt项目生成可执行的exe程序可按照以下步骤进行&#xff1a; 1、在qt中构建运行生成.exe文件&#xff1b; 2、从自定义的路径中取出exe文件放在一个单独的空文件夹中&#xff08;exe文件在该文件夹中的release文件夹中&#xff09;&#xff1b; 3、从开始程序中搜索qt&#xf…