swagger2 同一个实体用在多个不同的controller接口展示不同的字段

情景

同一个实体用在多个不同的controller接口展示不同的字段,如果用过spring Validation校验框架应该都懂它里面有个分组概念,作用就是同一个实体用在多个接口里面但是校验参数不同,但swagger并没有分组概念,swagger显然做不到类似作用,ApiModelProperty注解的有个hidden属性,但这个作用只能要么全显示要么全隐藏,那下面的方法就是针对同一个实体在一个接口的某些属性是隐藏,在另一个接口中是显示的这种情况。

解决方法

1.依赖

<!--swagger2-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.8.0</version>
</dependency>
<!--swagger2 UI-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

2.自定义注解

添加ApiIgp和ApiNeed自定义注解。
ApiIgp排除实体类中不显示在swagger文档的属性
ApiNeed是只显示实体类中的某些属性

tip:这两个注解都只能操作实体的属性,如果实体中不存在的属性不会报错也不会生效

ApiIgp

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

/**
 * 自定义aop注解 支持swagger的动态属性 排除属性
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIgp {
    String[] value(); //对象属性值
}

ApiNeed

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

/**
 * 自定义aop注解 支持swagger的动态属性 (只)需要属性
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiNeed {
    //对象属性值
    String[] value();
}

3.重写swagger

添加两个配置类 MyParameterBuilderPlugin和MyOperationBuilderPlugin。一个大概针对的是json数据,另一个是非json数据

MyParameterBuilderPlugin

import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.honger1234.annotation.ApiIgp;
import com.honger1234.annotation.ApiNeed;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute;
import org.apache.ibatis.javassist.bytecode.ConstPool;
import org.apache.ibatis.javassist.bytecode.annotation.Annotation;
import org.apache.ibatis.javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
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.Random;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 重写 swagger2 的 ParameterBuilderPlugin 支持自定义白名单黑名单注解
 * 局限:只能对post中的json数据生效,如果要对get的拼接参数请看MyOperationBuilderPlugin
 */
@Component
@Order
@Slf4j
public class MyParameterBuilderPlugin implements ParameterBuilderPlugin {
    @Autowired
    private TypeResolver typeResolver;

    @Override
    public void apply(ParameterContext parameterContext) {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
        Class<?> originClass = parameterContext.resolvedMethodParameter().getParameterType().getErasedType();

        // 排除属性
        ApiIgp igpOptional = null;
        @SuppressWarnings("Guava")
        Optional<ApiIgp> apiIgpOptional = methodParameter.findAnnotation(ApiIgp.class);
        if ( apiIgpOptional.isPresent() ) {
            igpOptional = apiIgpOptional.get();
        }

        // 需要属性
        ApiNeed needOptional = null;
        @SuppressWarnings("Guava")
        Optional<ApiNeed> apiNeedOptional = methodParameter.findAnnotation(ApiNeed.class);
        if ( apiNeedOptional.isPresent() ) {
            needOptional = apiNeedOptional.get();
        }

        if (null != igpOptional || null != needOptional ) {
            Random random = new Random();
            //model 名称
            String name = originClass.getSimpleName() + "_" + UUID.randomUUID().toString().replace("-", "");
            try {
                // 排除 (黑名单)
                if ( null != igpOptional ) {
                    String[] properties = igpOptional.value();
                    parameterContext.getDocumentationContext()
                            .getAdditionalModels()
                            //向documentContext的Models中添加我们新生成的Class
                            .add(typeResolver.resolve(createRefModelIgp(properties, originClass.getPackage()+"."+name, originClass)));
                }
                // 需要 (白名单)
                if ( null != needOptional ) {
                    String[] properties = needOptional.value();
                    parameterContext.getDocumentationContext()
                            .getAdditionalModels()
                            //向documentContext的Models中添加我们新生成的Class
                            .add(typeResolver.resolve(createRefModelNeed(properties, originClass.getPackage()+"."+name, originClass)));
                }
            } catch (Exception e) {
                log.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);
        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) {
            log.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> 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) {
            log.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;
    }
}

MyOperationBuilderPlugin

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.honger1234.annotation.ApiIgp;
import com.honger1234.annotation.ApiNeed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
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.Operation;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Lists.newArrayList;
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;

/**
 * 重写 swagger2 的 ModelAttributeParameterExpander 支持get请求的自定义注解
 * 局限:json数据不生效
 */
@SuppressWarnings({"Guava", "rawtypes"})
@Component
@Order
@Slf4j
public class MyOperationBuilderPlugin extends OperationParameterReader implements OperationBuilderPlugin {

    private final EnumTypeDeterminer enumTypeDeterminer;
    private final ModelAttributeParameterExpander expander;

    private Boolean changed;

    @Autowired
    private DocumentationPluginsManager pluginsManager;

    @Autowired
    public MyOperationBuilderPlugin(ModelAttributeParameterExpander expander, EnumTypeDeterminer enumTypeDeterminer) {
        super(expander, enumTypeDeterminer);
        this.enumTypeDeterminer = enumTypeDeterminer;
        this.expander = expander;
    }


    /**
     * Implement this method to override the Operation using the OperationBuilder available in the context
     *
     * @param context - context that can be used to override the parameter attributes
     * @see Operation
     * @see OperationBuilder
     */
    @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 ) {
                log.error("动态更改swagger参数错误", e);
            }
        }
        context.operationBuilder().parameters(context.getGlobalOperationParameters());
    }

    @Override
    public boolean supports(DocumentationType documentationType) {
        return super.supports(documentationType);
    }


    @SuppressWarnings("Guava")
    private List<Parameter> readParameters(final OperationContext context) {

        List<ResolvedMethodParameter> methodParameters = context.getParameters();
        List<Parameter> parameters = newArrayList();

        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.getDocumentationContext()));
                } else {
                    tempItems = new ArrayList<>(Collections.singleton(pluginsManager.parameter(parameterContext)));
//                    tempItems = new ArrayList<>();
                }

                // 判断遍历 是否有自定义注解 有就进行操作
                Optional<ApiIgp> apiIgpOptional = methodParameter.findAnnotation(ApiIgp.class);
                if ( apiIgpOptional.isPresent() ) {
                    String[] properties = apiIgpOptional.get().value();
                    tempItems = tempItems.stream().filter(parameter -> {

                        for (String property : properties) {
                            // 匹配黑名单
                            if ( property.equals(parameter.getName())
                                    || parameter.getName().startsWith(property + ".") ) {
                                return false;
                            }
                        }
                        return true;
                    }).collect(Collectors.toList());
                    changed = true;
                }

                Optional<ApiNeed> apiNeedOptional = methodParameter.findAnnotation(ApiNeed.class);
                if ( apiNeedOptional.isPresent() ) {
                    String[] properties = apiNeedOptional.get().value();
                    tempItems = tempItems.stream().filter(parameter -> {
                        for (String property : properties) {
                            // 匹配 白名单
                            if ( property.equals(parameter.getName())
                                    || 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);

    }
}

测试

实体

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class UseInfo {

    @ApiModelProperty(value = "用户名")
    private String username;
    @ApiModelProperty(value = "密码",hidden = true)
    private String password;
    @ApiModelProperty(value = "年龄")
    private Integer age;
}

接口

@ApiOperation(value = "排除属性注解")
@GetMapping("/swaggerTest1")
public String swaggerTest1(@ApiIgp ({"password"})@RequestBody UseInfo useInfo){
	return "success";
}

@ApiOperation(value = "只需要属性注解")
@GetMapping("/swaggerTest2")
public String swaggerTest2(@ApiNeed({"username","age"}) UseInfo useInfo){
	return "success";
}

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值