# SpringBoot集成SpringDoc OpenAPI(Swagger)企业级实战教程

本文以企业级开发标准为基准,从零开始讲解SpringBoot 3.5.10集成SpringDoc OpenAPI(适配SpringBoot 3.x,替代传统Swagger2)的完整流程,修复接口扫描冲突、整合完整JWT认证体系、实现用户/订单双模块开发,涵盖基础配置、高级定制、JWT完整集成、接口测试、生产环境管控等核心实战要点,所有代码可直接复制运行,适配实际企业开发场景。

# 1. 开发环境确认

确保开发环境满足以下标准化配置(SpringBoot 3.x强制要求):

  • IDE:IntelliJ IDEA 2023+(Ultimate/Community版均可)
  • JDK:17(SpringBoot 3.x最低兼容JDK 17,推荐17/21)
  • SpringBoot:3.5.10
  • Maven:3.9.4+
  • SpringDoc OpenAPI:2.6.0(适配SpringBoot 3.5.10的最新稳定版,兼容度最高)
  • JJWT:0.12.5(SpringBoot 3.x适配的JWT工具,实现接口认证)

# 2. 项目初始化

新建SpringBoot项目

001

企业级标准项目结构

初始化后按以下结构整理(补充JWT、多模块相关包,解决后续扫描/依赖问题):

SpringBoot-Swagger/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── cc/
│   │   │       └── hep/
│   │   │           ├── SpringBootSwaggerApplication.java  // 项目启动类
│   │   │           ├── common/                            // 通用工具包
│   │   │           │   └── utils/                         // 工具类子包
│   │   │           │       └── JwtUtil.java               // JWT核心工具类(生成/解析/校验)
│   │   │           ├── config/                            // 核心配置包
│   │   │           │   ├── OpenApiConfig.java            // SpringDoc核心配置(分组+JWT+全局信息)
│   │   │           │   ├── WebMvcConfig.java             // Web配置(注册JWT拦截器)
│   │   │           │   └── interceptor/                   // 拦截器子包
│   │   │           │       └── JwtInterceptor.java        // JWT全局拦截器(接口认证)
│   │   │           ├── controller/                       // 控制器包(多业务模块)
│   │   │           │   ├── UserController.java           // 用户模块控制器(含登录接口)
│   │   │           │   └── OrderController.java          // 订单模块控制器
│   │   │           ├── model/                            // 实体类包
│   │   │           │   ├── User.java                     // 用户实体(敏感字段隐藏)
│   │   │           │   └── Order.java                    // 订单实体
│   │   │           └── service/                          // 业务层包
│   │   │               ├── UserService.java              // 用户业务接口
│   │   │               ├── OrderService.java             // 订单业务接口
│   │   │               └── impl/                         // 业务实现类子包
│   │   │                   ├── UserServiceImpl.java      // 用户业务实现
│   │   │                   └── OrderServiceImpl.java     // 订单业务实现
│   │   └── resources/
│   │       ├── application.yml                          // 开发环境核心配置(修复扫描冲突)
│   │       └── application-prod.yml                     // 生产环境配置(屏蔽Swagger/JWT测试)
│   └── test/                                             // 测试包(默认生成,无需修改)
└── pom.xml                                               // Maven依赖(完整,含SpringDoc/JWT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 3. 完整Maven依赖配置

整合SpringDoc核心依赖、JWT认证依赖、Web核心依赖、Lombok,解决版本适配问题,直接替换项目原有pom.xml:

<?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>3.5.10</version>
		<relativePath/>
	</parent>
	<groupId>cc.hep</groupId>
	<artifactId>SpringBoot-Swagger</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringBoot-Swagger</name>
	<description>SpringBoot 3.5.10 + SpringDoc OpenAPI(分组+JWT)企业级实战</description>
	<properties>
		<java.version>17</java.version>
		<springdoc.version>2.6.0</springdoc.version>
	</properties>
	<dependencies>
		<!-- Spring Web核心 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- SpringDoc OpenAPI(Swagger替代,适配SpringBoot3.x) -->
		<dependency>
			<groupId>org.springdoc</groupId>
			<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
			<version>${springdoc.version}</version>
		</dependency>
		<!-- Lombok(简化实体/业务代码) -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!-- JWT核心依赖:jjwt(SpringBoot3.x适配0.12.5) -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.12.5</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.12.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.12.5</version>
			<scope>runtime</scope>
		</dependency>
		<!-- 序列化依赖(非必需,防止令牌解析序列化问题) -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>
		<!-- 测试依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

添加完成后,点击IDEA右侧「Maven」→「Reload All Maven Projects」刷新依赖,确保无红色报错。

# 4. 核心配置文件

resources/application.yml:

# 服务器配置
server:
  port: 8080
  servlet:
    context-path: /api # 企业级接口统一前缀

# SpringDoc OpenAPI核心配置(核心:扫描包+UI配置,注释冲突的paths-to-match)
springdoc:
  packages-to-scan: cc.hep.controller # 仅扫描控制器包,提升性能
  # 注释掉这行!与分组路径冲突,导致扫描失效
  # paths-to-match: /api/**
  swagger-ui:
    path: /swagger-ui/index.html # UI访问路径
    operationsSorter: method # 接口按请求方式排序
    tagsSorter: alpha # 分组标签按字母排序
    persistAuthorization: true # 持久化JWT认证信息
  api-docs:
    enabled: true # 开启API文档JSON生成
    path: /api-docs # 文档JSON数据路径

# 自定义OpenAPI文档元信息
openapi:
  info:
    title: 企业级微服务接口文档
    description: 基于SpringBoot 3.5.10 + SpringDoc OpenAPI的多模块接口文档(用户+订单)
    version: 2.0.0
    contact:
      name: 架构组
      email: arch@hep.cc
      url: https://www.hep.cc
    license:
      name: Apache 2.0
      url: https://www.apache.org/licenses/LICENSE-2.0.html
  # 多环境服务器配置
  server:
    dev:
      url: http://localhost:8080/api
      desc: 开发环境
    test:
      url: http://test.hep.cc/api
      desc: 测试环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 5. SpringDoc核心配置类

整合接口分组(用户/订单)、JWT全局认证配置、文档元信息、服务器多环境,OpenApiConfig.java直接创建在cc.hep.config包下:

package cc.hep.config;

import io.swagger.v3.oas.models.OpenAPI;
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.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * SpringDoc OpenAPI 核心配置类
 * 修复:移除路径冲突,增加兜底扫描配置,确保接口正常显示
 */
@Configuration
public class OpenApiConfig {

    // ========== 从application.yml注入文档元信息 ==========
    @Value("${openapi.info.title}")
    private String title;
    @Value("${openapi.info.description}")
    private String description;
    @Value("${openapi.info.version}")
    private String version;
    @Value("${openapi.info.contact.name}")
    private String contactName;
    @Value("${openapi.info.contact.email}")
    private String contactEmail;
    @Value("${openapi.info.contact.url}")
    private String contactUrl;
    @Value("${openapi.info.license.name}")
    private String licenseName;
    @Value("${openapi.info.license.url}")
    private String licenseUrl;

    // ========== 从application.yml注入多环境服务器信息 ==========
    @Value("${openapi.server.dev.url}")
    private String devServerUrl;
    @Value("${openapi.server.dev.desc}")
    private String devServerDesc;
    @Value("${openapi.server.test.url}")
    private String testServerUrl;
    @Value("${openapi.server.test.desc}")
    private String testServerDesc;

    // ========== 全局OpenAPI配置(整合:元信息+JWT+多环境服务器) ==========
    @Bean
    public OpenAPI openApi() {
        // 多环境服务器
        Server devServer = new Server().url(devServerUrl).description(devServerDesc);
        Server testServer = new Server().url(testServerUrl).description(testServerDesc);

        // 文档元信息
        Info info = new Info()
                .title(title)
                .description(description)
                .version(version)
                .contact(new Contact().name(contactName).email(contactEmail).url(contactUrl))
                .license(new License().name(licenseName).url(licenseUrl));

        // JWT安全方案配置
        SecurityScheme jwtScheme = new SecurityScheme()
                .type(SecurityScheme.Type.HTTP)
                .scheme("bearer")
                .bearerFormat("JWT")
                .name("Authorization")
                .in(SecurityScheme.In.HEADER);

        // 全局JWT认证
        SecurityRequirement jwtSecurity = new SecurityRequirement().addList("JWT_AUTH");

        // 构建OpenAPI
        return new OpenAPI()
                .info(info)
                .servers(List.of(devServer, testServer))
                .addSecurityItem(jwtSecurity)
                .components(new io.swagger.v3.oas.models.Components()
                        .addSecuritySchemes("JWT_AUTH", jwtScheme));
    }

    // ========== 接口分组1:用户模块(兜底配置:明确扫描控制器+排除无用路径) ==========
    @Bean
    public GroupedOpenApi userModuleApi() {
        return GroupedOpenApi.builder()
                .group("用户模块")
                .pathsToMatch("/user/**") // 匹配/user下所有接口(相对于context-path:/api)
                .pathsToExclude("/none/**") // 排除无意义路径,避免扫描干扰
                .packagesToScan("cc.hep.controller") // 扫描控制器包
                .build();
    }

    // ========== 接口分组2:订单模块(同用户模块兜底配置) ==========
    @Bean
    public GroupedOpenApi orderModuleApi() {
        return GroupedOpenApi.builder()
                .group("订单模块")
                .pathsToMatch("/order/**") // 匹配/order下所有接口
                .pathsToExclude("/none/**")
                .packagesToScan("cc.hep.controller")
                .build();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

# 6. 企业级多模块业务开发(用户+订单)

按实体类→业务层→控制器的分层规范开发,整合SpringDoc注解(接口文档描述)、敏感字段隐藏、参数校验,所有代码可直接复制。

# 6.1 实体类开发

# 6.1.1 用户实体(User.java)

包含敏感字段隐藏(密码)、Swagger字段描述,创建在cc.hep.model包下:

package cc.hep.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户实体类(企业级规范:Serializable+Lombok+Schema注解)
 */
@Data
@Schema(description = "用户实体类")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @Schema(description = "用户ID", example = "1001", requiredMode = Schema.RequiredMode.REQUIRED)
    private Long id;

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

    @Schema(description = "用户密码(加密存储)", example = "e10adc3949ba59abbe56e057f20f883e", hidden = true)
    private String password; // hidden=true:文档中隐藏敏感字段

    @Schema(description = "用户邮箱", example = "zhangsan@hep.cc")
    private String email;

    @Schema(description = "创建时间", example = "2026-02-04 10:00:00")
    private LocalDateTime createTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 6.1.2 订单实体(Order.java)

与用户实体规范一致,创建在cc.hep.model包下:

package cc.hep.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单实体类(新增,订单模块)
 */
@Data
@Schema(description = "订单实体类")
public class Order implements Serializable {
    private static final long serialVersionUID = 1L;

    @Schema(description = "订单ID", example = "20260204001", requiredMode = Schema.RequiredMode.REQUIRED)
    private String orderId;

    @Schema(description = "用户ID", example = "1001", requiredMode = Schema.RequiredMode.REQUIRED)
    private Long userId;

    @Schema(description = "订单金额", example = "99.99", requiredMode = Schema.RequiredMode.REQUIRED)
    private BigDecimal amount;

    @Schema(description = "订单状态(0-待支付 1-已支付 2-已取消)", example = "0", requiredMode = Schema.RequiredMode.REQUIRED)
    private Integer status;

    @Schema(description = "订单创建时间", example = "2026-02-04 14:00:00")
    private LocalDateTime createTime;

    @Schema(description = "支付时间", example = "2026-02-04 14:05:00", nullable = true)
    private LocalDateTime payTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 6.2 业务层开发(Service)

模拟数据库操作(企业级开发中替换为MyBatis/MyBatis-Plus),实现基础的增删改查,按接口+实现类规范开发。

# 6.2.1 用户业务层

(1)用户业务接口(UserService.java)

创建在cc.hep.service包下:

package cc.hep.service;

import cc.hep.model.User;

import java.util.List;

/**
 * 用户业务层接口(企业级接口隔离规范)
 */
public interface UserService {
    /**
     * 根据ID查询用户
     */
    User getUserById(Long id);

    /**
     * 查询所有用户
     */
    List<User> listAllUsers();

    /**
     * 添加用户
     */
    Boolean addUser(User user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

(2)用户业务实现类(UserServiceImpl.java)

创建在cc.hep.service.impl包下:

package cc.hep.service.impl;

import cc.hep.model.User;
import cc.hep.service.UserService;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 用户业务实现类
 * 模拟数据库操作,企业级开发中替换为MyBatis/MyBatis-Plus持久层调用
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public User getUserById(Long id) {
        User user = new User();
        user.setId(id);
        user.setUsername("zhangsan");
        user.setEmail("zhangsan@hep.cc");
        user.setCreateTime(LocalDateTime.now());
        return user;
    }

    @Override
    public List<User> listAllUsers() {
        List<User> userList = new ArrayList<>();
        // 模拟用户1
        User user1 = new User();
        user1.setId(1001L);
        user1.setUsername("zhangsan");
        user1.setEmail("zhangsan@hep.cc");
        user1.setCreateTime(LocalDateTime.now());
        // 模拟用户2
        User user2 = new User();
        user2.setId(1002L);
        user2.setUsername("lisi");
        user2.setEmail("lisi@hep.cc");
        user2.setCreateTime(LocalDateTime.now());

        userList.add(user1);
        userList.add(user2);
        return userList;
    }

    @Override
    public Boolean addUser(User user) {
        // 模拟添加逻辑,自动设置创建时间
        user.setCreateTime(LocalDateTime.now());
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 6.2.2 订单业务层

(1)订单业务接口(OrderService.java)

创建在cc.hep.service包下:

package cc.hep.service;

import cc.hep.model.Order;

import java.util.List;

/**
 * 订单业务层接口
 */
public interface OrderService {
    /**
     * 根据订单ID查询订单
     */
    Order getOrderById(Long orderId);

    /**
     * 根据用户ID查询订单列表
     */
    List<Order> listOrderByUserId(Long userId);

    /**
     * 创建订单
     */
    Boolean createOrder(Order order);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

(2)订单业务实现类(OrderServiceImpl.java)

创建在cc.hep.service.impl包下:

package cc.hep.service.impl;

import cc.hep.model.Order;
import cc.hep.service.OrderService;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 订单业务实现类
 * 模拟数据库操作,企业级开发中替换为持久层调用
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order getOrderById(Long orderId) {
        Order order = new Order();
        order.setOrderId(orderId);
        order.setUserId(1001L);
        order.setAmount(new BigDecimal("99.99"));
        order.setStatus(1);
        order.setCreateTime(LocalDateTime.now());
        return order;
    }

    @Override
    public List<Order> listOrderByUserId(Long userId) {
        List<Order> orderList = new ArrayList<>();
        // 模拟订单1
        Order order1 = new Order();
        order1.setOrderId(20260204001L);
        order1.setUserId(userId);
        order1.setAmount(new BigDecimal("99.99"));
        order1.setStatus(1);
        order1.setCreateTime(LocalDateTime.now());
        // 模拟订单2
        Order order2 = new Order();
        order2.setOrderId(20260204002L);
        order2.setUserId(userId);
        order2.setAmount(new BigDecimal("199.99"));
        order2.setStatus(2);
        order2.setCreateTime(LocalDateTime.now());

        orderList.add(order1);
        orderList.add(order2);
        return orderList;
    }

    @Override
    public Boolean createOrder(Order order) {
        // 模拟创建逻辑,自动设置创建时间
        order.setCreateTime(LocalDateTime.now());
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 6.3 控制器开发(Controller)

整合SpringDoc注解(接口文档描述)、参数校验、响应状态码规范,用户控制器新增登录接口(生成JWT令牌),所有接口被JWT拦截器保护(登录接口除外)。

# 6.3.1 用户控制器(UserController.java)

创建在cc.hep.controller包下,包含登录接口(获取JWT令牌)+ 基础用户接口:

package cc.hep.controller;

import cc.hep.utils.JwtUtil;
import cc.hep.model.User;
import cc.hep.service.UserService;
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.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/user")
@Tag(name = "用户管理接口", description = "用户查询、新增、登录等核心接口(登录无需认证,其余需JWT)")
public class UserController {

    @Autowired
    private UserService userService;
    // 注入JWT工具类
    @Autowired
    private JwtUtil jwtUtil;

    // ========== 新增:登录接口(白名单,放行,生成JWT令牌) ==========
    @PostMapping("/login")
    @Operation(summary = "用户登录", description = "账号密码校验通过后,返回JWT令牌(模拟:账号zhangsan,密码123456)")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "登录成功,返回JWT令牌"),
            @ApiResponse(responseCode = "400", description = "账号/密码为空"),
            @ApiResponse(responseCode = "401", description = "账号或密码错误")
    })
    public ResponseEntity<Map<String, Object>> login(
            @RequestParam @Parameter(description = "用户名", example = "zhangsan", required = true) String username,
            @RequestParam @Parameter(description = "密码", example = "123456", required = true) String password
    ) {
        Map<String, Object> result = new HashMap<>();
        // 1. 参数校验
        if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
            result.put("code", 400);
            result.put("msg", "账号/密码不能为空");
            return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
        }
        // 2. 模拟账号密码校验(企业级开发中替换为MyBatis查询数据库)
        if (!"zhangsan".equals(username) || !"123456".equals(password)) {
            result.put("code", 401);
            result.put("msg", "账号或密码错误");
            return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED);
        }
        // 3. 校验通过:生成JWT令牌(用户ID固定为1001,模拟)
        String token = jwtUtil.generateToken(1001L, username);
        // 4. 返回结果(含令牌,前端需保存)
        result.put("code", 200);
        result.put("msg", "登录成功");
        result.put("token", token); // 纯令牌(Swagger授权时需加Bearer前缀)
        result.put("token_format", "Bearer " + token); // 完整格式(可直接复制到Swagger)
        return new ResponseEntity<>(result, HttpStatus.OK);
    }

    // ========== 原有接口(无需修改,已被JWT拦截器保护) ==========
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "传入用户ID,返回用户信息(密码字段隐藏)")
    @Parameters({@Parameter(name = "id", description = "用户ID", required = true, example = "1001")})
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "查询成功"),
            @ApiResponse(responseCode = "400", description = "参数错误(ID为空/非正数)"),
            @ApiResponse(responseCode = "404", description = "用户不存在")
    })
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        if (id == null || id <= 0) return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        User user = userService.getUserById(id);
        return user == null ? new ResponseEntity<>(HttpStatus.NOT_FOUND) : new ResponseEntity<>(user, HttpStatus.OK);
    }

    @GetMapping("/list")
    @Operation(summary = "查询所有用户", description = "返回系统所有用户列表(不含密码)")
    @ApiResponse(responseCode = "200", description = "查询成功,返回用户列表")
    public ResponseEntity<List<User>> listAllUsers() {
        return new ResponseEntity<>(userService.listAllUsers(), HttpStatus.OK);
    }

    @PostMapping("/add")
    @Operation(summary = "新增用户", description = "传入用户信息,完成用户新增(创建时间自动生成)")
    @ApiResponses({
            @ApiResponse(responseCode = "201", description = "新增成功"),
            @ApiResponse(responseCode = "400", description = "参数错误(用户名为空)")
    })
    public ResponseEntity<Boolean> addUser(@RequestBody User user) {
        if (user == null || user.getUsername() == null || user.getUsername().isEmpty()) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<>(userService.addUser(user), HttpStatus.CREATED);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

# 6.3.2 订单控制器(OrderController.java)

创建在cc.hep.controller包下,所有接口需JWT认证:

package cc.hep.controller;

import cc.hep.model.Order;
import cc.hep.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 订单模块控制器(新增,企业级规范)
 */
@RestController
@RequestMapping("/order")
@Tag(name = "订单管理接口", description = "订单查询、创建等核心接口(需JWT认证)")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/{orderId}")
    @Operation(summary = "根据订单ID查询订单", description = "传入订单ID,返回订单详细信息")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "查询成功"),
            @ApiResponse(responseCode = "400", description = "订单ID为空"),
            @ApiResponse(responseCode = "404", description = "订单不存在")
    })
    public ResponseEntity<Order> getOrderByOrderId(@PathVariable @Parameter(description = "订单ID", example = "20260204001", required = true) String orderId) {
        if (orderId == null || orderId.isEmpty()) return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        Order order = orderService.getOrderByOrderId(orderId);
        return order == null ? new ResponseEntity<>(HttpStatus.NOT_FOUND) : new ResponseEntity<>(order, HttpStatus.OK);
    }

    @GetMapping("/user/{userId}")
    @Operation(summary = "根据用户ID查询订单列表", description = "传入用户ID,返回该用户所有订单")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "查询成功"),
            @ApiResponse(responseCode = "400", description = "用户ID为空/非正数")
    })
    public ResponseEntity<List<Order>> listOrderByUserId(@PathVariable @Parameter(description = "用户ID", example = "1001", required = true) Long userId) {
        if (userId == null || userId <= 0) return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        return new ResponseEntity<>(orderService.listOrderByUserId(userId), HttpStatus.OK);
    }

    @PostMapping("/create")
    @Operation(summary = "创建订单", description = "传入订单信息,完成订单创建(状态默认待支付)")
    @ApiResponses({
            @ApiResponse(responseCode = "201", description = "创建成功"),
            @ApiResponse(responseCode = "400", description = "参数错误(订单ID/用户ID为空)")
    })
    public ResponseEntity<Boolean> createOrder(@RequestBody Order order) {
        if (order == null || order.getOrderId() == null || order.getUserId() == null) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<>(orderService.createOrder(order), HttpStatus.CREATED);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 7. 完整JWT认证体系集成

实现JWT工具类(生成/解析/校验)、JWT拦截器(全局接口认证)、Web配置(注册拦截器+配置白名单),确保只有携带有效令牌的请求才能访问业务接口,登录接口/Swagger路径放行。

# 7.1 JWT核心工具类(JwtUtil.java)

封装令牌生成、解析、校验、从请求头提取令牌的方法,256位密钥适配JJWT 0.12.5,创建在cc.hep.common.utils包下:

package cc.hep.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类(企业级标准封装,SpringBoot3.x+JJWT0.12.5)
 * 功能:生成令牌、解析令牌中的用户信息、校验令牌、从请求头提取令牌
 */
@Component
public class JwtUtil {
    // 1. 256位密钥(JJWT0.12+要求至少256位,可自行生成,生产环境放配置文件/配置中心)
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("12345678901234567890123456789012".getBytes());
    // 2. 令牌过期时间:2小时(单位:毫秒,生产环境可配置)
    private static final long EXPIRE_TIME = 2 * 60 * 60 * 1000L;
    // 3. 请求头中Token的键名
    public static final String TOKEN_HEADER = "Authorization";
    // 4. Token前缀(与Swagger配置一致)
    public static final String TOKEN_PREFIX = "Bearer ";

    /**
     * 生成JWT令牌(传入用户ID、用户名,自定义载荷)
     */
    public String generateToken(Long userId, String username) {
        // 自定义载荷(存储用户关键信息,不存敏感信息如密码)
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);
        // 生成令牌
        return Jwts.builder()
                .claims(claims) // 载荷
                .subject(username) // 主题(可选)
                .issuedAt(new Date()) // 签发时间
                .expiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) // 过期时间
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256) // 签名算法+密钥
                .compact(); // 生成令牌字符串
    }

    /**
     * 从令牌中解析出载荷(获取用户信息)
     */
    public Claims getClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                    .verifyWith(SECRET_KEY) // 验证密钥
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();
        } catch (Exception e) {
            return null; // 令牌无效/过期/签名错误,返回null
        }
    }

    /**
     * 校验令牌是否有效(未过期+签名正确)
     */
    public boolean isTokenValid(String token) {
        Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return false;
        }
        // 检查是否过期
        return !claims.getExpiration().before(new Date());
    }

    /**
     * 从Http请求头中提取令牌(自动去掉Bearer前缀)
     */
    public String extractTokenFromRequest(HttpServletRequest request) {
        String header = request.getHeader(TOKEN_HEADER);
        if (StringUtils.hasText(header) && header.startsWith(TOKEN_PREFIX)) {
            return header.substring(TOKEN_PREFIX.length()); // 去掉前缀,返回纯令牌
        }
        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

# 7.2 JWT全局拦截器(JwtInterceptor.java)

接口执行前校验令牌,无令牌/令牌无效返回401未授权,令牌有效则解析用户信息放入请求,创建在cc.hep.config.interceptor包下:

package cc.hep.config.interceptor;

import cc.hep.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * JWT拦截器(全局校验令牌,企业级标准)
 * 逻辑:放行白名单 → 提取Token → 校验Token → 解析用户信息(可选)→ 放行/拦截
 */
@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 前置拦截(接口执行前校验Token)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 提取令牌(从请求头)
        String token = jwtUtil.extractTokenFromRequest(request);
        // 2. 校验令牌:无令牌/令牌无效 → 拦截(返回401未授权)
        if (token == null || !jwtUtil.isTokenValid(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401状态码
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"msg\":\"未授权,请先登录获取令牌\"}");
            return false; // 拦截请求
        }
        // 3. 令牌有效:解析用户信息(可选,放入request供后续接口使用)
        Claims claims = jwtUtil.getClaimsFromToken(token);
        request.setAttribute("userId", claims.get("userId"));
        request.setAttribute("username", claims.get("username"));
        // 4. 放行请求
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 7.3 Web配置类(WebMvcConfig.java)

注册JWT拦截器+配置白名单,放行Swagger所有相关路径、用户登录接口,避免Swagger无法访问/登录接口被拦截,创建在cc.hep.config包下:

package cc.hep.config;

import cc.hep.config.interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * WebMvc配置类:注册JWT拦截器+配置白名单(放行无需认证的路径)
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private JwtInterceptor jwtInterceptor;

    /**
     * 注册拦截器,配置拦截规则+白名单
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/api/**") // 拦截所有/api前缀的接口(企业级统一前缀)
                .excludePathPatterns( // 白名单:放行以下路径,无需认证
                        // 1. Swagger所有相关路径(必须全部放行,否则Swagger页面打不开/授权失败)
                        "/api/swagger-ui/**",
                        "/api/api-docs/**",
                        "/api/v3/api-docs/**",
                        "/api/swagger-resources/**",
                        "/api/webjars/**",
                        // 2. 登录接口(核心,放行后才能获取令牌)
                        "/api/user/login"
                );
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 8. 生产环境屏蔽配置

企业级开发中,生产环境必须屏蔽Swagger文档(避免接口信息泄露)、禁用JWT测试相关功能,创建resources/application-prod.yml:

# 生产环境核心配置:屏蔽Swagger所有功能
springdoc:
  api-docs:
    enabled: false # 禁用API文档JSON生成
  swagger-ui:
    enabled: false # 禁用Swagger UI页面

# 服务器配置与开发环境保持一致
server:
  port: 8080
  servlet:
    context-path: /api
1
2
3
4
5
6
7
8
9
10
11
12

生产环境启动方式(指定环境配置):

# 方式1:jar包启动
java -jar SpringBoot-Swagger-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

# 方式2:IDEA中启动
Run/Debug Configurations → VM options → 添加:-Dspring.profiles.active=prod
1
2
3
4
5

# 9. Swagger UI接口文档访问与测试流程

# 9.1 项目启动

运行cc.hep.SpringBootSwaggerApplication启动类,控制台无红色报错即启动成功,关键日志:

  • Tomcat started on port(s): 8080 (http)
  • Started SpringBootSwaggerApplication in X seconds

# 9.2 Swagger UI访问地址

支持以下两个地址(均能正常打开,SpringDoc做了路径映射):

  1. 基础地址:http://localhost:8080/api/swagger-ui/index.html
  2. 实际测试地址:http://localhost:8080/api/swagger-ui/swagger-ui/index.html

# 9.3 完整接口测试流程(以用户模块为例)

步骤1:登录接口获取JWT令牌

  1. 打开Swagger UI页面,左侧展开「用户管理接口」,找到POST /user/login接口;
  2. 点击接口右侧Try it out按钮(橙色),出现username和password输入框;
  3. 分别输入:username=zhangsan、password=123456;
  4. 点击下方Execute按钮(蓝色),向下滚动查看Response body;
  5. 复制token_format对应的值(已包含Bearer前缀),示例:
    Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwMDEsInVzZXJuYW1lIjoiemhhbmdzYW4iLCJzdWIiOiJ6aGFuZ3NhbiIsImlhdCI6MTcxNzUyNjQzNSwiZXhwIjoxNzE3NTMzNjM1fQ.2f9ZpRbQ7eU8X7s9D6F5G4H3J2K1L0M9N8O7P6Q5R4S3T2
    
    1

步骤2:Swagger全局JWT授权

  1. 点击Swagger页面右上角绿色Authorize按钮,弹出授权弹窗;
  2. 在弹窗输入框中直接粘贴步骤1复制的token_format值(无需修改);
  3. 点击弹窗底部Authorize按钮,弹窗顶部显示Authorized绿色提示;
  4. 点击Close关闭弹窗,右上角按钮变为红色Unauthorize(代表授权成功)。

步骤3:测试需要认证的接口(GET /user/{id})

  1. 左侧找到GET /user/{id}接口,点击Try it out按钮;
  2. 在id输入框中输入1001(合法参数);
  3. 点击Execute按钮,向下滚动查看测试结果:
    • Request Headers:包含Authorization: Bearer 你的令牌(授权生效);
    • Response Code:200(请求成功);
    • Response body:返回用户信息,无password字段(敏感字段隐藏成功)。

步骤4:异常场景测试(未授权访问)

  1. 点击右上角Unauthorize按钮,取消JWT授权;
  2. 再次调用GET /user/1001接口,点击Execute;
  3. 查看Response Code:401,Response body:{"code":401,"msg":"未授权,请先登录获取有效JWT令牌"}(拦截器生效)。

# 9.4 订单模块测试流程

与用户模块一致,先完成JWT授权,再调用订单接口(如GET /order/20260204001),验证接口正常返回数据即可。

002

# 10. 常见问题与解决方案

Swagger UI显示「No operations defined in spec!」

核心原因:全局路径匹配paths-to-match: /api/**与分组路径/user/**冲突; 解决方案:注释application.yml中的paths-to-match: /api/**,重启项目+浏览器强制刷新(F5)。

Swagger Authorize按钮点击无弹窗/授权后接口仍返回401

  1. 检查OpenApiConfig中JWT安全方案的key(JWT_AUTH)是否与SecurityRequirement中的key一致;
  2. 确认粘贴的令牌包含完整的Bearer前缀(注意英文空格,不可省略);
  3. 检查令牌是否过期(JWT工具类配置2小时,过期后重新登录获取);
  4. 检查JwtUtil中的256位密钥是否正确,生成/校验令牌使用同一密钥。

Swagger页面访问404

  1. 检查server.servlet.context-path是否配置为/api,访问地址必须携带该前缀;
  2. 检查WebMvcConfig的白名单是否包含所有Swagger相关路径(/api/swagger-ui/**等);
  3. 确认SpringDoc依赖已正确引入,Maven刷新后无报错。

JWT拦截器拦截登录接口/Swagger路径

解决方案:检查WebMvcConfig的excludePathPatterns,确保包含:

  • 所有Swagger相关路径(/api/swagger-ui/**、/api/api-docs/**等);
  • 登录接口路径(/api/user/login)。

启动项目时报错「Failed to create SecretKey for HS256」

核心原因:JJWT 0.12+要求密钥≥256位; 解决方案:修改JwtUtil中的密钥为32位字符串(如12345678901234567890123456789012),确保密钥字节数为32(256位)。

# 11. 核心总结

  1. SpringBoot3.x适配:放弃传统Swagger2,使用springdoc-openapi-starter-webmvc-ui核心依赖,注解使用io.swagger.v3.oas.annotations包下的;
  2. 扫描冲突修复:注释application.yml中的全局路径匹配paths-to-match: /api/**,由分组路径/user/**//order/**接管扫描;
  3. JWT集成核心:实现「工具类+拦截器+Web配置」,白名单放行Swagger路径和登录接口,其余接口统一认证;
  4. 企业级规范:敏感字段用@Schema(hidden=true)隐藏,生产环境通过多环境配置屏蔽Swagger,接口按「实体→业务→控制器」分层开发;
  5. Swagger测试规范:先通过登录接口获取有效令牌,再完成全局授权,最后测试业务接口,确保请求头携带Authorization令牌。

本教程所有代码可直接复制运行,适配SpringBoot 3.5.10最新版本,整合了企业开发中必备的接口文档、JWT认证、多模块开发、生产环境管控等功能,可直接作为实际项目的基础模板。

最近更新: 2026/2/4
 |  Copyright © -Present Kelsen All Rights Reserved. | Author KelsenJing