refactor: 根包由 com.bestsch.bschclassmanage 重命名为 com.yunhai.bschclassmanage

- git mv src/main/java/com/bestsch/bschclassmanage -> src/main/java/com/yunhai/bschclassmanage
- 255 个 Java 文件的 package/import 同步替换为 com.yunhai.bschclassmanage
- pom.xml 项目 groupId 设为 com.yunhai(与外部依赖 com.bestsch:bsch-* 无关,未改动)
- IDE 缓存 workspace.xml 路径同步更新(外部依赖坐标 com.bestsch:bsch-* 保持不动)
This commit is contained in:
Allen
2026-08-12 17:21:22 +08:00
parent 66cd0f1515
commit dc217ca7a9
258 changed files with 1082 additions and 1067 deletions
@@ -0,0 +1,828 @@
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 IllegalArgumentException("组名不能为空");
}
if (dto.getGroupType() == null) {
throw new IllegalArgumentException("组类型不能为空");
}
// 2. 重名校验(同学校同类型下组名唯一)
boolean nameExists;
SchTeacherCustomGroup oldEntity = null;
if (isNew) {
nameExists = teacherCustomGroupRepository.existsBySchIdAndGroupTypeAndGroupName(
schId, dto.getGroupType(), dto.getGroupName());
if (nameExists) {
throw new IllegalArgumentException("组名已存在:" + dto.getGroupName());
}
} else {
// 更新:排除自身,检查其他组同名
nameExists = teacherCustomGroupRepository.existsBySchIdAndGroupTypeAndGroupNameAndIdNot(
schId, dto.getGroupType(), dto.getGroupName(), dto.getId());
if (nameExists) {
throw new IllegalArgumentException("组名已存在:" + dto.getGroupName());
}
// 查询旧实体,用于日志对比
oldEntity = teacherCustomGroupRepository.findById(dto.getId())
.orElseThrow(() -> new IllegalArgumentException("组不存在,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 IllegalArgumentException("组ID无效");
}
if (teaIds == null) {
// 如果传 null,视为不修改;传空列表表示清空
return;
}
// 2. 检查组是否存在
SchTeacherCustomGroup group = teacherCustomGroupRepository.findById(groupId)
.orElseThrow(() -> new IllegalArgumentException("组不存在,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 IllegalArgumentException("组ID无效");
}
// 2. 检查组是否存在
SchTeacherCustomGroup group = teacherCustomGroupRepository.findById(groupId)
.orElseThrow(() -> new IllegalArgumentException("组不存在,ID=" + groupId));
// 3. 获取该组所有成员(全部记录)
List<SchTeacherCustomGroupInTeacher> allMembers = groupInTeacherRepository.findByCgGroupId(groupId);
if (allMembers.isEmpty()) {
// 如果组没有成员,但设置了组长,可以先添加成员,但这里要求必须从组员中选,所以先提示无成员
// 根据业务,可以先添加组员再设组长,这里抛出异常
throw new IllegalArgumentException("该组暂无成员,无法设置组长");
}
// 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 IllegalArgumentException("以下教师不是该组成员,无法设为组长:" + 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 IllegalArgumentException("组ID无效");
}
// 2. 查询组是否存在,并获取组基本信息(用于日志和缓存)
SchTeacherCustomGroup group = teacherCustomGroupRepository.findById(groupId)
.orElseThrow(() -> new IllegalArgumentException("组不存在,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);
}
}