springboot中form-data传值,不用@Requestbody修饰入参对象时,swagger-ui该怎么聚合它的属性

1、环境描述:在springboot+swagger v_2.9.2的环境下
前后端分离,restful风格接口。

2、提问原因:前后端约定使用form-data进行数据传递,后台接口入参很多都是:
public String findPage(String pageNo, String pageSize, Batch entity)这样的,

调试好swagger接口文档后发现:如果将参数Batch用@Requestbody修饰,接口文档中的参数parameter就是聚合显示,如果后台没有使用@Requestbody修饰,那么swagger将递归该参数对象Batch的所有属性,包括其中的Page,具体如下图:

(两种接收参数方式对swagger-ui的影响

图片描述:(不知道图片看的清不)

同一个接口,仅仅只是入参对象的修饰语不同,在swagger上居然区别这么大

左边的swagger上看起来是正常的,网络上很多人都是用这种@Requestbody方式,但是我们约定的是form-data,所以不能使用@Requestbody接收参数。而如果不用,那么swagger文档上看到的都是全部铺开的,不是很方便使用。

3、我尝试过的方法

1.我想过两个方向,一个是修改后台swagger处理数据的拼装逻辑,另一个方向是在页面端修改数据的位置和逻辑,也就是修改api-doc接口返回的数据,

==第一个方向:修改后台swagger处理数据的拼装结构,借鉴了(https://blog.csdn.net/u010579482/article/details/79990536) 中的一个思路,重写子类覆盖swagger主要处理参数数据的ModelAttributeParameterExpander,到目前为止还没有解决。

==第二个方向:修改页面端api-doc接口返回的数据,根据图上两种方式的对比,我们可以发现:

被@Requestbody修饰的入参对象,在swagger-ui的definitions中已经有了一个对象的定义或声明:

被@Requestbody修饰
图片说明

而没有被@Requestbody修饰的返回结果,在swagger-ui里返回结果就直接是在path.post.parameters中平铺在一起,也就是不方便所在,如果入参对象里有子对象,那递归出来就是一大片了。如下图:

图片说明

4、现状:到目前为止,还没有解决这个问题,各位大佬有时间的话教育下小弟,小弟在线等着,还望大佬们不吝赐教,[感激][感激][感激]

明白你的意思了,按照你的意思做了一下,篇幅有限,只展示主要代码

自定义注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FormData {
}

覆盖springfox.documentation.spring.web.readers.operation.OperationParameterReader

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class OperationParameterReaderSub extends OperationParameterReader {
    private final ModelAttributeParameterExpander expander;
    private final EnumTypeDeterminer enumTypeDeterminer;

    @Autowired
    private DocumentationPluginsManager pluginsManager;

    @Autowired
    public OperationParameterReaderSub(
            ModelAttributeParameterExpander expander,
            EnumTypeDeterminer enumTypeDeterminer,
            ApplicationContext applicationContext) {
        super(expander, enumTypeDeterminer);
        this.expander = expander;
        this.enumTypeDeterminer = enumTypeDeterminer;
        /*
         * 删除父类bean定义
         * DocumentationPluginsManager中使用ParameterBuilderPlugin类型获取bean,而不是OperationParameterReader类型
         * 所以@Primary注解并不能覆盖父类
         */
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        defaultListableBeanFactory.removeBeanDefinition("operationParameterReader");
    }

    private boolean shouldExpand(final ResolvedMethodParameter parameter, ResolvedType resolvedParamType) {
        return !parameter.hasParameterAnnotation(RequestBody.class)
                && !parameter.hasParameterAnnotation(FormData.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);

    }

    // 其他省略,直接从父类复制

}

覆盖springfox.documentation.spring.web.readers.operation.OperationModelsProvider

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class OperationModelsProviderSub extends OperationModelsProvider {

    private static final Logger LOG = LoggerFactory.getLogger(OperationModelsProvider.class);
    private final TypeResolver typeResolver;

    @Autowired
    public OperationModelsProviderSub(TypeResolver typeResolver, ApplicationContext applicationContext) {
        super(typeResolver);
        this.typeResolver = typeResolver;
        /*
         * 删除父类bean定义
         * DocumentationPluginsManager中使用OperationModelsProviderPlugin类型获取bean,而不是OperationModelsProvider类型
         * 所以@Primary注解并不能覆盖父类
         */
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        defaultListableBeanFactory.removeBeanDefinition("operationModelsProvider");
    }

    private void collectParameters(RequestMappingContext context) {
        LOG.debug("Reading parameters models for handlerMethod |{}|", context.getName());

        List<ResolvedMethodParameter> parameterTypes = context.getParameters();
        for (ResolvedMethodParameter parameterType : parameterTypes) {
            if (parameterType.hasParameterAnnotation(RequestBody.class)
                    || parameterType.hasParameterAnnotation(FormData.class) // 加上自定义的注解
                    || parameterType.hasParameterAnnotation(RequestPart.class)) {
                ResolvedType modelType = context.alternateFor(parameterType.getParameterType());
                LOG.debug("Adding input parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
                context.operationModelsBuilder().addInputParam(modelType);
            }
        }
        LOG.debug("Finished reading parameters models for handlerMethod |{}|", context.getName());
    }

    // 其他省略,直接从父类复制

}

示例controller

像使用RequestBody一样使用FromData,只变文档,没有其他副作用

    @GetMapping
    @ApiOperation(value = "测试")
    public String test(@FormData User user) {
        System.out.println(user);
        return "随便返回点什么";
    }

既然想要自己组装的返回格式又不想用@RequestBody,那就自己在后台写个json的组装工具然后通过response.getWriter().write(json);写出去不就行了

我使用的是springfox-swagger2,2.2.2版本
可以实现Swagger官方提供的插件:https://springfox.github.io/springfox/docs/snapshot/#plugins
我尝试了实现OperationBuilderPlugin,ParameterBuilderPlugin,OperationModelsProviderPlugin并在其中添加自定义的注解;
最后只留下了实现OperationModelsProviderPlugin的方法,这样可以展开DTO里的参数,和RequestBody效果相同;
不过我没有深入去了解这些,功能实现了就OK了,有空写一篇博客记录一下

请问解决了吗?遇到了相同的问题

大佬我也碰到这样的问题,请问有解决吗?

不用@Requestbody,用@ModelAttribute可以么

我们公司是这么处理的,新建一个param包,专门用来存放接收参数的实体类,然后用@ApiModel,@ApiModelProperty加文档注释,不知道这样不用@RequestBody是什么样,没试过