|
|
@@ -0,0 +1,145 @@
|
|
|
+# Spring Boot 3.2.5 → 4.1.1 升级说明
|
|
|
+
|
|
|
+升级日期:2026-10-19
|
|
|
+分支:`feature/spring-boot-4`(基于 `develop` 的 `147bbe6`,即已升到 3.5.16 的提交)
|
|
|
+
|
|
|
+## 一、版本变化
|
|
|
+
|
|
|
+| 组件 | 升级前 | 升级后 |
|
|
|
+|---|---|---|
|
|
|
+| Spring Boot | 3.2.5 | **4.1.1** |
|
|
|
+| Spring Framework | 6.1.x | 7.0.9(Boot 传递) |
|
|
|
+| Spring Security (crypto) | 6.2.x | 7.0.x |
|
|
|
+| Jakarta EE 基线 | 10(Servlet 6.0 / Tomcat 10.1) | **11(Servlet 6.1 / Tomcat 11.0.24)** |
|
|
|
+| Jackson | 2.15 | **3.1.5**(`tools.jackson.*`) |
|
|
|
+| MyBatis-Plus | 3.5.5 / spring-boot3-starter | **3.5.17 / spring-boot4-starter** |
|
|
|
+| JDK | 21 | 21(不变,Boot 4 基线为 17) |
|
|
|
+
|
|
|
+升级前先单独落了一版 3.5.16(提交 `147bbe6`),确认零改动即可通过全部回归,再进 4.x,避免跨两个大版本混在一起排错。
|
|
|
+
|
|
|
+## 二、代码改动(58 文件)
|
|
|
+
|
|
|
+### 1. Jackson 包名迁移(54 文件,纯 import)
|
|
|
+
|
|
|
+`com.fasterxml.jackson.databind|core` → `tools.jackson.databind|core`。
|
|
|
+
|
|
|
+**注解不动**:`@JsonProperty` / `@JsonIgnore` / `@JsonInclude` / `@JsonView` 仍在
|
|
|
+`com.fasterxml.jackson.annotation`(Jackson 3 为向后兼容刻意保留),涉及 63 个文件一行未改。
|
|
|
+
|
|
|
+批量脚本:`tools/migrate_jackson3.py`(可重复执行,`--dry` 只统计不写)。
|
|
|
+
|
|
|
+### 2. 两个自定义序列化器改写
|
|
|
+
|
|
|
+Jackson 3 把 `JsonSerializer` 更名 `ValueSerializer`,第三个参数换成 `SerializationContext`,
|
|
|
+异常由受检的 `IOException` 改为非受检的 `JacksonException`:
|
|
|
+
|
|
|
+```java
|
|
|
+// 旧
|
|
|
+public class PhpFloatSerializer extends JsonSerializer<Double> {
|
|
|
+ public void serialize(Double v, JsonGenerator gen, SerializerProvider p) throws IOException
|
|
|
+// 新
|
|
|
+public class PhpFloatSerializer extends ValueSerializer<Double> {
|
|
|
+ public void serialize(Double v, JsonGenerator gen, SerializationContext ctxt) throws JacksonException
|
|
|
+```
|
|
|
+
|
|
|
+- `common/util/PhpFloatSerializer.java`
|
|
|
+- `common/util/UserLiteSerializer.java`(另:`new ObjectMapper().setSerializationInclusion(..)`
|
|
|
+ 不可用,改为 `JsonMapper.builder().build()`;默认包含策略本就是 ALWAYS)
|
|
|
+
|
|
|
+这两个类被 8 处 `@JsonSerialize(using = ...)` 引用,改完实体侧无需改动。
|
|
|
+
|
|
|
+### 3. Redis 序列化器重写
|
|
|
+
|
|
|
+`config/RedisConfig.java`:
|
|
|
+
|
|
|
+- `new ObjectMapper()` + `setVisibility(..)` + `enableDefaultTyping(NON_FINAL)` 全部不可用
|
|
|
+ (Jackson 3 的 mapper 不可变,这些方法已移除)。
|
|
|
+- 改用 Spring Data Redis 4 新增的 `GenericJacksonJsonRedisSerializer`:
|
|
|
+ ```java
|
|
|
+ GenericJacksonJsonRedisSerializer.builder().enableUnsafeDefaultTyping().build()
|
|
|
+ ```
|
|
|
+ 语义等价于原 `GenericJackson2JsonRedisSerializer` / default typing(写 `@class` 类型信息),
|
|
|
+ `RedisTemplate<String,Object>` 与 `RedisCacheManager` 共用同一个工厂方法。
|
|
|
+- `config/RedisConfigCacheManager.java` 的 `DEFAULT_PAIR` 同步换成 `RedisConfig.jsonSerializer()`。
|
|
|
+
|
|
|
+> 注:项目内 `@Cacheable` 用量为 0,缓存管理器实际未被业务使用,因此即便有旧缓存格式差异也不影响业务。
|
|
|
+
|
|
|
+### 4. MyBatis-Plus 两处适配
|
|
|
+
|
|
|
+1. **分页插件拆包**:MP 3.5.9 起 `PaginationInnerInterceptor` 移到独立模块,需显式引入
|
|
|
+ `com.baomidou:mybatis-plus-jsqlparser`(版本随 `mybatis-plus.version`)。
|
|
|
+ 不加会报 `cannot find symbol: class PaginationInnerInterceptor`。
|
|
|
+2. **JSON TypeHandler 换 Jackson 3 版**:`Goods.deliveryType` 原来用 `JacksonTypeHandler`(Jackson 2 实现),
|
|
|
+ Boot 4 不再带 Jackson 2,启动时会 `NoClassDefFoundError: com/fasterxml/jackson/core/JacksonException`。
|
|
|
+ 改为 MP 3.5.17 提供的 `Jackson3TypeHandler`。
|
|
|
+
|
|
|
+### 5. Lombok 注解处理器需显式声明
|
|
|
+
|
|
|
+Boot 4 的 `maven-compiler-plugin` 3.15.0 不再从 classpath 自动发现注解处理器,
|
|
|
+只配 `<parameters>true</parameters>` 时 `@Data` 完全不生成方法(一开始报了 200 个
|
|
|
+"cannot find symbol: getXxx")。pom 里补:
|
|
|
+
|
|
|
+```xml
|
|
|
+<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>
|
|
|
+```
|
|
|
+
|
|
|
+## 三、不受影响的部分(已核实)
|
|
|
+
|
|
|
+- **时间格式**:实体时间字段是 `Integer` 时间戳 + `getCreateTimeText()` 手工 `TimeUtil.format`,
|
|
|
+ 不走 Jackson 日期序列化。Jackson 3 把 `WRITE_DATES_AS_TIMESTAMPS` 默认改为 ISO-8601 的变化
|
|
|
+ 对本项目出参**零影响**,`spring.jackson.date-format` 保留即可。
|
|
|
+- **`@JsonView`**:`spring.jackson.mapper.default-view-inclusion: true` 在 Boot 4 仍有效
|
|
|
+ (已实测 `goods/list` 不含 `content`、`goods/detail` 含 `content`)。
|
|
|
+- **`spring.jackson.date-format` / `time-zone` / `mapper.*`**:配置项在 4.1.1 中均仍存在。
|
|
|
+- **`ObjectNode.remove(Collection)` / `createObjectNode()` / `valueToTree()`**:签名未变,`ApiView` 只改了 import。
|
|
|
+- **多模块拦截器**:`WebMvcConfig` / `StoreAuthInterceptor` / `AdminAuthInterceptor` / `ApiAuthInterceptor` 未改。
|
|
|
+- **PHP `$2y$` 口令**:Spring Security 7 的 `BCryptPasswordEncoder` 仍能校验
|
|
|
+ (用 PHP 8.2 `password_hash` 生成 `$2y$` 哈希实测登录成功)。
|
|
|
+
|
|
|
+## 四、唯一的行为变化(可接受)
|
|
|
+
|
|
|
+Jackson 3 默认开启 `SORT_PROPERTIES_ALPHABETICALLY`,实体经 `valueToTree` 后**字段顺序变为字母序**
|
|
|
+(如 `goodsId` 之前排在 `goodsName` 前,现在 `aloneGradeEquity` 开头)。字段名与值不变,
|
|
|
+前端按名取值不受影响;手工 `put` 的 `ObjectNode` 仍保持插入顺序。
|
|
|
+旧的 `compare_*.py` 若按字节比 JSON 会误报,这些脚本已随驼峰改造废弃。
|
|
|
+
|
|
|
+## 五、回归结果(全部在 Boot 4.1.1 上跑)
|
|
|
+
|
|
|
+| 脚本 | 内容 | 结果 |
|
|
|
+|---|---|---|
|
|
|
+| `test_api_batch1.py` | api 只读接口 | 52 / 0 |
|
|
|
+| `test_api_batch2.py` | 购物车/结算/订单/地址 | 97 / 0 |
|
|
|
+| `test_api_batch3.py` | 用户中心/售后/充值/上传 | 99 / 0 |
|
|
|
+| `test_api_batch4.py` | 登录/验证码/收银台/支付回调 | 77 / 0 |
|
|
|
+| `test_admin_api.py` | 平台后台 | 45 / 0 |
|
|
|
+| `test_token_user.py` | store 登录态(Redis) | 20 / 0 |
|
|
|
+| `smoke_camel.py` | 驼峰冒烟 | 全部通过 |
|
|
|
+
|
|
|
+合计 **390 项断言,0 失败**。
|
|
|
+
|
|
|
+## 六、回滚方式
|
|
|
+
|
|
|
+```bash
|
|
|
+git checkout develop # Boot 3.5.16,随时可发布
|
|
|
+# 或
|
|
|
+git checkout feature/spring-boot-4
|
|
|
+```
|
|
|
+
|
|
|
+## 七、后续可选
|
|
|
+
|
|
|
+- Boot 4 仍保留 `spring-boot-jackson2` 模块(`spring.jackson2.*` 属性),
|
|
|
+ 万一某个第三方库只支持 Jackson 2,可用它过渡,但属废弃路径,不建议长期依赖。
|
|
|
+- 若将来引入 `@Cacheable`,注意 Boot 4 + Jackson 3 的缓存值格式与 Jackson 2 的
|
|
|
+ `GenericJackson2JsonRedisSerializer` 不完全一致(`@class` vs 数组式 typing),切流时先清缓存。
|