Swagger2接口参数实体类字段自定义显示

使用swagger文档的时候发现实体类参数每次都是全部显示出来,导致和前端对接很麻烦,就产生了想要自定义的实体类参数的想法,于是在百度了很久,找到了flymoringbird大神的文章Swagger2 自定义注解 :解决一个简单的model类 适用于controller的多个方法,参考了一下发现只能实现body传参,query传参还是没有办法实现,于是又开始了百度,找到了x-easy大神swagger2 同一个实体用在多个不同的controller接口展示不同的字段的文章,最终实现了功能。
话不多说,直接上代码:

pom.xml

这里只贴出了最重要的类,其他的还请自行查找

 <properties><swagger.version>2.9.2</swagger.version><swagger-models.version>1.6.2</swagger-models.version><transmittable.version>2.12.1</transmittable.version></properties><!--swagger2--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version><exclusions><exclusion><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId></exclusion></exclusions></dependency><!-- 防止进入swagger页面报类型转换错误For input string: "",排除swagger-models引用, ,手动增加1.6.2版本 --><dependency><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId><version>${swagger-models.version}</version></dependency><!--swagger2-ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><!--注意这里用的是alibaba 的 javassist 的包最好--><dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>${transmittable.version}</version></dependency>

自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author 李永杰* @date 2021/10/11* @description swagger ApiModel参数排除字段*/@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiExclude {/*** 忽略的属性值*/String[] value() default {};
}import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author 李永杰* @date 2021/10/11* @description swagger ApiModel参数包含字段*/
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiInclude {/*** 需要的属性值*/String[] value() default {};
}

测试参数实体类

/*** @author 李永杰* @date 2021/11/4* @description*/
@Data
@ApiModel("测试参数")
public class Demo {@ApiModelProperty("参数A")private String a;@ApiModelProperty("参数B")private String b;@ApiModelProperty("参数C")private String c;@ApiModelProperty("参数D")private String d;
}

静态常量

 /*** swagger 自定义model 前缀*/public static String MY_MODEL_NAME_PRE="SWAGGER";

最重要的两个plugin类

下面这个是解决body传参的

import cn.hutool.core.util.IdUtil;
import com.alibaba.ttl.threadpool.agent.internal.javassist.*;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.AnnotationsAttribute;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.ConstPool;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.annotation.Annotation;
import com.alibaba.ttl.threadpool.agent.internal.javassist.bytecode.annotation.StringMemberValue;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.ruoyi.common.annotation.ApiExclude;
import com.ruoyi.common.annotation.ApiInclude;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;/*** @author 李永杰* @date 2021/11/05* @description 读取自定义注解的dto属性并动态生成model, 只支持body参数(注意这里用的是alibaba的 javassist 的包最好)*/
@Order
@Configuration
public class SwaggerBodyParameterPlugin implements ParameterBuilderPlugin {private static final Logger logger = LoggerFactory.getLogger(SwaggerBodyParameterPlugin.class);@Autowiredprivate TypeResolver typeResolver;@Overridepublic void apply(ParameterContext parameterContext) {ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();Class<?> originClass = parameterContext.resolvedMethodParameter().getParameterType().getErasedType();// 排除属性ApiExclude apiExclude = null;@SuppressWarnings("Guava")Optional<ApiExclude> apiExcludeOptional = methodParameter.findAnnotation(ApiExclude.class);if (apiExcludeOptional.isPresent()) {apiExclude = apiExcludeOptional.get();}// 需要属性ApiInclude apiInclude = null;@SuppressWarnings("Guava")Optional<ApiInclude> apiIncludeOptional = methodParameter.findAnnotation(ApiInclude.class);if (apiIncludeOptional.isPresent()) {apiInclude = apiIncludeOptional.get();}if (null != apiExclude || null != apiInclude) {//model名称,注意名称不能一样String name = Constants.MY_MODEL_NAME_PRE.concat(originClass.getSimpleName()).concat(IdUtil.objectId());try {// 排除 (黑名单)if (null != apiExclude) {String[] properties = apiExclude.value();parameterContext.getDocumentationContext().getAdditionalModels()//向documentContext的Models中添加我们新生成的Class.add(typeResolver.resolve(createRefModelIgp(properties, originClass.getPackage() + "." + name, originClass)));}// 需要 (白名单)if (null != apiInclude) {String[] properties = apiInclude.value();parameterContext.getDocumentationContext().getAdditionalModels()//向documentContext的Models中添加我们新生成的Class.add(typeResolver.resolve(createRefModelNeed(properties, originClass.getPackage() + "." + name, originClass)));}} catch (Exception e) {e.printStackTrace();logger.error("swagger切面异常", e);}//修改Map参数的ModelRef为我们动态生成的classparameterContext.parameterBuilder().parameterType("body").modelRef(new ModelRef(name)).name(name);}}/*** 创建自定义mode给swagger2 排除参数** @param properties 需要排除的参数* @param name       model 名称* @param origin     originClass* @return r*/private Class<?> createRefModelIgp(String[] properties, String name, Class<?> origin) {ClassPool pool = ClassPool.getDefault();// 动态创建一个classCtClass ctClass = pool.makeClass(name);if (ctClass != null) {// 这行代码很重要 如果没有的话 那就不能和devTool 一起使用// 销毁旧的类ctClass.detach();}try {Field[] fields = origin.getDeclaredFields();List<Field> fieldList = Arrays.asList(fields);List<String> ignoreProperties = Arrays.asList(properties);// 过滤掉 properties 的参数List<Field> dealFields = fieldList.stream().filter(s -> !ignoreProperties.contains(s.getName())).collect(Collectors.toList());addField2CtClass(dealFields, origin, ctClass);return ctClass.toClass();} catch (Exception e) {logger.error("swagger切面异常", e);return null;}}/*** 创建自定义mode给swagger2 需要参数** @param properties 需要的参数* @param name       model 名称* @param origin     originClass* @return r*/private Class<?> createRefModelNeed(String[] properties, String name, Class<?> origin) {ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass(name);try {Field[] fields = origin.getDeclaredFields();List<Field> fieldList = Arrays.asList(fields);List<String> needProperties = Arrays.asList(properties);// 过滤掉 非 properties 的参数List<Field> dealFields = fieldList.stream().filter(s -> needProperties.contains(s.getName())).collect(Collectors.toList());addField2CtClass(dealFields, origin, ctClass);return ctClass.toClass();} catch (Exception e) {logger.error("swagger切面异常", e);return null;}}private void addField2CtClass(List<Field> dealFields, Class<?> origin, CtClass ctClass) throws NoSuchFieldException, NotFoundException, CannotCompileException {// 倒序遍历for (int i = dealFields.size() - 1; i >= 0; i--) {Field field = dealFields.get(i);CtField ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);ctField.setModifiers(Modifier.PUBLIC);ApiModelProperty ampAnno = origin.getDeclaredField(field.getName()).getAnnotation(ApiModelProperty.class);String attributes = java.util.Optional.ofNullable(ampAnno).map(ApiModelProperty::value).orElse("");//添加model属性说明if (StringUtils.isNotBlank(attributes)) {ConstPool constPool = ctClass.getClassFile().getConstPool();AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);ann.addMemberValue("value", new StringMemberValue(attributes, constPool));attr.addAnnotation(ann);ctField.getFieldInfo().addAttribute(attr);}ctClass.addField(ctField);}}@Overridepublic boolean supports(DocumentationType documentationType) {return true;}}

下面这个是解决query传参的

import com.fasterxml.classmate.ResolvedType;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.ruoyi.common.annotation.ApiExclude;
import com.ruoyi.common.annotation.ApiInclude;
import com.ruoyi.common.constant.Constants;
import org.apache.poi.ss.formula.functions.T;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import springfox.documentation.builders.BuilderDefaults;
import springfox.documentation.builders.OperationBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.readers.operation.OperationParameterReader;
import springfox.documentation.spring.web.readers.parameter.ExpansionContext;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander;import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;import static com.google.common.base.Predicates.not;
import static springfox.documentation.schema.Collections.isContainerType;
import static springfox.documentation.schema.Maps.isMapType;
import static springfox.documentation.schema.Types.isBaseType;
import static springfox.documentation.schema.Types.typeNameFor;/*** @author 李永杰* @date 2021/11/5* @description 读取自定义注解的dto属性并动态生成model, 只支持query参数*/
@Order
@Configuration
public class SwaggerQueryParameterPlugin extends OperationParameterReader implements OperationBuilderPlugin {private static final Logger logger = LoggerFactory.getLogger(SwaggerQueryParameterPlugin.class);private final EnumTypeDeterminer enumTypeDeterminer;private final ModelAttributeParameterExpander expander;private Boolean changed;@Autowiredprivate DocumentationPluginsManager pluginsManager;public SwaggerQueryParameterPlugin(ModelAttributeParameterExpander expander, EnumTypeDeterminer enumTypeDeterminer) {super(expander, enumTypeDeterminer);this.enumTypeDeterminer = enumTypeDeterminer;this.expander = expander;}@Overridepublic void apply(OperationContext context) {changed = false;List<Parameter> parameters = readParameters(context);if (changed) {// 反射给parameters赋值try {Field parametersField = OperationBuilder.class.getDeclaredField("parameters");parametersField.setAccessible(true);List<Parameter> source = BuilderDefaults.nullToEmptyList(parameters);parametersField.set(context.operationBuilder(), source);} catch (Exception e) {logger.error("动态更改swagger参数错误", e);}}context.operationBuilder().parameters(context.getGlobalOperationParameters());}@Overridepublic boolean supports(DocumentationType delimiter) {return super.supports(delimiter);}@SuppressWarnings("Guava")private List<Parameter> readParameters(final OperationContext context) {List<ResolvedMethodParameter> methodParameters = context.getParameters();List<Parameter> parameters = new ArrayList();for (ResolvedMethodParameter methodParameter : methodParameters) {ResolvedType alternate = context.alternateFor(methodParameter.getParameterType());if (!shouldIgnore(methodParameter, alternate, context.getIgnorableParameterTypes())) {ParameterContext parameterContext = new ParameterContext(methodParameter,new ParameterBuilder(),context.getDocumentationContext(),context.getGenericsNamingStrategy(),context);List<Parameter> tempItems;if (shouldExpand(methodParameter, alternate)) {tempItems = expander.expand(new ExpansionContext("", alternate, context));} else {tempItems = new ArrayList<>(Collections.singleton(pluginsManager.parameter(parameterContext)));}// 判断遍历 是否有自定义注解 有就进行操作Optional<ApiExclude> apiExcludeOptional = methodParameter.findAnnotation(ApiExclude.class);if (apiExcludeOptional.isPresent()) {String[] properties = apiExcludeOptional.get().value();tempItems = tempItems.stream().filter(parameter -> {for (String property : properties) {//匹配 黑名单 注意这里和x-easy大神的判断不一样//去掉了 ||!parameter.getName().contains(Constants.MY_MODEL_NAME_PRE),//测试中发现加了这个判断回导致query排除参数失效,这里是我dedug一步步发现的if (property.equals(parameter.getName())|| parameter.getName().startsWith(property + ".")){return false;}}return true;}).collect(Collectors.toList());changed = true;}Optional<ApiInclude> apiIncludeOptional = methodParameter.findAnnotation(ApiInclude.class);if (apiIncludeOptional.isPresent()) {String[] properties = apiIncludeOptional.get().value();tempItems = tempItems.stream().filter(parameter -> {for (String property : properties) {//匹配 白名单if (property.equals(parameter.getName())|| parameter.getName().contains(Constants.MY_MODEL_NAME_PRE)|| parameter.getName().startsWith(property + ".")) {return true;}}return false;}).collect(Collectors.toList());changed = true;}parameters.addAll(tempItems);}}return FluentIterable.from(parameters).filter(not(hiddenParams())).toList();}private Predicate<Parameter> hiddenParams() {return Parameter::isHidden;}private boolean shouldIgnore(final ResolvedMethodParameter parameter,ResolvedType resolvedParameterType,final Set<Class> ignorableParamTypes) {if (ignorableParamTypes.contains(resolvedParameterType.getErasedType())) {return true;}return FluentIterable.from(ignorableParamTypes).filter(isAnnotation()).filter(parameterIsAnnotatedWithIt(parameter)).size() > 0;}private Predicate<Class> parameterIsAnnotatedWithIt(final ResolvedMethodParameter parameter) {return parameter::hasParameterAnnotation;}private Predicate<Class> isAnnotation() {return Annotation.class::isAssignableFrom;}private boolean shouldExpand(final ResolvedMethodParameter parameter, ResolvedType resolvedParamType) {return !parameter.hasParameterAnnotation(RequestBody.class)&& !parameter.hasParameterAnnotation(RequestPart.class)&& !parameter.hasParameterAnnotation(RequestParam.class)&& !parameter.hasParameterAnnotation(PathVariable.class)&& !isBaseType(typeNameFor(resolvedParamType.getErasedType()))&& !enumTypeDeterminer.isEnum(resolvedParamType.getErasedType())&& !isContainerType(resolvedParamType)&& !isMapType(resolvedParamType);}
}

接口controller类


import com.ruoyi.common.annotation.ApiExclude;
import com.ruoyi.common.annotation.ApiInclude;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;/*** @author 李永杰* @date 2021/10/14* @description 登录模块*/
@Api(tags = "自定义参数测试模块")
@RestController
@RequestMapping("/test")
public class DemoController extends BaseController {@PostMapping("/testExcludeBody")@ApiOperation("body参数测试排除")public AjaxResult testExcludeBody(@ApiExclude({"a", "b"}) @RequestBody Demo demo) {return AjaxResult.success(demo);}@PostMapping("/testIncludeBody")@ApiOperation("body参数测试包含")public AjaxResult testIncludeBody(@ApiInclude({"a", "b"}) @RequestBody Demo demo) {return AjaxResult.success(demo);}@GetMapping("/testExcludeQuery")@ApiOperation("query参数测试排除")public AjaxResult testExcludeQuery(@ApiExclude({"a", "b"}) Demo demo) {return AjaxResult.success(demo);}@GetMapping("/testIncludeQuery")@ApiOperation("query参数测试包含")public AjaxResult testIncludeQuery(@ApiInclude({"a", "b"}) Demo demo) {return AjaxResult.success(demo);}
}

测试结果

body参数测试排除

query参数测试排除

body参数测试包含

query参数测试包含

参考文档

https://www.jianshu.com/p/09a4619fb0f7
https://blog.csdn.net/flymoringbird/article/details/102919322

Swagger实体类参数分组相关推荐

  1. java 调用.net webservice axis2_java利用axis2调用.net写的webservice,传递自定义的实体类参数...

    利用axis2可以很方便的自动生成客户端代码,同时对复杂参数类型的传递也很方便,本文的服务端以.net开发,有一个自定义的实体类作为参数,客户端用java,简单介绍一下利用axis2的wsdl2jav ...

  2. 字符串下划线驼峰映射实体类参数json对象

    前言 有时候通过Feign会接收到老系统发来的实体类,和数据库保持一致,都是下划线的属性名,而你需要驼峰映射,你拿到的只是原始数据,网上很多办法,取字段转换属性名,利用注解映射,或者设置工具类之类的, ...

  3. 关于spring MVC 绑定json字符串与实体类绑定

    1 如果前台传json字符串,后台用@RequestBody 接收 前端 "content-Type":"application/json", 2  前台用fo ...

  4. swagger参数注解,后台使用@RequestBody注解的实体类,但只需要传实体类中的一个属性

    一开始是这个样子的 @ApiOperation(value = "删除用户", notes = "根据用户名删除指定用户", httpMethod = &quo ...

  5. 实体类 接口_spring-boot-route(五)整合Swagger生成接口文档

    目前,大多数公司都采用了前后端分离的开发模式,为了解决前后端人员的沟通问题,后端人员在开发接口的时候会选择使用swagger2来生成对应的接口文档,swagger2提供了强大的页面调试功能,这样可以有 ...

  6. swagger隐藏实体类字段_你还在用 Swagger?试试这个神器!

    Java技术栈 www.javastack.cn 关注优质文章 今天给大家安利一款接口文档生成器--JApiDocs. swagger想必大家都用过吧,非常方便,功能也十分强大.如果要说swagger ...

  7. mybatis参数有list和实体类_Mybatis的几种传参方式,你了解吗?

    目录 前言 单个参数 多个参数 使用索引[不推荐] 使用@Param 使用Map POJO[推荐] List传参 数组传参 总结 前言 前几天恰好面试一个应届生,问了一个很简单的问题:你了解过Myba ...

  8. Spring MVC如何接收浏览器传递来的请求参数--request--形参--实体类封装

    阅读目录 1. 通过HttpServletRequest获得请求参数和数据 2. 处理方法形参名==请求参数名 3. 如果形参名跟请求参数名不一样怎么办呢?用@RequestParam注解 4. 用实 ...

  9. aop+注解 实现对实体类的字段校验_SpringBoot实现通用的接口参数校验

    来自:掘金,作者:cipher 链接:https://juejin.im/post/5af3c25b5188253064651c76 原文链接:http://www.ciphermagic.cn/sp ...

最新文章

  1. 编写高性能Java代码的最佳实践
  2. python中for循环缩进_Python减少循环层次和缩进的技巧分析
  3. 今日头条Marketing API小工具(.Net Core版本)
  4. Java BigDecimal add()方法与示例
  5. 计算机网络实验之局域网应用,计算机网络实验之局域网的配置
  6. linux c开发项目,linux c 服务器开发项目
  7. spring 笔记2:Spring MVC : Did not find handler method for 问题的解决
  8. Android开发之SDCardUtils工具类。java工具详细代码,附源代码。判断SD卡是否挂载等功能...
  9. 二叉树的遍历方法总结与c++实现
  10. 我对“硬盘分区”的愚见
  11. 安卓 webrtc 开启h264 软编解码
  12. (最完美)红米Note 5A的usb调试模式在哪里打开的步骤
  13. linux怎么读取群辉数据,Linux下读取群晖SHR/RAID硬盘组 Data Recovering of Synology DSM SHR/RAID Volume...
  14. VMware Fusion自动调整分辨率的问题
  15. 为什么阿里巴巴禁止开发人员 boolean 类型变量使用 isXXX 来命名?
  16. Educational Codeforces Round 95 (Rated for Div. 2)D. Trash Problem(权值线段树+离散化)
  17. html文件损坏怎么恢复,如何修复损坏Word文档恢复受损文档中的文字
  18. 场景金融丨中国银行潜江分行上线湖北首个“中银E农通”系统
  19. 使用PHP生成Excel文件并发送附件到邮箱
  20. DOS命令下的move路径的问题

热门文章

  1. org module之org-id
  2. 【收藏】FAT文件系统原理——MBR(主引导记录
  3. 斯坦福学长带你深入了解人工智能
  4. [JAVA毕业设计]高校教材征订管理系统源码获取和系统演示
  5. mysql varchar2多少合适_MySQL中varchar最大长度是多少(仅学习)
  6. steam泰坦之旅dlc_《泰坦之旅十周年纪念版》新DLC上线Steam 售价50元
  7. SCL+顺控GRAPH西门子PLC1500 SCL程序 包括PLC程序,触摸屏程序 灌装线程序有配方
  8. gor实现线上HTTP流量复制压测引流
  9. ROS下进行串口通讯
  10. JavaScript -- webAPI 随记02