简单的swagger分组

6/1/2022

# 一、背景与问题

1024创新实验室的小冬最近接了一个小项目,项目需求就是一个简单的内容发布,分为后管创建资讯、小程序端进行内容展示。整个项目分析下来,没有必要进行项目分包,用一个项目即可解决。

小冬在进行后端接口开发时为了前端使用方便,需要进行接口文档的分组展示

# 二、架构与思想

查看Swagger配置信息

swagger:
  tag-class: net.lab1024.sa.admin.constant.AdminSwaggerTagConst
1
2

在对应的SwaggerTagConst中添加静态内部类,对应的类名将作为swagger接口文档的分组名称

    public static class Business {
    }

    public static class System {
    }
1
2
3
4
5

# 三、原理

Swagger分组的实现方式是通过定义DocketBean的方式来实现的,分组的名称是以Bean的名称来定义. 通过这个原理,我们可以通过反射解析对应SwaggerTagConst类,以对应的类名作为Docket的Bean名称, 然后通过Spring的自定义Bean的方式来实现Docket的动态注入。
参考代码:SwaggerConfig

package net.lab1024.sa.common.config;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import io.swagger.annotations.Api;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.constant.RequestHeaderConst;
import net.lab1024.sa.common.common.swagger.SwaggerApiModelPropertyEnumPlugin;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 根据SwaggerTagConst内部类动态生成Swagger group
 *
 * @Author 1024创新实验室-主任: 卓大
 * @Date 2020-03-25 22:54:46
 * @Wechat zhuoda1024
 * @Email lab1024@163.com
 * @Copyright 1024创新实验室 ( https://1024lab.net )
 */
@Slf4j
@EnableSwagger2
@Configuration
@Conditional(SystemEnvironmentConfig.class)
public class SwaggerConfig implements EnvironmentAware, BeanDefinitionRegistryPostProcessor {

    /**
     * 文档标题
     */
    private String title;

    /**
     * 文档描述
     */
    private String description;

    /**
     * api版本
     */
    private String version;

    /**
     * service url
     */
    private String teamUrl;

    /**
     * host
     */
    private String host;

    private String tagClass;

    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)
    public SwaggerApiModelPropertyEnumPlugin swaggerEnum() {
        return new SwaggerApiModelPropertyEnumPlugin();
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.title = environment.getProperty("swagger.title");
        this.description = environment.getProperty("swagger.description");
        this.version = environment.getProperty("swagger.version");
        this.host = environment.getProperty("swagger.host");
        this.tagClass = environment.getProperty("swagger.tag-class");
        this.teamUrl = environment.getProperty("swagger.team-url");
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        Map<String, List<String>> groupMap = this.buildGroup();
        for (Map.Entry<String, List<String>> entry : groupMap.entrySet()) {
            String group = entry.getKey();
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Docket.class, () -> this.baseDocket(group, entry.getValue()));
            BeanDefinition beanDefinition = builder.getRawBeanDefinition();
            registry.registerBeanDefinition(group + "Api", beanDefinition);
        }
    }

    @SneakyThrows
    private Map<String, List<String>> buildGroup() {
        Class<?> clazz = Class.forName(tagClass);
        Class<?>[] innerClazz = clazz.getClasses();
        Map<String, List<String>> groupMap = new HashMap<>(16);
        for (Class<?> cls : innerClazz) {
            String group = cls.getSimpleName();
            List<String> apiTags = Lists.newArrayList();
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                boolean isFinal = Modifier.isFinal(field.getModifiers());
                if (isFinal) {
                    apiTags.add(field.get(null).toString());
                }
            }
            groupMap.put(group, apiTags);
        }
        return groupMap;
    }

    private Docket baseDocket(String groupName, List<String> apiTagList) {
        // 配置全局参数
        List<Parameter> parameterList = this.generateParameter();

        Docket docket = new Docket(DocumentationType.SWAGGER_2).groupName(groupName)
                .forCodeGeneration(true)
                .select()
                // 过滤规则
                .apis(this.getControllerPredicate(apiTagList))
                // 与 过滤规则 controller 包路径 二选一
                // .apis(RequestHandlerSelectors.basePackage(packAge))
                .paths(PathSelectors.any())
                .build().apiInfo(this.apiInfo())
                .globalOperationParameters(parameterList);
        if (StringUtils.isNotBlank(host)) {
            docket = docket.host(host);
        }
        return docket;
    }

    private Predicate<RequestHandler> getControllerPredicate(List<String> apiTagList) {
        Predicate<RequestHandler> methodPredicate = (input) -> {
            Api api = null;
            Optional<Api> apiOptional = input.findControllerAnnotation(Api.class);
            if (apiOptional.isPresent()) {
                api = apiOptional.get();
            }
            if (api == null) {
                return false;
            }
            List<String> tags = Arrays.asList(api.tags());
            if (apiTagList.containsAll(tags)) {
                return true;
            }
            return false;
        };
        Predicate controllerPredicate = Predicates.or(RequestHandlerSelectors.withClassAnnotation(RestController.class), RequestHandlerSelectors.withClassAnnotation(Controller.class));
        return Predicates.and(controllerPredicate, methodPredicate);
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title(title)
                .description(description)
                .version(version)
                .termsOfServiceUrl(teamUrl)
                .contact(new Contact("1024lab", teamUrl, "1024lab@sina.com"))
                .build();
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    /**
     * 生成共用请求参数
     *
     * @return
     */
    private List<Parameter> generateParameter() {
        // 配置全局参数 token
        Parameter token = new ParameterBuilder().name(RequestHeaderConst.TOKEN)
                .description("token")
                .modelRef(new ModelRef("string"))
                .parameterType("header").defaultValue("1")
                .required(false)
                .build();
        return Lists.newArrayList(token);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207

# 联系我们

1024创新实验室-主任:卓大 (opens new window),混迹于各个技术圈,研究过计算机,熟悉点 java,略懂点前端。
1024创新实验室(河南·洛阳) (opens new window) 致力于成为中原领先、国内一流的技术团队,以技术创新为驱动,合作各类项目。

加 主任 “卓大” 微信
拉你入群,一起学习
关注 “小镇程序员”
分享代码与生活、技术与赚钱
请 “1024创新实验室” 喝咖啡
支持我们的开源与分享

告白气球 (钢琴版)
JESSE T