package com.yunhai.bschclassmanage.service.impl; import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.entity.ImportParams; import com.yunhai.bschclassmanage.Repository.*; import com.yunhai.bschclassmanage.annotation.OperationLog; import com.yunhai.bschclassmanage.entity.*; import com.yunhai.bschclassmanage.enums.CustomGroupType; import com.yunhai.bschclassmanage.enums.NodeType; import com.yunhai.bschclassmanage.model.constant.LogModule; import com.yunhai.bschclassmanage.model.dto.AddMemberDTO; import com.yunhai.bschclassmanage.model.dto.SchCustomGroupDTO; import com.yunhai.bschclassmanage.model.dto.SetLeaderDTO; import com.yunhai.bschclassmanage.model.excel.CustomGroupExcelColumn; import com.yunhai.bschclassmanage.model.excel.ErrorExcelColumn; import com.yunhai.bschclassmanage.model.vo.SchCustomGroupVO; import com.yunhai.bschclassmanage.model.vo.SelectIntVO; import com.yunhai.bschclassmanage.model.vo.SelectStrVO; import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService; import com.yunhai.bschclassmanage.service.ISchCustomGroupService; import com.yunhai.bschclassmanage.service.ISchDicSubjectService; import com.yunhai.bschclassmanage.service.ISchTeacherService; import com.yunhai.bschclassmanage.utils.*; import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.exception.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; @Slf4j @Service public class SchCustomGroupService implements ISchCustomGroupService { @Autowired private SchTeacherCustomGroupRepository teacherCustomGroupRepository; @Autowired private SchTeacherCustomGroupInGradeRepository groupInGradeRepository; @Autowired private SchTeacherCustomGroupInSubjectRepository groupInSubjectRepository; @Autowired private SchTeacherCustomGroupInTeacherRepository groupInTeacherRepository; @Autowired private ISchTeacherService schTeacherService; @Autowired private IBaseSchsystemGradeService baseSchsystemGradeService; @Autowired private ISchDicSubjectService schDicSubjectService; @Autowired private RedisUtil redisUtil; /** * 获取学校的自定义组列表(年级组/教研组/备课组),包含年级、学科、组长、组员信息 * * @param schId 学校ID * @param type 组类型:1-年级组,2-教研组,3-备课组 * @return 组VO列表 */ @Override public List getSchCustomGroup(Integer schId, Integer type) { if (schId == null || type == null) { return Collections.emptyList(); } List groups = teacherCustomGroupRepository.findBySchIdAndGroupType(schId, type) .stream() .sorted(Comparator.comparing(SchTeacherCustomGroup::getSort, Comparator.nullsLast(Comparator.naturalOrder()))) .collect(Collectors.toList()); if (groups.isEmpty()) { return Collections.emptyList(); } List groupIds = groups.stream().map(SchTeacherCustomGroup::getId).collect(Collectors.toList()); // 2. 批量查询关联数据 List gradeRelations = groupInGradeRepository.findByCgGroupIdIn(groupIds); Map> groupIdToGradeIds = gradeRelations.stream() .collect(Collectors.groupingBy(SchTeacherCustomGroupInGrade::getCgGroupId, Collectors.mapping(SchTeacherCustomGroupInGrade::getGradeId, Collectors.toList()))); List subjectRelations = groupInSubjectRepository.findByCgGroupIdIn(groupIds); Map> groupIdToSubIds = subjectRelations.stream() .collect(Collectors.groupingBy(SchTeacherCustomGroupInSubject::getCgGroupId, Collectors.mapping(SchTeacherCustomGroupInSubject::getSubId, Collectors.toList()))); List teacherRelations = groupInTeacherRepository.findByCgGroupIdIn(groupIds); Map> groupIdToLeaderIds = teacherRelations.stream() .filter(r -> r.getGroupType() != null && r.getGroupType() == 1) .collect(Collectors.groupingBy(SchTeacherCustomGroupInTeacher::getCgGroupId, Collectors.mapping(SchTeacherCustomGroupInTeacher::getTeaId, Collectors.toList()))); Map> groupIdToMemberIds = teacherRelations.stream() .filter(r -> r.getGroupType() != null && r.getGroupType() == 2) .collect(Collectors.groupingBy(SchTeacherCustomGroupInTeacher::getCgGroupId, Collectors.mapping(SchTeacherCustomGroupInTeacher::getTeaId, Collectors.toList()))); Set allTeacherIds = new HashSet<>(); groupIdToLeaderIds.values().forEach(allTeacherIds::addAll); groupIdToMemberIds.values().forEach(allTeacherIds::addAll); groups.forEach(g -> { if (g.getLeaderId() != null) allTeacherIds.add(g.getLeaderId()); }); // 教师名称映射 - 保证 effectively final List allTeachers = schTeacherService.getSchTeacherCacheList(schId); Map teacherNameMap = Optional.ofNullable( allTeachers.stream() .filter(t -> allTeacherIds.contains(t.getTeaId())) .collect(Collectors.toMap(SchTeacher::getTeaId, SchTeacher::getName, (a, b) -> a)) ).orElse(Collections.emptyMap()); // // 年级名称 - effectively final // Map gradeNameMap = Optional.ofNullable( // baseSchsystemGradeService.getGradeNameMap(schId, null, null) // ).orElse(Collections.emptyMap()); // // 学科名称 - effectively final // Map subjectNameMap = Optional.ofNullable( // schDicSubjectService.toIdAndNameMap(schId, null) // ).orElse(Collections.emptyMap()); // Map subIdToName = Optional.ofNullable( // subjectNameMap.entrySet().stream() // .collect(Collectors.toMap( // e -> { // try { // return Integer.parseInt(e.getKey()); // } catch (NumberFormatException ex) { // return -1; // } // }, // Map.Entry::getValue, // (a, b) -> a // )) // ).orElse(Collections.emptyMap()); // 组装VO List result = new ArrayList<>(groups.size()); for (SchTeacherCustomGroup group : groups) { Integer groupId = group.getId(); // List gradeVOList = convertToSelectIntVO( // groupIdToGradeIds.getOrDefault(groupId, Collections.emptyList()), // gradeNameMap, "未知年级" // ); // // List subjectVOList = convertToSelectStrVO( // groupIdToSubIds.getOrDefault(groupId, Collections.emptyList()), // subIdToName, "未知学科" // ); Integer leaderId = groupIdToLeaderIds.getOrDefault(groupId, Collections.emptyList()) .stream().findFirst().orElse(group.getLeaderId()); SelectIntVO leaderVO = null; if (leaderId != null) { String leaderName = teacherNameMap.getOrDefault(leaderId, "未知教师"); leaderVO = new SelectIntVO(leaderId, leaderName); } List memberIds = new ArrayList<>(groupIdToMemberIds.getOrDefault(groupId, Collections.emptyList())); if (leaderId != null) { memberIds.remove(leaderId); } memberIds = memberIds.stream().distinct().collect(Collectors.toList()); List memberVOList = memberIds.stream() .map(tid -> new SelectIntVO(tid, teacherNameMap.getOrDefault(tid, "未知教师"))) .collect(Collectors.toList()); SchCustomGroupVO vo = SchCustomGroupVO.builder() .cgId(groupId) .cgName(group.getGroupName()) .gradeIds(groupIdToGradeIds.get(groupId)) .subjectIds(groupIdToSubIds.get(groupId)) .cgLeader(leaderVO != null ? Collections.singletonList(leaderVO) : Collections.emptyList()) .cgMember(memberVOList) .build(); result.add(vo); } return result; } // ========== 辅助方法 ========== /** * 将年级ID列表转换为 SelectIntVO 列表 */ private List convertToSelectIntVO(List ids, Map nameMap, String defaultName) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } return ids.stream() .map(id -> new SelectIntVO(id, nameMap.getOrDefault(id, defaultName))) .collect(Collectors.toList()); } /** * 将学科ID列表转换为 SelectStrVO 列表 */ private List convertToSelectStrVO(List ids, Map nameMap, String defaultName) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } return ids.stream() .map(id -> new SelectStrVO(String.valueOf(id), nameMap.getOrDefault(id, defaultName))) .collect(Collectors.toList()); } //添加(变价)组 关联年级 关联学科 // ========== 保存/更新组(含年级、学科关联) ========== @OperationLog(module = LogModule.CUSTOMGROUP) // 可调整为 LogModule.GROUP @Transactional(rollbackFor = Exception.class) public void saveSchCustomGroup(UserContext.UserInfo user, SchCustomGroupDTO dto) { Integer schId = user.getSchId(); boolean isNew = dto.getId() == null || dto.getId() == 0; // 1. 参数校验 if (dto.getGroupName() == null || dto.getGroupName().trim().isEmpty()) { throw new DException("组名不能为空"); } if (dto.getGroupType() == null) { throw new DException("组类型不能为空"); } // 2. 重名校验(同学校同类型下组名唯一) boolean nameExists; SchTeacherCustomGroup oldEntity = null; if (isNew) { nameExists = teacherCustomGroupRepository.existsBySchIdAndGroupTypeAndGroupName( schId, dto.getGroupType(), dto.getGroupName()); if (nameExists) { throw new DException("组名已存在:" + dto.getGroupName()); } } else { // 更新:排除自身,检查其他组同名 nameExists = teacherCustomGroupRepository.existsBySchIdAndGroupTypeAndGroupNameAndIdNot( schId, dto.getGroupType(), dto.getGroupName(), dto.getId()); if (nameExists) { throw new DException("组名已存在:" + dto.getGroupName()); } // 查询旧实体,用于日志对比 oldEntity = teacherCustomGroupRepository.findById(dto.getId()) .orElseThrow(() -> new DException("组不存在,ID=" + dto.getId())); } // 3. 构建新实体并设置基础字段 SchTeacherCustomGroup entity; if (isNew) { entity = new SchTeacherCustomGroup(); entity.setSchId(schId); entity.setGroupName(dto.getGroupName()); entity.setGroupType(dto.getGroupType()); entity.setSort(dto.getSort() != null ? dto.getSort() : 0); OtherUtil.setBaseEntityValue(user, entity); // 设置创建时间、创建人 } else { entity = oldEntity; // 更新允许修改的字段 entity.setGroupName(dto.getGroupName()); entity.setGroupType(dto.getGroupType()); entity.setSort(dto.getSort() != null ? dto.getSort() : entity.getSort()); OtherUtil.setBaseEntityValue(user, entity); // 更新修改时间、修改人 } // 4. 保存组基本信息 SchTeacherCustomGroup saved = teacherCustomGroupRepository.save(entity); Integer groupId = saved.getId(); // 5. 处理年级关联(全量替换) if (dto.getGradeIds() != null) { updateGradeRelations(groupId, dto.getGradeIds()); } // 6. 处理学科关联(全量替换) if (dto.getSubjectIds() != null) { updateSubjectRelations(groupId, dto.getSubjectIds()); } // 7. 清除缓存(如果有) String cacheKey = RedisConstants.SCHOOL_CUSTOM_GROUP + "_" + schId; redisUtil.del(cacheKey); // 8. 构建日志描述 String description = buildCustomGroupLogDescription(saved, isNew, oldEntity); LogContextHolder.setDescription(description); } @Transactional public void updateGradeRelations(Integer groupId, List gradeIds) { groupInGradeRepository.deleteByCgGroupId(groupId); if (gradeIds != null && !gradeIds.isEmpty()) { List list = gradeIds.stream() .map(gid -> { SchTeacherCustomGroupInGrade rel = new SchTeacherCustomGroupInGrade(); rel.setCgGroupId(groupId); rel.setGradeId(gid); return rel; }) .collect(Collectors.toList()); groupInGradeRepository.saveAll(list); } } @Transactional public void updateSubjectRelations(Integer groupId, List subjectIds) { groupInSubjectRepository.deleteByCgGroupId(groupId); if (subjectIds != null && !subjectIds.isEmpty()) { List list = subjectIds.stream() .map(sid -> { SchTeacherCustomGroupInSubject rel = new SchTeacherCustomGroupInSubject(); rel.setCgGroupId(groupId); rel.setSubId(sid); return rel; }) .collect(Collectors.toList()); groupInSubjectRepository.saveAll(list); } } private String buildCustomGroupLogDescription(SchTeacherCustomGroup newGroup, boolean isNew, SchTeacherCustomGroup oldGroup) { StringBuilder sb = new StringBuilder(); if (isNew) { sb.append("新增自定义组:").append(newGroup.getGroupName()); sb.append(",类型:").append(newGroup.getGroupType()); sb.append(",排序:").append(newGroup.getSort()); } else { sb.append("修改自定义组:").append(newGroup.getGroupName()); // 可记录变更详情(如有需要) if (!oldGroup.getGroupName().equals(newGroup.getGroupName())) { sb.append(",组名从【").append(oldGroup.getGroupName()).append("】改为【").append(newGroup.getGroupName()).append("】"); } if (!oldGroup.getGroupType().equals(newGroup.getGroupType())) { sb.append(",类型从【").append(oldGroup.getGroupType()).append("】改为【").append(newGroup.getGroupType()).append("】"); } } return sb.toString(); } //添加(保存)组员 // ========== 添加组员 ========== // ========== 全量保存组员(替换原有组员,自动排除组长) ========== /** * 全量保存组员(groupType=2) * - 先删除该组所有组员(groupType=2) * - 再批量插入传入的组员(自动过滤掉组长) * - 若 teaIds 为空,则清空组员 * * @param user 当前操作用户(用于日志) * @param dto 请求参数,包含组ID和全量组员ID列表 */ @OperationLog(module = LogModule.CAMPUS) @Transactional(rollbackFor = Exception.class) public void saveMembers(UserContext.UserInfo user, AddMemberDTO dto) { Integer groupId = dto.getGroupId(); List teaIds = dto.getTeaIds(); // 1. 参数校验 if (groupId == null || groupId <= 0) { throw new DException("组ID无效"); } if (teaIds == null) { // 如果传 null,视为不修改;传空列表表示清空 return; } // 2. 检查组是否存在 SchTeacherCustomGroup group = teacherCustomGroupRepository.findById(groupId) .orElseThrow(() -> new DException("组不存在,ID=" + groupId)); // 3. 查询当前组长ID(如果有),用于从组员列表中排除 Integer leaderId = groupInTeacherRepository.findByCgGroupIdAndGroupType(groupId, 1) .stream().findFirst().map(SchTeacherCustomGroupInTeacher::getTeaId).orElse(null); // 4. 过滤传入的组员:去掉 null、去掉组长(如果组长在列表中) List finalMemberIds = teaIds.stream() .filter(Objects::nonNull) .filter(tid -> !tid.equals(leaderId)) .distinct() .collect(Collectors.toList()); // 5. 全量替换:先删除所有组员(groupType=2),再插入新组员 groupInTeacherRepository.deleteByCgGroupIdAndGroupType(groupId, 2); if (!finalMemberIds.isEmpty()) { List newRelations = finalMemberIds.stream() .map(tid -> { SchTeacherCustomGroupInTeacher rel = new SchTeacherCustomGroupInTeacher(); rel.setCgGroupId(groupId); rel.setGroupType(2); rel.setTeaId(tid); return rel; }) .collect(Collectors.toList()); groupInTeacherRepository.saveAll(newRelations); } // 6. 清除缓存 String cacheKey = RedisConstants.SCHOOL_CUSTOM_GROUP + "_" + group.getSchId(); redisUtil.del(cacheKey); // 7. 构建日志描述 String description = String.format("保存组【%s】(ID=%d)的组员,共 %d 人,教师ID:%s", group.getGroupName(), groupId, finalMemberIds.size(), finalMemberIds); LogContextHolder.setDescription(description); } //设置组长 // ========== 设置多个组长(覆盖式,从组员中选取) ========== /** * 设置组的多个组长 * - 将当前所有组长(groupType=1)降级为组员(groupType=2) * - 将传入的教师ID列表设为组长(groupType=1) * - 传入的教师必须是该组的现有成员(否则会抛出异常) * - 传入空列表则清空组长 * * @param user 当前操作用户 * @param dto 请求参数 */ @OperationLog(module = LogModule.CAMPUS) @Transactional(rollbackFor = Exception.class) public void setLeaders(UserContext.UserInfo user, SetLeaderDTO dto) { Integer groupId = dto.getGroupId(); List newLeaderIds = dto.getLeaderIds() != null ? dto.getLeaderIds() : Collections.emptyList(); // 1. 参数校验 if (groupId == null || groupId <= 0) { throw new DException("组ID无效"); } // 2. 检查组是否存在 SchTeacherCustomGroup group = teacherCustomGroupRepository.findById(groupId) .orElseThrow(() -> new DException("组不存在,ID=" + groupId)); // 3. 获取该组所有成员(全部记录) List allMembers = groupInTeacherRepository.findByCgGroupId(groupId); if (allMembers.isEmpty()) { // 如果组没有成员,但设置了组长,可以先添加成员,但这里要求必须从组员中选,所以先提示无成员 // 根据业务,可以先添加组员再设组长,这里抛出异常 throw new DException("该组暂无成员,无法设置组长"); } // 4. 将传入的ID去重 List uniqueLeaderIds = newLeaderIds.stream() .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); // 5. 校验传入的ID是否都在组成员中 Set existTeaIds = allMembers.stream() .map(SchTeacherCustomGroupInTeacher::getTeaId) .collect(Collectors.toSet()); List invalidIds = uniqueLeaderIds.stream() .filter(tid -> !existTeaIds.contains(tid)) .collect(Collectors.toList()); if (!invalidIds.isEmpty()) { throw new DException("以下教师不是该组成员,无法设为组长:" + invalidIds); } // 6. 将当前所有组长降级为组员(groupType改为2) for (SchTeacherCustomGroupInTeacher member : allMembers) { if (member.getGroupType() == 1) { member.setGroupType(2); } } // 7. 将新的组长ID对应的记录升级为组长(groupType=1) for (SchTeacherCustomGroupInTeacher member : allMembers) { if (uniqueLeaderIds.contains(member.getTeaId())) { member.setGroupType(1); } } // 8. 批量保存(使用 saveAll) groupInTeacherRepository.saveAll(allMembers); // 9. 清除缓存 String cacheKey = RedisConstants.SCHOOL_CUSTOM_GROUP + "_" + group.getSchId(); redisUtil.del(cacheKey); // 10. 构建日志描述 String description = String.format("设置组【%s】(ID=%d)的组长为:%s", group.getGroupName(), groupId, uniqueLeaderIds.isEmpty() ? "无" : uniqueLeaderIds.toString()); LogContextHolder.setDescription(description); } //删除组 // ========== 删除组(级联删除所有关联) ========== /** * 删除自定义组及其所有关联数据 * - 删除组基本信息 * - 删除组-年级关联 * - 删除组-学科关联 * - 删除组-教师关联(包括组长和组员) * * @param user 当前操作用户(用于日志) * @param groupId 组ID */ @OperationLog(module = LogModule.CAMPUS) @Transactional(rollbackFor = Exception.class) public void deleteSchCustomGroup(UserContext.UserInfo user, Integer groupId) { // 1. 参数校验 if (groupId == null || groupId <= 0) { throw new DException("组ID无效"); } // 2. 查询组是否存在,并获取组基本信息(用于日志和缓存) SchTeacherCustomGroup group = teacherCustomGroupRepository.findById(groupId) .orElseThrow(() -> new DException("组不存在,ID=" + groupId)); // 3. 级联删除关联数据 // 3.1 删除年级关联 groupInGradeRepository.deleteByCgGroupId(groupId); // 3.2 删除学科关联 groupInSubjectRepository.deleteByCgGroupId(groupId); // 3.3 删除教师关联(包括组长和组员) groupInTeacherRepository.deleteByCgGroupId(groupId); // 3.4 删除组基本信息 teacherCustomGroupRepository.deleteById(groupId); // 4. 清除缓存 String cacheKey = RedisConstants.SCHOOL_CUSTOM_GROUP + "_" + group.getSchId(); redisUtil.del(cacheKey); // 5. 构建日志描述 String description = String.format("删除自定义组:%s(ID=%d),类型:%d", group.getGroupName(), groupId, group.getGroupType()); LogContextHolder.setDescription(description); } @OperationLog(module = LogModule.CUSTOMGROUP) @Transactional(rollbackFor = Exception.class) public void importCustomGroup(UserContext.UserInfo user, MultipartFile file) { List errorMsgArr = new ArrayList<>(); ImportParams params = new ImportParams(); params.setTitleRows(1); params.setHeadRows(1); Date now = new Date(); try (InputStream is = file.getInputStream()) { // ---------- 1. 提前加载基础数据并构建映射 ---------- List schTeacherList = schTeacherService.getSchTeacherList(user.getSchId()); List gradeList = baseSchsystemGradeService.getGradeSelect(user.getSchId(), null, null); Map subjectMap = schDicSubjectService.toIdAndNameMap(user.getSchId(), null); List existingGroupList = teacherCustomGroupRepository.findBySchId(user.getSchId()); // 教师姓名 -> ID Map teacherNameToId = schTeacherList.stream() .collect(Collectors.toMap( t -> t.getName().trim(), SchTeacher::getId, (a, b) -> a )); // 年级名称 -> ID Map gradeNameToId = gradeList.stream() .collect(Collectors.toMap( vo -> vo.getName().trim(), SelectIntVO::getId, (a, b) -> a )); // 学科名称 -> ID(subjectMap 是 ID->Name,需反转) Map subjectNameToId = subjectMap.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getValue, Map.Entry::getKey, (a, b) -> a )); // ---------- 2. 解析 Excel ---------- List list; try { list = ExcelImportUtil.importExcel(is, CustomGroupExcelColumn.class, params); } catch (Exception e) { log.error("Excel解析失败", e); throw new DException("Excel 数据格式有误,请检查所有单元格类型是否正确。错误详情:" + e.getMessage()); } if (list == null || list.isEmpty()) { throw new DException("上传的 Excel 中没有有效数据行,请检查后重新上传!"); } List dtoList = new ArrayList<>(); int currentRow = params.getTitleRows() + params.getHeadRows(); // 初始为表头行号 for (CustomGroupExcelColumn item : list) { int row = ++currentRow; // 当前数据行号 // ---------- 3. 基础校验 ---------- List validateMsgs = ValidationUtil.validateMsgs(item); if (!validateMsgs.isEmpty()) { errorMsgArr.add(new ErrorExcelColumn(row, "导入规则", String.join("\r\n", validateMsgs))); continue; } // ---------- 4. 组类型 ---------- Integer groupTypeId = CustomGroupType.getIdByName(item.getGroupType()); if (groupTypeId == null) { errorMsgArr.add(new ErrorExcelColumn(row, item.getGroupType(), "组类型选项不存在")); continue; } // ---------- 5. 构建 DTO ---------- SchCustomGroupDTO dto = new SchCustomGroupDTO(); dto.setGroupType(groupTypeId); dto.setGroupName(item.getGroupName()); // 必须要有 getGroupName() // --- 年级 --- if (StringUtils.isNotBlank(item.getGradeBin())) { List gradeNames = Arrays.asList(item.getGradeBin().split(",")); List notFound = gradeNames.stream() .map(String::trim) .filter(name -> !gradeNameToId.containsKey(name)) .collect(Collectors.toList()); if (!notFound.isEmpty()) { errorMsgArr.add(new ErrorExcelColumn(row, String.join(",", notFound), "年级不存在")); continue; } dto.setGradeIds(gradeNames.stream() .map(String::trim) .map(gradeNameToId::get) .distinct() .collect(Collectors.toList())); } // --- 学科 --- if (StringUtils.isNotBlank(item.getSubjectBin())) { List subjectNames = Arrays.asList(item.getSubjectBin().split(",")); List notFound = subjectNames.stream() .map(String::trim) .filter(name -> !subjectNameToId.containsKey(name)) .collect(Collectors.toList()); if (!notFound.isEmpty()) { errorMsgArr.add(new ErrorExcelColumn(row, String.join(",", notFound), "学科不存在")); continue; } dto.setSubjectIds(subjectNames.stream() .map(String::trim) .map(subjectNameToId::get) .distinct() .collect(Collectors.toList())); } // --- 组员 --- if (StringUtils.isNotBlank(item.getMemberName())) { List memberNames = Arrays.asList(item.getMemberName().split(",")); List notFound = memberNames.stream() .map(String::trim) .filter(name -> !teacherNameToId.containsKey(name)) .collect(Collectors.toList()); if (!notFound.isEmpty()) { errorMsgArr.add(new ErrorExcelColumn(row, String.join(",", notFound), "组员教师不存在")); continue; } dto.setMemberIds(memberNames.stream() .map(String::trim) .map(teacherNameToId::get) .distinct() .collect(Collectors.toList())); } // --- 组长 --- if (StringUtils.isNotBlank(item.getLeaderName())) { List leaderNames = Arrays.asList(item.getLeaderName().split(",")); List notFound = leaderNames.stream() .map(String::trim) .filter(name -> !teacherNameToId.containsKey(name)) .collect(Collectors.toList()); if (!notFound.isEmpty()) { errorMsgArr.add(new ErrorExcelColumn(row, String.join(",", notFound), "组长教师不存在")); continue; } dto.setLeaderIds(leaderNames.stream() .map(String::trim) .map(teacherNameToId::get) .distinct() .collect(Collectors.toList())); } dtoList.add(dto); } // ---------- 6. 如果有错误,抛出 ---------- if (!errorMsgArr.isEmpty()) { throw new DException(ErrorCode.CUSTOM,"导入失败,请检查错误列表",errorMsgArr); } // ---------- 7. 保存主表 ---------- List saveList = new ArrayList<>(); for (SchCustomGroupDTO dto : dtoList) { // 查找是否已存在(按学校、组名、组类型) Optional existOpt = existingGroupList.stream() .filter(g -> g.getGroupName().equals(dto.getGroupName()) && g.getGroupType().equals(dto.getGroupType())) .findFirst(); SchTeacherCustomGroup entity; if (existOpt.isPresent()) { entity = existOpt.get(); // 更新基本信息(如果需要,可保留其他字段) entity.setGroupName(dto.getGroupName()); entity.setGroupType(dto.getGroupType()); // 如果实体有 sort 字段,可设置 entity.setSort(...) entity.setUpdateDate(now); entity.setUpdateUserId(user.getUserId()); } else { entity = new SchTeacherCustomGroup(); entity.setSchId(user.getSchId()); entity.setGroupName(dto.getGroupName()); entity.setGroupType(dto.getGroupType()); entity.setSort(0); // 或 dto.getSort() entity.setCreateDate(now); entity.setCreateUserId(user.getUserId()); entity.setUpdateDate(now); entity.setUpdateUserId(user.getUserId()); } saveList.add(entity); } // 批量保存主表,获取带 ID 的实体列表 saveList = teacherCustomGroupRepository.saveAll(saveList); // ---------- 8. 保存关联关系(年级、学科、组长、组员) ---------- // 将 dtoList 与 saveList 关联(按顺序对应) for (int i = 0; i < saveList.size(); i++) { SchTeacherCustomGroup group = saveList.get(i); SchCustomGroupDTO dto = dtoList.get(i); Integer groupId = group.getId(); // 8.1 年级关联 if (dto.getGradeIds() != null && !dto.getGradeIds().isEmpty()) { updateGradeRelations(groupId, dto.getGradeIds()); } else { // 如果为空,删除所有年级关联(若要求全量替换) groupInGradeRepository.deleteByCgGroupId(groupId); } // 8.2 学科关联 if (dto.getSubjectIds() != null && !dto.getSubjectIds().isEmpty()) { updateSubjectRelations(groupId, dto.getSubjectIds()); } else { groupInSubjectRepository.deleteByCgGroupId(groupId); } // 8.3 教师关联(组长 + 组员) // 先删除该组所有教师关联,再重新插入 groupInTeacherRepository.deleteByCgGroupId(groupId); List teacherRelations = new ArrayList<>(); // 添加组长(groupType = 1) if (dto.getLeaderIds() != null) { for (Integer leaderId : dto.getLeaderIds()) { SchTeacherCustomGroupInTeacher rel = new SchTeacherCustomGroupInTeacher(); rel.setCgGroupId(groupId); rel.setGroupType(1); rel.setTeaId(leaderId); teacherRelations.add(rel); } } // 添加组员(groupType = 2),排除组长 Set leaderIdSet = dto.getLeaderIds() != null ? new HashSet<>(dto.getLeaderIds()) : Collections.emptySet(); if (dto.getMemberIds() != null) { for (Integer memberId : dto.getMemberIds()) { // 如果该教师同时也是组长,则不再作为组员(避免重复) if (leaderIdSet.contains(memberId)) { continue; } SchTeacherCustomGroupInTeacher rel = new SchTeacherCustomGroupInTeacher(); rel.setCgGroupId(groupId); rel.setGroupType(2); rel.setTeaId(memberId); teacherRelations.add(rel); } } if (!teacherRelations.isEmpty()) { groupInTeacherRepository.saveAll(teacherRelations); } } // 9. 清除缓存 String cacheKey = RedisConstants.SCHOOL_CUSTOM_GROUP + "_" + user.getSchId(); redisUtil.del(cacheKey); // 10. 记录日志(如有) importLogs(user, saveList); } catch (DException e) { throw e; } catch (Exception e) { log.error("导入自定义组时发生系统异常,操作人:" + user.getUserName(), e); throw new DException("导入失败,请联系管理员!
错误详情:
" + e.getMessage()); } } /** * 记录导入日志 * @param user 操作用户 * @param groups 导入的组列表 */ private void importLogs(UserContext.UserInfo user, List groups) { if (groups == null || groups.isEmpty()) { return; } // 提取组名称列表 String groupNames = groups.stream() .map(SchTeacherCustomGroup::getGroupName) .collect(Collectors.joining("、")); String description = "导入自定义组:" + groupNames; // 方式2:使用 LogContextHolder(与 @OperationLog 配合) LogContextHolder.setDescription(description); // 方式3:仅记录到普通日志文件 log.info("用户 {} 导入了自定义组:{}", user.getUserName(), groupNames); } }