基于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://xiahunao.cn/news/3225533.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

C#中简单Socket编程

C#中简单Socket编程 Socket分为面向连接的套接字(TCP套接字)和面向消息的套接字(UDP 套接字)。我们平时的网络编程是对Socket进行操作。 接下来&#xff0c;我用C#语言来进行简单的TCP通信和UDP通信。 一、TCP通信 新建项目SocketTest&#xff0c;首先添加TCP通信的客户端代…

来聊聊Redis持久化AOF管道通信的设计

写在文章开头 最近遇到很多烦心事&#xff0c;希望通过技术来得以放松&#xff0c;今天这篇文章笔者希望会通过源码的方式分析一下AOF如何通过Linux父子进程管道通信的方式保证进行AOF异步重写时还能实时接收用户处理的指令生成的AOF字符串&#xff0c;从而保证尽可能的可靠性…

神经网络中的激活函数

目录 一、什么是激活函数&#xff1a;二、如何选择激活函数&#xff1a;1.Sigmoid激活函数&#xff1a;2.线性激活函数&#xff1a;3.ReLU激活函数&#xff1a; 一、什么是激活函数&#xff1a; 激活函数是神经网络中的一种函数&#xff0c;它在神经元中起到了非线性映射的作用…

【附源码】ttkbootstrap实现GUI信息管理系统

【附源码】ttkbootstrap实现GUI信息管理系统 文章目录 【附源码】ttkbootstrap实现GUI信息管理系统效果预览环境搭建功能实现展示学生信息表格新增学生信息表单修改学生信息表单删除学生信息 代码解析完整代码运行和测试结尾 效果预览 环境搭建 Python 3.8 ttkbootstrap 1.10.…

IPD流程验证阶段模板及表单

目录 简介 内容brief&#xff08;部分截图&#xff09; 作者简介 简介 前面几期分享了 IPD 开发流程中的&#xff0c; 概念、计划、开发阶段的相关资料。 今天就来分享一下验证阶段的资料及表单内容。 在 IPD 流程的这个阶段&#xff0c; 就不仅仅是测试功能的实现这么…

AD9361的0x05E寄存器的说明

AD9361的0x05E寄存器在配置过程中扮演着重要的角色&#xff0c;特别是在与基带锁相环&#xff08;Base Band PLL, BB-PLL&#xff09;的状态监测相关时。以下是对AD9361的0x05E寄存器的详细说明&#xff1a; 一、功能概述 AD9361的0x05E寄存器通常用于监测BB-PLL的状态&#…

【国产开源可视化引擎Meta2d.js】鹰眼地图

鹰眼地图 画布右下角弹出一个缩略导航地图&#xff0c;鼠标点击可以跳到指定位置。 在线体验&#xff1a; 乐吾乐2D可视化 示例&#xff1a; // 显示缩略地图 meta2d.showMap();// 关闭缩略地图 meta2d.hideMap();

多会话 Telnet 日志记录器

创建一个多会话 Telnet 日志记录器可以实现对多个 Telnet 会话进行连接、监控和记录日志。以下是一个基本的 Python 示例&#xff0c;使用 telnetlib 库来实现多会话 Telnet 日志记录器&#xff0c;并使用 threading 模块来处理多个会话。 1、问题背景 我们需要编写一个脚本&a…

合合信息大模型加速器重磅上线,释放智能文档全新可能

目录 0 写在前面1 高速文档解析引擎&#xff1a;拓宽大模型认知边界2 文本嵌入模型acge&#xff1a;克服大模型感知缺陷3 行业赋能&#xff1a;以百川智能为例总结 0 写在前面 随着人工智能技术的飞速发展&#xff0c;大模型以强大的数字处理能力和深度学习能力&#xff0c;不…

Spark RDD优化

Spark RDD优化 一、分区优化二、持久化优化三、依赖优化四、共享变量优化五、提交模式与运行模式优化六、其他优化 一、分区优化 分区数调整&#xff1a;RDD的分区数可以通过repartition和coalesce方法进行调整。合理的分区数可以提高并行度&#xff0c;但过多的分区会增加管…

数据库之DML

1&#xff0c;创建表 mysql> create table student(-> id int primary key,-> name varchar(20) not null,-> grade float-> );插入记录 mysql> insert into student values(1,monkey,98.5); Query OK, 1 row affected (0.01 sec)一次性插入多条记录 mysql…

Rti DDS qos

1. parent.allow_interfaces_list 字符串列表&#xff0c;每个字符串标识一系列接口地址或接口名称。接口必须指定为逗号分隔的字符串&#xff0c;每个逗号分隔一个接口。 例如&#xff0c;以下是可接受的字符串&#xff1a; 192.168.1.1 192.168.1.* 192.168.* 192.* e…

高速电吹风方案介绍,多档温度风速调节,转速可达105000RPM

高速电吹风是这几年很火的一种电动小家电&#xff0c;能够在较短时间内完成头发干燥&#xff0c;减少对头发的热损伤。可以通过高速电机和风扇来产生高速风流&#xff0c;迅速将头发表面的水分吹干。高速电吹风通常配有多种档位风速和温度可以设置&#xff0c;用户可以根据需要…

VS安装Qt扩展工具

1-Visual Studio中安装QT插件 **插件下载地址&#xff1a;**http://download.qt.io/development_releases/vsaddin/ 关闭VS,双击下载的QT插件&#xff0c;默认安装即可&#xff1b; &#xff08;1&#xff09;配置Qt的MSVC编译器安装路径 打开Visual Studio&#xff0c;在菜单栏…

CentOS 6.5配置国内在线yum源和制作openssh 9.8p1 rpm包 —— 筑梦之路

CentOS 6.5比较古老的版本了&#xff0c;而还是有一些古老的项目仍然在使用。 环境说明 1. 更换国内在线yum源 CentOS 6 在线可用yum源配置——筑梦之路_centos6可用yum源-CSDN博客 cat > CentOS-163.repo << EOF [base] nameCentOS-$releasever - Base - 163.com …

unity使用 MQTT复现plant simulate仿真

unity使用 MQTT复现plant simulate仿真 一、plant simulate端配置 1、plant simulate MQTT组件配置,该组件在类库的信息流类目下,端口不变,填写ip即可; 2、设备配置界面,在控件入口和出口处各挂一个脚本,当物料出入该设备时会分别触发执行这两个脚本,粘贴如下代码; E…

Windows 黑暗模式是什么意思?如何开启它?

随着计算机和移动设备的普及&#xff0c;长时间盯着屏幕已经成为现代人生活和工作的常态。为了减轻眼睛疲劳&#xff0c;并在低光环境中提供更舒适的视觉体验&#xff0c;许多操作系统和应用程序都引入了黑暗模式&#xff08;Dark Mode&#xff09;。 Windows 黑暗模式就是其中…

(补充):java各种进制和文本、图像、音频在计算机中的存储方式

文章目录 前言一、进制1 逢几进一2 常见进制在java中的表示3 进制中的转换(1)任意进制转十进制(2)十进制转其他进制二、计算机中的存储1 计算机的存储规则(文本数据)(1)ASCII码表(2)编码规则的发展演化2 计算机的存储规则(图片数据)(1)分辨率、像素(2)黑白图与灰度…

基于Java中的SSM框架实现疫情冷链追溯系统项目【项目源码+论文说明】

基于Java中的SSM框架实现疫情冷链追溯系统演示 摘要 近几年随着城镇化发展和居民消费水平的不断提升&#xff0c;人们对健康生活方式的追求意识逐渐加强&#xff0c;生鲜食品逐渐受到大众青睐&#xff0c;诸如盒马鲜生、7-fresh等品牌生鲜超市&#xff0c;一时间如雨后春笋般迅…

单片机中有FLASH为啥还需要EEROM?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 一是EEPROM操作简单&…