1
+ package cn .iocoder .yudao .framework .swagger .config ;
2
+
3
+ import com .github .xiaoymin .knife4j .annotations .ApiSupport ;
4
+ import com .github .xiaoymin .knife4j .core .conf .ExtensionsConstants ;
5
+ import com .github .xiaoymin .knife4j .core .conf .GlobalConstants ;
6
+ import com .github .xiaoymin .knife4j .spring .configuration .Knife4jProperties ;
7
+ import com .github .xiaoymin .knife4j .spring .configuration .Knife4jSetting ;
8
+ import com .github .xiaoymin .knife4j .spring .extension .OpenApiExtensionResolver ;
9
+ import io .swagger .v3 .oas .annotations .tags .Tag ;
10
+ import io .swagger .v3 .oas .models .OpenAPI ;
11
+ import lombok .extern .slf4j .Slf4j ;
12
+ import org .apache .commons .lang3 .ArrayUtils ;
13
+ import org .springdoc .core .customizers .GlobalOpenApiCustomizer ;
14
+ import org .springdoc .core .properties .SpringDocConfigProperties ;
15
+ import org .springframework .beans .factory .config .BeanDefinition ;
16
+ import org .springframework .context .annotation .ClassPathScanningCandidateComponentProvider ;
17
+ import org .springframework .context .annotation .Configuration ;
18
+ import org .springframework .context .annotation .Primary ;
19
+ import org .springframework .core .type .filter .AnnotationTypeFilter ;
20
+ import org .springframework .util .CollectionUtils ;
21
+ import org .springframework .web .bind .annotation .RestController ;
22
+
23
+ import java .lang .annotation .Annotation ;
24
+ import java .util .*;
25
+ import java .util .stream .Collectors ;
26
+
27
+ /**
28
+ * 增强扩展属性支持
29
+ *
30
+ * 参考 <a href="https://github.com/xiaoymin/knife4j/issues/913">Spring Boot 3.4 以上版本 /v3/api-docs 解决接口报错,依赖修复</a>
31
+ *
32
+ * @since 4.1.0
33
+
34
+ * 2022/12/11 22:40
35
+ */
36
+ @ Primary
37
+ @ Configuration
38
+ @ Slf4j
39
+ public class Knife4jOpenApiCustomizer extends com .github .xiaoymin .knife4j .spring .extension .Knife4jOpenApiCustomizer
40
+ implements GlobalOpenApiCustomizer {
41
+
42
+ final Knife4jProperties knife4jProperties ;
43
+ final SpringDocConfigProperties properties ;
44
+
45
+ public Knife4jOpenApiCustomizer (Knife4jProperties knife4jProperties , SpringDocConfigProperties properties ) {
46
+ super (knife4jProperties ,properties );
47
+ this .knife4jProperties = knife4jProperties ;
48
+ this .properties = properties ;
49
+ }
50
+
51
+ @ Override
52
+ public void customise (OpenAPI openApi ) {
53
+ if (knife4jProperties .isEnable ()) {
54
+ Knife4jSetting setting = knife4jProperties .getSetting ();
55
+ OpenApiExtensionResolver openApiExtensionResolver = new OpenApiExtensionResolver (setting , knife4jProperties .getDocuments ());
56
+ // 解析初始化
57
+ openApiExtensionResolver .start ();
58
+ Map <String , Object > objectMap = new HashMap <>();
59
+ objectMap .put (GlobalConstants .EXTENSION_OPEN_SETTING_NAME , setting );
60
+ objectMap .put (GlobalConstants .EXTENSION_OPEN_MARKDOWN_NAME , openApiExtensionResolver .getMarkdownFiles ());
61
+ openApi .addExtension (GlobalConstants .EXTENSION_OPEN_API_NAME , objectMap );
62
+ addOrderExtension (openApi );
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 往 OpenAPI 内 tags 字段添加 x-order 属性
68
+ *
69
+ * @param openApi openApi
70
+ */
71
+ private void addOrderExtension (OpenAPI openApi ) {
72
+ if (CollectionUtils .isEmpty (properties .getGroupConfigs ())) {
73
+ return ;
74
+ }
75
+ // 获取包扫描路径
76
+ Set <String > packagesToScan =
77
+ properties .getGroupConfigs ().stream ()
78
+ .map (SpringDocConfigProperties .GroupConfig ::getPackagesToScan )
79
+ .filter (toScan -> !CollectionUtils .isEmpty (toScan ))
80
+ .flatMap (List ::stream )
81
+ .collect (Collectors .toSet ());
82
+ if (CollectionUtils .isEmpty (packagesToScan )) {
83
+ return ;
84
+ }
85
+ // 扫描包下被 ApiSupport 注解的 RestController Class
86
+ Set <Class <?>> classes = packagesToScan .stream ()
87
+ .map (packageToScan -> scanPackageByAnnotation (packageToScan , RestController .class ))
88
+ .flatMap (Set ::stream )
89
+ .filter (clazz -> clazz .isAnnotationPresent (ApiSupport .class ))
90
+ .collect (Collectors .toSet ());
91
+ if (!CollectionUtils .isEmpty (classes )) {
92
+ // ApiSupport oder 值存入 tagSortMap<Tag.name,ApiSupport.order>
93
+ Map <String , Integer > tagOrderMap = new HashMap <>();
94
+ classes .forEach (clazz -> {
95
+ Tag tag = getTag (clazz );
96
+ if (Objects .nonNull (tag )) {
97
+ ApiSupport apiSupport = clazz .getAnnotation (ApiSupport .class );
98
+ tagOrderMap .putIfAbsent (tag .name (), apiSupport .order ());
99
+ }
100
+ });
101
+ // 往 openApi tags 字段添加 x-order 增强属性
102
+ if (openApi .getTags () != null ) {
103
+ openApi .getTags ().forEach (tag -> {
104
+ if (tagOrderMap .containsKey (tag .getName ())) {
105
+ tag .addExtension (ExtensionsConstants .EXTENSION_ORDER , tagOrderMap .get (tag .getName ()));
106
+ }
107
+ });
108
+ }
109
+ }
110
+ }
111
+
112
+ private Tag getTag (Class <?> clazz ) {
113
+ // 从类上获取
114
+ Tag tag = clazz .getAnnotation (Tag .class );
115
+ if (Objects .isNull (tag )) {
116
+ // 从接口上获取
117
+ Class <?>[] interfaces = clazz .getInterfaces ();
118
+ if (ArrayUtils .isNotEmpty (interfaces )) {
119
+ for (Class <?> interfaceClazz : interfaces ) {
120
+ Tag anno = interfaceClazz .getAnnotation (Tag .class );
121
+ if (Objects .nonNull (anno )) {
122
+ tag = anno ;
123
+ break ;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return tag ;
129
+ }
130
+
131
+ private Set <Class <?>> scanPackageByAnnotation (String packageName , final Class <? extends Annotation > annotationClass ) {
132
+ ClassPathScanningCandidateComponentProvider scanner =
133
+ new ClassPathScanningCandidateComponentProvider (false );
134
+ scanner .addIncludeFilter (new AnnotationTypeFilter (annotationClass ));
135
+ Set <Class <?>> classes = new HashSet <>();
136
+ for (BeanDefinition beanDefinition : scanner .findCandidateComponents (packageName )) {
137
+ try {
138
+ Class <?> clazz = Class .forName (beanDefinition .getBeanClassName ());
139
+ classes .add (clazz );
140
+ } catch (ClassNotFoundException ignore ) {
141
+ }
142
+ }
143
+ return classes ;
144
+ }
145
+
146
+ }
0 commit comments