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

8.3 KiB
Raw Blame History

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 快照用法保留。