Knife4j/Swagger2 忽略实体类或 List 属性,含接收 MyBatis Plus 分页 Page 参数、返回 Page 对象写法
import cn.hutool.core.util.ReflectUtil;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.members.ResolvedField;
import com.fasterxml.classmate.members.ResolvedMember;
import com.fasterxml.classmate.members.ResolvedMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.Maps;
import springfox.documentation.schema.Types;
import springfox.documentation.schema.property.bean.AccessorsProvider;
import springfox.documentation.schema.property.field.FieldProvider;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.schema.AlternateTypeProvider;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.readers.parameter.ExpansionContext;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeField;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterMetadataAccessor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static com.irms.config.swagger.ParameterTypeDeterminer.determineScalarParameterType;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.springframework.util.StringUtils.isEmpty;
import static springfox.documentation.schema.Collections.collectionElementType;
import static springfox.documentation.schema.Collections.isContainerType;
import static springfox.documentation.schema.Types.isVoid;
import static springfox.documentation.schema.Types.typeNameFor;
/**
* 用于覆盖 ModelAttributeParameterExpander,大部分为源码,切记升级 swagger 版本后需重新修改源码
*/
@Primary
@Component
public class MyModelAttributeParameterExpander extends ModelAttributeParameterExpander {
private static final Logger LOG = LoggerFactory.getLogger(ModelAttributeParameterExpander.class);
private final FieldProvider fields;
private final AccessorsProvider accessors;
private final EnumTypeDeterminer enumTypeDeterminer;
@Autowired
private DocumentationPluginsManager pluginsManager;
@Autowired
public MyModelAttributeParameterExpander(
FieldProvider fields,
AccessorsProvider accessors,
EnumTypeDeterminer enumTypeDeterminer) {
// 加了一行 super
super(fields, accessors, enumTypeDeterminer);
this.fields = fields;
this.accessors = accessors;
this.enumTypeDeterminer = enumTypeDeterminer;
}
public List<Parameter> expand(ExpansionContext context) {
List<Parameter> parameters = new ArrayList<>();
Set<PropertyDescriptor> propertyDescriptors = propertyDescriptors(context.getParamType().getErasedType());
Map<Method, PropertyDescriptor> propertyLookupByGetter
= propertyDescriptorsByMethod(context.getParamType().getErasedType(), propertyDescriptors);
Iterable<ResolvedMethod> getters = accessors.in(context.getParamType()).stream()
.filter(onlyValidGetters(propertyLookupByGetter.keySet())).collect(toList());
Map<String, ResolvedField> fieldsByName =
StreamSupport.stream(this.fields.in(context.getParamType()).spliterator(), false)
.collect(toMap((ResolvedMember::getName), identity()));
LOG.debug("Expanding parameter type: {}", context.getParamType());
final AlternateTypeProvider alternateTypeProvider = context.getDocumentationContext().getAlternateTypeProvider();
List<ModelAttributeField> attributes =
allModelAttributes(
propertyLookupByGetter,
getters,
fieldsByName,
alternateTypeProvider);
attributes.stream()
.filter(simpleType().negate())
.filter(recursiveType(context).negate())
.forEach((each) -> {
LOG.debug("Attempting to expand expandable property: {}", each.getName());
parameters.addAll(
expand(
context.childContext(
nestedParentName(context.getParentName(), each),
each.getFieldType(),
context.getOperationContext())));
});
Stream<ModelAttributeField> collectionTypes = attributes.stream()
.filter(isCollection().and(recursiveCollectionItemType(context.getParamType()).negate()));
collectionTypes.forEachOrdered((each) -> {
LOG.debug("Attempting to expand collection/array field: {}", each.getName());
ResolvedType itemType = collectionElementType(each.getFieldType());
if (Types.isBaseType(itemType) || enumTypeDeterminer.isEnum(itemType.getErasedType())) {
parameters.add(simpleFields(context.getParentName(), context, each));
} else {
ExpansionContext childContext = context.childContext(
nestedParentName(context.getParentName(), each),
itemType,
context.getOperationContext());
if (!context.hasSeenType(itemType)) {
parameters.addAll(expand(childContext));
}
}
});
Stream<ModelAttributeField> simpleFields = attributes.stream().filter(simpleType());
simpleFields.forEach((each) -> {
parameters.add(simpleFields(context.getParentName(), context, each));
});
return parameters.stream()
.filter(((Predicate<Parameter>) Parameter::isHidden).negate())
.filter(voidParameters().negate())
.collect(toList());
}
private List<ModelAttributeField> allModelAttributes(
Map<Method, PropertyDescriptor> propertyLookupByGetter,
Iterable<ResolvedMethod> getters,
Map<String, ResolvedField> fieldsByName,
AlternateTypeProvider alternateTypeProvider) {
Stream<ModelAttributeField> modelAttributesFromGetters = StreamSupport.stream(getters.spliterator(), false)
.map(toModelAttributeField(fieldsByName, propertyLookupByGetter, alternateTypeProvider));
Stream<ModelAttributeField> modelAttributesFromFields = fieldsByName.values().stream()
.filter(ResolvedMember::isPublic)
.map(toModelAttributeField(alternateTypeProvider));
return Stream.concat(
modelAttributesFromFields,
modelAttributesFromGetters)
.collect(toList());
}
private Function<ResolvedField, ModelAttributeField> toModelAttributeField(
final AlternateTypeProvider alternateTypeProvider) {
return input -> new ModelAttributeField(
alternateTypeProvider.alternateFor(input.getType()),
input.getName(),
input,
input);
}
private Predicate<Parameter> voidParameters() {
return input -> isVoid(input.getType().orElse(null));
}
private Predicate<ModelAttributeField> recursiveCollectionItemType(final ResolvedType paramType) {
return input -> Objects.equals(collectionElementType(input.getFieldType()), paramType);
}
private Parameter simpleFields(
String parentName,
ExpansionContext context,
ModelAttributeField each) {
LOG.debug("Attempting to expand field: {}", each);
String dataTypeName = ofNullable(typeNameFor(each.getFieldType().getErasedType()))
.orElse(each.getFieldType().getErasedType().getSimpleName());
LOG.debug("Building parameter for field: {}, with type: ", each, each.getFieldType());
ParameterExpansionContext parameterExpansionContext = new ParameterExpansionContext(
dataTypeName,
parentName,
// '' 在 '' 中不为 public。无法从外部软件包访问
determineScalarParameterType(
context.getOperationContext().consumes(),
context.getOperationContext().httpMethod()),
new ModelAttributeParameterMetadataAccessor(
each.annotatedElements(),
each.getFieldType(),
each.getName()),
context.getDocumentationContext().getDocumentationType(),
new ParameterBuilder());
return pluginsManager.expandParameter(parameterExpansionContext);
}
private Predicate<ModelAttributeField> recursiveType(final ExpansionContext context) {
return input -> context.hasSeenType(input.getFieldType());
}
private Predicate<ModelAttributeField> simpleType() {
return isCollection().negate().and(isMap().negate())
.and(
belongsToJavaPackage()
.or(isBaseType())
.or(isEnum()));
}
private Predicate<ModelAttributeField> isCollection() {
return input -> isContainerType(input.getFieldType());
}
private Predicate<ModelAttributeField> isMap() {
return input -> Maps.isMapType(input.getFieldType());
}
private Predicate<ModelAttributeField> isEnum() {
return input -> enumTypeDeterminer.isEnum(input.getFieldType().getErasedType());
}
private Predicate<ModelAttributeField> belongsToJavaPackage() {
return input -> ClassUtils.getPackageName(input.getFieldType().getErasedType()).startsWith("");
}
private Predicate<ModelAttributeField> isBaseType() {
return input -> Types.isBaseType(input.getFieldType())
|| input.getFieldType().isPrimitive();
}
private Function<ResolvedMethod, ModelAttributeField> toModelAttributeField(
final Map<String, ResolvedField> fieldsByName,
final Map<Method, PropertyDescriptor> propertyLookupByGetter,
final AlternateTypeProvider alternateTypeProvider) {
return input -> {
String name = propertyLookupByGetter.get(input.getRawMember()).getName();
return new ModelAttributeField(
fieldType(alternateTypeProvider, input),
name,
input,
fieldsByName.get(name));
};
}
private Predicate<ResolvedMethod> onlyValidGetters(final Set<Method> methods) {
return input -> methods.contains(input.getRawMember());
}
private String nestedParentName(String parentName, ModelAttributeField attribute) {
String name = attribute.getName();
ResolvedType fieldType = attribute.getFieldType();
if (isContainerType(fieldType) && !Types.isBaseType(collectionElementType(fieldType))) {
name += "[0]";
}
if (isEmpty(parentName)) {
return name;
}
return String.format("%s.%s", parentName, name);
}
private ResolvedType fieldType(AlternateTypeProvider alternateTypeProvider, ResolvedMethod method) {
return alternateTypeProvider.alternateFor(method.getType());
}
private Set<PropertyDescriptor> propertyDescriptors(final Class<?> clazz) {
try {
// 跳过有 @MyApiIgnore 注解的属性
List<PropertyDescriptor> propertyDescriptorList = new ArrayList<>();
for (PropertyDescriptor descriptor : getBeanInfo(clazz).getPropertyDescriptors()) {
// 此处获取属性可以自己写,主要是要能获取父类属性
Field field = ReflectUtil.getField(clazz, descriptor.getName());;
if (field != null && field.isAnnotationPresent(MyApiIgnore.class)) {
continue;
}
propertyDescriptorList.add(descriptor);
}
return new HashSet<>(propertyDescriptorList);
} catch (IntrospectionException e) {
LOG.warn(String.format("Failed to get bean properties on (%s)", clazz), e);
}
return emptySet();
}
private Map<Method, PropertyDescriptor> propertyDescriptorsByMethod(
final Class<?> clazz,
Set<PropertyDescriptor> propertyDescriptors) {
return propertyDescriptors.stream()
.filter(input -> input.getReadMethod() != null
&& !clazz.isAssignableFrom(Collection.class)
&& !"isEmpty".equals(input.getReadMethod().getName()))
.collect(toMap(PropertyDescriptor::getReadMethod, identity()));
}
BeanInfo getBeanInfo(Class<?> clazz) throws IntrospectionException {
return Introspector.getBeanInfo(clazz);
}
public DocumentationPluginsManager getPluginsManager() {
return pluginsManager;
}
public void setPluginsManager(DocumentationPluginsManager pluginsManager) {
this.pluginsManager = pluginsManager;
}
}