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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user