Files
UserclassService/.workbuddy/memory/2026-08-14.md
T
2026-08-14 12:20:52 +08:00

84 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-08-14
## 独立 Bug 修复:`OtherUtil.jsonParseList` 泛型擦除导致 ClassCastException
### 现象
- `POST /oapi/user/AddUser`(同步用户接口,非 `api/` 包,未被 UserContext 重构涉及)返回 `code:6 / 内部服务错误`。
- 服务器真实异常:`java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.yunhai.platform.utils.model.BoaUser`
位置 `SyncUserService.syncAddOrUpdate(SyncUserService.java:54)`(增强 for 循环 `for (BoaUser user : userList)`)。
- 连带次级错误(非致命、异步):审计日志 `sys_operation_log.request_params` 列存中文报 `Incorrect string value`(列字符集疑似 latin1/非 utf8mb4),被 `SysOperationLogServiceImpl` 捕获打印「异步落库失败」。
### 根因
- `SyncUserController.AddUser` 用 `OtherUtil.jsonParseList(jsonString, BoaUser.class)` 解析成 `List<BoaUser>`。
- 原 `jsonParseList` 实现:`objectMapper.readValue(json, new TypeReference<List<T>>() {})`。**Java 泛型擦除**使运行时 `List<T>` 退化为原始 `List`,Jackson 把元素反序列化为 `LinkedHashMap`;遍历时强转 `BoaUser` 即 `ClassCastException`。
- 同文件 `jsonParseArrayTree` 用 `objectMapper.getTypeFactory().constructCollectionType(List.class, elementClass)` 才正确——原 `jsonParseList` 注释写「TypeReference 保留泛型信息」是错的。
### 修复
- `utils/OtherUtil.java` 的 `jsonParseList`:改用 `constructCollectionType(List.class, targetClass)` 构建 `JavaType` 再 `readValue`。
- 影响面:顺带修好 `UpdateUser`、`DeleteUser`(同用 `jsonParseList`,之前同样会 ClassCast)。
### 待跟进(非本次阻塞)
- 审计日志表 `sys_operation_log.request_params` 列字符集问题:存中文报 `Incorrect string value`。需改 DDL 为 `utf8mb4`(或确认连接字符集)。属 DB schema,不影响主业务返回,已口头提示用户。
### 环境注意(延续)
- 本机 `mvn compile` 仍受 Lombok 命令行限制;改动在 IDE 验证。
## 深层根因 & 二次修复:`AddUser` 反序列化失败(JSON 与框架 BoaUser 结构不匹配)
### 现象(修复 ClassCast 后仍未解决)
- `jsonParseList` 改 `constructCollectionType` 后,`AddUser` 仍 "反序列化失败"(500)。
- 原因浮出:调用方下发的 JSON 是**自定义格式**,并非 yunhai 框架 `BoaUser`:
- `groups:[{"groupId":12,"no":null,"schoolId":3}]`、`roleList:[{"roleId":5,"schId":3}]`
- 原服务却按 `BoaUser` 调 `user.getSchool().getId()`、`group.getId()`、`group.getSort()`、`user.getRoleIds()`
- 用 `BoaUser.class` 反序列化时,Jackson(默认 `FAIL_ON_UNKNOWN_PROPERTIES=true`)对未知字段 `groups`/`roleList` 抛 `UnrecognizedPropertyException` → 仍 500。
### 修复(按用户指示:加 DTO 补字段)
- 新增 3 个 DTO(`model/tool` 包,与 `DeleteUser` 同风格、`@Data` + `@JsonIgnoreProperties(ignoreUnknown=true)`):
- `SyncUserDTO`:id, account, name, idcard, phone, email, gender(Integer), birthday, status(Integer), profilePicture, `List<SyncGroupDTO> groups`, `List<SyncRoleDTO> roleList`
- `SyncGroupDTO`:groupId, no(Integer), schoolId
- `SyncRoleDTO`:roleId, schId
- `SyncUserController`:`jsonParseList(jsonString, SyncUserDTO.class)`;`ISyncUserService.syncAddOrUpdate(List<SyncUserDTO>)`。
- 重写 `SyncUserService`:去掉 `BoaUser`/`BoaGroup` 依赖,按真实字段映射:
- 角色判定改用 `user.getRoleList().stream().map(SyncRoleDTO::getRoleId)` 比 `UserRole` 值;
- `group.getId()`→`group.getGroupId()`;`user.getSchool().getId()`→`group.getSchoolId()`;`group.getSort()`→`group.getNo()`;`stuClassNo` = `String.valueOf(group.getNo())`。
- `SchStudent.schId` 继承自 `BaseEntity`(存在);实体字段类型与 DTO 对齐(gender/status 为 Integer,idcard/account/name/phone 为 String)。
### 结论
- 该接口此前(含重构前)从未真正可用:先因 `LinkedHashMap` 强转炸,后为 `BoaUser` 字段不匹配炸。现在用匹配 JSON 的 DTO 彻底解耦。
- 注意 JSON 中 `roleId=5`(BUREAU_LEADER_USER),在当前样本数据下既不命中 isStudent 也不命中 isTeacher,故不会落库;这是调用方业务数据问题,非代码缺陷。
## 优化 `SyncUserService`
- 重写 `service/impl/sync/SyncUserService.java`,优化点:
1. 角色判定改为每个用户只遍历一次 `roleList`(`collectRoleIds` 提取去重 Set),`isStudent`/`isTeacher` 改判集合包含,去除重复流式遍历。
2. `deleteUser` 中 `studentDeletes`/`teacherDeletes` 同一过滤条件算了两次 → 合并为单次 `validDeletes`;删除死代码 `groupsBySchool`。
3. 修复教师分组删除 `toMap` 2 段键在「同教师同 groupId 跨多学校」时被静默丢键的 bug → 改用 `Collectors.groupingBy` 收集 `List<SchTeacherGroup>`,删除全部匹配分组。
4. 缓存清除去重:delete 时按 (schId,classId)/(schId) 去重后调用 `clearClassStudentCaches`/`clearTeacherCaches`(新增的批量方法)。
5. 补齐 add/update 后的缓存失效:此前只在 delete 清缓存,add/update 后班级学生缓存/学校教师缓存会陈旧 → 现同步保存后收集受影响键并 `clearClassStudentCaches`/`clearTeacherCaches`。
- 行为保持:事务注解、角色归类(学生优先于教师)、字段映射不变。
## 优化 `SchClassUpgradeService`(学年升级)
- `upgradeSingleSystem` 原本每个年级各调一次 `queryService.getSchClassCacheFilter` 反序列化全校班级列表、再逐年级 `saveAll`;改为**基于 SET 的批量 `@Modifying` 更新**:
- 新增 `SchClassRepository.batchUpgradeGrade(schId, 非最高年级列表, now)`:`grade_id = grade_id + 1` 单语句原子完成(仅基础教育)。
- 新增 `SchClassRepository.batchGraduate(schId, maxGrade, now)`:最高年级 `graduate = true`。
- 用 `?1 ?2 ?3` 位置参数避免受现有 lettuce `@Param` 误用影响;`clearAutomatically = true` 与 `SchStudentRepository` 风格一致。
- `upgradeAllClasses` 优化/修复:
- `schType` 提升到学制循环外只查一次(原在学制内重复查)。
- `getCurrentYearTerm()` 加 null 守卫,避免 NPE。
- 原 `schType.equals(...)` 在 `getSchType` 返回 null 时 NPE → 改为常量 `.equals(schType)` 判等,null 安全。
- 升级日志保存包 try/catch,避免日志写入失败掩盖升级结果。
- 升级后除 `clearClassCache` 外补 `clearGradeateClassCache`,消除毕业后毕业缓存脏数据(原漏清)。
- 行为保持:仅基础教育非最高年级升级、最高年级毕业;失败不阻断其他学制;失败记录可重试(existsBy...Status=true 不命中)。
## 全局修正 Repository 的 `@Param` 错误注解
- 发现 7 个 Repository 的 `@Query` 命名参数绑定误用了 `io.lettuce.core.dynamic.annotation.Param`(Lettuce Redis 注解),而非 Spring Data 的 `org.springframework.data.repository.query.Param`;后者不被 Spring Data 识别,运行时触发「Parameter binding not found」,属潜在崩溃隐患。
- 统一替换为 `org.springframework.data.repository.query.Param`:SchClassRepository、SchClassHistoryRepository、SchDicCourseRepository、SchTeacherRepository、SchDicMajorsRepository、SchStudentRepository、SchTeacherGroupRepository。
- 顺带在 SchClassRepository 补 `import java.util.Date;`(批量升级方法用到)并恢复 `Page`/`Pageable` 导入。
- 现状:本沙箱无 Maven 无法编译验证,需本机 `mvn compile` 确认。
## 修复 `SchClassUpgradeService` 中 `JdbcTemplate` 报红
- 现象:IDE 中标红 `private JdbcTemplate jdbcTemplate;`。
- 根因:`pom.xml` 仅引入 `spring-boot-starter-data-jpa`(其传递依赖 `spring-boot-starter-jdbc`/`spring-jdbc` 含 `JdbcTemplate`),但 IDE 未正确解析传递依赖时 `JdbcTemplate` 类找不到 → 报红。
- 修复:
1. 在 `pom.xml` 显式新增 `spring-boot-starter-jdbc` 依赖(置于 data-jpa 之后),保证 `JdbcTemplate` 类与自动配置 Bean 稳定可用。
2. `SchClassUpgradeService` 由字段注入改为 Lombok `@RequiredArgsConstructor` 构造器注入,消除 IDEA 「Could not autowire. No beans of 'JdbcTemplate' type found」的自动注入识别告警,同时符合 Spring 推荐写法。`backupAllSnapshot` 的批量 INSERT...SELECT 快照用法保留。