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

本贴最后更新于 1603 天前,其中的信息可能已经沧海桑田

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/Hpluvalbe/article/details/107102063
问题描述

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.ServiceModelToSwagger2MapperImpl.mapDocumentation

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

代码实现

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

  1. ApiResponseBodyReader
package com.lsp.config.swagger; import com.fasterxml.classmate.ResolvedType; import com.google.common.base.Optional; import com.lsp.entity.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; /** * 注册一个OperationBuilderPlugin * @author lsp */ @Component public class ApiResponseBodyReader implements OperationBuilderPlugin { /** * 是否支持此类DocumentationType * @param delimiter 当前文档类型 * @return 如果支持当前类型,则返回true */ @Override public boolean supports(DocumentationType delimiter) { return true; } /** * 如果有需要,可对operationContext执行操作 * @param operationContext 方法上下文 */ @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; } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. ApiResponseFields
package com.lsp.config.swagger; 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(); } 12345678910111213141516
  1. ApiResponseObject
package com.lsp.config.swagger; 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(); } 12345678910111213141516171819
  1. ApiResponseProperty
package com.lsp.config.swagger; public @interface ApiResponseProperty { String name(); String description() default ""; String type(); } 123456789101112
  1. ModelCache
package com.lsp.config.swagger; import com.lsp.entity.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<>(); } 12345678910111213141516
  1. SwaggerAop
package com.lsp.config.swagger; import io.swagger.models.Model; import io.swagger.models.ModelImpl; import io.swagger.models.Swagger; import io.swagger.models.properties.Property; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import java.util.Map; @Aspect @Component public class SwaggerAop { @Pointcut(value = "execution(public * springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl.mapDocumentation(..))") public void point(){ } @Around("point()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Swagger swagger = (Swagger) proceedingJoinPoint.proceed(); Map<String, Model> returnValue = swagger.getDefinitions() ; returnValue.putAll(ModelCache.extra_cache); ModelCache.specified_cache.entrySet().forEach(e->{ String key = e.getKey(); String model_name = e.getValue().v1; String[] fields = e.getValue().v2; Model model = returnValue.get(model_name); if(model == null){ throw new RuntimeException("不存在的类型"+model_name); } Map<String, Property> properties = model.getProperties(); ModelImpl newModel = new ModelImpl(); newModel.setDescription(model.getDescription()); for(String field : fields){ Property property = properties.get(field); if(property == null){ throw new RuntimeException("不存在的属性"+field); } newModel.property(field,property); } returnValue.put(key,newModel); }); return swagger; } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. Value2
package com.lsp.entity; /** * 存放多个实例的便捷类 */ public class Value2<T,V> { public final T v1; public final V v2; public Value2(T v1, V v2) { this.v1 = v1; this.v2 = v2; } } 1234567891011121314151617

使用方法:

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; } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
最终效果

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

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

结语

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

补充:项目地址

  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
zhaozhizheng
没有人会关心你付出过多少努力,撑得累不累,摔得痛不痛,他们只会看你最后站在什么位置,然后羡慕或者鄙夷 北京