本文档基于
ylrz_his_scrm_java项目现状编写,目标版本:Spring Boot 2.7.18(2.x 末代 LTS,仍支持 Java 8)。
| 项 | 当前 | 升级后(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 迁移) |
| 维度 | 数量 |
|---|---|
| Java 源文件 | ~8500 |
| Maven 模块 | 36+ |
| 可运行 Spring Boot 应用 | 28 |
SecurityConfig(需迁移) |
26 |
SwaggerConfig(Springfox) |
23 |
HandlerInterceptorAdapter(已废弃) |
37 |
| 共享配置文件 | fs-service/src/main/resources/application-common.yml |
| 阶段 | 内容 | 预估 |
|---|---|---|
| 阶段 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 人全职,视测试深度而定)。
git checkout -b feature/spring-boot-2.7.18
mvn package、能否启动。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
建议一次性升 BOM,不要停留在 2.3/2.5 中间版本——你们从 2.2 直接到 2.7,中间破坏性变更(路径匹配、循环依赖、Security API)集中处理更高效。
fs-common / fs-framework(基础)
↓
fs-service(业务核心)
↓
fs-admin / fs-user-app / fs-company(主入口)
↓
其余 API / 任务 / MQ 模块
pom.xml<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.18</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<properties>
<!-- 核心 -->
<spring-boot.version>2.7.18</spring-boot.version>
<!-- 第三方 starter(与 Boot 2.7 对齐) -->
<mybatis-spring-boot.version>2.3.2</mybatis-spring-boot.version>
<druid.version>1.2.23</druid.version>
<!-- Redisson:Boot 2.7 对应 spring-data-27 -->
<redisson.version>3.27.2</redisson.version>
<!-- MyBatis-Plus:当前 3.1.0 过旧,建议升级 -->
<mybatis-plus.version>3.5.5</mybatis-plus.version>
</properties>
升级到 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 等安全相关 pinpagehelper(你们有 SQL 注入 CVE 注释,需验证 BOM 版本是否 ≥ 5.3.1)根 pom.xml 与 fs-service/pom.xml 中:
<!-- 改前 -->
<artifactId>redisson-spring-data-22</artifactId>
<!-- 改后 -->
<artifactId>redisson-spring-data-27</artifactId>
fs-service/pom.xml 中排除 redisson-spring-data-32 的注释也需同步更新。
当前各可运行模块 pin 了 2.1.1.RELEASE(如 fs-admin/pom.xml),建议:
方案 A(推荐):在根 pom.xml 的 pluginManagement 中统一管理:
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
子模块删除 <version>2.1.1.RELEASE</version>。
涉及模块(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 等。
fs-common/pom.xml<!-- 改前 -->
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
<!-- 改后 -->
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version> <!-- 3.5.5 -->
当前 fs-common/pom.xml 显式依赖了 spring-expression 6.2.0(Spring 6),与 Boot 2.7 的 Spring 5.3 不兼容。
<!-- 建议:删除该依赖,或改为不指定 version,由 BOM 管理 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<!-- 删除 <version>6.2.0</version> -->
</dependency>
Boot 2.7 仍支持 mysql-connector-java,但官方已迁移到 com.mysql:mysql-connector-j。短期可不改;长期建议替换:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
# 全量编译(先不要求启动)
mvn clean compile -DskipTests
# 检查冲突
mvn dependency:tree -pl fs-admin | findstr /i "spring-security spring-framework spring-expression"
在 fs-service/src/main/resources/application-common.yml 的 spring: 节点下增加:
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,可移除此配置。
Boot 2.4+ 部分 server.tomcat.* 属性已更名,你们当前使用:
server:
tomcat:
max-threads: 800 # 建议改为 max-threads 或 threads.max
min-spare-threads: 30 # 建议改为 threads.min-spare
启动时若出现 deprecated 警告,按 Boot 2.7 文档调整即可,不影响功能。
WebSecurityConfigurerAdapter 已在 5.7 移除SecurityConfig 需迁移适用模块: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
| 模块 | 文件 | 特殊点 |
|---|---|---|
| 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生效。
不要 26 个文件各改一遍。 建议:
fs-framework 新增 2 个基类/工具:
PermitAllSecurityConfiguration(A 类)JwtSecurityConfigurationSupport(B 类,抽取 filter 链构建)SecurityConfig 改为 @Import 或 @Configuration + @Bean SecurityFilterChain@ConfigurationProperties 或子类 override 注入fs-user-app 现有写法)@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;
}
}
fs-framework 现有写法)@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(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()) |
升级后 Spring Security 升至 5.7.x,需重新评估以下类:
| 文件 | 用途 | 建议 |
|---|---|---|
EagerSecurityHeadersBeanPostProcessor |
CVE-2026-22732 缓解 | 5.7 有官方修复,验证后可删除 |
TimingSafeAuthenticationManagerBeanPostProcessor |
CVE-2026-22746 缓解 | 5.7.23+ 有修复;先保留,官方版本覆盖后删除 |
TimingSafeDaoAuthenticationProvider |
同上 | 同上 |
org.springframework.security.* 包下自研类 |
覆盖框架类 | 升级后重点回归登录接口 |
注意:不要同时保留自研 TimingSafeDaoAuthenticationProvider 与官方修复版行为,避免双重逻辑。
SecurityConfig 编译通过@PreAuthorize / @Secured 注解权限仍生效swagger.version)SwaggerConfig + 多个 ResourcesConfig 注册 Swagger 静态资源application-common.yml 中 swagger.enabled: false(生产默认关闭)spring.mvc.pathmatch.matching-strategy: ant_path_matcher/swagger-ui.html 验证已知风险:Springfox 已停止维护,与 Boot 2.7 是「勉强兼容」,不是长期方案。
迁移到 springdoc-openapi 1.7.x(支持 Boot 2.7 + Java 8):
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
SwaggerConfig 合并为 1 个公共配置或删除(springdoc 自动扫描)@Api → @Tag,@ApiOperation → @Operation(可渐进)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
多个模块的 RepeatSubmitInterceptor、AuthorizationInterceptor 继承了 HandlerInterceptorAdapter。
HandlerInterceptor 接口,删除 extends HandlerInterceptorAdapterfieldStrategy: NOT_EMPTY 等全局配置是否仍兼容(3.5 部分枚举有变更)MyBatisConfig.java 若仅做别名扫描,一般无需改动javax.servlet 等保持不变)fs-service 等业务 Service / Controller 预计改动极少| 优先级 | 模块 | 验证重点 |
|---|---|---|
| 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 |
cd fs-admin
mvn spring-boot:run -Dspring-boot.run.profiles=dev
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 启动报循环依赖 | 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 |
@PreAuthorize阶段 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 不需要做:
javax.* → jakarta.* 包名替换spring-data-32文档生成日期:2026-06-08