在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,就随便填了

我这里设置的是一般日志与异常日志分离,方便我们更快定位异常

Logo

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

更多推荐