828 lines
38 KiB
Java
828 lines
38 KiB
Java
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);
|
||
}
|
||
} |