Prechádzať zdrojové kódy

add 新增 ruoyi-common-doc 整合 springdoc 基于 javadoc 实现无注解零入侵生成接口文档

疯狂的狮子li 4 rokov pred
rodič
commit
bbec3b5d8d
21 zmenil súbory, kde vykonal 615 pridanie a 387 odobranie
  1. 19 15
      config/dev/application-common.yml
  2. 44 0
      pom.xml
  3. 5 0
      ruoyi-auth/pom.xml
  4. 1 0
      ruoyi-common/pom.xml
  5. 7 0
      ruoyi-common/ruoyi-common-bom/pom.xml
  6. 5 0
      ruoyi-common/ruoyi-common-core/pom.xml
  7. 5 6
      ruoyi-common/ruoyi-common-doc/pom.xml
  8. 78 0
      ruoyi-common/ruoyi-common-doc/src/main/java/com/ruoyi/common/doc/config/SwaggerAutoConfiguration.java
  9. 99 0
      ruoyi-common/ruoyi-common-doc/src/main/java/com/ruoyi/common/doc/config/properties/SwaggerProperties.java
  10. 272 0
      ruoyi-common/ruoyi-common-doc/src/main/java/com/ruoyi/common/doc/handler/OpenApiHandler.java
  11. 1 0
      ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  12. 0 133
      ruoyi-common/ruoyi-common-swagger/src/main/java/com/ruoyi/common/swagger/config/SwaggerAutoConfiguration.java
  13. 0 41
      ruoyi-common/ruoyi-common-swagger/src/main/java/com/ruoyi/common/swagger/config/SwaggerBeanPostProcessor.java
  14. 0 143
      ruoyi-common/ruoyi-common-swagger/src/main/java/com/ruoyi/common/swagger/config/properties/SwaggerProperties.java
  15. 0 2
      ruoyi-common/ruoyi-common-swagger/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  16. 5 0
      ruoyi-example/ruoyi-demo/pom.xml
  17. 5 0
      ruoyi-example/ruoyi-stream-mq/pom.xml
  18. 54 47
      ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/GatewayExceptionHandler.java
  19. 5 0
      ruoyi-modules/ruoyi-gen/pom.xml
  20. 5 0
      ruoyi-modules/ruoyi-resource/pom.xml
  21. 5 0
      ruoyi-modules/ruoyi-system/pom.xml

+ 19 - 15
config/dev/application-common.yml

@@ -63,10 +63,6 @@ dubbo:
     log-level: info
 
 spring:
-  mvc:
-    pathmatch:
-      # 适配 boot 2.6 路由与 springfox 兼容
-      matching-strategy: ANT_PATH_MATCHER
   # 资源信息
   messages:
     # 国际化资源文件路径
@@ -242,17 +238,25 @@ mybatis-plus:
 swagger:
   # 是否开启swagger
   enabled: true
-  # 标题
-  title: '标题:RuoYi-Cloud-Plus微服务权限管理系统_接口文档'
-  # 描述
-  description: '描述:微服务权限管理系统, 具体包括XXX,XXX模块...'
-  # 版本
-  version: '版本号:系统版本...'
-  # 作者信息
-  contact:
-    name: Lion Li
-    email: crazylionli@163.com
-    url: https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus
+  info:
+    # 标题
+    title: '标题:RuoYi-Cloud-Plus微服务权限管理系统_接口文档'
+    # 描述
+    description: '描述:微服务权限管理系统, 具体包括XXX,XXX模块...'
+    # 版本
+    version: '版本号:系统版本...'
+    # 作者信息
+    contact:
+      name: Lion Li
+      email: crazylionli@163.com
+      url: https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus
+  components:
+    # 鉴权方式配置
+    security-schemes:
+      apiKey:
+        type: APIKEY
+        in: HEADER
+        name: ${sa-token.token-name}
 
 # seata配置
 seata:

+ 44 - 0
pom.xml

@@ -28,6 +28,8 @@
         <dynamic-ds.version>3.5.1</dynamic-ds.version>
         <velocity.version>2.3</velocity.version>
         <fastjson.version>1.2.83</fastjson.version>
+        <swagger.core.version>2.2.0</swagger.core.version>
+        <springdoc.version>1.6.9</springdoc.version>
         <poi.version>5.2.2</poi.version>
         <easyexcel.version>3.1.1</easyexcel.version>
         <hutool.version>5.8.4</hutool.version>
@@ -35,6 +37,7 @@
         <lock4j.version>2.2.2</lock4j.version>
         <xxl-job.version>2.3.1</xxl-job.version>
         <satoken.version>1.30.0</satoken.version>
+        <lombok.version>1.18.24</lombok.version>
         <logstash.version>7.1.1</logstash.version>
 
         <!-- 统一 guava 版本 解决隐式漏洞问题 -->
@@ -178,6 +181,30 @@
                 <version>${p6spy.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>io.swagger.core.v3</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger.core.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-webmvc-core</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-javadoc</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.apache.poi</groupId>
                 <artifactId>poi</artifactId>
@@ -300,6 +327,23 @@
                     <source>${java.version}</source>
                     <target>${java.version}</target>
                     <encoding>${project.build.sourceEncoding}</encoding>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>com.github.therapi</groupId>
+                            <artifactId>therapi-runtime-javadoc-scribe</artifactId>
+                            <version>0.13.0</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.springframework.boot</groupId>
+                            <artifactId>spring-boot-configuration-processor</artifactId>
+                            <version>${spring-boot.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
                 </configuration>
             </plugin>
         </plugins>

+ 5 - 0
ruoyi-auth/pom.xml

@@ -46,6 +46,11 @@
             <artifactId>ruoyi-common-security</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-web</artifactId>

+ 1 - 0
ruoyi-common/pom.xml

@@ -16,6 +16,7 @@
         <module>ruoyi-common-excel</module>
         <module>ruoyi-common-core</module>
         <module>ruoyi-common-redis</module>
+        <module>ruoyi-common-doc</module>
         <module>ruoyi-common-security</module>
         <module>ruoyi-common-satoken</module>
         <module>ruoyi-common-web</module>

+ 7 - 0
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -22,6 +22,13 @@
                 <version>${project.version}</version>
             </dependency>
 
+            <!-- 接口模块 -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-common-doc</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
             <!-- 安全模块 -->
             <dependency>
                 <groupId>com.ruoyi</groupId>

+ 5 - 0
ruoyi-common/ruoyi-common-core/pom.xml

@@ -73,6 +73,11 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>

+ 5 - 6
ruoyi-common/ruoyi-common-swagger/pom.xml → ruoyi-common/ruoyi-common-doc/pom.xml

@@ -9,7 +9,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>ruoyi-common-swagger</artifactId>
+    <artifactId>ruoyi-common-doc</artifactId>
 
     <description>
         ruoyi-common-swagger系统接口
@@ -23,15 +23,14 @@
             <artifactId>ruoyi-common-core</artifactId>
         </dependency>
 
-        <!-- SpringBoot Web -->
         <dependency>
-            <groupId>com.ruoyi</groupId>
-            <artifactId>ruoyi-common-web</artifactId>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-webmvc-core</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>cn.dev33</groupId>
-            <artifactId>sa-token-core</artifactId>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-javadoc</artifactId>
         </dependency>
 
     </dependencies>

+ 78 - 0
ruoyi-common/ruoyi-common-doc/src/main/java/com/ruoyi/common/doc/config/SwaggerAutoConfiguration.java

@@ -0,0 +1,78 @@
+package com.ruoyi.common.doc.config;
+
+import com.ruoyi.common.doc.handler.OpenApiHandler;
+import com.ruoyi.common.doc.config.properties.SwaggerProperties;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import lombok.RequiredArgsConstructor;
+import org.springdoc.core.*;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Swagger 文档配置
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@AutoConfiguration(before = SpringDocConfiguration.class)
+@EnableConfigurationProperties(SwaggerProperties.class)
+@ConditionalOnProperty(name = "swagger.enabled", havingValue = "true", matchIfMissing = true)
+public class SwaggerAutoConfiguration {
+
+    private final SwaggerProperties swaggerProperties;
+
+    @Bean
+    @ConditionalOnMissingBean(OpenAPI.class)
+    public OpenAPI openApi() {
+        OpenAPI openApi = new OpenAPI();
+        // 文档基本信息
+        SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo();
+        Info info = convertInfo(infoProperties);
+        openApi.info(info);
+        // 扩展文档信息
+        openApi.externalDocs(swaggerProperties.getExternalDocs());
+        openApi.tags(swaggerProperties.getTags());
+        openApi.paths(swaggerProperties.getPaths());
+        openApi.components(swaggerProperties.getComponents());
+        List<SecurityRequirement> list = new ArrayList<>();
+        list.add(new SecurityRequirement().addList("apikey"));
+        openApi.security(list);
+
+        return openApi;
+    }
+
+    private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) {
+        Info info = new Info();
+        info.setTitle(infoProperties.getTitle());
+        info.setDescription(infoProperties.getDescription());
+        info.setContact(infoProperties.getContact());
+        info.setLicense(infoProperties.getLicense());
+        info.setVersion(infoProperties.getVersion());
+        return info;
+    }
+
+    /**
+     * 自定义 openapi 处理器
+     */
+    @Bean
+    public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
+                                         SecurityService securityParser,
+                                         SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+                                         Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
+                                         Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
+        return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+    }
+
+}

+ 99 - 0
ruoyi-common/ruoyi-common-doc/src/main/java/com/ruoyi/common/doc/config/properties/SwaggerProperties.java

@@ -0,0 +1,99 @@
+package com.ruoyi.common.doc.config.properties;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+import java.util.List;
+
+/**
+ * swagger 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "swagger")
+public class SwaggerProperties {
+
+    /**
+     * 是否开启 openApi 文档
+     */
+    private Boolean enabled = true;
+
+    /**
+     * 文档基本信息
+     */
+    @NestedConfigurationProperty
+    private InfoProperties info = new InfoProperties();
+
+    /**
+     * 扩展文档地址
+     */
+    @NestedConfigurationProperty
+    private ExternalDocumentation externalDocs;
+
+    /**
+     * 标签
+     */
+    private List<Tag> tags = null;
+
+    /**
+     * 路径
+     */
+    @NestedConfigurationProperty
+    private Paths paths = null;
+
+    /**
+     * 组件
+     */
+    @NestedConfigurationProperty
+    private Components components = null;
+
+    /**
+     * <p>
+     * 文档的基础属性信息
+     * </p>
+     *
+     * @see io.swagger.v3.oas.models.info.Info
+     *
+     * 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
+     */
+    @Data
+    public static class InfoProperties {
+
+        /**
+         * 标题
+         */
+        private String title = null;
+
+        /**
+         * 描述
+         */
+        private String description = null;
+
+        /**
+         * 联系人信息
+         */
+        @NestedConfigurationProperty
+        private Contact contact = null;
+
+        /**
+         * 许可证
+         */
+        @NestedConfigurationProperty
+        private License license = null;
+
+        /**
+         * 版本
+         */
+        private String version = null;
+
+    }
+
+}

+ 272 - 0
ruoyi-common/ruoyi-common-doc/src/main/java/com/ruoyi/common/doc/handler/OpenApiHandler.java

@@ -0,0 +1,272 @@
+package com.ruoyi.common.doc.handler;
+
+import cn.hutool.core.io.IoUtil;
+import io.swagger.v3.core.jackson.TypeNameResolver;
+import io.swagger.v3.core.util.AnnotationsUtils;
+import io.swagger.v3.oas.annotations.tags.Tags;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.tags.Tag;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springdoc.core.OpenAPIService;
+import org.springdoc.core.PropertyResolverUtils;
+import org.springdoc.core.SecurityService;
+import org.springdoc.core.SpringDocConfigProperties;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 自定义 openapi 处理器
+ * 对源码功能进行修改 增强使用
+ */
+@SuppressWarnings("all")
+public class OpenApiHandler extends OpenAPIService {
+
+    /**
+     * The constant LOGGER.
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPIService.class);
+
+    /**
+     * The Context.
+     */
+    private ApplicationContext context;
+
+    /**
+     * The Security parser.
+     */
+    private final SecurityService securityParser;
+
+    /**
+     * The Mappings map.
+     */
+    private final Map<String, Object> mappingsMap = new HashMap<>();
+
+    /**
+     * The Springdoc tags.
+     */
+    private final Map<HandlerMethod, Tag> springdocTags = new HashMap<>();
+
+    /**
+     * The Open api builder customisers.
+     */
+    private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;
+
+    /**
+     * The server base URL customisers.
+     */
+    private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;
+
+    /**
+     * The Spring doc config properties.
+     */
+    private final SpringDocConfigProperties springDocConfigProperties;
+
+    /**
+     * The Open api.
+     */
+    private OpenAPI openAPI;
+
+    /**
+     * The Cached open api map.
+     */
+    private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();
+
+    /**
+     * The Is servers present.
+     */
+    private boolean isServersPresent;
+
+    /**
+     * The Server base url.
+     */
+    private String serverBaseUrl;
+
+    /**
+     * The Property resolver utils.
+     */
+    private final PropertyResolverUtils propertyResolverUtils;
+
+    /**
+     * The javadoc provider.
+     */
+    private final Optional<JavadocProvider> javadocProvider;
+
+    /**
+     * The Basic error controller.
+     */
+    private static Class<?> basicErrorController;
+
+    static {
+        try {
+            //spring-boot 2
+            basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController");
+        } catch (ClassNotFoundException e) {
+            //spring-boot 1
+            try {
+                basicErrorController = Class.forName("org.springframework.boot.autoconfigure.web.BasicErrorController");
+            } catch (ClassNotFoundException classNotFoundException) {
+                //Basic error controller class not found
+                LOGGER.trace(classNotFoundException.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Instantiates a new Open api builder.
+     *
+     * @param openAPI                   the open api
+     * @param securityParser            the security parser
+     * @param springDocConfigProperties the spring doc config properties
+     * @param propertyResolverUtils     the property resolver utils
+     * @param openApiBuilderCustomizers the open api builder customisers
+     * @param serverBaseUrlCustomizers  the server base url customizers
+     * @param javadocProvider           the javadoc provider
+     */
+    public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser,
+                          SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+                          Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
+                          Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
+                          Optional<JavadocProvider> javadocProvider) {
+        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
+        if (openAPI.isPresent()) {
+            this.openAPI = openAPI.get();
+            if (this.openAPI.getComponents() == null)
+                this.openAPI.setComponents(new Components());
+            if (this.openAPI.getPaths() == null)
+                this.openAPI.setPaths(new Paths());
+            if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
+                this.isServersPresent = true;
+        }
+        this.propertyResolverUtils = propertyResolverUtils;
+        this.securityParser = securityParser;
+        this.springDocConfigProperties = springDocConfigProperties;
+        this.openApiBuilderCustomisers = openApiBuilderCustomizers;
+        this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
+        this.javadocProvider = javadocProvider;
+        if (springDocConfigProperties.isUseFqn())
+            TypeNameResolver.std.setUseFqn(true);
+    }
+
+    @Override
+    public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
+
+        Set<Tag> tags = new HashSet<>();
+        Set<String> tagsStr = new HashSet<>();
+
+        buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
+        buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
+
+        if (!CollectionUtils.isEmpty(tagsStr))
+            tagsStr = tagsStr.stream()
+                .map(str -> propertyResolverUtils.resolve(str, locale))
+                .collect(Collectors.toSet());
+
+        if (springdocTags.containsKey(handlerMethod)) {
+            Tag tag = springdocTags.get(handlerMethod);
+            tagsStr.add(tag.getName());
+            if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+                openAPI.addTagsItem(tag);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tagsStr)) {
+            if (CollectionUtils.isEmpty(operation.getTags()))
+                operation.setTags(new ArrayList<>(tagsStr));
+            else {
+                Set<String> operationTagsSet = new HashSet<>(operation.getTags());
+                operationTagsSet.addAll(tagsStr);
+                operation.getTags().clear();
+                operation.getTags().addAll(operationTagsSet);
+            }
+        }
+
+        if (isAutoTagClasses(operation)) {
+
+
+            if (javadocProvider.isPresent()) {
+                String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
+                if (StringUtils.isNotBlank(description)) {
+                    Tag tag = new Tag();
+
+                    // 自定义部分 修改使用java注释当tag名
+                    List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
+                    // tag.setName(tagAutoName);
+                    tag.setName(list.get(0));
+                    operation.addTagsItem(list.get(0));
+
+                    tag.setDescription(description);
+                    if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+                        openAPI.addTagsItem(tag);
+                    }
+                }
+            } else {
+                String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
+                operation.addTagsItem(tagAutoName);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tags)) {
+            // Existing tags
+            List<Tag> openApiTags = openAPI.getTags();
+            if (!CollectionUtils.isEmpty(openApiTags))
+                tags.addAll(openApiTags);
+            openAPI.setTags(new ArrayList<>(tags));
+        }
+
+        // Handle SecurityRequirement at operation level
+        io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
+            .getSecurityRequirements(handlerMethod);
+        if (securityRequirements != null) {
+            if (securityRequirements.length == 0)
+                operation.setSecurity(Collections.emptyList());
+            else
+                securityParser.buildSecurityRequirement(securityRequirements, operation);
+        }
+
+        return operation;
+    }
+
+    private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
+        // method tags
+        Set<Tags> tagsSet = AnnotatedElementUtils
+            .findAllMergedAnnotations(method, Tags.class);
+        Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
+            .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
+        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
+        if (!CollectionUtils.isEmpty(methodTags)) {
+            tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
+            List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
+            addTags(allTags, tags, locale);
+        }
+    }
+
+    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
+        Optional<Set<Tag>> optionalTagSet = AnnotationsUtils
+            .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
+        optionalTagSet.ifPresent(tagsSet -> {
+            tagsSet.forEach(tag -> {
+                tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
+                tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
+                if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+                    tags.add(tag);
+            });
+        });
+    }
+
+}

+ 1 - 0
ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.ruoyi.common.doc.config.SwaggerAutoConfiguration

+ 0 - 133
ruoyi-common/ruoyi-common-swagger/src/main/java/com/ruoyi/common/swagger/config/SwaggerAutoConfiguration.java

@@ -1,133 +0,0 @@
-package com.ruoyi.common.swagger.config;
-
-import cn.dev33.satoken.config.SaTokenConfig;
-import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
-import com.ruoyi.common.core.utils.StringUtils;
-import com.ruoyi.common.swagger.config.properties.SwaggerProperties;
-import io.swagger.models.auth.In;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.*;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spi.service.contexts.SecurityContext;
-import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
-import springfox.documentation.spring.web.plugins.Docket;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Predicate;
-
-/**
- * Swagger 文档配置
- *
- * @author Lion Li
- */
-@AutoConfiguration
-@EnableKnife4j
-@EnableConfigurationProperties(SwaggerProperties.class)
-@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
-public class SwaggerAutoConfiguration {
-
-    @Autowired
-    private SaTokenConfig saTokenConfig;
-
-    @Value("${spring.application.name}")
-    private String appName;
-
-    /**
-     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
-     */
-    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");
-
-    private static final String BASE_PATH = "/**";
-
-    @Bean
-    public Docket api(SwaggerProperties swaggerProperties) {
-        // base-path处理
-        if (swaggerProperties.getBasePath().isEmpty()) {
-            swaggerProperties.getBasePath().add(BASE_PATH);
-        }
-        // noinspection unchecked
-        List<Predicate<String>> basePath = new ArrayList<>();
-        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));
-
-        // exclude-path处理
-        if (swaggerProperties.getExcludePath().isEmpty()) {
-            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
-        }
-
-        List<Predicate<String>> excludePath = new ArrayList<>();
-        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));
-
-        ApiSelectorBuilder builder = new Docket(DocumentationType.OAS_30)
-            .enable(swaggerProperties.getEnabled())
-            .host(swaggerProperties.getHost())
-            .apiInfo(apiInfo(swaggerProperties))
-            .select()
-            .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()));
-
-        swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));
-        swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));
-
-        return builder.build()
-            .securitySchemes(securitySchemes())
-            .securityContexts(securityContexts())
-            .pathMapping(StringUtils.substring(appName, appName.indexOf("-") + 1));
-    }
-
-    /**
-     * 安全模式,这里指定token通过Authorization头请求头传递
-     */
-    private List<SecurityScheme> securitySchemes() {
-        List<SecurityScheme> apiKeyList = new ArrayList<>();
-        String header = saTokenConfig.getTokenName();
-        apiKeyList.add(new ApiKey(header, header, In.HEADER.toValue()));
-        return apiKeyList;
-    }
-
-    /**
-     * 安全上下文
-     */
-    private List<SecurityContext> securityContexts() {
-        List<SecurityContext> securityContexts = new ArrayList<>();
-        securityContexts.add(
-            SecurityContext.builder()
-                .securityReferences(defaultAuth())
-                .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
-                .build());
-        return securityContexts;
-    }
-
-    /**
-     * 默认的全局鉴权策略
-     */
-    private List<SecurityReference> defaultAuth() {
-        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
-        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
-        authorizationScopes[0] = authorizationScope;
-        List<SecurityReference> securityReferences = new ArrayList<>();
-        securityReferences.add(new SecurityReference(saTokenConfig.getTokenName(), authorizationScopes));
-        return securityReferences;
-    }
-
-    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
-        return new ApiInfoBuilder()
-            .title(swaggerProperties.getTitle())
-            .description(swaggerProperties.getDescription())
-            .license(swaggerProperties.getLicense())
-            .licenseUrl(swaggerProperties.getLicenseUrl())
-            .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
-            .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
-            .version(swaggerProperties.getVersion())
-            .build();
-    }
-}

+ 0 - 41
ruoyi-common/ruoyi-common-swagger/src/main/java/com/ruoyi/common/swagger/config/SwaggerBeanPostProcessor.java

@@ -1,41 +0,0 @@
-package com.ruoyi.common.swagger.config;
-
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.util.ReflectionUtils;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
-import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
-import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
-
-import java.lang.reflect.Field;
-import java.util.List;
-
-/**
- * swagger 在 springboot 2.6.x 不兼容问题的处理
- *
- * @author ruoyi
- */
-@SuppressWarnings("all")
-public class SwaggerBeanPostProcessor implements BeanPostProcessor {
-    @Override
-    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-        if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
-            customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
-        }
-        return bean;
-    }
-
-    private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
-        mappings.removeIf(mapping -> mapping.getPatternParser() != null);
-    }
-
-    private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
-        try {
-            Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
-            field.setAccessible(true);
-            return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
-        } catch (IllegalArgumentException | IllegalAccessException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-}

+ 0 - 143
ruoyi-common/ruoyi-common-swagger/src/main/java/com/ruoyi/common/swagger/config/properties/SwaggerProperties.java

@@ -1,143 +0,0 @@
-package com.ruoyi.common.swagger.config.properties;
-
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * swagger 配置属性
- *
- * @author Lion Li
- */
-@Data
-@ConfigurationProperties("swagger")
-public class SwaggerProperties {
-
-    /**
-     * 是否开启swagger
-     */
-    private Boolean enabled;
-
-    /**
-     * swagger会解析的包路径
-     **/
-    private String basePackage = "";
-
-    /**
-     * swagger会解析的url规则
-     **/
-    private List<String> basePath = new ArrayList<>();
-
-    /**
-     * 在basePath基础上需要排除的url规则
-     **/
-    private List<String> excludePath = new ArrayList<>();
-
-    /**
-     * 标题
-     **/
-    private String title = "";
-
-    /**
-     * 描述
-     **/
-    private String description = "";
-
-    /**
-     * 版本
-     **/
-    private String version = "";
-
-    /**
-     * 许可证
-     **/
-    private String license = "";
-
-    /**
-     * 许可证URL
-     **/
-    private String licenseUrl = "";
-
-    /**
-     * 服务条款URL
-     **/
-    private String termsOfServiceUrl = "";
-
-    /**
-     * host信息
-     **/
-    private String host = "";
-
-    /**
-     * 联系人信息
-     */
-    private Contact contact = new Contact();
-
-    /**
-     * 全局统一鉴权配置
-     **/
-    private Authorization authorization = new Authorization();
-
-    @Data
-    @NoArgsConstructor
-    public static class Contact {
-
-        /**
-         * 联系人
-         **/
-        private String name = "";
-        /**
-         * 联系人url
-         **/
-        private String url = "";
-        /**
-         * 联系人email
-         **/
-        private String email = "";
-
-    }
-
-    @Data
-    @NoArgsConstructor
-    public static class Authorization {
-
-        /**
-         * 鉴权策略ID,需要和SecurityReferences ID保持一致
-         */
-        private String name = "";
-
-        /**
-         * 需要开启鉴权URL的正则
-         */
-        private String authRegex = "^.*$";
-
-        /**
-         * 鉴权作用域列表
-         */
-        private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
-
-        private List<String> tokenUrlList = new ArrayList<>();
-
-    }
-
-    @Data
-    @NoArgsConstructor
-    public static class AuthorizationScope {
-
-        /**
-         * 作用域名称
-         */
-        private String scope = "";
-
-        /**
-         * 作用域描述
-         */
-        private String description = "";
-
-    }
-
-}

+ 0 - 2
ruoyi-common/ruoyi-common-swagger/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1,2 +0,0 @@
-com.ruoyi.common.swagger.config.SwaggerAutoConfiguration
-com.ruoyi.common.swagger.config.SwaggerBeanPostProcessor

+ 5 - 0
ruoyi-example/ruoyi-demo/pom.xml

@@ -52,6 +52,11 @@
             <artifactId>ruoyi-common-log</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-web</artifactId>

+ 5 - 0
ruoyi-example/ruoyi-stream-mq/pom.xml

@@ -61,6 +61,11 @@
             <artifactId>ruoyi-common-security</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-web</artifactId>

+ 54 - 47
ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/GatewayExceptionHandler.java

@@ -1,47 +1,54 @@
-package com.ruoyi.gateway.handler;
-
-import com.ruoyi.gateway.utils.WebFluxUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
-import org.springframework.cloud.gateway.support.NotFoundException;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.annotation.Order;
-import org.springframework.http.server.reactive.ServerHttpResponse;
-import org.springframework.web.server.ResponseStatusException;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-/**
- * 网关统一异常处理
- *
- * @author ruoyi
- */
-@Slf4j
-@Order(-1)
-@Configuration
-public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
-
-    @Override
-    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
-        ServerHttpResponse response = exchange.getResponse();
-
-        if (exchange.getResponse().isCommitted()) {
-            return Mono.error(ex);
-        }
-
-        String msg;
-
-        if (ex instanceof NotFoundException) {
-            msg = "服务未找到";
-        } else if (ex instanceof ResponseStatusException) {
-            ResponseStatusException responseStatusException = (ResponseStatusException) ex;
-            msg = responseStatusException.getMessage();
-        } else {
-            msg = "内部服务器错误";
-        }
-
-        log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
-
-        return WebFluxUtils.webFluxResponseWriter(response, msg);
-    }
-}
+package com.ruoyi.gateway.handler;
+
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.gateway.utils.WebFluxUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.cloud.gateway.support.NotFoundException;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.server.RequestPath;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * 网关统一异常处理
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Order(-1)
+@Configuration
+public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
+
+    private static final String[] IGNORE_PATH = new String[]{"/favicon.ico"};
+
+    @Override
+    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
+        ServerHttpResponse response = exchange.getResponse();
+
+        if (exchange.getResponse().isCommitted()) {
+            return Mono.error(ex);
+        }
+
+        String msg;
+
+        if (ex instanceof NotFoundException) {
+            msg = "服务未找到";
+        } else if (ex instanceof ResponseStatusException) {
+            ResponseStatusException responseStatusException = (ResponseStatusException) ex;
+            msg = responseStatusException.getMessage();
+        } else {
+            msg = "内部服务器错误";
+        }
+
+        RequestPath path = exchange.getRequest().getPath();
+        if (!StringUtils.equalsAny(path.toString(), IGNORE_PATH)) {
+            log.error("[网关异常处理]请求路径:{},异常信息:{}", path, ex.getMessage());
+        }
+
+        return WebFluxUtils.webFluxResponseWriter(response, msg);
+    }
+}

+ 5 - 0
ruoyi-modules/ruoyi-gen/pom.xml

@@ -59,6 +59,11 @@
             <artifactId>ruoyi-common-log</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-web</artifactId>

+ 5 - 0
ruoyi-modules/ruoyi-resource/pom.xml

@@ -47,6 +47,11 @@
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-dubbo</artifactId>

+ 5 - 0
ruoyi-modules/ruoyi-system/pom.xml

@@ -58,6 +58,11 @@
             <artifactId>ruoyi-common-dict</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-web</artifactId>