SpringBoot3.3.0集成Knife4j4.5.0实战

原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),以下是升级过程与注意事项等

版本信息

  • JDK 21
  • Maven 3.9.6
  • SpringBoot 3.3.0
  • Knife4j 4.5.0(截止2024-06-18最新仍为4.5.0)

一、pom.xml引入依赖


    org.springframework.boot
    spring-boot-starter-parent
    3.3.0
    



    ...
    
        com.github.xiaoymin
        knife4j-openapi3-jakarta-spring-boot-starter
        4.5.0
    
    ...

二、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: "Copyright ©️ 2024 Keyidea. All Rights Reversed"
    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格式,另外,文件存放位置如下

SpringBoot3.3.0集成Knife4j4.5.0实战_第1张图片
Knife4j补充文档存放位置

实际呈现如下


SpringBoot3.3.0集成Knife4j4.5.0实战_第2张图片
补充文档

三、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版本中使用中文分组会出现页面访问异常
 * 

* 解决:(2024-06-18)保持原有分组名group不变,新增displayName中文名称; * 特别注意:设置displayName后,knife4j.documents.name配置文档时,需使用displayName名称 *

* * @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("[email protected]")) .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 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 entry : responses.entrySet()) { String key = entry.getKey(); ApiResponse apiResponse = entry.getValue(); if (apiResponse != null) { content = apiResponse.getContent(); break; } } // 获取全部全局响应自定义列表 Map map = StatusCode.toMap(); // 设置全局响应码 for (Map.Entry 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;


/**
 * 

* 卫星TLE信息表 *

* * @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> 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 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导入数据格式请参看模板:TLE文本导入模板") @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 v4.0版本针对参数解析ParameterObject的问题说明
  • SpringBoot接收参数场景

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

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

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

SpringBoot3.3.0集成Knife4j4.5.0实战_第3张图片
Knife4j4.5.0注解关于注解@ApiOperationSupport属性说明

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

八、待解决问题

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

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

SpringBoot3.3.0集成Knife4j4.5.0实战_第4张图片
includeParameters设置部分参数仍显示全部全部

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

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


SpringBoot3.3.0集成Knife4j4.5.0实战_第5张图片
设置多响应码时出现响应码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 toMap()
    {
        Map map = new HashMap<>(32);
        for (StatusCode value : StatusCode.values())
        {
            map.put(value.getCodeValue(), value.getMsg());
        }
        return map;
    }

    /**
     * 转为List集合数据
     *
     * @return 枚举对象List集合
     */
    public static List> toList()
    {
        List> list = new ArrayList<>(32);
        Map 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 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 {
        /**
         * 记录总数
         */
        @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等网站链接均能查看全部文章。

官方

  • 3.1 增强模式 | Knife4j

接口排序问题

  • knife4j 4.3.0版本,@ApiSupport、@ApiSort排序不会自动生成x-order扩展属性 · Issue #I7U2I0 · 萧明/knife4j - Gitee.com【解决了类Controller排序问题】

  • knife4j 中接口分组排序的方法_knife4j 接口排序-CSDN博客

SpringBoot2.x升级至3.x相关

  • SpringBoot2.7升级项目到Springboot3.1踩坑指南(jdk17/jdk21)_升级到 jdk17 springboot3.1
  • Spring Boot 3 之SpringBoot 版本升级最佳实践指南
  • 记录从SpringBoot2.x升级到SpringBoot3.x的心得
  • SpringBoot2.7升级到3.0的实践分享 - 踩刀诗人
  • 记录SpringBoot2.7.5升级SpringBoot3.0.0问题_springboot2.7升级3.0
  • Springboot3.0升级填坑-腾讯云开发者社区-腾讯云
  • JeecgBoot 框架升级至 Spring Boot3 的实战步骤-腾讯云开发者社区-腾讯云
  • How to prevent logback from outputting its own status at the start of every log when using a layout - Stack Overflow
  • Spring Boot3.0升级,踩坑之旅,附解决方案(二) - 掘金

MyBatis Plus升级相关

  • springboot3.2 整合 mybatis-plus_java.lang.illegalargumentexception: invalid value【解决引入最新MP依赖报错问题】

Knife4j升级相关

  • 关于SpringBoot2.7.18升级到3.2.x后的Knife4j使用的系列问题汇总(已全部解决) · Issue #775 · xiaoymin/knife4j
  • SpringBoot3整合Knife4j4.x版本(Swagger3、OpenApi3)_knife4j openapi3【有解决单文件多文件示例等】
  • SpringBoot 整合 knfe4j ,使用 OpenAPI3 规范_knife4j-openapi3
  • springboot3.2集成knife4j_springboot3.2 knife4j
  • SpringBoot3整合Knife4j_springboot3 knife4j
  • SpringBoot3中Swagger整合knife4j和springdoc的配置说明
  • SpringBoot3.x版本将swagger2.0升级到swagger3.0,使用knife4j-openapi3-jakarta-spring-boot-starter依赖
  • SpringBoot 使用 OpenAPI3 规范整合 knife4j的详细过程
  • Swagger系列:SpringBoot3.x中使用Knife4j - Code技术分享
  • SpringBoot3登录拦截器导致不能正常访问knife4j_knife4j放行后被拦截
  • Knife4j文档请求异常(基于SpringBoot3,查找原因并解决)
  • SpringBoot整合knife4j_knife4j集成springboot
  • SpringBoot 3.0整合OpenAPI使用教程_knife4j-openapi3-jakarta-spring-boot-starter
  • knife4j文档请求异常 · Issue #749 · xiaoymin/knife4j【Knife4j纯yml配置参考】

涉及Redis相关

  • Springboot从2.x升级到3.x以后redis默认配置调整-阿里云开发者社区
  • SpringBoot 2.x / 3.x 整合 Redis ( RedisTemplate 操作五大常用数据类型)

其他【启动告警解决】

  • Spring源码系列:BeanDefinition源码解析 - 掘金
  • 解决is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) - 神一样的存在 - 博客园
  • 解决:is not eligible for getting processed by all BeanPostProcessors-CSDN博客
最后编辑于:2024-12-09 22:14:37


喜欢的朋友记得点赞、收藏、关注哦!!!

你可能感兴趣的:(面试题汇总与解析,spring,cloud,spring,boot,spring,技术,css3)