问题描述
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哈哈~。
欢迎留言和点赞!