This commit is contained in:
2026-08-26 14:41:01 +08:00
parent c1ea8bc25a
commit 3220ad3013
3 changed files with 96 additions and 12 deletions
@@ -45,6 +45,11 @@ public interface SchStudentRepository extends JpaRepository<SchStudent, Integer>
boolean existsBySchIdAndClassIdAndNameAndIdNot(Integer schId,Integer classId, String name, Integer id);
/**
* 判断同一班级内是否已存在相同学号的学生(排除自身,用于学号导入时的重复校验)
*/
boolean existsBySchIdAndClassIdAndStuClassNoAndIdNot(Integer schId, Integer classId, String stuClassNo, Integer id);
@Modifying(clearAutomatically = true)
@Query("UPDATE SchStudent s SET s.classId = :newClassId, s.groupId = :newClassId, s.updateDate = :updateDate, s.updateUserId = :updateUserId WHERE s.id IN (:ids) AND s.schId = :schId")
int batchUpdateClassId(@Param("ids") List<Integer> ids,
@@ -346,6 +346,47 @@ public class SchStudentImportService {
return errors;
}
/**
* 校验学号导入数据中的重复项(账号、学号)
* @param importList 导入数据列表
* @return 错误信息列表
*/
private List<String> validateClassNoDuplicates(List<StudentClassNoExcelColumn> importList) {
List<String> errors = new ArrayList<>();
// 1. 校验 "账号" 在本次导入中是否重复(避免同一学生被重复处理)
Map<String, Long> accountCountMap = importList.stream()
.filter(dto -> StringUtils.isNotBlank(dto.getAccount()))
.collect(Collectors.groupingBy(
dto -> dto.getAccount(),
Collectors.counting()
));
accountCountMap.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.forEach(entry -> errors.add(
String.format("账号 '%s' 在表格中出现了 %d 次,请检查并去重",
entry.getKey(), entry.getValue())
));
// 2. 校验 "学号" 在本次导入中是否重复
Map<String, Long> classNoCountMap = importList.stream()
.filter(dto -> StringUtils.isNotBlank(dto.getClazzNo()))
.collect(Collectors.groupingBy(
dto -> dto.getClazzNo(),
Collectors.counting()
));
classNoCountMap.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.forEach(entry -> errors.add(
String.format("学号 '%s' 在表格中出现了 %d 次,请检查并去重",
entry.getKey(), entry.getValue())
));
return errors;
}
public void studentClassNoTemplate(HttpServletResponse response,
Integer schId,Integer systemId,Integer gradeId,Integer classId){
@@ -419,9 +460,17 @@ public class SchStudentImportService {
throw new DException("上传的 Excel 中没有有效数据行,请检查后重新上传!");
}
// Excel数据重复验证(账号、学号)
{
List<String> validateDuplicatesMsgs = validateClassNoDuplicates(list);
if (validateDuplicatesMsgs.size() > 0) {
errorMsgArr.add(new ErrorExcelColumn(0, "数据重复", String.join("\r\n", validateDuplicatesMsgs)));
}
}
// 4. 业务校验与转换
List<SchStudent> saveStudentList = new ArrayList<>();
int currentRow = params.getTitleRows() + params.getHeadRows() ; // 从第2行开始
int currentRow = params.getTitleRows() + params.getHeadRows() + 1; // 第一行数据从第2行开始
for (StudentClassNoExcelColumn item : list) {
// Excel数据验证
@@ -429,6 +478,7 @@ public class SchStudentImportService {
List<String> validateMsgs = ValidationUtil.validateMsgs(item);
if (validateMsgs.size() > 0) {
errorMsgArr.add(new ErrorExcelColumn(currentRow, "导入规则", String.join("\r\n", validateMsgs)));
currentRow++;
continue;
}
}
@@ -438,17 +488,22 @@ public class SchStudentImportService {
{
Optional<SchStudent> accountOpt = schStudentRepository.findByAccount(item.getAccount());
if (accountOpt.isPresent()) {
if (!accountOpt.get().getSchId().equals(schId) && accountOpt.get().getTransferStatus().equals(TransferStatus.IN)) {
SchStudent existing = accountOpt.get();
// 跨校防护(空安全):账号属于其他学校时一律拒绝,避免修改其他学校的学生数据
if (!schId.equals(existing.getSchId())) {
String schoolName = existing.getSchId() == null ? "未知学校"
: comSchTreeService.getSchoolName(existing.getSchId());
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getStuName(),
"禁止将" + comSchTreeService.getSchoolName(accountOpt.get().getSchId()) + "正常学生导入!"));
"禁止将" + schoolName + "学生导入!"));
currentRow++;
continue;
} else {
BeanUtils.copyProperties(accountOpt.get(), stu);
stu.setUpdateDate(now);
stu.setUpdateUserId(userId);
}
BeanUtils.copyProperties(existing, stu);
stu.setUpdateDate(now);
stu.setUpdateUserId(userId);
} else {
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getAccount(),item.getStuName()+"学生账号未找到!"));
currentRow++;
continue;
}
}
@@ -465,10 +520,12 @@ public class SchStudentImportService {
gradeId2 = gradeIdOpt.get();
if(gradeId!=null&&!gradeId2.equals(gradeId)){
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getGradeName(), StudentExcelColumn.GRADE_NAME + "参数年级与模板年级不一致!"));
currentRow++;
continue;
}
} else {
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getGradeName(), StudentExcelColumn.GRADE_NAME + "不存在!"));
currentRow++;
continue;
}
}
@@ -482,20 +539,35 @@ public class SchStudentImportService {
if(classId!=null){
if(!schClassOpt.get().getClassId().equals(classId)){
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getClazzName(), StudentExcelColumn.CLAZZ_NAME + "参数班级与模板班级不一致!"));
currentRow++;
continue;
}
}
// 学生当前班级必须与模板班级一致,避免改错其他班级学生的学号
if (!Objects.equals(stu.getClassId(), schClassOpt.get().getClassId())) {
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getClazzName(), "学生当前班级与模板班级不一致!"));
currentRow++;
continue;
}
} else {
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getClazzName(), StudentExcelColumn.CLAZZ_NAME + "不存在!"));
currentRow++;
continue;
}
}
// 学号
// 学号(为空时保留原学号,避免误清空)
{
stu.setStuClassNo(item.getClazzNo());
if (StringUtils.isNotBlank(item.getClazzNo())) {
// 同班级内学号重复校验(排除学生自身)
boolean classNoExists = schStudentRepository.existsBySchIdAndClassIdAndStuClassNoAndIdNot(
schId, stu.getClassId(), item.getClazzNo(), stu.getId());
if (classNoExists) {
errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getClazzNo(), "该班级已存在相同学号的学生!"));
currentRow++;
continue;
}
stu.setStuClassNo(item.getClazzNo());
try {
String digits = item.getClazzNo().replaceAll("\\D+", "");
if (StringUtils.isNotBlank(digits)) {
@@ -507,6 +579,8 @@ public class SchStudentImportService {
}
}
saveStudentList.add(stu);
classIds.add(stu.getClassId());
currentRow++;
}
// 5. 若有错误,抛出异常(携带明细)
@@ -450,7 +450,7 @@ public class SchStudentLogService {
LocalDateTime now = LocalDateTime.now();
List<OperationLogRecord> logs = new ArrayList<>();
for (SchStudent stu : saveStudentList) {
String content = "";
String content;
Optional<SchClass> schClassOpt = schClassList.stream()
.filter(s -> s.getClassId().equals(stu.getClassId()))
.findFirst();
@@ -461,8 +461,12 @@ public class SchStudentLogService {
.map(SelectIntVO::getName)
.findFirst()
.orElse("未知年级");
content = "导入学生[" + stu.getName() + "]学号["+stu.getStuClassNo()+"]】成功,用户信息:"
content = "导入学生[" + stu.getName() + "]学号[" + stu.getStuClassNo() + "]成功,用户信息:"
+ JSON.toJSONString(stu, NO_PWD_FILTER)
+ ",所在年级班级:" + gradeName + schClassOpt.get().getName();
} else {
content = "导入学生[" + stu.getName() + "]学号[" + stu.getStuClassNo() + "]成功,用户信息:"
+ JSON.toJSONString(stu, NO_PWD_FILTER) + ",班级:未知";
}
OperationLogRecord log = logService.buildLogRecord(user, MODULE_STUDENT, content, now);
logs.add(log);
@@ -470,6 +474,7 @@ public class SchStudentLogService {
if (!logs.isEmpty()) {
logService.saveAllLogAsync(logs);
}
log.debug("操作人:{},学生学号导入日志成功: {}", user.getUserName(), JSON.toJSONString(saveStudentList, NO_PWD_FILTER));
} catch (Exception e) {
log.error("操作人:" + user.getUserName() + ",学生学号导入日志失败: {}", e.getMessage(), e);
}