swagger:处理Map类型的参数,文件上传以及下载

swagger配置

pom.xml
<properties>
	<swagger.version>2.7.0</swagger.version>
	<swagger.bootstrap.version>1.9.5</swagger.bootstrap.version>
</properties>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>${swagger.version}</version>
</dependency>
<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>swagger-bootstrap-ui</artifactId>
	<version>${swagger.bootstrap.version}</version>
</dependency>
配置
package net.sinorock.aj.common.config;


import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;

import static com.google.common.collect.Lists.newArrayList;


@Configuration
@EnableSwagger2
//EnableSwaggerBootstrapUI 开启增强功能,比如文件上传和下载功能~可不开启
@EnableSwaggerBootstrapUI  
public class SwaggerConfig extends WebMvcConfigurerAdapter
{

    @Bean
    public Docket createBaseRestApi()
    {
        ApiInfo apiInfo = new ApiInfoBuilder().title("行政执法系统-base").description(
            "API接口文档公布").termsOfServiceUrl("").version("0.1").build();
        return new Docket(DocumentationType.SWAGGER_2).groupName("APP接口-base").apiInfo(
            apiInfo).select()
            // 加了ApiOperation注解的类,才生成接口文档
         .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
            // 包下的类,才生成接口文档
            .apis(RequestHandlerSelectors.basePackage("net.sinorock.aj.modules.base")).paths(
                PathSelectors.any()).build().securitySchemes(security());
    }

	// 如果有多个,可直接向下写
}
访问项目地址
  http://localhost:8086/aj/doc.html

处理Map参数

自定义注解
package net.sinorock.aj.common.annotation;


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


/**
 * {@code ApiJsonObject} ApiJsonObject 实体对象
 * @author nov
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
    ApiJsonProperty[] value();
    String name();
}

package net.sinorock.aj.common.annotation;


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


/**
 * {@code ApiJsonProperty} ApiJsonProperty 实体属性
 * @author nov
 */
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {

    String key(); // key

    String example() default "";// 示例

    String type() default "string";// 支持string、int、double

    String description() default ""; 参数描述

    boolean required() default true;// 是否必传
}

切面设置
package net.sinorock.aj.common.config;


import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;
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 net.sinorock.aj.common.annotation.ApiJsonObject;
import net.sinorock.aj.common.annotation.ApiJsonProperty;

import java.util.Map;


/**
 * {@code MapReaderForApi} 将map入参匹配到swagger文档的工具类
 * @author nov
 */
@Component
@Order
public class MapReaderForApi implements ParameterBuilderPlugin
{

    /**
     * 类型处理器
     */
    @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
    @Autowired
    private TypeResolver typeResolver;

    /**
     * 动态生成的虚拟DTO Class的包路径
     */
    private final static String BASEPACKAGE = "net.sinorock.aj.modules.dto.";

    @Override
    public void apply(ParameterContext parameterContext)
    {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();

        // 判断是否需要修改对象ModelRef,这里我判断的是Map类型和String类型需要重新修改ModelRef对象
        if (methodParameter.getParameterType().canCreateSubtype(Map.class)
            || methodParameter.getParameterType().canCreateSubtype(String.class))
        {
            // 根据参数上的ApiJsonObject注解中的参数动态生成Class
            @SuppressWarnings("Guava")
            Optional<ApiJsonObject> optional = methodParameter.findAnnotation(ApiJsonObject.class);
            if (optional.isPresent())
            {
                // model 名称
                String name = optional.get().name();
                ApiJsonProperty[] properties = optional.get().value();

                // 向documentContext的Models中添加我们新生成的Class
                parameterContext.getDocumentationContext().getAdditionalModels().add(
                    typeResolver.resolve(createRefModel(properties, name)));

                // 修改Map参数的ModelRef为我们动态生成的class
                parameterContext.parameterBuilder().parameterType("body").modelRef(
                    new ModelRef(name)).name(name);
            }
        }
    }

    /**
     * {@code createRefModel} 
     * <p>根据propertys中的值动态生成含有Swagger注解的javaBeen</p>
     * @author nov
     * @param propertys 属性
     * @param name 名字
     * @return java.lang.Class
     */
    @SuppressWarnings("rawtypes")
    private Class createRefModel(ApiJsonProperty[] propertys, String name)
    {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(BASEPACKAGE + name);

        try
        {
            for (ApiJsonProperty property : propertys)
            {
                ctClass.addField(createField(property, ctClass));
            }
            return ctClass.toClass();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * {@code createField} 
     * <p>根据property的值生成含有swagger apiModelProperty注解的属性</p>
     * @author nov
     * @param property 属性
     * @param ctClass 类
     * @return javassist.CtField
     * @throws NotFoundException 异常
     * @throws CannotCompileException 异常
     */
    private CtField createField(ApiJsonProperty property, CtClass ctClass)
        throws NotFoundException,
        CannotCompileException
    {
        CtField ctField = new CtField(getFieldType(property.type()), property.key(), ctClass);
        ctField.setModifiers(Modifier.PUBLIC);

        ConstPool constPool = ctClass.getClassFile().getConstPool();

        AnnotationsAttribute attr = new AnnotationsAttribute(constPool,
            AnnotationsAttribute.visibleTag);
        Annotation ann = new Annotation("io.swagger.annotations.ApiModelProperty", constPool);
        ann.addMemberValue("value", new StringMemberValue(property.description(), constPool));
        // string类型
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName())))
        {
            ann.addMemberValue("example", new StringMemberValue(property.example(), constPool));
        }

        // int类型
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName())))
        {
            ann.addMemberValue("example",
                new IntegerMemberValue(Integer.parseInt(property.example()), constPool));
        }

        // double类型
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(Double.class.getName())))
        {
            ann.addMemberValue("example",
                new DoubleMemberValue(Double.parseDouble(property.example()), constPool));
        }
        ann.addMemberValue("required", new BooleanMemberValue(property.required(), constPool));

        attr.addAnnotation(ann);
        ctField.getFieldInfo().addAttribute(attr);

        return ctField;
    }

    /**
     * {@code getFieldType} 
     * @author nov
     * @param type 类型
     * @return javassist.CtClass
     * @throws NotFoundException 异常
     */
    private CtClass getFieldType(String type)
        throws NotFoundException
    {
        CtClass fileType = null;
        switch (type)
        {
            case "string":
                fileType = ClassPool.getDefault().get(String.class.getName());
                break;
            case "int":
                fileType = ClassPool.getDefault().get(Integer.class.getName());
                break;
            case "double":
                fileType = ClassPool.getDefault().get(Double.class.getName());
                break;
            default:
                break;
        }
        return fileType;
    }

    @Override
    public boolean supports(DocumentationType delimiter)
    {
        return true;
    }
}

使用示例
/**
     * {@code list} 系统用户列表
     * @author nov
     * @param params 参数
     * @return net.sinorock.aj.common.web.def.R
     */
    @PostMapping("/list")
    @ApiOperation("系统用户列表")
    @ApiOperationSupport(responses = @DynamicResponseParameters(name = "userRD", properties = {
            @DynamicParameter(value = "状态码", name = "code"),
            @DynamicParameter(value = "消息", name = "msg"),
            @DynamicParameter(value = "数据", name = "page.list", dataTypeClass = SysUserEntity.class),
            @DynamicParameter(value = "数据", name = "page")}))
    public R list(@ApiJsonObject(name = "userRequestModel", value = {
            @ApiJsonProperty(key = "isPaging", example = "false", description = "是否分页", required = false),
            @ApiJsonProperty(key = "page", example = "1", description = "页数,不传默认是1", required = false),
            @ApiJsonProperty(key = "limit", example = "10", description = "页大小,不传默认是10", required = false),
            @ApiJsonProperty(key = "username", example = "史祖平", description = "用户姓名", required = false),
            @ApiJsonProperty(key = "cardId", example = "JSZF02090057", description = "证件号", required = false),
            @ApiJsonProperty(key = "deptCode", example = "3202", description = "部门id", required = false),
            @ApiJsonProperty(key = "status", example = "1", description = "状态  0:禁用   1:正常", required = false)}) @RequestBody Map<String, Object> params) {
        PageUtils page = sysUserService.queryPage(params);
        return R.ok().page(page);
    }

说明:
@ApiOperationSupport :属性responses:用于返回的参数说明
@DynamicResponseParameters:注解中name属性相当于给返回的实体的名字,如果重名的话,是会覆盖的
@ApiJsonObject:本人自定义的注解 name也是实体属性的名字,必须唯一。

此切面的作用是在项目启动的时候,根据方法上的注解,在运行时动态生成对象。

效果展示

图一是@ApiJsonObject的作用
图二是@ApiOperationSupport 中responses属性的作用
图三是@ApiOperationSupport中生成实体的位置,可在此查看
图一 @ApiJsonObject的作用
图二是@ApiOperationSupport 中responses属性的作用

@ApiOperationSupport中生成实体的位置

文件上传

@ApiImplicitParams 是swagger的参数说明

	@PostMapping("/upload")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "file", value = "文件流对象", required = true, dataType = "__File", paramType = "form"),
        @ApiImplicitParam(name = "sourceId", value = "来源id,不做处理", paramType = "query")})
    @ApiOperation("附件上传")
    @ApiOperationSupport(responses = @DynamicResponseParameters(name = "accseeoryRUpload", properties = {
        @DynamicParameter(value = "状态码", name = "code"),
        @DynamicParameter(value = "消息", name = "msg"),
        @DynamicParameter(value = "附件路径", name = "path"),
        @DynamicParameter(value = "附件id", name = "accessoryId"),
        @DynamicParameter(value = "来源id", name = "sourceId")}))
    R upload(MultipartFile file,@RequestParam(required = false) String sourceId)
        throws IOException
    {
        String userId = ShiroUtils.getUserId();
        AccessoryEntity accessoryEntity = accessoryService.upload(file, userId);
        return R.ok();
    }

在这里插入图片描述
在这里插入图片描述

文件下载

@GetMapping(value = "/download", produces = "application/octet-stream")
    @ApiOperation("附件下载")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "accessoryId", value = "附件id", required = true, paramType = "query")})
    void download(@RequestParam("accessoryId") String accessoryId, HttpServletResponse response)
        throws IOException
    {
        accessoryService.downLoad(accessoryId, response);
    }

在这里插入图片描述
不过,本人按照官网说明使用中发现其支持下载的内容是有限的。
本人上传pdf,二进制流返回下载之后是txt…
在这里插入图片描述

参数忽略:ignoreParameters

ApiOperationSupport 的 ignoreParameters :有时候某个实体的作为请求实体时,有字段是多余了。为方便前端对接,可以使用ignoreParameters 进行忽略,这样swagger的请求示例会自动忽略。方便前端~

 	@PostMapping("/handleFzysAudit")
    @ApiOperation("法规预审")
    @ApiOperationSupport(ignoreParameters = {"auditBo.submitterUserName", "auditBo.submitDeptName",
            "auditBo.submitTime", "auditBo.handlerUserName", "auditBo.handlerDeptName",
            "auditBo.handlerTime", "auditBo.msgContent", "auditBo.submitterUserId",
            "auditBo.submitDeptId", "auditBo.submitTime", "auditBo.title",
            "auditBo.auditResult"}, responses = @DynamicResponseParameters(name = "auditR", properties = {
            @DynamicParameter(value = "状态码", name = "code"),
            @DynamicParameter(value = "消息", name = "msg")}))
    public R handleFzysAudit(@RequestBody AuditFzshBo auditBo) throws OperaException, IllegalAccessException, InstantiationException {
        //.....
    }

最后附上swagger-ui 官网

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值