基于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
    @NoArgsConstructor
    private static class TmlyDocParamWrapper<T> {
        /**
         * 是否数组
         */
        private Byte isArray;

        /**
         * 数组元素类型
         */
        private String arrayType;

        private List<T> data;
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【深度学习】PyTorch深度学习笔记02-线性模型

1. 监督学习 2. 数据集的划分 3. 平均平方误差MSE 4. 线性模型Linear Model 用穷举法确定线性模型的参数 import numpy as np import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0] y_data [2.0, 4.0, 6.0]def forward(x):return x * w# loss function 是 均方根误差 lo…

YoloV8改进策略:卷积篇Kan行天下之JacobiKAN,KAN遇见Jacobi多项式

摘要 将Kolmogorov-Arnold Networks (KAN) 中的B-spline替换为Jacobi多项式是一个很有创意的想法,因为Jacobi多项式在函数逼近方面表现出色,并且具有递归计算的特性。经过测试,Jacobi多项式的KAN在YoloV8中,取得了非常不错的涨点效果。下面我将概述如何构建基于Jacobi多项…

【库架一体立体库】与【传统立体库】对比

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 随着冷链物流行业的快速发展&#xff0c;对于冷藏设施的要求也在不断提高。库架一体式智能立体冷藏库以其高效、节能、智能化的特点&#xff0c;正逐渐成为行业发展的新趋势。 分享一…

大模型应用中什么是SFT(监督微调)?

大模型应用中什么是SFT&#xff08;监督微调&#xff09;&#xff1f; 一、SFT的基本概念 监督微调&#xff08;Supervised Fine-Tuning, SFT&#xff09;是对已经预训练的模型进行特定任务的训练&#xff0c;以提高其在该任务上的表现。预训练模型通常在大量通用数据上进行训…

算法:字符串相关

目录 题目一&#xff1a;最长公共前缀 题目二&#xff1a;最长回文子串 题目三&#xff1a;二进制求和 题目四&#xff1a;字符串相乘 题目一&#xff1a;最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀 如果不存在公共前缀&#xff0c;返回空字符串 "…

mysql判断时间段是否重合

mysql判断时间段是否重合 SELECT CASE WHEN t1.start_time < t2.end_time AND t1.end_time > t2.start_time THEN ‘重合’ ELSE ‘不重合’ END AS result FROM table_name t1, table_name t2 WHERE t1.id <> t2.id;

产品经理-交互设计动手实践(11)

业内有很多画交互的工具&#xff0c;这里不过多介绍&#xff0c;互联网公司最常用的工具是Axure,墨刀,蓝湖,小瀑 它是一个专业的快速原型设计工具&#xff0c;使用它能够快速创建线框图、流程图、原型和规格说明文档。 它能快速、高效地创建原型&#xff0c;同时支持多人协作设…

不想成为失业大军,就要学习六西格玛?

最近&#xff0c;优思学院收到一封邮件&#xff0c;这封邮件的发送者是一位完成了我们六西格玛绿带课程的学生。 他的公司裡有20%的工程师被裁员&#xff0c;但值得注意的是&#xff0c;留下来的工程师中有70%人竟然都持有六西格玛绿带或黑带证书。 他的公司不仅希望利用这些…

科普文:深入理解Mybatis

概叙 (1) JDBC JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。 优点…

React文档内网搭建

React文档内网搭建流程 官网地址 官网中文地址 通过官网我们可以找到React的github存储库 ReactGitHub 在介绍中可以找到对应的文档存储库 React文档存储库 此存储库是英文文档地址,我们通过中文文档地址以及该存储库作者目录下找到中文存储库 React文档中文存储库 下载…

JavaSE语法 | 初识Java!!!

初识Java 一、Java开发环境二、初步认识Java的main方法2.1 main方法的实现2.2 运行Java程序 三、注释四、标识符五、关键字 一、Java开发环境 IDEA版本&#xff1a;IntelliJ IDEA Community Edition 2022.3.3 JDK17 Windows 11 二、初步认识Java的main方法 2.1 main方法的实…

C语言入门-1.数据的类型、数据的输入输出

数据类型常量变量&#xff08;整型-浮点-字符&#xff09; 数据类型 基本类型 整型int 符号常量 定义一个整形变量时要使用关键字int #include <stdio.h> //符号常量练习 #define PI 3 2 int main() {int i PI * 2;printf("i%d\n",i);return 0; } //7 …

解密 AI 客服:LangChain+ChatGPT 打造智能客服新时代

你需要了解 ChatGPT ChatGPT 是 OpenAI 开发的一种基于人工智能技术的自然语言处理模型。它可以通过对大量文本数据进行训练&#xff0c;自动生成高质量的回答和对话。ChatGPT 具有高效、准确、自然的特点&#xff0c;可以帮助人们更加高效地处理信息和交流。 ChatGPT 有很多…

QT TCP多线程网络通信

学习目标&#xff1a; TCP网络通信编程 学习前置环境 运行环境:qt creator 4.12 QT TCP网络通信编程-CSDN博客 Qt 线程 QThread类详解-CSDN博客 学习内容 使用多线程技术实现服务端计数器 核心代码 客户端 客户端&#xff1a;负责连接服务端&#xff0c;每次连接次数1。…

启动tomcat时提示The JRE_HOME environment variable is not defined correctly

我的情况是在已经安装过jdk后&#xff0c;启动tomcat时出现以下问题 原因是环境变量配置不正确导致的 首先确认一下jre的实际安装路径 然后修改环境变量配置文件 vim /etc/profile 添加以下内容&#xff0c;JRE_HOME为实际jre的路径 然后保存退出 让文件生效一下 source…

Docker-搭建部署Jenkins(保姆篇)

文章目录 Jenkins部署拉取镜像启动容器查看初始密码关闭CSRF Jenkins页面使用解决插件下载缓慢访问jenkins页面推荐插件安装创建一个管理员账号实例配置页面展示 更多相关内容可查看 Jenkins部署 拉取镜像 如果想拉取对应版本请指明版本号 docker pull jenkins/jenkins:lts-…

数据分析入门指南:表结构数据(三)

在数字化转型的浪潮中&#xff0c;表结构数据作为企业决策支持系统的核心要素&#xff0c;其重要性日益凸显。本文深入剖析了表结构数据的本质特征、高效处理策略&#xff0c;并探讨了其在现代商业智能环境中的广泛应用&#xff0c;旨在为数据分析师与决策者提供前沿洞察与实战…

电脑屏幕亮度怎么调?3个技巧,指尖轻松调控明亮度

你是否曾因为屏幕亮度的不合适而感到眼睛疲劳&#xff1f;是否曾在深夜加班时&#xff0c;被电脑屏幕刺眼的亮度搅得心烦意乱&#xff1f;电脑屏幕亮度怎么调呢&#xff1f;本文将为你介绍3个简便易行的技巧&#xff0c;让指尖轻松掌控屏幕亮度&#xff0c;享受舒适的观看体验。…

前端vue 实现取色板 的选择

大概就是这样的 一般的web端框架 都有自带的 的 比如 ant-design t-design 等 前端框架 都是带有这个的 如果遇到没有的我们可以自己尝试开发一下 简单 的 肯定比不上人家的 但是能用 能看 说的过去 我直接上代码了 其实这个取色板 就是一个input type 是color 的input …

Vue组件通信props和$emit用法

父传子&#xff0c;通过props 子传父&#xff0c;通过$emit App.vue <template><div class"app" style"border: 3px solid #000; margin: 10px">我是APP组件<!-- 1.给组件标签&#xff0c;添加属性方式 赋值 --><!-- 添加属性传值 …