Swagger使用Map接受参数时,页面如何显示具体参数及说明
1.需求说明
项目为:SpringBoot+Knife+MyBatisPlus
后端使用Map接受参数,要求在swagger页面上显示具体的参数名称、类型及说明
2.解决方案
1.参数数量少
当Map接受参数数量少时,可以使用Swagger自带的注解 @ApiImplicitParams+@ApiImplicitParam,具体如下:
@GetMapping("/list")
@ApiImplicitParams({
@ApiImplicitParam(name = "code", value = "字典编号", paramType = "query", dataType = "string"),
@ApiImplicitParam(name = "dictValue", value = "字典名称", paramType = "query", dataType = "string")
})
@ApiOperationSupport(order = 2)
@ApiOperation(value = "列表", notes = "传入dict")
public R list(@ApiIgnore @RequestParam Map<String, Object> dict) {
}
2.参数数量大且有具体的实体类或DTO
-
自定义注解 @ApiGlobalModel
package com.zjjg.dlbp.config.annotation; /** * @author 石智铭 * @Date 2022-10-10 */ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Swagger扩展注解 * 用于 application/json请求 * 并使用诸如Map或JSONObject等非具体实体类接收参数时,对参数进行进一步描述 */ @Target({ ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiGlobalModel { /** * 字段集合容器 * * @return Global model */ Class<?> component(); /** * 分隔符 * * @return separator */ String separator() default ","; /** * 实际用到的字段 * 可以是字符串数组,也可以是一个字符串 多个字段以分隔符隔开: "id,name" * 注意这里对应的是component里的属性名,但swagger显示的字段名实际是属性注解上的name * * @return value */ String[] value() default { }; }
-
编写处理注解对应的插件
package com.zjjg.dlbp.config; import cn.hutool.core.util.IdUtil; import com.fasterxml.classmate.TypeResolver; import com.zjjg.dlbp.config.annotation.ApiGlobalModel; import io.swagger.annotations.ApiModelProperty; import javassist.*; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.StringMemberValue; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springblade.core.tool.utils.Func; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import springfox.documentation.schema.ModelRef; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.ParameterBuilderPlugin; import springfox.documentation.spi.service.contexts.ParameterContext; import java.util.*; import java.util.stream.Collectors; /** * @author 石智铭 * @Date 2022-10-10 * 将map入参匹配到swagger文档中 * plugin加载顺序,默认是最后加载 */ @Component @Order @AllArgsConstructor public class ApiGlobalModelBuilder implements ParameterBuilderPlugin { private static final Logger logger = LoggerFactory.getLogger(ApiGlobalModelBuilder.class); private final TypeResolver typeResolver; @Override public void apply(ParameterContext context) { try { // 从方法或参数上获取指定注解的Optional Optional<ApiGlobalModel> optional = context.getOperationContext().findAnnotation(ApiGlobalModel.class); if (!optional.isPresent()) { optional = context.resolvedMethodParameter().findAnnotation(ApiGlobalModel.class); } if (optional.isPresent()) { Class originClass = context.resolvedMethodParameter().getParameterType().getErasedType(); String name = originClass.getSimpleName() + "Model" + IdUtil.objectId(); ApiGlobalModel apiAnnotation = optional.get(); String[] fields = apiAnnotation.value(); String separator = apiAnnotation.separator(); ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass(name); ctClass.setModifiers(Modifier.PUBLIC); //处理 javassist.NotFoundException pool.insertClassPath(new ClassClassPath(apiAnnotation.component())); CtClass globalCtClass = pool.getCtClass(apiAnnotation.component().getName()); // 将生成的Class添加到SwaggerModels context.getDocumentationContext() .getAdditionalModels() .add(typeResolver.resolve(createRefModel(fields,separator,globalCtClass,ctClass))); // 修改Json参数的ModelRef为动态生成的class context.parameterBuilder() .parameterType("body") .modelRef(new ModelRef(name)).name(name).description("body"); } } catch (Exception e) { logger.error("@ApiGlobalModel Error",e); } } @Override public boolean supports(DocumentationType delimiter) { return true; } /** * 根据fields中的值动态生成含有Swagger注解的javaBeen modelClass */ private Class createRefModel(String[] fieldValues,String separator,CtClass origin,CtClass modelClass) throws NotFoundException, CannotCompileException, ClassNotFoundException { List<CtField> allField=getAllFields(origin); List<CtField> modelField; if (Func.isEmpty(fieldValues)){ modelField = allField; }else { List<String> mergeField = merge(fieldValues, separator); modelField = allField.stream().filter(e->mergeField.contains(e.getName())).collect(Collectors.toList()); } createCtFields(modelField, modelClass); return modelClass.toClass(); } public void createCtFields(List<CtField> modelField, CtClass ctClass) throws CannotCompileException, ClassNotFoundException, NotFoundException { for (CtField ctField : modelField) { CtField field = new CtField(ClassPool.getDefault().get(ctField.getType().getName()), ctField.getName(), ctClass); field.setModifiers(Modifier.PUBLIC); ApiModelProperty annotation = (ApiModelProperty) ctField.getAnnotation(ApiModelProperty.class); String apiModelPropertyValue = java.util.Optional.ofNullable(annotation).map(s -> s.value()).orElse(""); //添加model属性说明 if (StringUtils.isNotBlank(apiModelPropertyValue)) { 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(apiModelPropertyValue, constPool)); attr.addAnnotation(ann); field.getFieldInfo().addAttribute(attr); } ctClass.addField(field); } } /** * 获取本类及其父类的字段属性 字段属性去重 * @param clazz 当前类对象 * @return 字段数组 */ public List<CtField> getAllFields(CtClass clazz) throws NotFoundException { List<CtField> fieldList = new ArrayList<>(); while (clazz != null){ fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); clazz = clazz.getSuperclass(); } return fieldList.stream().collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<CtField>(Comparator.comparing(CtField::getName))), ArrayList::new) ); } /** * 字符串列表 分隔符 合并 * A{"a","b,c","d"} => B{"a","d","b","c"} * * @param arr arr * @return list */ private List<String> merge(String[] arr, String separator) { List<String> tmp = new ArrayList<>(); Arrays.stream(arr).forEach(s -> { if (s.contains(separator)) { tmp.addAll(Arrays.asList(s.split(separator))); } else { tmp.add(s); } }); return tmp; } }
- 在具体Controller上添加注解
/** * 修改 */ @PostMapping("/update") @ApiOperationSupport(order = 2) @ApiOperation(value = "修改结构物:DLBP-0002-002", notes = "修改结构物:DLBP-0002-002") @ApiGlobalModel(component = SlopeDTO.class) public R update(@RequestBody Map<String,Object> slopeDTO) { return R.status(slopeService.updateSlope(slopeDTO)); }
- 存在问题
- 无法展示实体类中对象属性或对象集合
-