# 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*