SpringBoot3.3.0集成Knife4j4.5.0实战

SpringBoot3.3.0集成Knife4j4.5.0实战

静美书斋关注IP属地: 江苏

2024.06.07 17:22:34字数 2,226阅读 931

原SpringBoot2.7.18升级至3.3.0之后,Knife4j进行同步升级(Spring Boot 3 只支持OpenAPI3规范),从原3.0.3(knife4j-spring-boot-starter)版本升级至4.5.0(knife4j-openapi3-jakarta-spring-boot-starter),以下是升级过程与注意事项等

版本信息

一、pom.xml引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version><!-- 2.7.18↑-->
    <relativePath/>
</parent>

<dependencies>
    ...
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        <version>4.5.0</version>
    </dependency>
    ...
</dependencies>

二、yml中配置

# Knife4j配置
# springdoc-openapi配置
springdoc:
  # get请求多参数时不需要添加额外的@ParameterObject和@Parameter注解
  default-flat-param-object: true
  # 启用swaggerUI
  swagger-ui:
    #自定义swagger前端请求路径,输入http:127.0.0.1:8080/swagger-ui.html会自动重定向到swagger页面
    path: /swagger-ui.html
    enabled: true
#    tags-sorter: alpha # 标签的排序方式 alpha:按照子母顺序排序(@ApiSupport注解排序不生效,因此需要设置)
#    operations-sorter: alpha # 接口的排序方式 alpha:按照子母顺序排序(@ApiOperationSupport注解排序生效,因此这里不作设置)
    operations-sorter: order # 设置规则为order,该规则会使用Knife4j的增强排序扩展规则`x-order`
  # 启用文档,默认开启
  api-docs:
    path: /v3/api-docs    #swagger后端请求地址
    enabled: true
# knife4j相关配置 可以不用改
knife4j:
  enable: true    #开启knife4j,无需添加@EnableKnife4j注解
  setting:
    language: ZH_CN   # 中文:ZH_CN 英文:EN
    enable-swagger-models: true
    enable-dynamic-parameter: false
    footer-custom-content: "<strong>Copyright ©️ 2024 Keyidea. All Rights Reversed</strong>"
    enable-footer-custom: true
    enable-footer: true
    enable-document-manage: true
  documents: #文档补充说明
    - name: MarkDown语法说明
      locations: classpath:static/markdown/grammar/*
      group: 01-系统接口 # 此处分组必须使用在Knife4jConfig已存在的分组名group,当存在displayName时,使用displayName名称
    - name: 补充文档
      locations: classpath:static/markdown/others/*
      group: 01-系统接口 # 此处分组必须使用在Knife4jConfig已存在的分组名group,当存在displayName时,使用displayName名称

说明:使用knife4j.documents配置补充文档时,需要注意,文档格式必须为markdown格式,另外,文件存放位置如下

img

Knife4j补充文档存放位置

实际呈现如下

img

补充文档

三、Knife4jConfig配置

StatusCode类见附录A

package cn.keyidea.common.config;

import cn.keyidea.common.constant.StatusCode;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
 * Knife4jConfig
 * 注意:分组名称暂时只能使用英文或数字,在4.0.0~4.5.0的Knife4j版本中使用中文分组会出现页面访问异常
 * <p>
 * 解决:(2024-06-18)保持原有分组名group不变,新增displayName中文名称;
 * 特别注意:设置displayName后,knife4j.documents.name配置文档时,需使用displayName名称
 * <p>
 *
 * @author qyd
 * @date 2024-04-13
 */
@Configuration
public class Knife4jConfig {

    private final static Logger logger = LoggerFactory.getLogger(Knife4jConfig.class);

    private static final String SERVICE_URL = "http://127.0.0.1:7004/tj4/doc.html";
    private static final String API_INFO_TITLE = "软件接口文档";
    private static final String API_INFO_VERSION = "V1.0";
    private static final String API_INFO_DESCRIPTION = "Api接口列表";
    private static final String API_INFO_LICENSE = "2024年度内部文档,违拷必究.";

    // 2024集同接口
    @Bean
    public GroupedOpenApi api4() {
        return GroupedOpenApi.builder()
                .group("04-2024-api")
                .displayName("04-2024集同接口")
                .packagesToScan("cn.keyidea.second")
                // 自定义全局响应码
                .addOpenApiCustomizer((this::setCustomStatusCode))
                .build();
    }

    // 2023集同接口
    @Bean
    public GroupedOpenApi api3() {
        return GroupedOpenApi.builder()
                .group("03-2023-api")
                .displayName("03-2023集同接口")
                .packagesToScan("cn.keyidea.control")
                // 自定义全局响应码
                .addOpenApiCustomizer((this::setCustomStatusCode))
                .build();
    }

    // 业务接口
    @Bean
    public GroupedOpenApi api2() {

        return GroupedOpenApi.builder()
                .group("02-business-api")
                .displayName("02-业务接口")
                .packagesToScan("cn.keyidea.business")
                // .pathsToMatch("/v1/**")
                .addOpenApiMethodFilter(method -> method.isAnnotationPresent(io.swagger.v3.oas.annotations.Operation.class))
                // 自定义全局响应码
                .addOpenApiCustomizer((this::setCustomStatusCode))
                .build();
    }

    // 系统接口
    @Bean
    public GroupedOpenApi api1() {
        // 创建了一个api接口的分组
        return GroupedOpenApi.builder()
                // 分组名称,使用英文,中文访问异常(使用displayName设置中文名,避免直接使用group设置中文时访问异常)
                .group("01-sys-api")
                .displayName("01-系统接口") // 使用displayName设置中文接口分组名时,group仍不可或缺
                .packagesToScan("cn.keyidea.sys")
                // 自定义全局响应码
                .addOpenApiCustomizer((this::setCustomStatusCode))
                .build();
    }

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title(API_INFO_TITLE)
                        .description(API_INFO_DESCRIPTION)
                        .version(API_INFO_VERSION)
                        .contact(new Contact().name("Keyidea").email("support@keyidea.cn"))
                        .license(new License().name(API_INFO_LICENSE).url(SERVICE_URL))
                );
    }


    /**
     * 设置自定义错误码
     *
     * @param openApi openApi对象
     */
    private void setCustomStatusCode(OpenAPI openApi) {
        if (openApi.getPaths() != null) {
            Paths paths = openApi.getPaths();
            for (Map.Entry<String, PathItem> entry : paths.entrySet()) {
                String key = entry.getKey();
                PathItem value = entry.getValue();
                // put方式自定义全局响应码
                Operation put = value.getPut();
                // get方式自定义全局响应码
                Operation get = value.getGet();
                // delete方式自定义全局响应码
                Operation delete = value.getDelete();
                // post方式自定义全局响应码
                Operation post = value.getPost();
                if (put != null) {
                    put.setResponses(handleResponses(put.getResponses()));
                }
                if (get != null) {
                    get.setResponses(handleResponses(get.getResponses()));
                }
                if (delete != null) {
                    delete.setResponses(handleResponses(delete.getResponses()));
                }
                if (post != null) {
                    post.setResponses(handleResponses(post.getResponses()));
                }
            }
        }
    }

    /**
     * 处理不同请求方式中的自定义响应码
     * - 响应码中使用原有的响应体Content(否则会造成BaseRes中通用的data无法解析各自的对象)
     * - 使用原生的ApiResponses作为返回体(否则会造成前端响应示例和响应内容中丢失注释)
     *
     * @param responses 响应体集合
     * @return 返回处理后的响应体集合
     */
    private ApiResponses handleResponses(ApiResponses responses) {
        // 设置默认Content
        Content content = new Content();
        // 以下代码注释,因为无论如何都会从原生responses中获取到一个Content
        // MediaType mediaType = new MediaType();
        // Schema schema = new Schema();
        // schema.set$ref("#/components/schemas/BaseRes");
        // mediaType.setSchema(schema);
        // content.addMediaType("*/*", mediaType);

        // 从原来的responses中获取原生Content
        for (Map.Entry<String, ApiResponse> entry : responses.entrySet()) {
            String key = entry.getKey();
            ApiResponse apiResponse = entry.getValue();
            if (apiResponse != null) {
                content = apiResponse.getContent();
                break;
            }
        }

        // 获取全部全局响应自定义列表
        Map<Integer, String> map = StatusCode.toMap();
        // 设置全局响应码
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            ApiResponse api = new ApiResponse();
            api.setContent(content);
            api.description(entry.getValue());
            responses.addApiResponse(entry.getKey() + "", api);
        }
        return responses;
    }
}

四、ShiroConfig中放行Swagger相关路径

如果SpringBoot未集成Shiro,那么此处无需关注。

...
// Shiro放行swagger2(Knife4j)
// filterMap.put("/doc.html", "anon");
// filterMap.put("/swagger-resources/**", "anon");
// filterMap.put("/v2/**", "anon");
// filterMap.put("/webjars/**", "anon");

// Shiro放行swagger3(Knife4j)
filterMap.put("/doc.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/v3/**", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/swagger-ui/**", "anon");
...

五、注解更新

swagger 3 注释的包是io.swagger.v3.oas.annotations

1.原生注解更新

# Controller注解更新
@Api@Tag
@ApiSort@ApiSupport

# 类接口注解更新
@ApiIgnore@Parameter(hidden = true)@Operation(hidden = true)@Hidden
@ApiImplicitParam@Parameter
@ApiImplicitParams@Parameters
@ApiOperation(value = "foo", notes = "bar")@Operation(summary = "foo", description = "bar")
@ApiResponse(code = 404, message = "foo")@ApiResponse(responseCode = "404", description = "foo")

# 实体类注解更新
@ApiModel@Schema
@ApiModelProperty(hidden = true)@Schema(accessMode = READ_ONLY)
@ApiModelProperty@Schema
@ApiParam@Parameter

2.全局替换示例

## 全局替换原有注解

@Api(tags
->
@Tag(name

@ApiSort(
->
@ApiSupport(order = 

, dataType = "Integer", dataTypeClass = Integer.class
-> 
, in = ParameterIn.DEFAULT

, dataType = "String", dataTypeClass = String.class
-> 
, in = ParameterIn.DEFAULT

, paramType = "path", in = ParameterIn.DEFAULT
, paramType = "path", dataType = "Integer", dataTypeClass = Integer.class
->
, in = ParameterIn.PATH

, dataType = "Date", dataTypeClass = Date.class
->


@ApiOperation(value
-> 
@Operation(summary

@ApiImplicitParams
-> 
@Parameters

@ApiModel(value | @ApiModelProperty(value
->
@Schema(name | @Schema(description


required = true | required = false (限定为entity或vo等实体类包进行更换)
->
requiredMode = Schema.RequiredMode.REQUIRED
requiredMode = Schema.RequiredMode.NOT_REQUIRED


## javax注解更改(jakarta)

import javax.xxx;
->
import jakarta.xxx;

六、典型应用

1.文件上传(与自定义错误码)

Knife4jConfig类中已经完美解决了全局自定义错误码,因此在单个接口中已不建议再写,除非有特殊要求。
以下接口类中自定义错误码仅为示例。

···
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
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.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
···
import org.springframework.web.multipart.MultipartFile;
···

/**
 * 系统公共类
 *
 * @author qyd
 * @date 2022-10-17
 */
@ApiSupport(order = 1)
@Tag(name = "1-系统公共类", description = "系统公共类")
@RestController
// @RequestMapping(name = "/sys/common/", produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping("/sys/common/")
public class CommonController {

    private final static Logger logger = LoggerFactory.getLogger(CommonController.class);

    @Autowired
    private SysFileLogService sysFileService;


    @SysLogAnnotation(module = "公共类", serviceDesc = "公共类-文件上传", serviceType = ConstantsExpand.ServiceType.UPLOAD)
    @ApiOperationSupport(author = "qyd", order = 1)
    @Operation(summary = "文件上传", description = "")
    @Parameters({
            @Parameter(name = "file", description = "单文件上传", required = true, schema = @Schema(type = "file", format = "binary"), in = ParameterIn.DEFAULT),
            @Parameter(name = "fileType", description = "文件类型", required = true, example = "txt", in = ParameterIn.DEFAULT),
            @Parameter(name = "type", description = "是否使用文件原始名称:1-使用,其他-不使用(使用随机UUID)", required = false, example = "1", in = ParameterIn.DEFAULT)
    })
    @ApiResponses({
        @ApiResponse(responseCode = "1000", description = "响应成功"),
        @ApiResponse(responseCode = "1001", description = "非法字段"),
    })
    @PostMapping("uploadFile")
    public BaseRes uploadFile(@RequestPart(value = "file", required = true) MultipartFile file,
                              @RequestParam(value = "fileType", required = true) String fileType,
                              @RequestParam(value = "type", required = false) Integer type) {
        return sysFileService.uploadFile(file, fileType, type);
    }
}

2.实体类(分页参数基类PageObject

package cn.keyidea.common.bean;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.io.Serializable;

/**
 * 分页基类 分页参数对象
 *
 * @author qyd
 * @date 2024-06-05
 */
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(name = "PageObject", description = "分页对象")
public class PageObject implements Serializable {

    // 前端实际使用天基三期(React)写法当前页使用的是page
    @NotNull(message = "当前页不能为NULL")
    @Schema(description = "当前页,默认1", name = "page", example = "1", type = "integer", requiredMode = Schema.RequiredMode.REQUIRED)
    private Integer page;

    @NotNull(message = "分页数不能为NULL")
    @Schema(description = "分页数,默认15", name = "pageSize", example = "15", type = "integer", requiredMode = Schema.RequiredMode.REQUIRED)
    private Integer pageSize;

    @Schema(description = "排序字段", name = "orderBy", example = "", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private String orderBy;

    @Schema(description = "排序方式:false-asc,true-desc", name = "desc", type = "boolean", example = "false", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Boolean desc;

}

3.接口类-分页查询/新增/更新/删除/导入示例

已TLE数据的增删查改为例进行说明。

package cn.keyidea.business.controller;

import cn.keyidea.business.entity.Tle;
import cn.keyidea.business.service.TleService;
import cn.keyidea.common.annotation.SysLogAnnotation;
import cn.keyidea.common.bean.BaseRes;
import cn.keyidea.common.constant.ConstantsExpand;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
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.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;


/**
 * <p>
 * 卫星TLE信息表
 * </p>
 *
 * @author qyd
 * @since 2022-10-12
 */
@ApiSupport(order = 2)
@Tag(name = "2-卫星TLE管理", description = "卫星两行根数管理")
@RestController
@RequestMapping("/v1/tle")
public class TleController {

    private final static Logger logger = LoggerFactory.getLogger(TleController.class);

    @Autowired
    private TleService tleService;

    @SysLogAnnotation(module = "TLE管理", serviceDesc = "TLE管理-分页查询", serviceType = ConstantsExpand.ServiceType.QUERY)
    @ApiOperationSupport(author = "qyd", order = 1)
    @Operation(summary = "分页查询")
    @Parameters({
            @Parameter(name = "sceneId", description = "场景ID", required = false, example = "1", in = ParameterIn.DEFAULT),
            @Parameter(name = "tleCode", description = "节点标识,支撑模糊查询", required = false, example = "0101", in = ParameterIn.DEFAULT),
            @Parameter(name = "type", description = "卫星类型:0-低轨卫星,1-中轨卫星,2-高轨卫星,3-天基用户", required = false, example = "0", in = ParameterIn.DEFAULT),
            @Parameter(name = "current", description = "当前页", required = true, example = "1", in = ParameterIn.DEFAULT),
            @Parameter(name = "pageSize", description = "分页数", required = true, example = "15", in = ParameterIn.DEFAULT)
    })
    @GetMapping("listPage")
    public BaseRes<BaseRes.DataList<Tle>> listPage(@RequestParam(value = "sceneId", required = false) Integer sceneId,
                                                   @RequestParam(value = "tleCode", required = false) String tleCode,
                                                   @RequestParam(value = "type", required = false) Integer type,
                                                   @RequestParam(value = "current", required = true, defaultValue = "1") Integer pageNumber,
                                                   @RequestParam(value = "pageSize", required = true, defaultValue = "15") Integer pageSize) {
        Page<Tle> page = new Page<>(pageNumber, pageSize);
        return tleService.listPage(page, sceneId, tleCode, type);
    }

    @SysLogAnnotation(module = "TLE管理", serviceDesc = "TLE管理-TLE详情", serviceType = ConstantsExpand.ServiceType.QUERY)
    @ApiOperationSupport(author = "qyd", order = 2)
    @Operation(summary = "TLE详情")
    @Parameter(name = "id", description = "主键ID", required = true, example = "1", in = ParameterIn.PATH)
    @GetMapping("getById/{id}")
    public BaseRes getById(@PathVariable(value = "id", required = true) Integer id) {

        return tleService.getOneById(id);
    }

    @SysLogAnnotation(module = "TLE管理", serviceDesc = "TLE管理-TLE新增", serviceType = ConstantsExpand.ServiceType.ADD)
    @ApiOperationSupport(author = "qyd", order = 3, includeParameters = {
            "tle.tleCode",
            "tle.line1",
            "tle.line2",
            "tle.sceneId",
            "tle.remark"
    })
    @Operation(summary = "TLE新增", description = "")
    @PostMapping("add")
    public BaseRes add(@Valid @RequestBody Tle tle) {
        return tleService.add(tle);
    }

    @SysLogAnnotation(module = "TLE管理", serviceDesc = "TLE管理-TLE更新", serviceType = ConstantsExpand.ServiceType.UPDATE)
    @ApiOperationSupport(author = "qyd", order = 4, includeParameters = {
            "tle.id",
            "tle.tleCode",
            "tle.line1",
            "tle.line2",
            "tle.sceneId",
            "tle.remark"
    })
    @Operation(summary = "TLE更新")
    @PutMapping("update")
    public BaseRes update(@Valid @RequestBody Tle tle) {
        return tleService.update(tle);
    }


    @SysLogAnnotation(module = "TLE管理", serviceDesc = "TLE管理-TLE更新", serviceType = ConstantsExpand.ServiceType.DELETE)
    @ApiOperationSupport(author = "qyd", order = 5)
    @Operation(summary = "TLE删除", description = "")
    @Parameter(name = "id", description = "主键ID", required = true, example = "1", in = ParameterIn.PATH)
    @DeleteMapping("delete/{id}")
    public BaseRes delete(@PathVariable(value = "id", required = true) Integer id) {
        return tleService.delete(id);
    }


    @SysLogAnnotation(module = "TLE管理", serviceDesc = "TLE管理-TLE导入", serviceType = ConstantsExpand.ServiceType.IMPORT)
    @ApiOperationSupport(author = "qyd", order = 6)
    @Operation(summary = "TLE导入", description = "TLE导入数据格式请参看模板:<a href='template/txt/tle.txt'>TLE文本导入模板</a>")
    @Parameters({
            @Parameter(name = "file", description = "单文件上传", required = true, schema = @Schema(type = "file", format = "binary"), in = ParameterIn.DEFAULT),
            @Parameter(name = "sceneId", description = "场景ID", required = true, example = "1", in = ParameterIn.DEFAULT)
    })
    @PostMapping(value = "importTle")
    public BaseRes importTle(@RequestPart(value = "file", required = true) MultipartFile file,
                             @RequestParam(value = "sceneId", required = true) Integer sceneId) {
        return tleService.importTle(file, sceneId);
    }

}

七、FAQ

1.关于Controller排序说明

a) 使用tags-sorter排序问题说明

使用tags-sorter的alpha排序,是为字母排序,会造成在@Tag的name中使用00-xx/01-xx/02-xx/.../10-xxx进行说明时,排序为00-xx/10-xx/01-xx/.../09-xxx,为了对排序进行强一致,所以废弃使用tags-sorter的alpha排序,使用注解@ApiSupport进行排序定义。

b) 解决使用@ApiSupport不生效问题
  1. 移除yml中对tags-sorter的alpha排序(注释掉);
  2. 在控制器上给@ApiSupport注解,按照order值进行自定义排序,你想让哪个在前,order值就小一些,我一般是从1开始;
  3. 在注解@Tag中的description要给描述,不能是空字符串,否则@ApiSupport不生效;
  4. 弃用tags-sorter的alpha排序更改使用@ApiSupport排序后,需重启程序,此时浏览器最好清除缓存后重新访问Knife4j。

2.关于接口分组无法使用中文问题解决

a) 问题回溯

Knife4jConfig配置类中使用group进行中文分组时,会造成doc.html访问异常,推测是底层编码问题所致。

b) 解决方法
  1. Knife4jConfig配置类中,配置GroupedOpenApi时,group使用英文,displayName使用中文(doc.html最终显示displayName名称);
  2. Knife4jConfig配置类中使用displayName名称时,在yml中配置补充文档时,设置knife4j.documents.name时使用displayName名称,而不是group名称,切记!

3.关于响应内容中不出现注释内容说明

当响应内容中不出现注释时,点击右上角显示说明,触发一次关闭或勾选,即可出现注释内容。(群友推测可能是当响应结果过多时显示BUG问题,必须关闭勾选触发一次显示说明的事件)

4.关于Controller层中GET请求且接收参数为对象时的配置注意事项

参考以下两篇文章

集成Knife4j后,针对GET请求且接收参数为对象时,需要在yml中配置springdoc.default-flat-param-object=true;且在接受参数时使用注解@ModelAttribute

5.关于过滤参数注解@ApiOperationSupport使用

从Knife4j4.0.0开始,@ApiOperationSupport注解中的ignoreParametersincludeParameters属性不再提供支持。如果需要进行精确显示提供的参数,官方建议是新建VO类。

img

Knife4j4.5.0注解关于注解@ApiOperationSupport属性说明

官方说明:3.11 过滤请求参数 | Knife4j

八、待解决问题

1.设置includeParameters无效[影响指数:5/5]【见7.5章节】

POST请求中使用includeParameters给部分对象参数时无效,界面会显示全部对象字段。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

includeParameters设置部分参数仍显示全部全部

2.设置多响应码时界面显示响应状态为tab[影响指数:4/5]

接口上设置多响应码时在前端响应状态中会显示多个tab,导致导出文档时,对同一个接口会出现多次响应状态描述

img

设置多响应码时出现响应码Tab

附录

附录A:状态码枚举定义类(StatusCode.java)

package cn.keyidea.common.constant;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 状态码枚举定义
 *
 * @author qyd
 * @date 2022-10-13
 */
public enum StatusCode
{

    SUCCESS(1000, "请求成功"),
    INVALID_PARAM(1001, "非法字段"),
    SYSTEM_BUSY(1002, "系统忙"),
    INVALID_MASTER_KEY(1003, "无接口访问权限"),
    FAILURE(1004, "请求失败"),
    UNAUTHORIZED(1005, "未授权"),
    INVALID_TOKEN(2001, "TOKEN失效"),
    CONNECT_TIMED_OUT(3001, "请求超时"),
    HTTP_REQ_ERROR(3002, "HTTP请求出错");

    /**
     * 错误码
     */
    private final int code;
    /**
     * 错误描述信息
     */
    private final String msg;

    StatusCode(int code, String msg)
    {
        this.code = code;
        this.msg = msg;
    }

    public String getMsg()
    {
        return this.msg;
    }

    public String getCode()
    {
        return this.code + "";
    }

    public int getCodeValue()
    {
        return this.code;
    }

    /**
     * 转为Map集合数据
     *
     * @return 枚举对象Map集合
     */
    public static Map<Integer, String> toMap()
    {
        Map<Integer, String> map = new HashMap<>(32);
        for (StatusCode value : StatusCode.values())
        {
            map.put(value.getCodeValue(), value.getMsg());
        }
        return map;
    }

    /**
     * 转为List集合数据
     *
     * @return 枚举对象List集合
     */
    public static List<Map<String, String>> toList()
    {
        List<Map<String, String>> list = new ArrayList<>(32);
        Map<String, String> map = null;
        for (StatusCode item : StatusCode.values())
        {
            map = new HashMap<>();
            map.put("code", item.getCode());
            map.put("msg", item.getMsg());
            list.add(map);
        }
        map = null;
        return list;
    }
}

附录B:通用响应封装类(BaseRes.java)

package cn.keyidea.common.bean;

import cn.keyidea.common.constant.StatusCode;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * 通用响应封装,范式返回(Swagger要求)
 *
 * @author qyd
 */
@Data
public class BaseRes<T> implements Serializable {

    /**
     * 错误码
     */
    @Schema(name = "code", description = "错误码,当code为1000时返回正常,其余返回异常", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
    public Integer code;

    /**
     * 错误提示信息
     */
    @Schema(name = "msg", description = "错误提示信息,当code为非1000时返回提示信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "请求成功")
    public String msg;

    /**
     * 附加返回数据
     */
    @Schema(name = "data", description = "附加返回数据,当code为1000时返回数据")
    public T data;

    public static class DataList<T> {
        /**
         * 记录总数
         */
        @Schema(name = "total", description = "记录总数")
        public Integer total;
        /**
         * 数据列表
         */
        @Schema(name = "list", description = "数据列表")
        public T list;

        public DataList(Integer total, T list) {
            this.total = total;
            this.list = list;
        }
    }

    /**
     * 给ObjectMapper用的,代码中不要调用
     */
    public BaseRes() {

    }

    /**
     * 自定义返回码和提示消息
     *
     * @param code 错误码
     * @param msg  提示文字
     */
    public BaseRes(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public BaseRes(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    /**
     * 返回成功,但是没有附加数据
     *
     * @return BaseRes对象
     */
    public static BaseRes success() {
        return new BaseRes(StatusCode.SUCCESS.getCodeValue(), "请求成功");
    }

    /**
     * 返回成功,带附加数据
     *
     * @param data 附加数据
     * @return BaseRes对象
     */
    public static BaseRes successData(Object data) {
        BaseRes value = new BaseRes(StatusCode.SUCCESS.getCodeValue(), "请求成功");
        value.data = data;
        return value;
    }

    /**
     * 返回参数无效响应
     *
     * @return BaseRes对象
     */
    public static BaseRes invalidParam() {
        return new BaseRes(StatusCode.INVALID_PARAM.getCodeValue(), "参数无效");
    }

    /**
     * 返回参数无效响应,自定义错误提示
     *
     * @param msg 提示文字
     * @return BaseRes对象
     */
    public static BaseRes invalidParam(String msg) {
        return new BaseRes(StatusCode.INVALID_PARAM.getCodeValue(), msg);
    }

    /**
     * 返回系统忙无效响应
     *
     * @return BaseRes对象
     */
    public static BaseRes systemBusy() {
        return new BaseRes(StatusCode.SYSTEM_BUSY.getCodeValue(), "系统忙");
    }

    /**
     * 返回master key无效响应
     *
     * @return BaseRes对象
     */
    public static BaseRes invalidMasterkey() {
        return new BaseRes(StatusCode.INVALID_MASTER_KEY.getCodeValue(), "没有接口访问权限");
    }

    /**
     * 返回失败,附带说明
     *
     * @return BaseRes对象
     */
    public static BaseRes fail(String msg) {
        return new BaseRes(StatusCode.FAILURE.getCodeValue(), msg);
    }

    /**
     * 返回错误信息时,仍然返回数据
     *
     * @param data 数据集
     * @param msg  错误信息
     * @return BaseRes对象
     */
    public static BaseRes failData(Object data, String msg) {
        return new BaseRes(StatusCode.FAILURE.getCodeValue(), msg, data);
    }

    /**
     * 登录失效的错误
     *
     * @return BaseRes对象
     */
    public static BaseRes invalidToken() {
        return new BaseRes(StatusCode.INVALID_TOKEN.getCodeValue(), "请先登录");
    }

    /**
     * 检查响应处理是否成功
     *
     * @return 成功返回true,否则false
     */
    @JsonIgnore
    public boolean isSuccess() {

        return (this.code.equals(StatusCode.SUCCESS.getCodeValue()));
    }

    /**
     * 返回分页列表数据
     *
     * @param total 记录总数
     * @param list  列表数据
     * @return rsp
     */
    public static BaseRes list(long total, Object list) {
        DataList data = new DataList((int) total, list);

        return BaseRes.successData(data);
    }
}

参考

以下参考截止[2024-06-20],CSDN等网站链接均能查看全部文章。

官方

接口排序问题

SpringBoot2.x升级至3.x相关

MyBatis Plus升级相关

Knife4j升级相关

涉及Redis相关

其他【启动告警解决】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值