基于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.mzph.cn/news/869696.shtml

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

相关文章

防火墙安全策略与用户认证综合实验

一、实验拓扑 二、实验需求 1.DMZ区内的服务器&#xff0c;办公区仅能在办公时间内<9:00-18:00>可以访问&#xff0c;生产区的设备全天可以访问 2.办公区不允许访问互联网&#xff0c;办公区和游客区允许访问互联网 3.办公区设备10.0.2.10不充许访问DMZ区的FTP服务器和HT…

【java计算机毕设】个人理财管理系统MySQL springboot html maven小组设计项目源码代码

目录 1项目功能 2项目介绍 3项目地址 1项目功能 【java计算机毕设】个人理财管理系统MySQL springboot html maven小组设计项目源码代码 2项目介绍 系统功能&#xff1a; 个人理财管理系统包括管理员、用户两种角色。 管理员功能&#xff1a; 用户信息管理&#xff08;…

ns3学习笔记(四):路由概述

基于官网文档的 Routing Overview 部分详细研究一下ns3中路由是怎么工作的 文档链接16.4. Routing overview — Model Library 一、概述 NS3整体的工作架构如下&#xff1a; 路由部分的工作架构如下&#xff1a; 路由部分目前大多数用到的算法都包含在Ipv4RoutingProtocol部分…

将格内多行文字展开成多格

表格的A列是分类&#xff0c;B列由多行文字组成&#xff0c;即分隔符是换行符。 AB1Account NumberInteraction21Jan 1,2023 - Hello.32Jan 2, 2023 - Good morning. Jan 3, 2023 - Good night. Jan 4, 20 Jan 5, 2023 - Good night. Jan 6, 2023 - Good afternoon.43Jan 1,20…

基于Java技术的网上图书商城系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术、SpringBoot框架 工具&#xff1a;Eclipse、Navicat、Maven 系统展示 首页 用户注册界面…

1996-2023年各省农村居民人均消费支出数据(无缺失)

1996-2023年各省农村居民人均消费支出数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;1996-2023年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;农村居民人均消费支出 4、范围&#xff1a;31省 5、缺失情况&#xff1a;无缺失 6、指标解释&…

视频汇聚平台EasyCVR设备录像回看请求播放时间和实际时间对不上,是何原因?

安防监控EasyCVR视频汇聚平台可提供多协议&#xff08;RTSP/RTMP/国标GB28181/GAT1400/海康Ehome/大华/海康/宇视等SDK&#xff09;的设备接入、音视频采集、视频转码、处理、分发等服务&#xff0c;系统具备实时监控、云端录像、回看、告警、平台级联以及多视频流格式分发等视…

Java中的公平锁和非公平锁

1、什么是公平锁和非公平锁 公平锁和非公平锁是指在多线程环境下&#xff0c;如何对锁进行获取的顺序和策略的不同。 公平锁是指多个线程按照申请锁的顺序来获取锁&#xff0c;即先到先得的策略。当一个线程释放锁之后&#xff0c;等待时间最长的线程将获得锁。公平锁的优点是保…

Redis主从部署

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

微信定时推送LeetCode每日一题,再也不怕没人喊你刷题了

前段时间发过一篇关于微信机器人开发的文章&#xff0c;讲述了如何快速开发一个微信机器人&#xff0c;本篇文章就来实现一个最近开发的一个功能案例&#xff0c;在这个案例中会遇到了各种问题&#xff0c;可以帮助大家减少自己去踩坑的时间。通过此案例也可以帮助你去扩想一些…

Profibus_DP转ModbusTCP网关模块连马保与上位机通讯

Profibus转ModbusTCP网关模块&#xff08;XD-ETHPB20&#xff09;广泛应用于工业自动化领域。例如&#xff0c;可以将Profibus网络中的传感器数据转换为ModbusTCP协议&#xff0c;实现数据的实时监控和远程控制。本文介绍了如何利用Profibus转ModbusTCP网关&#xff08;XD-ETHP…

【AMBA】AHB总线中的HTRANS、HSIZE、HBURST以及1K边界问题

HTRANS、HSIZE和HBURST共同决定下一次数据传输的地址。 HTRANS[1:0]定义传输类型 HTRANS[1:0]主机传输状态描述00IDLE空闲表示没有进行数据传输&#xff0c;默认状态。主设备虽然可能已经占用了总线&#xff0c;但是还没有开始传输数据。从设备需要返回OKAY响应01BUSY忙主设备…

前端面试39(关于git)

针对前端开发者的Git面试题可以覆盖Git的基础概念、常用命令、工作流程、团队协作、以及解决冲突等方面。以下是一些具体的Git面试 Git基础知识 什么是Git&#xff1f; Git是一个分布式版本控制系统&#xff0c;用于跟踪计算机文件的更改&#xff0c;并协调多个人共同在一个项…

基于视觉的具身导航

基于视觉的具身导航 背景 什么是&#xff08;视觉的&#xff1f;&#xff09;具身导航 输入机器人观测的当前图像 o t o_t ot​和目标图像 o G o_G oG​&#xff0c;输出当前时间步的控制动作 u t u_t ut​&#xff0c;最终到达指定地点的算法流程。 通用的评价指标 性能指…

Git常见命令和用法

Git 文件状态 Git 文件 2 种状态: 未跟踪:新文件&#xff0c;从未被 Git 管理过已跟踪:Git 已经知道和管理的文件 常用命令 命令作用注意git -v查看 git 版本git init初始化 git 仓库初始化之后有工作区、暂存区(本地库)、版本库git add 文件标识暂存某个文件文件标识以终…

人工智能建立在对象存储上的真正原因

tl;dr: 在这篇文章中&#xff0c;我们将探讨 AI 工作负载依赖高性能对象存储的四个技术原因。 1. 对非结构化数据没有限制 在当前的机器学习范式中&#xff0c;性能和能力与计算成比例&#xff0c;计算实际上是数据集大小和模型大小的代理&#xff08;神经语言模型的缩放定律&a…

C#开发:下载node.js指定版本

一、打开官网 二、找到指定版本 三、选择程序包msi下载 四、验证下载是否成功 cmd输入&#xff1a; node -v npm -v

请跳至打印机属性的“Adobe PDF设置”页面,取消选择“仅停靠系统字体;不使用文档字体”

场景&#xff1a; 当使用adobe pdf打印时&#xff0c;出现如下提示“请跳至打印机属性的“Adobe PDF设置”页面&#xff0c;取消选择“仅停靠系统字体&#xff1b;不使用文档字体””&#xff0c;该如何解决。 描述 □“仅停靠系统字体&#xff1b;不使用文档字体” 复选本框…

基坑安全:自动化监测系统的革新力量

在日新月异的基坑工程领域&#xff0c;基坑安全自动化监测系统犹如一位守护者&#xff0c;以其独特的优势&#xff0c;为工程的安全与质量保驾护航。该系统集先进的测量仪器、计算机技术与现代传感技术于一体&#xff0c;对基坑的围护结构及周边环境进行全方位、高精度的实时监…

从零开始学量化~Ptrade使用教程(四)——股票普通买卖与回购业务

股票普通买卖 股票买入 通过选择委托方向实现股票的买入与卖出&#xff0c;可根据输入的价格自动查询可买数量。 用鼠标点击【买入】&#xff0c;如图所示&#xff1a; 输入股票代码并选中后&#xff0c;选择委托类型&#xff0c;若为限价类型&#xff0c;输入委托价格&#xf…