Springboot 2.7 集成 Swagger 增强版接口框架 Knife4j 4.3 + springdoc OpenApi 3.0

参考原文链接:https://blog.csdn.net/Mrqiang9001/article/details/132305015

1 摘要


Swagger 作为一款服务端接口文档自动生成框架,早已深入人心,并且在市场上得到了广泛的应用。然而,Swagger 3.0 也就是 OpenApi 3.0 规范发布之后便停止了更新维护,出道就是巅峰。Knife4j 作为 Swagger 的增强版,是对 Swagger UI 做了优化,同时还有很多增强的功能。伴随着 Swagger 3.0 的停止更新,如今 Knife4j 从4.0开始已经逐渐使用 Springdoc 作为 swagger 的替代。Springdoc 针对 OpenApi 3.0 的适配做了较大的调整,其中注解与 Swagger 2 的基本不通用。作为新项目而言,使用社区维护活跃的开源框架显得非常重要。本文将介绍基于 Springboot 2.7 集成 Swagger 的增强框架 Knife4j 4.3 + Springdoc OpenApi 3.0 。

Knife4j 官方文档: https://doc.xiaominfo.com/docs/quick-start

Springdoc 官方文档: https://springdoc.org

SpringDoc注解解析:https://blog.csdn.net/qq_29012499/article/details/135433483

解决接口模块儿排序问题:knife4j、swagger、springdoc 返回接口分组排序问题  https://blog.csdn.net/neusoft2016/article/details/130975683

核心 Maven 依赖

 
<!-- mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

<!-- knife4j openapi3 接口文档 -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
           <version>4.3.0</version>
        </dependency>

 核心代码

application.yml

# spring-doc 接口文档
springdoc:
  api-docs:
    enabled: true # 是否启用接口文档
knife4j:
  enable: true # 是否启用 knife4j 增强,如果只是使用 knife4j 的 UI,则可以关闭

openApi 配置类


import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Comparator;
import java.util.stream.Collectors;

@OpenAPIDefinition(
        security = @SecurityRequirement(name = "Authorization")
)
@SecurityScheme(type = SecuritySchemeType.APIKEY, name = "Authorization", scheme = "Authorization", in = SecuritySchemeIn.HEADER)
@Configuration
public class OpenApiConfig {
    private String title = "SpringDoc API";
    private String description = "Knife4j OpenApi 3 description";
    private String version = "v0.0.1";

    @Bean
    public OpenAPI springOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title(title)
                        .description(description)
                        .version(version));
    }

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group(title)
                .pathsToMatch("/**")
                .addOpenApiCustomiser(sortTagsAlphabetically())
                .build();
    }

    //排序规则方法
    public OpenApiCustomiser sortTagsAlphabetically() {
        return openApi -> openApi.setTags(openApi.getTags()
                .stream()
                .sorted(Comparator.comparing(tag -> StringUtils.stripAccents(tag.getName())))
                .collect(Collectors.toList()));
    }

}

DTO

package com.sunmon.turtle.domain.dto;

import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;

import javax.validation.constraints.NotNull;

@Data
@ToString(callSuper = true)
@Schema(description = "用户表单实体")
@TableName(value = "user")
public class UserFormDTO {

    @NotNull(message = "注册手机号 不能为空")
    @Schema(description = "注册手机号", requiredMode = Schema.RequiredMode.REQUIRED)
    private String phone;

    @Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
    private String userName;

    @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
    private String password;

    @Schema(description = "用户id")
    private Long id;

    @Schema(description = "详细信息,JSON风格")
    private String info;

    @Schema(description = "账户余额")
    private Integer balance;
}

PO

package com.sunmon.turtle.domain.po;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.sunmon.turtle.utils.MyLocalDateTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;


@Data
@Component
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
//    @TableId//不指定 IdType 时,默认使用雪花算法生成数字(企业级开发时使用)
    @TableId(type= IdType.AUTO)//自增
    private Long id;
    private String userName;
    private String password;
    private String phone;
    private String gender;
    private String info;
    private String loginId;
    private Integer status;//用户状态(1启用 0停用)
    @TableField(exist = false)
    private String address;
    private Integer balance;

    //使用自定义处理器,解决 “mybatis-plus 在映射查询结果到 Java 对象时,如果LocalDateTime为空时,引发 NullPointerException”
    @TableField( typeHandler = MyLocalDateTypeHandler.class)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime regDate;

    @TableField( typeHandler = MyLocalDateTypeHandler.class, fill = FieldFill.INSERT)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

    @TableField( typeHandler = MyLocalDateTypeHandler.class, fill = FieldFill.UPDATE)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;


}

@Schema 注解中参数是否必填使用了一个枚举类 io.swagger.v3.oas.annotations.media.Schema.RequiredMode 这是 @Schema 注解的一个内部类,支持 3 种模式, @Schema 注解中多种属性也采用了枚举类的方式,这是与原来的 @ApiModelPropertity 注解区别比较大的地方。

VO

package com.sunmon.turtle.domain.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;

import java.time.LocalDateTime;

@Data
@ToString(callSuper = true)
@Schema(description = "用户 VO 实体")
public class UserVO {

    @Schema(description = "用户id")
    private Long id;

    @Schema(description = "用户名")
    private String userName;

    @Schema(description = "详细信息")
    private String info;

    @Schema(description = "用户状态(1启用 0停用)")
    private String status;

    @Schema(description = "账户余额")
    private Integer balance;

    @Schema(description = "注册时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//解决 MySQL日期前端显示问题——数据转换出来后多个 “T“
    private LocalDateTime regDate;

    @Schema(description = "创建时间")//, type = "dateTime",pattern = "yyyy-MM-dd HH:mm:ss"
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;

    @Schema(description = "最后更新时间")//, type = "dateTime",pattern = "yyyy-MM-dd HH:mm:ss"
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
}

Controller 控制层示例

package com.sunmon.turtle.controller;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.sunmon.turtle.domain.dto.UserFormDTO;
import com.sunmon.turtle.domain.query.UserQuery;
import com.sunmon.turtle.domain.vo.UserVO;
import com.sunmon.turtle.domain.po.User;
import com.sunmon.turtle.service.IUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@DS("db_turtle")
@Tag(name = "1 - 用户管理接口", description = "用户管理")
@RequestMapping("/users")
@RequiredArgsConstructor//构造时,用必要的参数生成构造函数
@RestController
public class UserController {

    private final IUserService userService;


    @Operation(summary = "01 用户新增接口")
    @PostMapping
    public boolean saveUser(@RequestBody UserFormDTO userDTO) {
        //1.把 DTO拷贝到PO
        User user = BeanUtil.copyProperties(userDTO, User.class);//HuTool工具包

        //2.新增
        return userService.save(user);
    }

    @Operation(summary = "02 根据 id 查询用户接口",
            parameters  = {
                    @Parameter( name = "用户id", required = true, in = ParameterIn.PATH, schema = @Schema(type = "integer")  )
            }  )
    @GetMapping("{id}")
    public UserVO queryUserById ( @PathVariable("id") Long id) {
        User user = userService.getById(id);

        UserVO userVo =  BeanUtil.copyProperties(user, UserVO.class);//HuTool工具包
        return userVo;
    }

    @Operation(summary = "03 删除用户接口",
            parameters  = {
                    @Parameter(name = "用户id", required = true, in = ParameterIn.PATH)
            }
            )
    @DeleteMapping("{id}")
    public boolean deleteUserById( @PathVariable("id") Long id) {
        return userService.removeById(id);
    }


    @Operation(summary = "05 扣减用户余额接口",
            parameters  = {
                    @Parameter(name = "用户id", required = true, in = ParameterIn.PATH),
                    @Parameter(name = "扣减的金额", required = true, in = ParameterIn.PATH)
            }
    )
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance( @PathVariable("id") Long id, @PathVariable("money") Integer money) {
        userService.deductBalance(id, money);
    }


    @Operation(summary = "04 根据 id 批量查询用户接口" ,
            parameters  = {
                    @Parameter(name = "用户id集合", required = true, in = ParameterIn.PATH, schema = @Schema(type = "list")  )
            }  )
    @GetMapping
    public List<UserVO> queryUserByIds( @RequestParam("ids") List<Long> ids) {
        List<User> users = userService.listByIds(ids);
        return BeanUtil.copyToList(users, UserVO.class);//HuTool工具包
    }


    @Operation(
            summary = "06 根据复杂条件查询用户接口"
    )
    //在SpringDoc中,如果你遇到了Get请求带有RequestBody注解,并且有自定义参数,但是没有参数框的问题,这通常是因为Swagger UI的一个已知限制,它不支持为GET请求显示请求体参数。
    //一种方法是使用POST请求替代GET请求,尽管这可能不符合RESTful API设计的原则。
    @PostMapping("/list")
    public List<UserVO> queryUsers( @RequestBody UserQuery query){
        //1.查询用户
        List<User> users = userService.queryUsers(
                query.getName(),query.getStatus(),query.getMaxBalance(),query.getMinBalance()
        );

        //2.把PO 拷贝 VO
        return BeanUtil.copyToList(users, UserVO.class);
    }

}

在 Controller 类中,原来 Swagger 2 的 @Api 注解替换为 @Tag, 原来的 @ApiOperation 注解替换为 @Operation。

在请求头中的参数,例如 GET 请求,如果是一个对象,则需要使用 @ParameterObject 注解,如果不添加,则无法在 Swagger 接口文档界面进行正常请求。

界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值