diff --git a/pom.xml b/pom.xml index e4770100e7..621b67a4af 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 1.6.0 1.18.38 - 3.4.5 + 3.5.4 1.6.3 UTF-8 diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 668249863a..ede38b34cc 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -17,10 +17,10 @@ 2025.08-SNAPSHOT 1.6.0 - 3.4.5 + 3.5.4 - 2.8.3 - 4.6.0 + 2.8.9 + 4.5.0 1.2.24 3.5.19 @@ -39,7 +39,7 @@ 2.2.7 9.0.0 - 3.4.5 + 3.5.2 0.33.0 8.0.2.RELEASE @@ -151,13 +151,19 @@ - com.github.xingfudeshi + com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter ${knife4j.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + org.springdoc - springdoc-openapi-starter-webmvc-api + springdoc-openapi-starter-webmvc-ui ${springdoc.version} diff --git a/yudao-framework/yudao-common/pom.xml b/yudao-framework/yudao-common/pom.xml index b28da08fe2..4ede9eff2d 100644 --- a/yudao-framework/yudao-common/pom.xml +++ b/yudao-framework/yudao-common/pom.xml @@ -60,7 +60,7 @@ org.springdoc - springdoc-openapi-starter-webmvc-api + springdoc-openapi-starter-webmvc-ui provided diff --git a/yudao-framework/yudao-spring-boot-starter-web/pom.xml b/yudao-framework/yudao-spring-boot-starter-web/pom.xml index 92ebc918f0..d909688741 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-web/pom.xml @@ -39,12 +39,12 @@ - com.github.xingfudeshi + com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter org.springdoc - springdoc-openapi-starter-webmvc-api + springdoc-openapi-starter-webmvc-ui diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/Knife4jOpenApiCustomizer.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/Knife4jOpenApiCustomizer.java new file mode 100644 index 0000000000..f8996f75be --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/Knife4jOpenApiCustomizer.java @@ -0,0 +1,146 @@ +package cn.iocoder.yudao.framework.swagger.config; + +import com.github.xiaoymin.knife4j.annotations.ApiSupport; +import com.github.xiaoymin.knife4j.core.conf.ExtensionsConstants; +import com.github.xiaoymin.knife4j.core.conf.GlobalConstants; +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties; +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jSetting; +import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.models.OpenAPI; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RestController; + +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 增强扩展属性支持 + * + * 参考 Spring Boot 3.4 以上版本 /v3/api-docs 解决接口报错,依赖修复 + * + * @since 4.1.0 + * @author xiaoymin@foxmail.com + * 2022/12/11 22:40 + */ +@Primary +@Configuration +@Slf4j +public class Knife4jOpenApiCustomizer extends com.github.xiaoymin.knife4j.spring.extension.Knife4jOpenApiCustomizer + implements GlobalOpenApiCustomizer { + + final Knife4jProperties knife4jProperties; + final SpringDocConfigProperties properties; + + public Knife4jOpenApiCustomizer(Knife4jProperties knife4jProperties, SpringDocConfigProperties properties) { + super(knife4jProperties,properties); + this.knife4jProperties = knife4jProperties; + this.properties = properties; + } + + @Override + public void customise(OpenAPI openApi) { + if (knife4jProperties.isEnable()) { + Knife4jSetting setting = knife4jProperties.getSetting(); + OpenApiExtensionResolver openApiExtensionResolver = new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments()); + // 解析初始化 + openApiExtensionResolver.start(); + Map objectMap = new HashMap<>(); + objectMap.put(GlobalConstants.EXTENSION_OPEN_SETTING_NAME, setting); + objectMap.put(GlobalConstants.EXTENSION_OPEN_MARKDOWN_NAME, openApiExtensionResolver.getMarkdownFiles()); + openApi.addExtension(GlobalConstants.EXTENSION_OPEN_API_NAME, objectMap); + addOrderExtension(openApi); + } + } + + /** + * 往 OpenAPI 内 tags 字段添加 x-order 属性 + * + * @param openApi openApi + */ + private void addOrderExtension(OpenAPI openApi) { + if (CollectionUtils.isEmpty(properties.getGroupConfigs())) { + return; + } + // 获取包扫描路径 + Set packagesToScan = + properties.getGroupConfigs().stream() + .map(SpringDocConfigProperties.GroupConfig::getPackagesToScan) + .filter(toScan -> !CollectionUtils.isEmpty(toScan)) + .flatMap(List::stream) + .collect(Collectors.toSet()); + if (CollectionUtils.isEmpty(packagesToScan)) { + return; + } + // 扫描包下被 ApiSupport 注解的 RestController Class + Set> classes = packagesToScan.stream() + .map(packageToScan -> scanPackageByAnnotation(packageToScan, RestController.class)) + .flatMap(Set::stream) + .filter(clazz -> clazz.isAnnotationPresent(ApiSupport.class)) + .collect(Collectors.toSet()); + if (!CollectionUtils.isEmpty(classes)) { + // ApiSupport oder 值存入 tagSortMap + Map tagOrderMap = new HashMap<>(); + classes.forEach(clazz -> { + Tag tag = getTag(clazz); + if (Objects.nonNull(tag)) { + ApiSupport apiSupport = clazz.getAnnotation(ApiSupport.class); + tagOrderMap.putIfAbsent(tag.name(), apiSupport.order()); + } + }); + // 往 openApi tags 字段添加 x-order 增强属性 + if (openApi.getTags() != null) { + openApi.getTags().forEach(tag -> { + if (tagOrderMap.containsKey(tag.getName())) { + tag.addExtension(ExtensionsConstants.EXTENSION_ORDER, tagOrderMap.get(tag.getName())); + } + }); + } + } + } + + private Tag getTag(Class clazz) { + // 从类上获取 + Tag tag = clazz.getAnnotation(Tag.class); + if (Objects.isNull(tag)) { + // 从接口上获取 + Class[] interfaces = clazz.getInterfaces(); + if (ArrayUtils.isNotEmpty(interfaces)) { + for (Class interfaceClazz : interfaces) { + Tag anno = interfaceClazz.getAnnotation(Tag.class); + if (Objects.nonNull(anno)) { + tag = anno; + break; + } + } + } + } + return tag; + } + + private Set> scanPackageByAnnotation(String packageName, final Class annotationClass) { + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass)); + Set> classes = new HashSet<>(); + for (BeanDefinition beanDefinition : scanner.findCandidateComponents(packageName)) { + try { + Class clazz = Class.forName(beanDefinition.getBeanClassName()); + classes.add(clazz); + } catch (ClassNotFoundException ignore) { + } + } + return classes; + } + +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java index 295e3238ff..dec79d8eb6 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.Import; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; @@ -46,6 +47,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_ @ConditionalOnClass({OpenAPI.class}) @EnableConfigurationProperties(SwaggerProperties.class) @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 +@Import(Knife4jOpenApiCustomizer.class) public class YudaoSwaggerAutoConfiguration { // ========== 全局 OpenAPI 配置 ========== diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 1decd79cda..c0d79ac881 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -48,7 +48,7 @@ springdoc: default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 knife4j: - enable: false # TODO 芋艿:需要关闭增强,具体原因见:https://github.com/xiaoymin/knife4j/issues/874 + enable: true setting: language: zh_cn