swagger2返回值Map,Json,实体类部分字段注释描述信息说明

问题描述

swagger2没有提供描述返回值的api,导致不能注解map类型的返回值,不能返回json,也不能描述只返回一个实体类中的部分字段的情况。我们需要自己实现这个功能。

网上找到的思路

实际上我在网上发现有人实现了这个功能,实现的原理是使用第三方jar包生成一个类,这个类里包括返回值里应该有的字段,这些字段使用原生的swagger注解,再让swagger去解析这个类。

这样做的优点是确实把参数信息加入了swagger的缓存中;缺点是需要生成额外的类。

这个思路的链接在这里

我自己的思路

我采用了另一种思路。我的实现思路是通过搜索’/v2/api-docs’找到了springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation()方法,发现其实就是返回了一个Swagger对象(参数信息都存在Swagger对象的definitions属性里)。swagger又是注入了spring容器中进行管理的,那么就很好说了,直接对这个方法进行切面编程即可,不过我没有直接切这个方法,是选择了更深层的springfox.documentation.swagger2.mappers.ModelMapper.mapModels方法(这个方法的返回值直接被set进了Swagger对象的definitions属性里)。

这样做的优点是不需要生成额外的类;缺点是没有能把参数信息实际上加入到swagger的缓存里,只是在访问’/v2/api-docs’时修改了返回值而已。

代码实现

下面就我的实现代码了,图省事的小伙伴可以直接贴在项目里。
1、ApiResponseObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.lsp.config.swagger2;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiResponseObject {

    String name() default "";

    String description() default "";

    ApiResponseProperty[] properties();

}

2、ApiResponseProperty

1
2
3
4
5
6
7
8
9
10
11
package com.lsp.config.swagger2;

public @interface ApiResponseProperty {

    String name();

    String description() default "";

    String type();

}

3、ApiResponseFields

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.lsp.config.swagger2;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiResponseFields {

    String modelName() default "";

    String[] fields();
}

4、ModelCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.lsp.config.swagger2;

import com.lsp.model.Value2;
import io.swagger.models.Model;

import java.util.HashMap;
import java.util.Map;

public class ModelCache {

    static Map<String, Model> extra_cache = new HashMap<>();

    static Map<String, Value2<String,String[]>> specified_cache = new HashMap<>();

}

5、ApiResponseBodyReader

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
208
209
210
211
package com.lsp.config.swagger2;

import com.fasterxml.classmate.ResolvedType;
import com.google.common.base.Optional;
import com.lsp.model.Value2;
import io.swagger.annotations.ApiModel;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.*;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;

import java.util.HashSet;
import java.util.UUID;

@Component
public class ApiResponseBodyReader implements OperationBuilderPlugin {

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    @Override
    public void apply(OperationContext operationContext) {
        //此方法上有没有ApiResponseObject注解
        boolean apiResponseObjectHandle = false;

        apiResponseObjectHandle = apiResponseObjectHandle(operationContext);

        //若此方法上没有ApiResponseObject注解
        if(!apiResponseObjectHandle){
            apiResponseFields(operationContext);
        }
    }

    /**
     * 处理ApiResponseFields注解
     * @param operationContext
     */
    private void apiResponseFields(OperationContext operationContext){
        Optional<ApiResponseFields> optional = operationContext.findAnnotation(ApiResponseFields.class);

        if(optional.isPresent() && !isVoid(operationContext)){

            ApiResponseFields responseFields = optional.get();

            String model_name =responseFields.modelName();

            if("".equals(model_name)){
                model_name = getModelName(operationContext);
            }

            String uuid = model_name + "-" + UUID();

            String[] fields = responseFields.fields();

            Value2<String, String[]> value2 = new Value2<>(model_name, fields);

            ModelCache.specified_cache.put(uuid,value2);

            addResponseMessage(operationContext,uuid);

        }

    }

    /**
     * 处理ApiResponseObject注解
     * @param operationContext
     * @return 若此方法上有ApiResponseObject注解,则返回true,否则返回false
     */
    private boolean apiResponseObjectHandle(OperationContext operationContext){
        Optional<ApiResponseObject> optional = operationContext.findAnnotation(ApiResponseObject.class);

        if(optional.isPresent()){

            ApiResponseObject apiResponseObject = optional.get();

            ModelImpl model = createModel(apiResponseObject);


            String model_name = null;

            if(isVoid(operationContext)){
                model_name = "Map";
            }else{
                model_name = getModelName(operationContext);
            }

            String uuid = model_name + "-" + UUID();

            model.setTitle(uuid);

            ModelCache.extra_cache.put(uuid,model);

            addResponseMessage(operationContext,uuid);

            return true;
        }

        return false;
    }


    private boolean isVoid(OperationContext operationContext){
        ResolvedType type = operationContext.getReturnType();

        Class<?> aClass = type.getErasedType();

        return aClass == void.class;
    }

    /**
     * 获取返回值信息的名字
     * @param operationContext
     * @return
     */
    private String getModelName(OperationContext operationContext){
        ResolvedType type = operationContext.getReturnType();

        Class<?> aClass = type.getErasedType();

        ApiModel apiModel = aClass.getAnnotation(ApiModel.class);

        String model_name = null;
        if(apiModel != null){
            model_name = apiModel.value();
        }

        if(model_name==null || "".equals(model_name)){
            model_name = aClass.getSimpleName();
        }


        return model_name;
    }

    /**
     * 为operationContext添加状态为200的返ResponseMessage
     * @param operationContext
     * @param typeName
     */
    private void addResponseMessage(OperationContext operationContext,String typeName){
        ResponseMessage responseMessage = new ResponseMessageBuilder()
                .code(200).responseModel(new ModelRef(typeName))
                .build();

        HashSet<ResponseMessage> responseMessages = new HashSet<>();
        responseMessages.add(responseMessage);


        operationContext.operationBuilder().responseMessages(responseMessages);
    }

    /**
     * 生成UUID
     * @return
     */
    private String UUID(){
        return UUID.randomUUID().toString();
    }

    /**
     * 根据apiResponseObject注解生成一个ModelImpl
     * @param apiResponseObject
     * @return
     */
    private ModelImpl createModel(ApiResponseObject apiResponseObject){

        ModelImpl result = new ModelImpl();

        //apiResponseObject的类型指定是object
        result.setType("object");

        ApiResponseProperty[] properties = apiResponseObject.properties();

        for(ApiResponseProperty apiResponseProperty : properties){

            String name = apiResponseProperty.name();
            String description = apiResponseProperty.description();
            String type = apiResponseProperty.type();

            Property property = null;

            if("string".equalsIgnoreCase(type)){
                property = new StringProperty();
            }else if("int".equalsIgnoreCase(type)){
                property = new IntegerProperty();
            }else if("date".equalsIgnoreCase(type)){
                property = new DateProperty();
            }else if("uuid".equalsIgnoreCase(type)){
                property = new UUIDProperty();
            }else{
                throw new RuntimeException("未支持的类型");
            }

            property.setDescription(description);

            result.property(name,property);

        }

        return result;
    }

}

6、ApiResponseBodyReader

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
208
209
210
211
package com.lsp.config.swagger2;

import com.fasterxml.classmate.ResolvedType;
import com.google.common.base.Optional;
import com.lsp.model.Value2;
import io.swagger.annotations.ApiModel;
import io.swagger.models.ModelImpl;
import io.swagger.models.properties.*;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;

import java.util.HashSet;
import java.util.UUID;

@Component
public class ApiResponseBodyReader implements OperationBuilderPlugin {

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    @Override
    public void apply(OperationContext operationContext) {
        //此方法上有没有ApiResponseObject注解
        boolean apiResponseObjectHandle = false;

        apiResponseObjectHandle = apiResponseObjectHandle(operationContext);

        //若此方法上没有ApiResponseObject注解
        if(!apiResponseObjectHandle){
            apiResponseFields(operationContext);
        }
    }

    /**
     * 处理ApiResponseFields注解
     * @param operationContext
     */
    private void apiResponseFields(OperationContext operationContext){
        Optional<ApiResponseFields> optional = operationContext.findAnnotation(ApiResponseFields.class);

        if(optional.isPresent() && !isVoid(operationContext)){

            ApiResponseFields responseFields = optional.get();

            String model_name =responseFields.modelName();

            if("".equals(model_name)){
                model_name = getModelName(operationContext);
            }

            String uuid = model_name + "-" + UUID();

            String[] fields = responseFields.fields();

            Value2<String, String[]> value2 = new Value2<>(model_name, fields);

            ModelCache.specified_cache.put(uuid,value2);

            addResponseMessage(operationContext,uuid);

        }

    }

    /**
     * 处理ApiResponseObject注解
     * @param operationContext
     * @return 若此方法上有ApiResponseObject注解,则返回true,否则返回false
     */
    private boolean apiResponseObjectHandle(OperationContext operationContext){
        Optional<ApiResponseObject> optional = operationContext.findAnnotation(ApiResponseObject.class);

        if(optional.isPresent()){

            ApiResponseObject apiResponseObject = optional.get();

            ModelImpl model = createModel(apiResponseObject);


            String model_name = null;

            if(isVoid(operationContext)){
                model_name = "Map";
            }else{
                model_name = getModelName(operationContext);
            }

            String uuid = model_name + "-" + UUID();

            model.setTitle(uuid);

            ModelCache.extra_cache.put(uuid,model);

            addResponseMessage(operationContext,uuid);

            return true;
        }

        return false;
    }


    private boolean isVoid(OperationContext operationContext){
        ResolvedType type = operationContext.getReturnType();

        Class<?> aClass = type.getErasedType();

        return aClass == void.class;
    }

    /**
     * 获取返回值信息的名字
     * @param operationContext
     * @return
     */
    private String getModelName(OperationContext operationContext){
        ResolvedType type = operationContext.getReturnType();

        Class<?> aClass = type.getErasedType();

        ApiModel apiModel = aClass.getAnnotation(ApiModel.class);

        String model_name = null;
        if(apiModel != null){
            model_name = apiModel.value();
        }

        if(model_name==null || "".equals(model_name)){
            model_name = aClass.getSimpleName();
        }


        return model_name;
    }

    /**
     * 为operationContext添加状态为200的返ResponseMessage
     * @param operationContext
     * @param typeName
     */
    private void addResponseMessage(OperationContext operationContext,String typeName){
        ResponseMessage responseMessage = new ResponseMessageBuilder()
                .code(200).responseModel(new ModelRef(typeName))
                .build();

        HashSet<ResponseMessage> responseMessages = new HashSet<>();
        responseMessages.add(responseMessage);


        operationContext.operationBuilder().responseMessages(responseMessages);
    }

    /**
     * 生成UUID
     * @return
     */
    private String UUID(){
        return UUID.randomUUID().toString();
    }

    /**
     * 根据apiResponseObject注解生成一个ModelImpl
     * @param apiResponseObject
     * @return
     */
    private ModelImpl createModel(ApiResponseObject apiResponseObject){

        ModelImpl result = new ModelImpl();

        //apiResponseObject的类型指定是object
        result.setType("object");

        ApiResponseProperty[] properties = apiResponseObject.properties();

        for(ApiResponseProperty apiResponseProperty : properties){

            String name = apiResponseProperty.name();
            String description = apiResponseProperty.description();
            String type = apiResponseProperty.type();

            Property property = null;

            if("string".equalsIgnoreCase(type)){
                property = new StringProperty();
            }else if("int".equalsIgnoreCase(type)){
                property = new IntegerProperty();
            }else if("date".equalsIgnoreCase(type)){
                property = new DateProperty();
            }else if("uuid".equalsIgnoreCase(type)){
                property = new UUIDProperty();
            }else{
                throw new RuntimeException("未支持的类型");
            }

            property.setDescription(description);

            result.property(name,property);

        }

        return result;
    }

}

使用方法:

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
package com.lsp.controller;

import com.lsp.config.swagger2.ApiResponseFields;
import com.lsp.config.swagger2.ApiResponseObject;
import com.lsp.config.swagger2.ApiResponseProperty;
import com.lsp.model.IMessage;
import com.lsp.model.Person;
import com.lsp.service.IService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * Created by lsp on 2019/12/22.
 */
@RestController
@Api(description = "用户接口")
public class IController {

    /**
     * 查看用户详情
     * @param id 用户id
     * @return 用户信息
     */
    @GetMapping("/select/{id}")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @ApiOperation(value = "查看用户详情",notes = "使用说明")
    @ApiResponseFields(fields = {"id","firstName","lastName","email","address"})
    public Person select(@ApiParam(name="用户id") @PathVariable int id){
        return null;
    }


    @PutMapping("/put/{id}")
    @ApiOperation(value = "更新用户信息",notes = "使用说明")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id",value = "用户id",paramType = "path",dataType = "MAP_>"),
            @ApiImplicitParam(name = "username",value="用户名",paramType = "query",dataType = "string"),
            @ApiImplicitParam(name = "password",value="密码",paramType = "query",dataType = "string")
    })
    @ApiResponseObject(properties = {
            @ApiResponseProperty(name = "username",description = "用户名",type = "string"),
            @ApiResponseProperty(name = "email",description = "用户邮箱",type = "string"),
            @ApiResponseProperty(name = "address",description = "用户住址",type = "string"),
    })
    public Map<String,Object> put(@ApiIgnore @PathVariable String id,
                        @ApiIgnore @RequestParam java.util.Map<String,Object> params){
        System.out.println(params);
        return null;
    }
}

最终效果

启动项目,访问http://127.0.0.1/swagger-ui.html#/。页面如图:
在这里插入图片描述
点开Models:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到后缀上加了一串uuid的我使用aop注入的信息,没有uuid后缀的则是swagger自己解析的信息。

点开i-controller如图:
在这里插入图片描述
在这里插入图片描述

结语

这个增强真的花了我很长时间才做完,大部分时间都用来找swagger的类信息了。最初很不愿意用aop去做,觉得是个取巧的办法,结果还是真香了哈哈。
我这个实现因为是用所有人都熟悉的aop思路去做的,所以很容易理解。大家也可以发挥自己的聪明才智进行扩展O(∩_∩)O哈哈~。
欢迎留言和点赞!