springboot4.x 之knife4j文档与日志和mybatis-plus搭建
本文介绍了在SpringBoot4.x项目中集成Knife4j生成API文档的完整方案。主要内容包括:1) 添加兼容SpringBoot4.x的Knife4j依赖;2) 配置OpenAPI文档信息,支持分组显示接口;3) 使用Swagger注解描述Controller和模型类;4) 访问Knife4j增强UI界面(doc.html);5) 提供进阶配置技巧,如按环境开关文档功能。文章还展示了完整的
在Spring Boot 4.x项目中集成Knife4j可以为你生成强大而美观的API文档。下面是一个清晰的搭建指南,其中已特别关注了依赖版本与Spring Boot 4.x的兼容性。
🛠️ 第一步:添加依赖
在项目的 pom.xml文件中添加Knife4j的依赖。Spring Boot 4.x 基于Spring Boot 3.x构建,因此需要使用支持OpenAPI 3.0和Jakarta EE的Knife4j starter。
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version> <!-- 建议使用最新版本 -->
</dependency>
关键点:请确保使用 knife4j-openapi3-jakarta-spring-boot-starter,它与Spring Boot 3.x/4.x兼容。老版本或错误的starter(如针对Swagger 2.x的)可能无法正常工作
。
⚙️ 第二步:创建配置类
创建一个配置类(例如 Knife4jConfig)来定制文档信息。这里推荐使用OpenAPI 3.0的配置方式。
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("你的项目API文档")
.version("1.0")
.description("Spring Boot 4.x项目API接口文档")
.contact(new Contact().name("开发者").email("dev@example.com")));
}
// 可选:接口分组配置
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public") // 分组名称
.pathsToMatch("/api/**") // 匹配的接口路径
.packagesToScan("com.yourpackage.controller") // 扫描的包
.build();
}
}
配置说明:
-
OpenAPIBean用于配置文档的全局信息,如标题、版本等。 -
GroupedOpenApiBean可用于将接口分组显示,这在模块化项目中非常实用。
📝 第三步:使用注解描述API
在Controller和模型类上使用Swagger注解,这样Knife4j才能生成详细的接口文档。
1. Controller层常用注解
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关操作接口") // 标签,用于接口分组
public class UserController {
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户", description = "通过用户ID获取详细的用户信息") // 接口描述
public User getUserById(
@Parameter(description = "用户ID", example = "123", required = true) // 参数描述
@PathVariable Long id) {
// ... 业务逻辑
}
@PostMapping
@Operation(summary = "创建新用户")
public User createUser(@RequestBody User user) {
// ... 业务逻辑
}
}
2. 模型类(DTO/VO)常用注解
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "用户实体") // 模型描述
public class User {
@Schema(description = "用户唯一标识", example = "1")
private Long id;
@Schema(description = "用户名", example = "张三", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@Schema(description = "用户邮箱", example = "user@example.com")
private String email;
// ... getters and setters
}
通过这些注解,你可以清晰地说明每个接口、参数和返回值的含义,极大提升文档可读性
。
🌐 第四步:访问与查看
完成以上步骤后,启动你的Spring Boot应用。
-
访问Knife4j增强UI:在浏览器中打开
http://你的服务器地址:端口号/doc.html。 -
访问原生Swagger UI:你也可以通过
http://你的服务器地址:端口号/swagger-ui.html访问原生界面。
Knife4j的界面(doc.html)提供了更友好的文档展示和在线调试功能。
💡 进阶配置与技巧
-
按环境启用:为了避免在生产环境暴露API文档,你可以通过配置控制其开关。
# application.yml knife4j: enable: true # 开发环境设为true
💡 实战例子
项目结构

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zxs</groupId>
<artifactId>zxs-log</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zxs-log</name>
<description>zxs-log</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<lombok.version>1.18.36</lombok.version>
<mybatis-plus.version>3.5.15</mybatis-plus.version>
<knife4j.version>4.5.0</knife4j.version>
<mysql.version>8.0.33</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- 文档相关 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version> <!-- 建议检查并使用最新版本 -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
server:
port: 8080
spring:
application:
name: zxs-log
profiles:
active: dev
knife4j:
enable: true
setting:
language: zh-CN
spring:
datasource:
url: jdbc:mysql://192.168.0.106:3306/zxs?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: baishou888
driver-class-name: com.mysql.cj.jdbc.Driver
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" scanPeriod="60 seconds" debug="false">
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n" />
<!-- <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %p %C - %m%n" />-->
<property name="LOG_HOME" value="logs"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--先将今天的日志保存在这个文件中-->
<file>${LOG_HOME}/log_debug.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/log_info.%d{yyyy-MM-dd}.%i.zip</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
<maxFileSize>10MB</maxFileSize>
<!--所有的日志文件最大20G,超过就会删除旧的日志-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/log_error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/log_error.%d{yyyy-MM-dd}.%i.zip</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.zxs.zxslog" level="DEBUG"/>
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
</springProfile>
<!-- 测试环境 -->
<springProfile name="test">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.zxs.zxslog" level="DEBUG"/>
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.zxs.zxslog" level="INFO"/>
</springProfile>
</configuration>
package com.zxs.zxslog.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
final String securitySchemeName = "bearerAuth"; // 定义安全方案的名称
return new OpenAPI()
.info(new Info().title("API 文档").version("1.0"))
// 添加全局安全要求,引用下面定义的安全方案
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
// 定义组件(安全方案)
.components(new Components()
.addSecuritySchemes(securitySchemeName,
new SecurityScheme()
.name(securitySchemeName) // 头参数名,通常为 Authorization
.type(SecurityScheme.Type.HTTP) // 类型为 HTTP
.scheme("bearer") // 方案为 bearer
.bearerFormat("JWT") // 令牌格式(可选)
));
}
}
package com.zxs.zxslog.config;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.GlobalOperationCustomizer;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import java.util.List;
@Slf4j
@Component
public class Knife4jOperationCustomizer implements GlobalOperationCustomizer {
@Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
// 检查该接口是否已经配置了安全要求,如果没有则添加
if (operation.getSecurity() == null) {
// 添加安全要求,这里的 "bearerAuth" 需要与方法一中定义的安全方案名称一致
operation.setSecurity(List.of(new SecurityRequirement().addList("bearerAuth")));
}
return operation;
}
}
package com.zxs.zxslog.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
/**
* 自动填充处理器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MetaObjectHandler() {
@Override
public void insertFill(MetaObject metaObject) {
System.out.println("insertFill执行了");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
System.out.println("updateFill执行了");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
};
}
}
package com.zxs.zxslog.ctl;
import com.zxs.zxslog.entity.User;
import com.zxs.zxslog.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户相关接口")
public class LoginController {
@Resource
private UserService userService;
@GetMapping
@Operation(summary = "获取用户列表", description = "获取用户列表")
public List<User> list() {
log.trace("trace");
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
return userService.list();
}
@GetMapping("/log")
@Operation(summary = "主动抛出异常", description = "主动抛出异常")
public List<User> log() {
System.out.println(100/0);
return userService.list();
}
@PostMapping
@Operation(summary = "保存用户", description = "保存用户")
public boolean save(@RequestBody User user) {
return userService.save(user);
}
@PutMapping
@Operation(summary = "更新用户", description = "根据用户id更新用户")
public boolean update(@RequestBody User user) {
return userService.updateById(user);
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据用户id删除用户")
public boolean delete(@PathVariable Long id) {
return userService.removeById(id);
}
@GetMapping("/getUserById/{id}")
@Operation(summary = "获取用户", description = "根据用户id获取用户")
public User getUserById(@PathVariable("id") Long id){
return userService.getById(id);
}
}
package com.zxs.zxslog.entity.common;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class Common implements Serializable {
@TableId
private Long id;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Schema(description = "是否删除:0-未删除,1-已删除")
private Integer deleted;
}
package com.zxs.zxslog.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zxs.zxslog.entity.common.Common;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user")
public class User extends Common {
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "邮箱")
private String email;
@Schema(description = "手机")
private String phone;
@Schema(description = "状态:0-禁用,1-启用")
private Integer status;
}
package com.zxs.zxslog.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zxs.zxslog.entity.User;
public interface UserMapper extends BaseMapper<User> {}
package com.zxs.zxslog.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zxs.zxslog.entity.User;
import com.zxs.zxslog.mapper.UserMapper;
import com.zxs.zxslog.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
package com.zxs.zxslog.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zxs.zxslog.entity.User;
public interface UserService extends IService<User> {
}
package com.zxs.zxslog;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
@Slf4j
@SpringBootApplication
@MapperScan("com.zxs.zxslog.mapper")
public class ZxsLogApplication {
@SneakyThrows
public static void main(String[] args) {
ConfigurableApplicationContext application=SpringApplication.run(ZxsLogApplication.class, args);
Environment env = application.getEnvironment();
log.info("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\thttp://localhost:{}\n\t" +
"External: \thttp://{}:{}\n\t"+
"Doc: \thttp://{}:{}/doc.html\n"+
"----------------------------------------------------------",
env.getProperty("spring.application.name"),
env.getProperty("server.port"),
InetAddress.getLocalHost().getHostAddress(),
env.getProperty("server.port"),
InetAddress.getLocalHost().getHostAddress(),
env.getProperty("server.port"));
}
}


由于我这里只是预留的Authorize,就随便填了


我这里设置的是一般日志与异常日志分离,方便我们更快定位异常
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)