# Spring Boot 2.2.13 → 2.7.18 升级指南
> 本文档基于 `ylrz_his_scrm_java` 项目现状编写,目标版本:**Spring Boot 2.7.18**(2.x 末代 LTS,仍支持 Java 8)。
---
## 一、升级概览
### 1.1 当前 vs 目标
| 项 | 当前 | 升级后(Boot 2.7.18 BOM 默认) |
|---|---|---|
| Spring Boot | 2.2.13.RELEASE | **2.7.18** |
| Spring Framework | 5.2.x | **5.3.31** |
| Spring Security | 5.2.15(手动覆盖) | **5.7.11**(随 BOM,可不再 pin) |
| Java | 1.8 | 1.8(不变) |
| Servlet API | javax.* | javax.*(**不涉及 Jakarta 迁移**) |
### 1.2 项目规模(影响工作量)
| 维度 | 数量 |
|---|---|
| Java 源文件 | ~8500 |
| Maven 模块 | 36+ |
| 可运行 Spring Boot 应用 | **28** |
| `SecurityConfig`(需迁移) | **26** |
| `SwaggerConfig`(Springfox) | **23** |
| `HandlerInterceptorAdapter`(已废弃) | **37** |
| 共享配置文件 | `fs-service/src/main/resources/application-common.yml` |
### 1.3 预估工期
| 阶段 | 内容 | 预估 |
|---|---|---|
| 阶段 0 | 准备与分支 | 0.5 天 |
| 阶段 1 | POM / 依赖 | 1–2 天 |
| 阶段 2 | 全局运行时配置 | 0.5 天 |
| 阶段 3 | Spring Security 迁移 | 3–5 天 |
| 阶段 4 | Swagger 兼容 | 1–3 天 |
| 阶段 5 | 编译修复 & 启动 | 2–3 天 |
| 阶段 6 | 回归测试 | 1–2 周 |
**合计:约 2–4 周**(1 人全职,视测试深度而定)。
---
## 二、升级前准备(阶段 0)
### 2.1 创建升级分支
```bash
git checkout -b feature/spring-boot-2.7.18
```
### 2.2 建立基线
- 记录当前各核心模块能否正常 `mvn package`、能否启动。
- 导出当前依赖树备查:
```bash
mvn dependency:tree -pl fs-admin -Dverbose > docs/upgrade-baseline-admin-deps.txt
mvn dependency:tree -pl fs-user-app -Dverbose > docs/upgrade-baseline-user-app-deps.txt
```
### 2.3 确定升级范围
建议**一次性升 BOM**,不要停留在 2.3/2.5 中间版本——你们从 2.2 直接到 2.7,中间破坏性变更(路径匹配、循环依赖、Security API)集中处理更高效。
### 2.4 升级顺序建议
```
fs-common / fs-framework(基础)
↓
fs-service(业务核心)
↓
fs-admin / fs-user-app / fs-company(主入口)
↓
其余 API / 任务 / MQ 模块
```
---
## 三、依赖与 POM 升级(阶段 1)
### 3.1 修改根 `pom.xml`
#### 3.1.1 升级 Spring Boot BOM
```xml
org.springframework.boot
spring-boot-dependencies
2.7.18
pom
import
```
#### 3.1.2 新增/调整 properties(建议值)
```xml
2.7.18
2.3.2
1.2.23
3.27.2
3.5.5
```
#### 3.1.3 移除或下调的手动覆盖
升级到 2.7.18 后,以下覆盖**可先删除**,改由 BOM 管理;删除后跑 `dependency:tree` 确认无 CVE 回退:
| 属性 / 依赖 | 当前 | 建议 |
|---|---|---|
| `spring-security.version` 及 core/web/config 三件套 | 5.2.15 手动 pin | **删除**,使用 BOM 5.7.x |
| `jackson.version` / `jackson-bom` | 2.12.7 手动 pin | **删除或改为仅覆盖个别 artifact** |
| `logback.version` | 1.2.13 | Boot 2.7 自带 1.2.12+,可删除 |
| `snakeyaml.version` | 1.33 | Boot 2.7 自带 1.30+,可删除 |
**保留**(仍可能有传递依赖冲突):
- `netty.version`、`hutool.version`、`okhttp.version`、`fastjson.version` 等安全相关 pin
- `pagehelper`(你们有 SQL 注入 CVE 注释,需验证 BOM 版本是否 ≥ 5.3.1)
#### 3.1.4 Redisson 适配(必改)
根 `pom.xml` 与 `fs-service/pom.xml` 中:
```xml
redisson-spring-data-22
redisson-spring-data-27
```
`fs-service/pom.xml` 中排除 `redisson-spring-data-32` 的注释也需同步更新。
#### 3.1.5 统一 spring-boot-maven-plugin
当前各可运行模块 pin 了 **2.1.1.RELEASE**(如 `fs-admin/pom.xml`),建议:
**方案 A(推荐)**:在根 `pom.xml` 的 `pluginManagement` 中统一管理:
```xml
org.springframework.boot
spring-boot-maven-plugin
${spring-boot.version}
```
子模块删除 `2.1.1.RELEASE`。
**涉及模块(27 个)**:fs-admin、fs-user-app、fs-company、fs-company-app、fs-doctor-app、fs-store、fs-qw-*、fs-live-*、fs-ad-*、fs-wx-api、fs-watch、fs-redis、fs-websocket、fs-ai-chat、fs-common-api、fs-repeat-api、fs-ipad-task、fs-qwhook* 等。
### 3.2 修改 `fs-common/pom.xml`
#### 3.2.1 MyBatis-Plus
```xml
mybatis-plus-boot-starter
3.1.0
mybatis-plus-boot-starter
${mybatis-plus.version}
```
#### 3.2.2 spring-expression 版本冲突(重要)
当前 `fs-common/pom.xml` 显式依赖了 **spring-expression 6.2.0**(Spring 6),与 Boot 2.7 的 Spring 5.3 **不兼容**。
```xml
org.springframework
spring-expression
```
### 3.3 MySQL 驱动(可选)
Boot 2.7 仍支持 `mysql-connector-java`,但官方已迁移到 `com.mysql:mysql-connector-j`。短期可不改;长期建议替换:
```xml
com.mysql
mysql-connector-j
```
### 3.4 验证命令
```bash
# 全量编译(先不要求启动)
mvn clean compile -DskipTests
# 检查冲突
mvn dependency:tree -pl fs-admin | findstr /i "spring-security spring-framework spring-expression"
```
---
## 四、全局运行时配置(阶段 2)
在 `fs-service/src/main/resources/application-common.yml` 的 `spring:` 节点下增加:
```yaml
spring:
# Boot 2.6+ 默认禁止循环依赖,老项目常见此问题
main:
allow-circular-references: true # 临时方案;启动稳定后逐步改为 false 并重构
# Boot 2.6+ 默认 PathPatternParser,与 Springfox 2.9.2 / 部分 antMatchers 不兼容
mvc:
pathmatch:
matching-strategy: ant_path_matcher # 短期保留 Springfox 时使用
```
> **说明**:`ant_path_matcher` 在 2.7 仍可用但已标记为过渡方案。若后续迁移到 springdoc-openapi,可移除此配置。
### 4.1 Tomcat 配置项更名(如遇警告)
Boot 2.4+ 部分 `server.tomcat.*` 属性已更名,你们当前使用:
```yaml
server:
tomcat:
max-threads: 800 # 建议改为 max-threads 或 threads.max
min-spare-threads: 30 # 建议改为 threads.min-spare
```
启动时若出现 deprecated 警告,按 Boot 2.7 文档调整即可,不影响功能。
---
## 五、Spring Security 迁移(阶段 3,工作量最大)
### 5.1 背景
- Boot 2.7 自带 **Spring Security 5.7.x**
- `WebSecurityConfigurerAdapter` 已在 5.7 **移除**
- 项目共 **26 个** `SecurityConfig` 需迁移
### 5.2 配置文件分类
#### A 类:全放行 + StrictHttpFirewall(17 个,改动模式相同)
适用模块:fs-user-app、fs-doctor-app、fs-live-app、fs-live-mq、fs-watch、fs-redis、fs-wx-api、fs-ad-api、fs-ad-new-api、fs-common-api、fs-repeat-api、fs-qwhook、fs-qwhook-msg、fs-qwhook-sop、fs-qw-mq、fs-qw-voice、fs-user-app-ai-chat、fs-company-app
参考现有代码:`fs-user-app/.../SecurityConfig.java`
#### B 类:JWT 完整鉴权(7 个,规则较多)
| 模块 | 文件 | 特殊点 |
|---|---|---|
| fs-framework | `fs-framework/.../SecurityConfig.java` | **fs-admin 依赖此模块**,规则最全 |
| fs-company | `fs-company/.../SecurityConfig.java` | 使用 `MD5PasswordEncoder` |
| fs-store | `fs-store/.../SecurityConfig.java` | 使用 `MD5PasswordEncoder` |
| fs-qw-api | `fs-qw-api/.../SecurityConfig.java` | JWT + 白名单 |
| fs-qw-api-msg | `fs-qw-api-msg/.../SecurityConfig.java` | JWT + 白名单 |
| fs-qw-company-api | `fs-qw-company-api/.../SecurityConfig.java` | JWT + 白名单 |
| fs-qw-task | `fs-qw-task/.../SecurityConfig.java` | JWT + 白名单 |
| fs-ipad-task | `fs-ipad-task/.../SecurityConfig.java` | JWT + 白名单 |
> **fs-admin** 无独立 SecurityConfig,通过 `fs-framework` 生效。
### 5.3 推荐重构策略(减少重复劳动)
**不要 26 个文件各改一遍。** 建议:
1. 在 `fs-framework` 新增 2 个基类/工具:
- `PermitAllSecurityConfiguration`(A 类)
- `JwtSecurityConfigurationSupport`(B 类,抽取 filter 链构建)
2. 各模块 `SecurityConfig` 改为 `@Import` 或 `@Configuration` + `@Bean SecurityFilterChain`
3. 模块级差异(白名单 URL)通过 `@ConfigurationProperties` 或子类 override 注入
### 5.4 迁移模板
#### A 类:全放行(替代 `fs-user-app` 现有写法)
```java
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedDoubleSlash(true);
return firewall;
}
}
```
#### B 类:JWT 鉴权(替代 `fs-framework` 现有写法)
```java
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {
@Autowired private UserDetailsService userDetailsService;
@Autowired private AuthenticationEntryPointImpl unauthorizedHandler;
@Autowired private LogoutSuccessHandlerImpl logoutSuccessHandler;
@Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter;
@Autowired private CorsFilter corsFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/captchaImage").anonymous()
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// ... 保留原有全部 antMatchers 路径 ...
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(frame -> frame.disable()))
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
);
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
http.addFilterBefore(corsFilter, LogoutFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
```
#### API 对照表
| 旧 API(5.2 / WebSecurityConfigurerAdapter) | 新 API(5.7 / SecurityFilterChain) |
|---|---|
| `extends WebSecurityConfigurerAdapter` | `@Bean SecurityFilterChain` |
| `@EnableGlobalMethodSecurity` | `@EnableMethodSecurity` |
| `configure(HttpSecurity http)` | `securityFilterChain(HttpSecurity http)` |
| `authorizeRequests()` | `authorizeHttpRequests()` |
| `antMatchers(...)` | `requestMatchers(...)` |
| `authenticationManagerBean()` | `AuthenticationConfiguration.getAuthenticationManager()` |
| `.csrf().disable()` | `.csrf(csrf -> csrf.disable())` |
### 5.5 处理 fs-common 中的 Security 补丁
升级后 Spring Security 升至 5.7.x,需重新评估以下类:
| 文件 | 用途 | 建议 |
|---|---|---|
| `EagerSecurityHeadersBeanPostProcessor` | CVE-2026-22732 缓解 | 5.7 有官方修复,**验证后可删除** |
| `TimingSafeAuthenticationManagerBeanPostProcessor` | CVE-2026-22746 缓解 | 5.7.23+ 有修复;先保留,官方版本覆盖后删除 |
| `TimingSafeDaoAuthenticationProvider` | 同上 | 同上 |
| `org.springframework.security.*` 包下自研类 | 覆盖框架类 | 升级后重点回归登录接口 |
**注意**:不要同时保留自研 `TimingSafeDaoAuthenticationProvider` 与官方修复版行为,避免双重逻辑。
### 5.6 Security 迁移检查清单
- [ ] 26 个 `SecurityConfig` 编译通过
- [ ] fs-admin 登录 / 登出 / JWT 刷新
- [ ] fs-user-app 接口鉴权(当前全 permitAll,确认是否符合预期)
- [ ] fs-company / fs-store 的 MD5 密码编码仍生效
- [ ] 各模块 Swagger 白名单路径仍可匿名访问
- [ ] `@PreAuthorize` / `@Secured` 注解权限仍生效
---
## 六、Swagger / Springfox 兼容(阶段 4)
### 6.1 现状
- 使用 **Springfox 2.9.2**(`swagger.version`)
- **23 个** `SwaggerConfig` + 多个 `ResourcesConfig` 注册 Swagger 静态资源
- 共享配置 `application-common.yml` 中 `swagger.enabled: false`(生产默认关闭)
### 6.2 短期方案(推荐先做,保证升级后能启动)
1. 阶段 2 已配置 `spring.mvc.pathmatch.matching-strategy: ant_path_matcher`
2. 保持 Springfox 2.9.2 不变
3. 启动后访问 `/swagger-ui.html` 验证
**已知风险**:Springfox 已停止维护,与 Boot 2.7 是「勉强兼容」,不是长期方案。
### 6.3 长期方案(可选,单独排期)
迁移到 **springdoc-openapi 1.7.x**(支持 Boot 2.7 + Java 8):
```xml
org.springdoc
springdoc-openapi-ui
1.7.0
```
- 删除 Springfox 依赖
- 23 个 `SwaggerConfig` 合并为 1 个公共配置或删除(springdoc 自动扫描)
- Controller 上 `@Api` → `@Tag`,`@ApiOperation` → `@Operation`(可渐进)
### 6.4 SwaggerConfig 文件清单
```
fs-admin/src/main/java/com/fs/web/core/config/SwaggerConfig.java
fs-user-app/.../SwaggerConfig.java
fs-company-app/.../core/config/SwaggerConfig.java
fs-doctor-app、fs-live-app、fs-live-mq、fs-watch、fs-redis、
fs-wx-api、fs-ad-api、fs-common-api、fs-repeat-api、
fs-qw-api、fs-qw-api-msg、fs-qw-company-api、fs-qw-task、
fs-qw-mq、fs-qw-voice、fs-ipad-task、
fs-qwhook、fs-qwhook-msg、fs-qwhook-sop、
fs-user-app-ai-chat
```
---
## 七、其他代码调整(阶段 5)
### 7.1 HandlerInterceptorAdapter(37 处,低优先级)
多个模块的 `RepeatSubmitInterceptor`、`AuthorizationInterceptor` 继承了 `HandlerInterceptorAdapter`。
- Boot 2.7 下**仍可编译运行**(deprecated)
- 建议后续改为直接实现 `HandlerInterceptor` 接口,删除 `extends HandlerInterceptorAdapter`
### 7.2 MyBatis / MyBatis-Plus
- 升级 MyBatis-Plus 3.1.0 → 3.5.5 后,关注:
- `fieldStrategy: NOT_EMPTY` 等全局配置是否仍兼容(3.5 部分枚举有变更)
- 分页插件、乐观锁、多租户等自定义配置
- 23 个 `MyBatisConfig.java` 若仅做别名扫描,一般无需改动
### 7.3 业务代码
- **无需 Jakarta 包名替换**(`javax.servlet` 等保持不变)
- `fs-service` 等业务 Service / Controller **预计改动极少**
- 重点回归:支付、订单、企微回调、直播、课程观看、Redis 锁
---
## 八、分模块启动验证(阶段 5–6)
### 8.1 启动顺序
| 优先级 | 模块 | 验证重点 |
|---|---|---|
| P0 | fs-admin | 后台登录、菜单权限、Swagger |
| P0 | fs-user-app | C 端 API、微信支付、订单 |
| P0 | fs-company | 企业端 JWT、CRM |
| P1 | fs-qw-api / fs-qw-task | 企微回调、定时任务 |
| P1 | fs-live-app / fs-live-mq | 直播、MQ 消费 |
| P2 | fs-qwhook* / fs-wx-api / fs-ad-* | 回调、广告 |
| P2 | 其余模块 | 能启动、核心接口 200 |
### 8.2 单模块启动示例
```bash
cd fs-admin
mvn spring-boot:run -Dspring-boot.run.profiles=dev
```
### 8.3 常见问题排查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 启动报循环依赖 | Boot 2.6+ 默认禁止 | `allow-circular-references: true` |
| Swagger 404 / 空文档 | PathPattern 不兼容 | `ant_path_matcher` |
| 403 增多 | Security 规则迁移遗漏 | 对比旧 `antMatchers` 列表 |
| Redisson 启动失败 | spring-data 版本不匹配 | 改用 `redisson-spring-data-27` |
| `NoClassDefFoundError: spring-expression` | fs-common pin 了 Spring 6 | 删除 version 6.2.0 |
| 静态资源 404 | ResourcesConfig 路径匹配变化 | 检查 `ResourcesConfig` |
---
## 九、回归测试清单(阶段 6)
### 9.1 基础设施
- [ ] 多数据源 / Druid 连接池正常
- [ ] Redis / Redisson 分布式锁
- [ ] MyBatis / MyBatis-Plus CRUD、分页
- [ ] 定时任务(fs-quartz)
- [ ] WebSocket(fs-websocket)
### 9.2 安全
- [ ] 各端登录(admin / user / company / doctor)
- [ ] JWT 过期与刷新
- [ ] 权限注解 `@PreAuthorize`
- [ ] 匿名白名单(支付回调、企微回调、物流回调)
### 9.3 业务链路
- [ ] 下单 → 支付 → 回调
- [ ] 售后 / 退款
- [ ] 课程观看 / 完播任务(fs-qw-task)
- [ ] 企微 SOP / 消息推送
- [ ] 直播下单 / 购物车
- [ ] 文件上传 OSS
---
## 十、推荐执行步骤汇总
```
阶段 0 建分支、打基线、确认回滚方案
↓
阶段 1 升 BOM 2.7.18 → Redisson 27 → MyBatis-Plus → 删 spring-expression 6.2.0
↓ 统一 spring-boot-maven-plugin
↓
阶段 2 application-common.yml 加 circular-ref + ant_path_matcher
↓
阶段 3 重构 SecurityConfig(先 fs-framework,再各模块)
↓ 评估删除 fs-common Security 补丁
↓
阶段 4 验证 Springfox(或排期 springdoc)
↓
阶段 5 mvn compile → 按 P0→P2 逐个启动修错
↓
阶段 6 全链路回归 → 预发验证 → 生产灰度
```
---
## 十一、不在本次范围内
以下内容属于 **Spring Boot 3.x** 范畴,本次 **2.7.18 不需要做**:
- Java 17+ 升级
- `javax.*` → `jakarta.*` 包名替换
- Spring Security 6.x API
- Redisson `spring-data-32`
---
## 十二、参考链接
- [Spring Boot 2.7 Release Notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes)
- [Spring Boot 2.7 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Migration-Guide)
- [Spring Security 5.7 Migration](https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html)
- [Spring Boot 2.6 PathPattern 变更说明](https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc)
---
*文档生成日期:2026-06-08*