前后端分离之swagger复习

前后端分离之swagger复习

前言

说起Swagger就不得不说前后端分离

当前最主流的前后端分离技术栈:Vue+Springboot

后端时代:

前端只用管理静态页面:html

最后统一交给后端,后端将其修改为jsp,在整个过程中,后端充当主力

前后端分离时代:

后端:后端控制层,服务层,数据访问层【后端团队】

前端:前端控制层,视图层【前端团队】

前端可以自定义一些伪后端数据:json,在写的时候就存在,不需要后端,前端工程依旧能够跑起来

那么前端后端如何交互?
此时我们可以考虑API

前后端分离好处:

前后端相对独立,松耦合
前后端设置可以部署在不同的服务器上
产生的问题:

前后弹集成联调,前端人员和后端人员无法做到及时协商,尽早解决,最终导致问题爆发;
解决方式:

首先指定schema[计划的提纲],实时更新最新的API,降低集成风险

早期:指定word计划文档

前后端分离时期:

前端测试后端接口:postman

后端提供接口,需要实时更新最新的消息及改动

1、Swagger

号称世界上最流行的Api框架
RestFul Api 文档在线自动生成工具,Api文档与Api定义同步更新
直接运行,可以在线测试API接口
支持多种语言:(Java,php等)
官网:http://swagger.io/

2、SpringBoot集成Swagger

1.引入库
2.导入依赖
swagger2

ui

2.9以上的需要2.6x以上版本的springboot

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

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

3.配置Swagger

@EnableSwagger2 用来开启Swagger2

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {

}

配置文件记得写加入

spring.mvc.pathmatch.matching-strategy=ant_path_matcher
#避免null空指针

4.进入测试

http://localhost:8080/swagger-ui.html

``

5. 配置Swagger基本信息

Swagger的Bean实例Docket:

@Configuration
 //开启Swagger2
@EnableSwagger2
public class SwaagerConfig {
    @Bean
    public Docket docket(){
      
        return new Docket()
          //未写入参数     
    }
  
}

源码

public class Docket implements DocumentationPlugin {
  

    public Docket(DocumentationType documentationType) {
        this.apiInfo = ApiInfo.DEFAULT;
        this.groupName = "default";
        this.enabled = true;
        this.genericsNamingStrategy = new DefaultGenericTypeNamingStrategy();
        this.applyDefaultResponseMessages = true;
        this.host = "";
        this.pathMapping = Optional.absent();
        this.apiSelector = ApiSelector.DEFAULT;
        this.enableUrlTemplating = false;
        this.vendorExtensions = Lists.newArrayList();
        this.documentationType = documentationType;
    }


    public Docket apiInfo(ApiInfo apiInfo) {
        this.apiInfo = (ApiInfo)BuilderDefaults.defaultIfAbsent(apiInfo, apiInfo);
        return this;
    }
}
然后apiinfo源码
    其中内部类
      static {
        DEFAULT = new ApiInfo("Api Documentation", "Api Documentation", "1.0", "urn:tos", DEFAULT_CONTACT, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
    }public Docket(DocumentationType documentationType),所以要传入一个type,看源码有三种 
public static final DocumentationType SWAGGER_12 = new DocumentationType("swagger", "1.2");
    public static final DocumentationType SWAGGER_2 = new DocumentationType("swagger", "2.0");
    public static final DocumentationType SPRING_WEB = new DocumentationType("spring-web", "1.0");


写入自己的信息

@Configuration
 //开启Swagger2
@EnableSwagger2
public class SwaagerConfig {
    @Bean
    public Docket docket(){
        //配置了swaggerbean的Docket的bean实例
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }
    //配置swagger的信息apiinfo

   private ApiInfo apiInfo(){
        //作者信息
        Contact contact=new Contact("nameyang","https://yangxingyue0623.github.io/","1573478674@qq.com");
        return new ApiInfo("titleyang的Swaggerapi文档",
                "描述加油加油",
                "1.0",
                "urn:tos",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());
    }

}

http://localhost:8080/swagger-ui.html 配置完后访问就变成自己信息

可以查看下面的接口信息。为什么访问swagger-ui.html,是因为extend libnary里面swagger依赖包的META-INFO里面的

6.Swagger配置扫描接口

一般用Docket.select()方法,所以继续配置

RequestHandlerSelectors()配置要扫描接口的方式
basePackage()指定扫描的包
any()扫描全部的包
none()都不扫描
withClassAnnotation()扫描类上的注解,参数是注解的反射对象
withMethodAnnotation()扫描方法上的注解

    @Bean
    public Docket docket(){

        //配置了Swagger的Docket的Bean实例
//        DocumentationType documentationType = new DocumentationType();
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //RequestHandlerSelectors,配置要扫描接口的方式
                //basePackage指定扫描的包
                //any扫描全部的包
                //none()都不扫描
                //withClassAnnotation()扫描类上的注解,参数是注解的反射对象
            //withClassAnnotation(RestController.class)扫描类上带rescontroller的注解
                //withMethodAnnotation()扫描方法上的注解
            //比如apis(RequestHandlerSelectors.basePackage())
                .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
                //paths(),过滤路径,只让/kuang开头的请求通过
                .paths(PathSelectors.ant("/kuang/**"))
            //ant只扫描/kuang/**
                .build();
    }

7.配置是否启动Swagger

enable是否启动swagger,如果为false,则swagger不能在浏览器中访问

return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
				.enable(false)  //其实是源码里面的

那么如果我们希望Swagger在某一个环境中使用,在其他时候不使用怎么办

首先判断环境

一般 application.properties spring.profiles.active=dev 生产环境

application-dev.yml 生产环境

application-pro.yml 正式环境

@EnableSwagger2
public class SwaagerConfig {
    @Bean
    public Docket docket(Environment environment){
        //是springformworker的
        //获取项目的生产环境

        //设置要显示的swagger环境
        Profiles profiles = Profiles.of("dev");
        //这个包下的org.springframework.core.env
        //通过environment.acceptsProfiles判断是否处在自己设定的环境当中
        boolean flag = environment.acceptsProfiles(profiles);


        //配置了swaggerbean的Docket的bean实例
        return new Docket(DocumentationType.SWAGGER_2)
                .enable(flag)

运行出现密码问题,在启动类上加入@EnableSwagger2

8. 配置API文档的分组

设置组名就是

return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
				.groupName("hello")

如果我们想配置多个分组怎么办呢?
创建多个docket对象,设置不同的组名

    @Bean
    public Docket docket1(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("A");
    }
    @Bean
    public Docket docket2(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("B");
    }
    @Bean
    public Docket docket3(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("C");
    }

img

9.实体类的配置

User类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
public String username;
public String password;

HelloController中:

//只要返回值中存在实体类,他就会被扫描到Swagger中
@PostMapping("/user")
public User user(){
    return new User("zs","123");
}

查看Swagger:

在这里插入图片描述

10.文档注解

为了方便交互,方便文档阅读,我们可以为文档添加说明信息

@ApiModel(“用户实体类”)为实体类添加说明信息

@ApiModelProperty(“用户名”)为实体类中的属性添加说明信息

@Api(tags = “控制器-hello”)为接口添加说明信息

@ApiOperation(“hello请求”)为请求增加说明信息

@ApiParam(“hello请求中的name”)为请求中的参数增加说明信息

HelloController中:

//只要返回值中存在实体类,他就会被扫描到Swagger中
@PostMapping("/user")
public User user(){
    return new User("zs","123");
}

@Data
@AllArgsConstructor
@NoArgsConstructor

@ApiModel("用户实体类")
public class User {
        @ApiModelProperty("用户名")
        public String username;
        @ApiModelProperty("密码")
        public String password;

}
 @ApiOperation("Hello控制类")
    @GetMapping(value="/hello2")
    public String hello2(@ApiParam("用户名") String username){
        return "Hello"+username;
    }

总结

  1. 我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
  2. 接口文档实时更新
  3. 可以在线测试

注意点:
在正式发布的时候,需要关闭Swagger,一是出于安全考虑,,二是节省内存

之前项目用到的Swagger

官网:API Documentation & Design Tools for Teams | Swagger

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。功能主要包含以下几点:

A. 使得前后端分离开发更加方便,有利于团队协作

B. 接口文档在线自动生成,降低后端开发人员编写接口文档的负担

C. 接口功能测试

使用Swagger只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。

直接使用Swagger, 需要按照Swagger的规范定义接口, 实际上就是编写Json文件,编写起来比较繁琐、并不方便, 。而在项目中使用,我们一般会选择一些现成的框架来简化文档的编写,而这些框架是基于Swagger的,如knife4j。

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。而我们要使用kinfe4j,需要在pom.xml中引入如下依赖即可:

<dependency>
 <groupId>com.github.xiaoymin</groupId>
 <artifactId>knife4j-spring-boot-starter</artifactId>
 <version>3.0.2</version>
</dependency>
3.0.3 Cannot resolve org.mapstruct:mapstruct:1.3.1.Final
<dependency>
 <groupId>com.github.xiaoymin</groupId>
 <artifactId>knife4j-spring-boot-starter</artifactId>
 <version>3.0.2</version>
</dependency>
3.0.3 Cannot resolve org.mapstruct:mapstruct:1.3.1.Final

3.2 使用方式

接下来,我们就将我们的项目集成Knife4j,来自动生成接口文档。这里我们还是需要再创建一个新的分支v1.2,在该分支中进行knife4j的集成,集成测试完毕之后,没有问题,我们再将v1.2分支合并到master。

使用knife4j,主要需要操作以下几步:

1). 导入knife4j的maven坐标

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>
(我用的版本优点问题,我调低了2.04)

2). 导入knife4j相关配置类

这里我们就不需要再创建一个新的配置类了,我们直接在WebMvcConfig配置类中声明即可。

A. 在该配置类中加上两个注解 @EnableSwagger2 @EnableKnife4j ,开启Swagger和Knife4j的功能。

B. 在配置类中声明一个Docket类型的bean, 通过该bean来指定生成文档的信息。

@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
	
    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射...");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }
	
    /**
     * 扩展mvc框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器...");
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用Jackson将Java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0,messageConverter);
    }
	
    @Bean
    public Docket createRestApi() {
        // 文档类型
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.itheima.reggie.controller"))
                .paths(PathSelectors.any())
                .build();
    }
	
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("瑞吉外卖")
                .version("1.0")
                .description("瑞吉外卖接口文档")
                .build();
    }
}

注意: Docket声明时,指定的有一个包扫描的路径,该路径指定的是Controller所在包的路径。因为Swagger在生成接口文档时,就是根据这里指定的包路径,自动的扫描该包下的@Controller, @RestController, @RequestMapping等SpringMVC的注解,依据这些注解来生成对应的接口文档。
————————————————

3). 设置静态资源映射

由于Swagger生成的在线文档中,涉及到很多静态资源,这些静态资源需要添加静态资源映射,否则接口文档页面无法访问。因此需要在 WebMvcConfig类中的addResourceHandlers方法中增加如下配置。

registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

4). 在LoginCheckFilter中设置不需要处理的请求路径

需要将Swagger及Knife4j相关的静态资源直接放行,无需登录即可访问,否则我们就需要登录之后,才可以访问接口文档的页面。

在原有的不需要处理的请求路径中,再增加如下链接:

"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"

3.3 查看接口文档

经过上面的集成配置之后,我们的项目集成Swagger及Knife4j就已经完成了,接下来我们可以重新启动项目,访问接口文档,访问链接为: http://localhost:8080/doc.html

img

我们可以看到,在所有的Controller中提供的所有的业务增删改查的接口,全部都已经自动生成了,我们通过接口文档可以看到请求的url、请求方式、请求参数、请求实例、响应的参数,响应的示例。 并且呢,我们也可以通过这份在线的接口文档,对接口进行测试。

img

注意: 由于我们服务端的Controller中的业务增删改查的方法,都是必须登录之后才可以访问的,所以,我们在测试时候,也是需要先访问登录接口。登录完成之后,我们可以再访问其他接口进行测试。

我们不仅可以在浏览器浏览生成的接口文档,Knife4j还支持离线文档,对接口文档进行下载,支持下载的格式有:markdown、html、word、openApi。

img

3.4 常用注解

3.4.1 问题说明

在上面我们直接访问Knife4j的接口文档页面,可以查看到所有的接口文档信息,但是我们发现,这些接口文档分类及接口描述都是Controller的类名(驼峰命名转换而来)及方法名,而且在接口文档中,所有的请求参数,响应数据,都没有中文的描述,并不知道里面参数的含义,接口文档的可读性很差。

img

外卖注解swaager

3.4.2 注解介绍

为了解决上述的问题,Swagger提供了很多的注解,通过这些注解,我们可以更好更清晰的描述我们的接口,包含接口的请求参数、响应数据、数据模型等。核心的注解,主要包含以下几个:

注解 位置 说明
@Api 类 加载Controller类上,表示对类的说明
@ApiModel 类(通常是实体类) 描述实体类的作用
@ApiModelProperty 属性 描述实体类的属性
@ApiOperation 方法 说明方法的用途、作用
@ApiImplicitParams 方法 表示一组参数说明
@ApiImplicitParam 方法 用在@ApiImplicitParams注解中,指定一个请求参数的各个方面的属性

3.4.3 注解测试

1). 实体类

可以通过 @ApiModel , @ApiModelProperty 来描述实体类及属性

@Data
@ApiModel("套餐")
public class Setmeal implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键")
    private Long id;
    
    //分类id
    @ApiModelProperty("分类id")
    private Long categoryId;
    
    //套餐名称
    @ApiModelProperty("套餐名称")
    private String name;

    //套餐价格
    @ApiModelProperty("套餐价格")
    private BigDecimal price;

    //状态 0:停用 1:启用
    @ApiModelProperty("状态")
    private Integer status;

    //编码
    @ApiModelProperty("套餐编号")
    private String code;

    //描述信息
    @ApiModelProperty("描述信息")
    private String description;

    //图片
    @ApiModelProperty("图片")
    private String image;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
}

2). 响应实体R

@Data
@ApiModel("返回结果")
public class R<T> implements Serializable{
@ApiModelProperty("编码")
private Integer code; //编码:1成功,0和其它数字为失败

@ApiModelProperty("错误信息")
private String msg; //错误信息

@ApiModelProperty("数据")
private T data; //数据

@ApiModelProperty("动态数据")
private Map map = new HashMap(); //动态数据

//省略静态方法 ....
}

3). Controller类及其中的方法

描述Controller、方法及其方法参数,可以通过注解: @Api, @APIOperation, @ApiImplicitParams, @ApiImplicitParam

@RestController
@RequestMapping("/setmeal")
@Slf4j
@Api(tags = "套餐相关接口")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private CategoryService categoryService;
@Autowired
private SetmealDishService setmealDishService;

/**
 * 新增套餐
 * @param setmealDto
 * @return
 */
@PostMapping
@CacheEvict(value = "setmealCache",allEntries = true)
@ApiOperation(value = "新增套餐接口")
public R<String> save(@RequestBody SetmealDto setmealDto){
    log.info("套餐信息:{}",setmealDto);

    setmealService.saveWithDish(setmealDto);

    return R.success("新增套餐成功");
}

/**
 * 套餐分页查询
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@GetMapping("/page")
@ApiOperation(value = "套餐分页查询接口")
@ApiImplicitParams({
        @ApiImplicitParam(name = "page",value = "页码",required = true),
        @ApiImplicitParam(name = "pageSize",value = "每页记录数",required = true),
        @ApiImplicitParam(name = "name",value = "套餐名称",required = false)
})
public R<Page> page(int page,int pageSize,String name){
    //分页构造器对象
    Page<Setmeal> pageInfo = new Page<>(page,pageSize);
    Page<SetmealDto> dtoPage = new Page<>();

    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    //添加查询条件,根据name进行like模糊查询
    queryWrapper.like(name != null,Setmeal::getName,name);
    //添加排序条件,根据更新时间降序排列
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    setmealService.page(pageInfo,queryWrapper);

    //对象拷贝
    BeanUtils.copyProperties(pageInfo,dtoPage,"records");
    List<Setmeal> records = pageInfo.getRecords();

    List<SetmealDto> list = records.stream().map((item) -> {
        SetmealDto setmealDto = new SetmealDto();
        //对象拷贝
        BeanUtils.copyProperties(item,setmealDto);
        //分类id
        Long categoryId = item.getCategoryId();
        //根据分类id查询分类对象
        Category category = categoryService.getById(categoryId);
        if(category != null){
            //分类名称
            String categoryName = category.getName();
            setmealDto.setCategoryName(categoryName);
        }
        return setmealDto;
    }).collect(Collectors.toList());

    dtoPage.setRecords(list);
    return R.success(dtoPage);
}

/**
 * 删除套餐
 * @param ids
 * @return
 */
@DeleteMapping
@CacheEvict(value = "setmealCache",allEntries = true)
@ApiOperation(value = "套餐删除接口")
public R<String> delete(@RequestParam List<Long> ids){
    log.info("ids:{}",ids);

    setmealService.removeWithDish(ids);

    return R.success("套餐数据删除成功");
}

/**
 * 根据条件查询套餐数据
 * @param setmeal
 * @return
 */
@GetMapping("/list")
@Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
@ApiOperation(value = "套餐条件查询接口")
public R<List<Setmeal>> list(Setmeal setmeal){
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
    queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    List<Setmeal> list = setmealService.list(queryWrapper);

    return R.success(list);
}
log.info("ids:{}",ids);

    setmealService.removeWithDish(ids);

    return R.success("套餐数据删除成功");
}

/**
 * 根据条件查询套餐数据
 * @param setmeal
 * @return
 */
@GetMapping("/list")
@Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
@ApiOperation(value = "套餐条件查询接口")
public R<List<Setmeal>> list(Setmeal setmeal){
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
    queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    List<Setmeal> list = setmealService.list(queryWrapper);

    return R.success(list);
}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值