getGroupTree 修改
This commit is contained in:
@@ -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)。
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user