Files
UserclassService/src/main/java/com/yunhai/bschclassmanage/service/impl/SchCustomGroupService.java
T
2026-08-14 09:04:58 +08:00

828 lines
38 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<SchCustomGroupVO> getSchCustomGroup(Integer schId, Integer type) {
if (schId == null || type == null) {
return Collections.emptyList();
}
List<SchTeacherCustomGroup> 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<Integer> groupIds = groups.stream().map(SchTeacherCustomGroup::getId).collect(Collectors.toList());
// 2. 批量查询关联数据
List<SchTeacherCustomGroupInGrade> gradeRelations = groupInGradeRepository.findByCgGroupIdIn(groupIds);
Map<Integer, List<Integer>> groupIdToGradeIds = gradeRelations.stream()
.collect(Collectors.groupingBy(SchTeacherCustomGroupInGrade::getCgGroupId,
Collectors.mapping(SchTeacherCustomGroupInGrade::getGradeId, Collectors.toList())));
List<SchTeacherCustomGroupInSubject> subjectRelations = groupInSubjectRepository.findByCgGroupIdIn(groupIds);
Map<Integer, List<String>> groupIdToSubIds = subjectRelations.stream()
.collect(Collectors.groupingBy(SchTeacherCustomGroupInSubject::getCgGroupId,
Collectors.mapping(SchTeacherCustomGroupInSubject::getSubId, Collectors.toList())));
List<SchTeacherCustomGroupInTeacher> teacherRelations = groupInTeacherRepository.findByCgGroupIdIn(groupIds);
Map<Integer, List<Integer>> groupIdToLeaderIds = teacherRelations.stream()
.filter(r -> r.getGroupType() != null && r.getGroupType() == 1)
.collect(Collectors.groupingBy(SchTeacherCustomGroupInTeacher::getCgGroupId,
Collectors.mapping(SchTeacherCustomGroupInTeacher::getTeaId, Collectors.toList())));
Map<Integer, List<Integer>> groupIdToMemberIds = teacherRelations.stream()
.filter(r -> r.getGroupType() != null && r.getGroupType() == 2)
.collect(Collectors.groupingBy(SchTeacherCustomGroupInTeacher::getCgGroupId,
Collectors.mapping(SchTeacherCustomGroupInTeacher::getTeaId, Collectors.toList())));
Set<Integer> 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<SchTeacher> allTeachers = schTeacherService.getSchTeacherCacheList(schId);
Map<Integer, String> 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<Integer, String> gradeNameMap = Optional.ofNullable(
// baseSchsystemGradeService.getGradeNameMap(schId, null, null)
// ).orElse(Collections.emptyMap());
// // 学科名称 - effectively final
// Map<String, String> subjectNameMap = Optional.ofNullable(
// schDicSubjectService.toIdAndNameMap(schId, null)
// ).orElse(Collections.emptyMap());
// Map<Integer, String> 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<SchCustomGroupVO> result = new ArrayList<>(groups.size());
for (SchTeacherCustomGroup group : groups) {
Integer groupId = group.getId();
// List<SelectIntVO> gradeVOList = convertToSelectIntVO(
// groupIdToGradeIds.getOrDefault(groupId, Collections.emptyList()),
// gradeNameMap, "未知年级"
// );
//
// List<SelectStrVO> 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<Integer> memberIds = new ArrayList<>(groupIdToMemberIds.getOrDefault(groupId, Collections.emptyList()));
if (leaderId != null) {
memberIds.remove(leaderId);
}
memberIds = memberIds.stream().distinct().collect(Collectors.toList());
List<SelectIntVO> 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<SelectIntVO> convertToSelectIntVO(List<Integer> ids, Map<Integer, String> 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<SelectStrVO> convertToSelectStrVO(List<String> ids, Map<Integer, String> 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<Integer> gradeIds) {
groupInGradeRepository.deleteByCgGroupId(groupId);
if (gradeIds != null && !gradeIds.isEmpty()) {
List<SchTeacherCustomGroupInGrade> 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<String> subjectIds) {
groupInSubjectRepository.deleteByCgGroupId(groupId);
if (subjectIds != null && !subjectIds.isEmpty()) {
List<SchTeacherCustomGroupInSubject> 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<Integer> 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<Integer> 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<SchTeacherCustomGroupInTeacher> 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<Integer> 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<SchTeacherCustomGroupInTeacher> allMembers = groupInTeacherRepository.findByCgGroupId(groupId);
if (allMembers.isEmpty()) {
// 如果组没有成员,但设置了组长,可以先添加成员,但这里要求必须从组员中选,所以先提示无成员
// 根据业务,可以先添加组员再设组长,这里抛出异常
throw new DException("该组暂无成员,无法设置组长");
}
// 4. 将传入的ID去重
List<Integer> uniqueLeaderIds = newLeaderIds.stream()
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
// 5. 校验传入的ID是否都在组成员中
Set<Integer> existTeaIds = allMembers.stream()
.map(SchTeacherCustomGroupInTeacher::getTeaId)
.collect(Collectors.toSet());
List<Integer> 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<ErrorExcelColumn> errorMsgArr = new ArrayList<>();
ImportParams params = new ImportParams();
params.setTitleRows(1);
params.setHeadRows(1);
Date now = new Date();
try (InputStream is = file.getInputStream()) {
// ---------- 1. 提前加载基础数据并构建映射 ----------
List<SchTeacher> schTeacherList = schTeacherService.getSchTeacherList(user.getSchId());
List<SelectIntVO> gradeList = baseSchsystemGradeService.getGradeSelect(user.getSchId(), null, null);
Map<String, String> subjectMap = schDicSubjectService.toIdAndNameMap(user.getSchId(), null);
List<SchTeacherCustomGroup> existingGroupList = teacherCustomGroupRepository.findBySchId(user.getSchId());
// 教师姓名 -> ID
Map<String, Integer> teacherNameToId = schTeacherList.stream()
.collect(Collectors.toMap(
t -> t.getName().trim(),
SchTeacher::getId,
(a, b) -> a
));
// 年级名称 -> ID
Map<String, Integer> gradeNameToId = gradeList.stream()
.collect(Collectors.toMap(
vo -> vo.getName().trim(),
SelectIntVO::getId,
(a, b) -> a
));
// 学科名称 -> ID(subjectMap 是 ID->Name,需反转)
Map<String, String> subjectNameToId = subjectMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey,
(a, b) -> a
));
// ---------- 2. 解析 Excel ----------
List<CustomGroupExcelColumn> 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<SchCustomGroupDTO> dtoList = new ArrayList<>();
int currentRow = params.getTitleRows() + params.getHeadRows(); // 初始为表头行号
for (CustomGroupExcelColumn item : list) {
int row = ++currentRow; // 当前数据行号
// ---------- 3. 基础校验 ----------
List<String> 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<String> gradeNames = Arrays.asList(item.getGradeBin().split(","));
List<String> 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<String> subjectNames = Arrays.asList(item.getSubjectBin().split(","));
List<String> 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<String> memberNames = Arrays.asList(item.getMemberName().split(","));
List<String> 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<String> leaderNames = Arrays.asList(item.getLeaderName().split(","));
List<String> 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<SchTeacherCustomGroup> saveList = new ArrayList<>();
for (SchCustomGroupDTO dto : dtoList) {
// 查找是否已存在(按学校、组名、组类型)
Optional<SchTeacherCustomGroup> 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<SchTeacherCustomGroupInTeacher> 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<Integer> 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("导入失败,请联系管理员! </br> 错误详情: </br>" + e.getMessage());
}
}
/**
* 记录导入日志
* @param user 操作用户
* @param groups 导入的组列表
*/
private void importLogs(UserContext.UserInfo user, List<SchTeacherCustomGroup> 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);
}
}