getGroupTree 修改

This commit is contained in:
2026-08-14 16:17:51 +08:00
parent 2c058cfa35
commit 9ae303a29d
10 changed files with 127 additions and 187 deletions
+17 -80
View File
@@ -1,83 +1,20 @@
# 2026-08-14
## 独立 Bug 修复:`OtherUtil.jsonParseList` 泛型擦除导致 ClassCastException
## UserContext schId 超管置 0
- 需求:全局设置 `UserContext.schId`,若用户 `TYPE=1`(超级管理员)则强制赋值 0(`UserType.SUPER_ADMINISTRATOR`)。
- 改动文件:`src/main/java/com/yunhai/bschclassmanage/aspect/ControllerUserContextAspect.java`
- `populateUserContext()` 末尾在 `setType` 后增加判断:超管则 `UserContext.setSchId(0)`,覆盖 currentrole 带入的 schId。
- 新增 import `com.yunhai.bschclassmanage.enums.UserType`。
- 说明:该 Aspect(@Order(1),覆盖所有 controller)是实际填充 schId 的位置;UserContextInterceptor 的 resolveUser 仍返回 null,未设置 schId。未能本地编译(无 mvn、wrapper 缺 properties)。
### 现象
- `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 快照用法保留。
## ComSchTreeService:BaseTree → BoaGroup 迁移
- 需求:将 `ComSchTreeService` 中树模型由 `com.yunhai.bschclassmanage.model.tool.BaseTree` 换成平台 `com.yunhai.platform.utils.model.BoaGroup`。
- 改动文件:`service/com/ComSchTreeService.java`、`service/IComSchTreeService.java`、`controller/api/SchTreeController.java`、`service/impl/SchTeacherService.java`(toTeacherGroupSelect + 私有 flattenTree)。
- 字段映射要点:
- BoaGroup 无 `expand`/`code`/`order`;有 `parentId`/`sort`(Integer)。
- 删除教师树节点上的 `setExpand`/`setCode`;`setOrder(String)` → `setSort(teacher.getSort())`;`sortTreeNodes` 排序改为按 `getSort()`(Integer, nullsLast)。
- `SchSystemTreeVO` 的 `setExpand`/`setSort` 保持不变(那是年级/班级树,另一套模型)。
- 接口 `getGroupTree()` 原声明为 `BoaGroup`(与实现 `List<BoaGroup>` 不匹配),改为 `List<BoaGroup>`。
- 红缓存 `SCHOOL_TREE_{schId}` 读取端由 BaseTree 改为 BoaGroup 解析;写入端未在本次仓库内找到(疑似外部/平台写入),解析向后兼容(缺字段为 null,多余字段忽略)。
- 注意:`BaseTree.java` 现已无引用,可删除(本次未删)。`expand` 字段消失会影响前端教师树自动展开,需前端确认。
- 未能本地编译(无 JDK)。
@@ -10,7 +10,7 @@ public class OperationLogSpecifications {
public static Specification<OperationLogRecord> schIdEquals(Integer schId) {
return (root, query, cb) -> {
if (schId == null || schId <= 0) {
if (schId == null || schId < 0) {
return cb.conjunction();
}
return cb.equal(root.get("schId"), schId);
@@ -1,6 +1,7 @@
package com.yunhai.bschclassmanage.aspect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.bschclassmanage.enums.UserType;
import com.yunhai.bschclassmanage.model.tool.UserSysRole;
import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.UserContext;
@@ -143,6 +144,13 @@ public class ControllerUserContextAspect {
UserContext.setType(boaUser != null ? boaUser.getType() : null);
// 超级管理员(TYPE = 1)不绑定具体学校,schId 统一置 0,
// 覆盖 currentrole 中可能带入的学校ID,保证数据隔离逻辑对超管一致识别
if (UserType.SUPER_ADMINISTRATOR.getId().equals(UserContext.getType())) {
UserContext.setSchId(0);
log.debug("Super admin detected(TYPE=1), force schId = 0");
}
String clientIp = OtherUtil.getClientIp();
UserContext.setIp(clientIp);
log.debug("Client IP set: {}", clientIp);
@@ -3,8 +3,8 @@ import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.bschclassmanage.config.Config;
import com.yunhai.bschclassmanage.model.tool.BaseTree;
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
import com.yunhai.platform.utils.model.BoaGroup;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.service.IComSchTreeService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -23,13 +23,13 @@ public class SchTreeController {
@GetMapping("getGroupTree")
public List<BaseTree> getGroupTree()
public List<BoaGroup> getGroupTree()
{
return comSchTreeService.getGroupTree();
}
@GetMapping("getTeacherTree")
public BaseTree getTeacherTree()
public BoaGroup getTeacherTree()
{
return comSchTreeService.getTeacherTree(UserContext.getSchId());
}
@@ -74,7 +74,7 @@ public class SchTreeController {
@GetMapping("getTeaTree")
public BaseTree getTeaTree()
public BoaGroup getTeaTree()
{
return comSchTreeService.getTeaTree(UserContext.getSchId());
}
@@ -55,13 +55,12 @@ public class SchsystemController {
throw new DException("当前学校管理员未关联有效的学校");
}
} else { // 超管
if (schId == null || schId <= 0) {
if (schId == null || schId < 0) {
throw new DException("学校ID不能为空且必须为正整数");
}
targetSchId = schId;
}
return schBinSchsystemService.getAcademicStageSchsystemList(targetSchId);
}
@@ -21,27 +21,27 @@ public class SyncUserController {
@PostMapping("AddUser")
public void AddUser(@RequestBody String jsonString) {
log.info("jsonString="+jsonString);
log.info("AddUser jsonString="+jsonString);
List<SyncUserDTO> userlist = OtherUtil.jsonParseList(jsonString, SyncUserDTO.class);
syncUserService.syncAddOrUpdate(userlist);
}
@PostMapping("UpdateUser")
public void UpdateUser(@RequestBody String jsonString) {
log.info("jsonString="+jsonString);
log.info("UpdateUser jsonString="+jsonString);
List<SyncUserDTO> userList = OtherUtil.jsonParseList(jsonString, SyncUserDTO.class);
syncUserService.syncAddOrUpdate(userList);
}
@PostMapping("DeleteUser")
public void DeleteUser(@RequestBody String jsonString) {
log.info("jsonString="+jsonString);
log.info("DeleteUser jsonString="+jsonString);
List<DeleteUser> deleteUserList= OtherUtil.jsonParseList(jsonString, DeleteUser.class);
syncUserService.deleteUser(deleteUserList);
}
@PostMapping("SyncUser")
public void SyncUser(@RequestBody String jsonString) {
log.info("jsonString="+jsonString);
log.info("SyncUser jsonString="+jsonString);
}
}
@@ -12,7 +12,8 @@ public enum UserType
//状态 0禁用 1 正常 2休学 3出国 4留学 5复学 6停学 7退学 8留级 9其他
SUPER_ADMINISTRATOR(1,"超级管理员"),
REGULAR_USER(2,"普通用户");
SCHOOL_ADMIN(2,"学校管理员"),
REGULAR_USER(3,"普通用户");
private Integer id;
private String name;
@@ -1,10 +1,10 @@
package com.yunhai.bschclassmanage.service;
import com.yunhai.bschclassmanage.entity.BaseDicGrade;
import com.yunhai.bschclassmanage.model.tool.BaseTree;
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.model.vo.SelectStrVO;
import com.yunhai.platform.utils.model.BoaGroup;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -24,13 +24,13 @@ public interface IComSchTreeService {
SchSystemTreeVO getHigherSchClassTree(Integer schId);
List<BaseTree> getSchTree(Integer schId);
List<BoaGroup> getSchTree(Integer schId);
BaseTree getTeacherTree(Integer schId);
BoaGroup getTeacherTree(Integer schId);
List<SelectIntVO> getSchoolList();
BaseTree getTeaTree(Integer schId);
BoaGroup getTeaTree(Integer schId);
void validateGroupsBelongToSchool(Integer schId,List<Integer> groupIds);
@@ -38,7 +38,7 @@ public interface IComSchTreeService {
List<SelectIntVO> getTeaGroupNameList(Integer schId, List<Integer> ids);
List<BaseTree> getGroupTree();
List<BoaGroup> getGroupTree();
String getSchoolName(Integer schId);
@@ -9,7 +9,6 @@ import com.yunhai.bschclassmanage.entity.*;
import com.yunhai.bschclassmanage.enums.AcademicStage;
import com.yunhai.bschclassmanage.enums.NodeType;
import com.yunhai.bschclassmanage.enums.SchType;
import com.yunhai.bschclassmanage.model.tool.BaseTree;
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.model.vo.SelectStrVO;
@@ -17,13 +16,16 @@ import com.yunhai.bschclassmanage.service.*;
import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.RedisConstants;
import com.yunhai.bschclassmanage.utils.RedisUtil;
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.model.BoaGroup;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.sql.Array;
import java.util.*;
import java.util.stream.Collectors;
@@ -104,10 +106,10 @@ public class ComSchTreeService implements IComSchTreeService {
}
public List<BaseTree> getSchTree(Integer schId) {
public List<BoaGroup> getSchTree(Integer schId) {
String cacheKey = RedisConstants.SCHOOL_TREE + "_" + schId;
String json = redisUtil.get(cacheKey,String.class);
List<BaseTree> treeList = OtherUtil.jsonParseArrayTree(json, BaseTree.class);
List<BoaGroup> treeList = OtherUtil.jsonParseArrayTree(json, BoaGroup.class);
return treeList;
}
@@ -131,15 +133,14 @@ public class ComSchTreeService implements IComSchTreeService {
* @param schId 学校ID
* @return 教师组节点(含子节点),若未找到则返回 null
*/
public BaseTree getTeaTree(Integer schId) {
List<BaseTree> treeList = getSchTree(schId);
public BoaGroup getTeaTree(Integer schId) {
List<BoaGroup> treeList = getSchTree(schId);
if (treeList == null || treeList.isEmpty()) {
return null;
}
// 遍历根节点列表(通常只有一个),递归查找 type=7
for (BaseTree root : treeList) {
root.setExpand(true);
BaseTree result = findNodeByType(root, NodeType.TEACHER.getId());
for (BoaGroup root : treeList) {
BoaGroup result = findNodeByType(root, NodeType.TEACHER.getId());
if (result != null) {
return result;
}
@@ -162,13 +163,13 @@ public class ComSchTreeService implements IComSchTreeService {
}
// 方式一:通过教师树校验(如有缓存)
BaseTree teaTree = getTeaTree(schId);
BoaGroup teaTree = getTeaTree(schId);
if (teaTree == null) {
throw new DException("该校暂无教师组");
}
List<BaseTree> allNodes = flattenTree(teaTree);
List<BoaGroup> allNodes = flattenTree(teaTree);
Set<Integer> validGroupIds = allNodes.stream()
.map(BaseTree::getId)
.map(BoaGroup::getId)
.collect(Collectors.toSet());
for (Integer groupId : groupIds) {
@@ -178,14 +179,22 @@ public class ComSchTreeService implements IComSchTreeService {
}
}
public List<BaseTree> getGroupTree() {
String cacheKey = "bsch.classmanage.group_tree";
// 直接从Redis获取JSON字符串
String json = redisUtil.get(cacheKey, String.class);
// 解析为树结构
List<BaseTree> treeList = OtherUtil.jsonParseArrayTree(json, BaseTree.class);
// 过滤并处理:type=2的节点停止递归
return filterTreeByType(treeList, NodeType.SCHOOL.getId());
// public List<BoaGroup> getGroupTree() {
// return YschBaseOpenApi.getGroupTree();
// String cacheKey = "bsch.classmanage.group_tree";
// // 直接从Redis获取JSON字符串
// String json = redisUtil.get(cacheKey, String.class);
// // 解析为树结构
// List<BoaGroup> treeList = OtherUtil.jsonParseArrayTree(json, BoaGroup.class);
// // 过滤并处理:type=2的节点停止递归
// return filterTreeByType(treeList, NodeType.SCHOOL.getId());
//}
public List<BoaGroup> getGroupTree() {
BoaGroup group = YschBaseOpenApi.getGroupTree();
return Collections.singletonList(group);
}
public List<SelectIntVO> getSchoolList() {
@@ -197,14 +206,14 @@ public class ComSchTreeService implements IComSchTreeService {
}
// 2. 从组树构建(扁平化收集所有学校节点)
List<BaseTree> treeList = getGroupTree();
List<BoaGroup> treeList = getGroupTree();
if (treeList == null || treeList.isEmpty()) {
return Collections.emptyList();
}
// 收集所有学校节点
List<BaseTree> schoolNodes = new ArrayList<>();
for (BaseTree root : treeList) {
List<BoaGroup> schoolNodes = new ArrayList<>();
for (BoaGroup root : treeList) {
flattenTreeByType(root, NodeType.SCHOOL.getId(), schoolNodes);
}
@@ -227,7 +236,7 @@ public class ComSchTreeService implements IComSchTreeService {
public Map<Integer, List<SelectIntVO>> getSchGroupTypeList(Integer schId, int type) {
Map<Integer, Integer> academicStageToSchsystemMap = schBinSchsystemService.getAcademicStageToSchsystemMap(schId);
Map<Integer, List<SelectIntVO>> resultMap = new HashMap<>();
List<BaseTree> schTree = getSchTree(schId);
List<BoaGroup> schTree = getSchTree(schId);
if (schTree == null || schTree.isEmpty()) {
return resultMap;
}
@@ -252,7 +261,7 @@ public class ComSchTreeService implements IComSchTreeService {
public String getTeaGroupName(Integer schId, Integer id) {
BaseTree tree = getTeaTree(schId);
BoaGroup tree = getTeaTree(schId);
if (tree == null) return null;
// 懒加载缓存(若树结构不变)
if (idToNameCache == null) {
@@ -266,7 +275,7 @@ public class ComSchTreeService implements IComSchTreeService {
public List<SelectIntVO> getTeaGroupNameList(Integer schId, List<Integer> ids) {
// 1. 构建缓存(如果尚未构建)
if (idToNameCache == null) {
BaseTree tree = getTeaTree(schId);
BoaGroup tree = getTeaTree(schId);
if (tree == null) {
return Collections.emptyList();
}
@@ -314,16 +323,15 @@ public class ComSchTreeService implements IComSchTreeService {
* - 组织节点 type=0,children 为子节点列表(保证非 null)
* - 教师节点 type=1,children 为空列表([])
*/
public BaseTree getTeacherTree(Integer schId) {
public BoaGroup getTeacherTree(Integer schId) {
// 1. 获取教师组织树(组织骨架)
BaseTree teaTree = getTeaTree(schId);
BoaGroup teaTree = getTeaTree(schId);
if (teaTree == null) {
teaTree = new BaseTree();
teaTree = new BoaGroup();
teaTree.setId(0);
teaTree.setName("教师组");
teaTree.setType(0);
teaTree.setChildren(new ArrayList<>());
teaTree.setExpand(true);
}
// 2. 获取该校所有教师
@@ -350,18 +358,18 @@ public class ComSchTreeService implements IComSchTreeService {
fixTreeTypes(teaTree, teacherIdSet, orgIdSet);
// 5. 构建组织ID -> 节点映射
Map<Integer, BaseTree> orgNodeMap = new HashMap<>();
Map<Integer, BoaGroup> orgNodeMap = new HashMap<>();
buildOrgNodeMap(teaTree, orgNodeMap);
// 6. 遍历教师,添加到对应组织
for (SchTeacher teacher : teacherList) {
List<Integer> groupIds = teaIdToGroupIds.getOrDefault(teacher.getTeaId(), Collections.emptyList());
if (groupIds.isEmpty()) {
BaseTree defaultGroup = getOrCreateDefaultGroup(teaTree, orgNodeMap);
BoaGroup defaultGroup = getOrCreateDefaultGroup(teaTree, orgNodeMap);
addTeacherToNode(defaultGroup, teacher);
} else {
for (Integer groupId : groupIds) {
BaseTree parentNode = orgNodeMap.get(groupId);
BoaGroup parentNode = orgNodeMap.get(groupId);
if (parentNode == null) {
parentNode = getOrCreateDefaultGroup(teaTree, orgNodeMap);
}
@@ -382,13 +390,13 @@ public class ComSchTreeService implements IComSchTreeService {
/**
* 递归确保所有节点的 children 不为 null,若为 null 则设置为空列表 []
*/
private void ensureChildrenNotEmpty(BaseTree node) {
private void ensureChildrenNotEmpty(BoaGroup node) {
if (node == null) return;
if (node.getChildren() == null) {
node.setChildren(new ArrayList<>());
}
if (node.getChildren() != null) {
for (BaseTree child : node.getChildren()) {
for (BoaGroup child : node.getChildren()) {
ensureChildrenNotEmpty(child);
}
}
@@ -401,7 +409,7 @@ public class ComSchTreeService implements IComSchTreeService {
* - 如果节点ID在 orgIdSet 中,设为 type=0
* - 其他情况:如果有 children(非空且非空列表)则设为 type=0,否则设为 type=0(默认为组织)
*/
private void fixTreeTypes(BaseTree node, Set<Integer> teacherIdSet, Set<Integer> orgIdSet) {
private void fixTreeTypes(BoaGroup node, Set<Integer> teacherIdSet, Set<Integer> orgIdSet) {
if (node == null) return;
// 根节点(id=0 或 其他)设为组织
if (node.getId().equals(0) || node.getType() == 7) {
@@ -420,7 +428,7 @@ public class ComSchTreeService implements IComSchTreeService {
}
// 递归处理子节点
if (node.getChildren() != null) {
for (BaseTree child : node.getChildren()) {
for (BoaGroup child : node.getChildren()) {
fixTreeTypes(child, teacherIdSet, orgIdSet);
}
}
@@ -430,19 +438,17 @@ public class ComSchTreeService implements IComSchTreeService {
* 将教师添加到指定组织节点(去重)
* 教师节点的 children 设置为空列表([]),而非 null
*/
private void addTeacherToNode(BaseTree parentNode, SchTeacher teacher) {
private void addTeacherToNode(BoaGroup parentNode, SchTeacher teacher) {
if (parentNode == null) return;
// 检查是否已存在该教师(按 teaId 去重)
if (isTeacherExists(parentNode, teacher.getTeaId())) {
return;
}
BaseTree teacherNode = new BaseTree();
BoaGroup teacherNode = new BoaGroup();
teacherNode.setId(teacher.getTeaId());
teacherNode.setName(teacher.getName());
teacherNode.setType(1);
teacherNode.setCode(null);
teacherNode.setOrder(teacher.getSort() != null ? String.valueOf(teacher.getSort()) : null);
teacherNode.setExpand(false);
teacherNode.setSort(teacher.getSort());
// ★ 修改点:教师节点的 children 设为空列表,而不是 null
teacherNode.setChildren(new ArrayList<>());
@@ -455,13 +461,13 @@ public class ComSchTreeService implements IComSchTreeService {
/**
* 递归构建组织 ID -> 节点映射(仅 type=0)
*/
private void buildOrgNodeMap(BaseTree node, Map<Integer, BaseTree> orgNodeMap) {
private void buildOrgNodeMap(BoaGroup node, Map<Integer, BoaGroup> orgNodeMap) {
if (node == null) return;
if (node.getType() == 0) {
orgNodeMap.put(node.getId(), node);
}
if (node.getChildren() != null) {
for (BaseTree child : node.getChildren()) {
for (BoaGroup child : node.getChildren()) {
buildOrgNodeMap(child, orgNodeMap);
}
}
@@ -470,16 +476,15 @@ public class ComSchTreeService implements IComSchTreeService {
/**
* 获取或创建“未分组”节点(ID = -1)
*/
private BaseTree getOrCreateDefaultGroup(BaseTree root, Map<Integer, BaseTree> orgNodeMap) {
private BoaGroup getOrCreateDefaultGroup(BoaGroup root, Map<Integer, BoaGroup> orgNodeMap) {
final Integer DEFAULT_GROUP_ID = -1;
BaseTree defaultGroup = orgNodeMap.get(DEFAULT_GROUP_ID);
BoaGroup defaultGroup = orgNodeMap.get(DEFAULT_GROUP_ID);
if (defaultGroup == null) {
defaultGroup = new BaseTree();
defaultGroup = new BoaGroup();
defaultGroup.setId(DEFAULT_GROUP_ID);
defaultGroup.setName("未分组");
defaultGroup.setType(0);
defaultGroup.setChildren(new ArrayList<>());
defaultGroup.setExpand(true);
if (root.getChildren() == null) {
root.setChildren(new ArrayList<>());
}
@@ -492,9 +497,9 @@ public class ComSchTreeService implements IComSchTreeService {
/**
* 检查节点下是否已存在指定 teaId 的教师
*/
private boolean isTeacherExists(BaseTree parentNode, Integer teaId) {
private boolean isTeacherExists(BoaGroup parentNode, Integer teaId) {
if (parentNode.getChildren() == null) return false;
for (BaseTree child : parentNode.getChildren()) {
for (BoaGroup child : parentNode.getChildren()) {
if (child.getType() == 1 && child.getId().equals(teaId)) {
return true;
}
@@ -506,36 +511,26 @@ public class ComSchTreeService implements IComSchTreeService {
* 排序:先按 order 排(若不为空),再按 name 排序(组织节点和教师节点分别处理)
* 这里只对教师节点排序,组织节点保留原顺序
*/
private void sortTreeNodes(BaseTree node) {
private void sortTreeNodes(BoaGroup node) {
if (node == null || node.getChildren() == null) return;
// 分离组织节点和教师节点
List<BaseTree> orgChildren = node.getChildren().stream()
List<BoaGroup> orgChildren = node.getChildren().stream()
.filter(c -> c.getType() == 0)
.collect(Collectors.toList());
List<BaseTree> teacherChildren = node.getChildren().stream()
List<BoaGroup> teacherChildren = node.getChildren().stream()
.filter(c -> c.getType() == 1)
.sorted(Comparator.comparing((BaseTree t) -> {
String order = t.getOrder();
if (order != null && !order.isEmpty()) {
try {
return Integer.parseInt(order);
} catch (NumberFormatException e) {
return Integer.MAX_VALUE;
}
}
return Integer.MAX_VALUE;
})
.thenComparing(BaseTree::getName, String.CASE_INSENSITIVE_ORDER))
.sorted(Comparator.comparing((BoaGroup t) -> t.getSort(), Comparator.nullsLast(Integer::compareTo))
.thenComparing(BoaGroup::getName, String.CASE_INSENSITIVE_ORDER))
.collect(Collectors.toList());
// 合并:先组织,后教师
List<BaseTree> sorted = new ArrayList<>();
List<BoaGroup> sorted = new ArrayList<>();
sorted.addAll(orgChildren);
sorted.addAll(teacherChildren);
node.setChildren(sorted);
// 递归排序子组织
for (BaseTree child : orgChildren) {
for (BoaGroup child : orgChildren) {
sortTreeNodes(child);
}
}
@@ -563,7 +558,7 @@ public class ComSchTreeService implements IComSchTreeService {
/**
* 递归查找指定 type 的节点(返回该节点及子树)
*/
private BaseTree findNodeByType(BaseTree node, int targetType) {
private BoaGroup findNodeByType(BoaGroup node, int targetType) {
if (node == null) {
return null;
}
@@ -571,8 +566,8 @@ public class ComSchTreeService implements IComSchTreeService {
return node; // 找到目标节点,返回整个节点(含其 children)
}
if (node.getChildren() != null) {
for (BaseTree child : node.getChildren()) {
BaseTree found = findNodeByType(child, targetType);
for (BoaGroup child : node.getChildren()) {
BoaGroup found = findNodeByType(child, targetType);
if (found != null) {
return found;
}
@@ -704,14 +699,14 @@ public class ComSchTreeService implements IComSchTreeService {
* @param stopType 停止递归的类型
* @return 过滤后的树列表
*/
private List<BaseTree> filterTreeByType(List<BaseTree> treeList, int stopType) {
private List<BoaGroup> filterTreeByType(List<BoaGroup> treeList, int stopType) {
if (treeList == null || treeList.isEmpty()) {
return treeList;
}
List<BaseTree> result = new ArrayList<>();
List<BoaGroup> result = new ArrayList<>();
for (BaseTree node : treeList) {
for (BoaGroup node : treeList) {
// 如果当前节点type等于stopType,保留该节点但清空子节点
if (node.getType() == stopType) {
// 保留该节点,但移除所有子节点(停止递归)
@@ -720,7 +715,7 @@ public class ComSchTreeService implements IComSchTreeService {
} else {
// type不等于stopType,递归处理子节点
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
List<BaseTree> filteredChildren = filterTreeByType(node.getChildren(), stopType);
List<BoaGroup> filteredChildren = filterTreeByType(node.getChildren(), stopType);
node.setChildren(filteredChildren);
}
result.add(node);
@@ -795,9 +790,9 @@ public class ComSchTreeService implements IComSchTreeService {
* 原有递归收集方法(单学段或无条件收集)
* 不进行分组,将所有符合类型的节点加入 result 列表
*/
private void collectNodesByType(List<BaseTree> treeList, int targetType,
private void collectNodesByType(List<BoaGroup> treeList, int targetType,
List<SelectIntVO> result) {
for (BaseTree node : treeList) {
for (BoaGroup node : treeList) {
if (node.getType() == targetType) {
SelectIntVO vo = new SelectIntVO();
vo.setId(node.getId());
@@ -819,12 +814,12 @@ public class ComSchTreeService implements IComSchTreeService {
* @param stageToSchsystemMap 学段ID→学制ID映射
* @param currentStageId 当前路径上最近匹配的学段ID(null表示尚未匹配)
*/
private void collectNodesGroupByStage(List<BaseTree> treeList, int targetType,
private void collectNodesGroupByStage(List<BoaGroup> treeList, int targetType,
Map<Integer, List<SelectIntVO>> resultMap,
Map<String, Integer> stageNameToId,
Map<Integer, Integer> stageToSchsystemMap,
Integer currentStageId) {
for (BaseTree node : treeList) {
for (BoaGroup node : treeList) {
// 1. 检查当前节点是否为学段(名称匹配),更新最近学段ID
Integer newStageId = currentStageId;
if (stageNameToId.containsKey(node.getName())) {
@@ -852,22 +847,22 @@ public class ComSchTreeService implements IComSchTreeService {
private Map<Integer, String> idToNameCache;
private void buildCache(BaseTree node) {
private void buildCache(BoaGroup node) {
if (node == null) return;
idToNameCache.put(node.getId(), node.getName());
if (node.getChildren() != null) {
for (BaseTree child : node.getChildren()) {
for (BoaGroup child : node.getChildren()) {
buildCache(child);
}
}
}
private List<BaseTree> flattenTree(BaseTree root) {
List<BaseTree> result = new ArrayList<>();
private List<BoaGroup> flattenTree(BoaGroup root) {
List<BoaGroup> result = new ArrayList<>();
if (root == null) return result;
result.add(root);
if (root.getChildren() != null) {
for (BaseTree child : root.getChildren()) {
for (BoaGroup child : root.getChildren()) {
result.addAll(flattenTree(child));
}
}
@@ -877,13 +872,13 @@ public class ComSchTreeService implements IComSchTreeService {
/**
* 递归扁平化树,收集指定类型的节点
*/
private void flattenTreeByType(BaseTree node, int type, List<BaseTree> collector) {
private void flattenTreeByType(BoaGroup node, int type, List<BoaGroup> collector) {
if (node == null) return;
if (node.getType() != null && node.getType() == type) {
collector.add(node);
}
if (node.getChildren() != null) {
for (BaseTree child : node.getChildren()) {
for (BoaGroup child : node.getChildren()) {
flattenTreeByType(child, type, collector);
}
}
@@ -10,9 +10,9 @@ import com.yunhai.bschclassmanage.model.constant.LogModule;
import com.yunhai.bschclassmanage.model.dto.SchTeacherDTO;
import com.yunhai.bschclassmanage.model.dto.TeaTransferGroupDTO;
import com.yunhai.bschclassmanage.model.dto.TeaTransferInGroupDTO;
import com.yunhai.bschclassmanage.model.tool.BaseTree;
import com.yunhai.bschclassmanage.model.vo.*;
import com.yunhai.bschclassmanage.service.IComSchTreeService;
import com.yunhai.platform.utils.model.BoaGroup;
import com.yunhai.bschclassmanage.service.ISchTeacherService;
import com.yunhai.bschclassmanage.service.impl.teacher.*;
import com.yunhai.bschclassmanage.utils.ModelMapperUtil;
@@ -244,12 +244,12 @@ public class SchTeacherService implements ISchTeacherService {
return Collections.emptyList();
}
BaseTree teacherGroupTree = comSchTreeService.getTeaTree(schId);
BoaGroup teacherGroupTree = comSchTreeService.getTeaTree(schId);
if (teacherGroupTree == null) {
return Collections.emptyList();
}
List<BaseTree> allNodes = flattenTree(teacherGroupTree);
List<BoaGroup> allNodes = flattenTree(teacherGroupTree);
return allNodes.stream()
.filter(node -> groupIds.contains(node.getId()))
@@ -311,14 +311,14 @@ public class SchTeacherService implements ISchTeacherService {
/**
* 递归扁平化树,返回所有节点列表(包括根节点)
*/
private List<BaseTree> flattenTree(BaseTree root) {
List<BaseTree> result = new ArrayList<>();
private List<BoaGroup> flattenTree(BoaGroup root) {
List<BoaGroup> result = new ArrayList<>();
if (root == null) {
return result;
}
result.add(root);
if (root.getChildren() != null) {
for (BaseTree child : root.getChildren()) {
for (BoaGroup child : root.getChildren()) {
result.addAll(flattenTree(child));
}
}