Swagger实体类参数分组

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);

    @Autowired
    private TypeResolver typeResolver;

    @Override
    public 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为我们动态生成的class
            parameterContext.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();
        // 动态创建一个class
        CtClass 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);
        }
    }

    @Override
    public 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;

    @Autowired
    private DocumentationPluginsManager pluginsManager;

    public SwaggerQueryParameterPlugin(ModelAttributeParameterExpander expander, EnumTypeDeterminer enumTypeDeterminer) {
        super(expander, enumTypeDeterminer);
        this.enumTypeDeterminer = enumTypeDeterminer;
        this.expander = expander;
    }

    @Override
    public 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());
    }

    @Override
    public 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参数测试排除
body参数测试排除
query参数测试排除
query参数测试排除

body参数测试包含
body参数测试包含

query参数测试包含
query参数测试包含

参考文档

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

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Swagger 中,实体类的属性顺序默认是按照属性名称的字母顺序排序的。如果需要对实体类的属性顺序进行调整,可以通过在实体类中添加 `@JsonPropertyOrder` 注解来实现。以下是一个示例代码: ```java import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonPropertyOrder({"firstName", "lastName", "age"}) public class User { private String firstName; private String lastName; private int age; // 省略 getter 和 setter 方法 } ``` 在上述代码中,我们使用 `@JsonPropertyOrder` 注解来定义实体类属性的顺序。其中,`{"firstName", "lastName", "age"}` 表示属性的顺序,按照该顺序依次排列。需要注意的是,如果一个属性没有在 `@JsonPropertyOrder` 注解中定义顺序,那么它将按照默认顺序排在最后。 在 Java 代码中,我们可以使用 `ObjectMapper` 的 `writeValueAsString` 方法将实体类对象序列化为 JSON 字符串。以下是一个示例代码: ```java import com.fasterxml.jackson.databind.ObjectMapper; public class Main { public static void main(String[] args) throws Exception { User user = new User(); user.setFirstName("John"); user.setLastName("Doe"); user.setAge(30); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(user); System.out.println(json); // {"firstName":"John","lastName":"Doe","age":30} } } ``` 在上述代码中,我们创建了一个 `User` 对象,并使用 `ObjectMapper` 将其序列化为 JSON 字符串。由于我们在 `User` 类中定义了属性的顺序,因此生成的 JSON 字符串中属性的顺序也是按照我们定义的顺序排列的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值