8.3 KiB
8.3 KiB
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> roleListSyncGroupDTO:groupId, no(Integer), schoolIdSyncRoleDTO: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,优化点:- 角色判定改为每个用户只遍历一次
roleList(collectRoleIds提取去重 Set),isStudent/isTeacher改判集合包含,去除重复流式遍历。 deleteUser中studentDeletes/teacherDeletes同一过滤条件算了两次 → 合并为单次validDeletes;删除死代码groupsBySchool。- 修复教师分组删除
toMap2 段键在「同教师同 groupId 跨多学校」时被静默丢键的 bug → 改用Collectors.groupingBy收集List<SchTeacherGroup>,删除全部匹配分组。 - 缓存清除去重:delete 时按 (schId,classId)/(schId) 去重后调用
clearClassStudentCaches/clearTeacherCaches(新增的批量方法)。 - 补齐 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类找不到 → 报红。 - 修复:
- 在
pom.xml显式新增spring-boot-starter-jdbc依赖(置于 data-jpa 之后),保证JdbcTemplate类与自动配置 Bean 稳定可用。 SchClassUpgradeService由字段注入改为 Lombok@RequiredArgsConstructor构造器注入,消除 IDEA 「Could not autowire. No beans of 'JdbcTemplate' type found」的自动注入识别告警,同时符合 Spring 推荐写法。backupAllSnapshot的批量 INSERT...SELECT 快照用法保留。
- 在