SpringBoot项目集成swagger

87 阅读6分钟

前言

在日常开发环境中,前后端联调接口是难以避免的,但是我们总是会遇到前后端开发人员关于请求路径,请求对象,响应类型等数据反复沟通的情况,这种条件下沟通成本无限增加。

对于这样的问题,之前一直没有很好的解决方案,直到它的出现,没错...这就是我们今天要讨论的神器:swagger,一款致力于解决接口规范化、标准化、文档化的开源库,一款真正的开发神器。

一:swagger是什么?

Swagger是一款RESTFUL接口的文档在线自动生成+功能测试功能软件。Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务。目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法,参数和模型紧密集成到服务器。

这个解释简单点来讲就是说,swagger是一款可以根据resutful风格生成的生成的接口开发文档,并且支持做测试的一款中间软件。

二:为什么要使用swaager?

2.1:对于后端开发人员来说

  • 不用再手写WiKi接口拼大量的参数,避免手写错误
  • 对代码侵入性低,采用全注解的方式,开发简单
  • 方法参数名修改、增加、减少参数都可以直接生效,不用手动维护
  • 缺点:增加了开发成本,写接口还得再写一套参数配置

2.2:对于前端开发来说

  • 后端定义,生成文档
  • 直接测试接口,实时检查参数和返回值,就可以快速定位是前端还是后端的问题

2.3:对于测试

  • 对于某些没有前端界面UI的功能,可以用它来测试接口
  • 操作简单,不用了解具体代码就可以操作

官方文档:swagger.io/docs/specif…

三、项目集成swagger

3.1:pom文件集成

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

3.2:config配置文件

public abstract class BaseSwaggerConfig {

    /**
     * 自定义Swagger配置
     */
    public abstract SwaggerProperties swaggerProperties();


    @Bean
    public Docket createRestApi() {
        SwaggerProperties swaggerProperties = swaggerProperties();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getApiBasePackage()))
                .paths(PathSelectors.any())
                .build();
        if (swaggerProperties.isEnableSecurity()) {
            docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());
        }
        return docket;
    }

    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactUrl(), swaggerProperties.getContactEmail()))
                .version(swaggerProperties.getVersion())
                .build();
    }

    private List<ApiKey> securitySchemes() {
        //设置请求头信息
        List<ApiKey> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
        result.add(apiKey);
        return result;
    }

    private List<SecurityContext> securityContexts() {
        //设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/*/.*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(pathRegex))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization", authorizationScopes));
        return result;
    }
}
@Slf4j //日志注解
@Configuration //spring管理
@EnableSwagger2 //开启swagger
public class SwaggerConfig extends BaseSwaggerConfig {

    @Override
    public SwaggerProperties swaggerProperties() {
        return SwaggerProperties.builder()
                //扫描包
                .apiBasePackage("com.xx.controller")
                //swagger标题
                .title("xx系统")
                //描述信息
                .description("xx服务")
                //开发版本
                .version("3.5.0")
                //
                .enableSecurity(true)
                .build();
    }

    /**
     * 创建API应用
     * apiInfo() 增加API相关信息
     * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
     * 本例采用指定扫描的包路径来定义指定要建立API的目录。
     *
     * @return
     */

    @Override
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xx.controller"))
                .paths(PathSelectors.any())
                .build();
    }
    /**
     * 创建该API的基本信息(这些基本信息会展现在文档页面中)
     * 访问地址:http://项目实际地址/swagger-ui.html
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("xx系统")
                .description("本地")
                .contact("purchase")
                .version("3.5.0")
                .build();
    }
}

访问图片

image.png

image.png 在线调试

image.png

3.3:注解

3.3.1:model

//对于对象的描述信息,对应在swagger的model页面可以找到
@ApiModel(value = "对象", description = "描述")
public class Model implements Serializable {

    private static final long serialVersionUID = 1L;
    //对象属性--展示内容
    @ApiModelProperty("id")
    private Long id;
}

3.3.2:controller

@RestController
@RequestMapping("xx")
//controller扫描类,注意必须添加tags,描述文本才会展示
@Api(tags = "管理")
public class TestController {

    @Autowired
    private TestService testService;

    @PostMapping("/test")
    //控制器接口描述信息
    @ApiOperation("测试")
    public Demo getTest(@RequestBody @Validated Demo param){
        return testService.test(param);
    }
    
    //响应对象不同编码设置 注意可以统一设置到value里面 ```
    //@ApiResponses(value = { 
    //@ApiResponse(code = 1000, message = "成功"),  
    //@ApiResponse(code = 1001, message = "失败"),
    //@ApiResponse(code = 1002, message = "缺少参数") 
    //})
    @ApiResponses(value = { @ApiResponse(code = 1000, message = "成功"),       
    @ApiResponse(code = 1001, message = "失败"),
    @ApiResponse(code = 1002, response = Film.class,message = "缺少参数") })
    public Demo test1(
    //参数描述 
    @ApiParam("电影名称")
    //参数字段
    @RequestParam("filmName") String filmName,
    //参数描述,是否允许为空
    @ApiParam(value = "分数", allowEmptyValue = true)
    @RequestParam("score") Short score,
    @ApiParam("发布时间")
    //参数字段,是否必填
    @RequestParam(value = "publishTime",required = false) String publishTime,
    @ApiParam("创建者id")
    @RequestParam("creatorId") Long creatorId
    ) {
      xxxx;
    }
    
    //字段数据集合
    @ApiImplicitParams({ 
    //属性
    @ApiImplicitParam(name = "filmName",
            //属性名
            value = "字段名1",
            //属性类型 -- 基础数据类型
            dataType = "String",
            //参数取值类型
            //form 以form表单提交
            //query == @RequrestParam
            //body == @RequestBody
            //path == @PathVarible
            //header 请求头
            paramType = "query",
            required = true), 
    @ApiImplicitParam(name = "id", 
            value = "电影id", 
            dataType = "int", 
            paramType = "query") 
    })
    public ResultModel deleteFilmByNameOrId(HttpServletRequest request) {
        xxxx;
    }
    
    //参数构建为一个请求对象 直接设置描述
    public Demo test3(@ApiParam("请求对象") @RequestBody Demo Demo)    {
        xxxx;
    }
}

以上是基础注解

四、使用swagger需要注意的问题

  • 对于只有一个HttpServletRequest参数的方法,如果参数小于5个,推荐使用 @ApiImplicitParams的方式单独封装每一个参数 参照test2;如果参数大于5个,采用定义一个对象去封装所有参数的属性,然后使用@APiParam的方式 参照test3
  • 默认的访问地址:ip:port/swagger-ui.html#/,但是在shiro中,会拦截所有的请求,必须加上默认访问路径(比如项目中,就是ip:port/context/swagger-ui.html#/),然后登陆后才可以看到
  • 在GET请求中,参数在Body体里面,不能使用@RequestBody。在POST请求,可以使用@RequestBody和@RequestParam,如果使用@RequestBody,对于参数转化的配置必须统一
  • controller必须指定请求类型,否则swagger会把所有的类型(6种)都生成出来
  • swagger在生产环境不能对外暴露,可以使用@Profile({“dev”, “prod”,“pre”})指定可以使用的环境

五、业务需求扫描接口

接口路径 http://xxxx/v2/api-docs 响应数据内容


{ "swagger": "2.0",
    #详情
    "info": {
        "description": "本地",
        "version": "3.5.0",
        "title": "测试系统",
        "contact": {
            "name": "test"
        }
    },
    #地址
    "host": "localhost:8080",
    "basePath": "/",
    #包含controller
    "tags": [
        {
            "name": "test",
            "description": "My Controller"
        }
    ],
    #请求路径
    "paths": {
        "/a/a": {
            "get": {
                "tags": [
                    "test"
                ],
                #接口说明
                "summary": "xxx",
                "operationId": "testUsingGET",
                "produces": [
                    "*/*"
                ],
                #响应说明
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "deprecated": false
            }
        },
       
    },
    "definitions": {
        #请求或响应对象
        "SysUser": {
            "type": "object",
            "properties": {
                "avatar": {
                    "type": "string"
                },
                "createBy": {
                    "type": "string"
                },
                "createTime": {
                    "type": "string",
                    "format": "date-time"
                },
                "delFlag": {
                    "type": "string"
                },
                "deptCode": {
                    "type": "string"
                },
                "deptId": {
                    "type": "integer",
                    "format": "int64"
                },
                "deptType": {
                    "type": "string"
                },
                "email": {
                    "type": "string"
                },
                "enterpriseId": {
                    "type": "integer",
                    "format": "int64"
                },
                "isChangePassword": {
                    "type": "integer",
                    "format": "int32"
                },
                "loginDate": {
                    "type": "string",
                    "format": "date-time"
                },
                "loginIp": {
                    "type": "string"
                },
                "nickName": {
                    "type": "string"
                },
                "password": {
                    "type": "string"
                },
                "passwordExpiryDate": {
                    "type": "string",
                    "format": "date-time"
                },
                "phonenumber": {
                    "type": "string"
                },
                "postIds": {
                    "type": "array",
                    "items": {
                        "type": "integer",
                        "format": "int64"
                    }
                },
                "relationName": {
                    "type": "string"
                },
                "remark": {
                    "type": "string"
                },
                "revision": {
                    "type": "integer",
                    "format": "int64"
                },
                "roleId": {
                    "type": "integer",
                    "format": "int64"
                },
                "roleIds": {
                    "type": "array",
                    "items": {
                        "type": "integer",
                        "format": "int64"
                    }
                },
                "salt": {
                    "type": "string"
                },
                "sex": {
                    "type": "string"
                },
                "status": {
                    "type": "string"
                },
                "updateBy": {
                    "type": "string"
                },
                "updateTime": {
                    "type": "string",
                    "format": "date-time"
                },
                "userId": {
                    "type": "integer",
                    "format": "int64"
                },
                "userLog": {
                    "type": "string"
                },
                "userName": {
                    "type": "string"
                },
                "userNo": {
                    "type": "string"
                },
                "userType": {
                    "type": "string"
                }
            },
            "title": "SysUser"
        }
    }
}

响应数据为json数据,包含加入扫描接口及对象,可以根据业务做出不同操作