导入同步修改

This commit is contained in:
2026-08-24 18:28:21 +08:00
parent dcb51347dd
commit 431a1415fe
14 changed files with 310 additions and 62 deletions
@@ -184,6 +184,14 @@ public class SchStudentController extends BaseController {
ruleList.add(rule); ruleList.add(rule);
} }
{
ImportRuleExcelColumn rule = new ImportRuleExcelColumn();
rule.setRuleItem("密码");
rule.setDescription("验证规则");
rule.setContent("长度 8-32;必须同时包含字母、数字、符号;不允许出现连续自然数序列(如 1234、6789)。");
ruleList.add(rule);
}
ExportParams params1 = new ExportParams("学生导入",comSchTreeService.getSchoolName(schId)); ExportParams params1 = new ExportParams("学生导入",comSchTreeService.getSchoolName(schId));
Map<String, Object> sheet1 = new HashMap<>(); Map<String, Object> sheet1 = new HashMap<>();
@@ -47,4 +47,11 @@ public class SyncUserController {
List<SyncUserDTO> userList = OtherUtil.jsonParseList(jsonString, SyncUserDTO.class); List<SyncUserDTO> userList = OtherUtil.jsonParseList(jsonString, SyncUserDTO.class);
syncUserService.syncAddOrUpdate(userList,true); syncUserService.syncAddOrUpdate(userList,true);
} }
@SyncOpLog(SyncDataType.SYNC_MOVE_USER)
@PostMapping("moveUser")
public void SyncMoveUser(@RequestBody String jsonString) {
List<SyncUserDTO> userList = OtherUtil.jsonParseList(jsonString, SyncUserDTO.class);
syncUserService.syncAddOrUpdate(userList,true);
}
} }
@@ -20,6 +20,7 @@ public enum SyncDataType
SYNC_ADD_USER("Sync-AddUser","添加用户"), SYNC_ADD_USER("Sync-AddUser","添加用户"),
SYNC_EDIT_USER("Sync-UpdateUser","编辑用户"), SYNC_EDIT_USER("Sync-UpdateUser","编辑用户"),
SYNC_DELETE_USER("Sync-DeleteUser","删除用户"), SYNC_DELETE_USER("Sync-DeleteUser","删除用户"),
SYNC_MOVE_USER("Sync-MoveUser","用户移组"),
SYNC_USER("Sync-SyncUser","全量用户"), SYNC_USER("Sync-SyncUser","全量用户"),
//推送 //推送
@@ -34,7 +35,12 @@ public enum SyncDataType
SEND_ADD_STUDENT("Send-AddStudent","添加学生"), SEND_ADD_STUDENT("Send-AddStudent","添加学生"),
SEND_EDIT_STUDENT("Send-UpdateStudent","编辑学生"), SEND_EDIT_STUDENT("Send-UpdateStudent","编辑学生"),
SEND_DELETE_STUDENT("Send-DeleteStudent","删除学生"); SEND_DELETE_STUDENT("Send-DeleteStudent","删除学生"),
SEND_MOVE_TEACHER("Send-MoveTeacher","教师移组"),
SEND_MOVE_STUDENT("Send-MoveStudent","学生调班"),
SEND_BATCH_TEACHER("Send-BatchAddTeacher","批量添加教师"),
SEND_BATCH_STUDENT("Send-BatchAddStudent","批量添加学生");
private String key; private String key;
private String name; private String name;
@@ -1,15 +1,18 @@
package com.yunhai.bschclassmanage.model.dto; package com.yunhai.bschclassmanage.model.dto;
import com.yunhai.bschclassmanage.entity.SchStudent;
import com.yunhai.bschclassmanage.model.vo.SchStudentVO; import com.yunhai.bschclassmanage.model.vo.SchStudentVO;
import com.yunhai.bschclassmanage.validation.PasswordCheckable;
import com.yunhai.bschclassmanage.validation.PasswordValidation;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data @Data
@SuperBuilder
@NoArgsConstructor @NoArgsConstructor
public class SchStudentDTO extends SchStudentVO { @PasswordValidation
public class SchStudentDTO extends SchStudent implements PasswordCheckable {
private String pwd; /** 密码校验见类级 @PasswordValidation:新增(id为null或<=0)必填且校验强度;更新(id>0)时密码为空跳过、非空则同样校验强度 */
private String pwd;
} }
@@ -1,6 +1,8 @@
package com.yunhai.bschclassmanage.model.dto; package com.yunhai.bschclassmanage.model.dto;
import com.yunhai.bschclassmanage.entity.SchTeacher; import com.yunhai.bschclassmanage.entity.SchTeacher;
import com.yunhai.bschclassmanage.validation.PasswordCheckable;
import com.yunhai.bschclassmanage.validation.PasswordValidation;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@@ -8,19 +10,20 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List; import java.util.List;
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class SchTeacherDTO extends SchTeacher { @PasswordValidation
public class SchTeacherDTO extends SchTeacher implements PasswordCheckable {
@NotNull(message = "组ID不能为空") @NotNull(message = "组ID不能为空")
@Min(value = 1, message = "至少选择一个组") @Min(value = 1, message = "至少选择一个组")
private Integer groupId; private Integer groupId;
private String pwd; /** 密码校验见类级 @PasswordValidation:新增(id为null或<=0)必填且校验强度;更新(id>0)时密码为空跳过、非空则同样校验强度 */
private String pwd;
} }
@@ -0,0 +1,13 @@
package com.yunhai.bschclassmanage.model.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class SendMoveGroupUserDTO {
@NotNull(message = "请选择移动教师")
private Integer userId;
@NotNull(message = "请选择移动组")
private Integer groupId;
}
@@ -5,8 +5,7 @@ import com.yunhai.bschclassmanage.entity.SchTeacher;
import com.yunhai.bschclassmanage.enums.NodeType; import com.yunhai.bschclassmanage.enums.NodeType;
import com.yunhai.bschclassmanage.model.dto.SchStudentDTO; import com.yunhai.bschclassmanage.model.dto.SchStudentDTO;
import com.yunhai.bschclassmanage.model.dto.SchTeacherDTO; import com.yunhai.bschclassmanage.model.dto.SchTeacherDTO;
import com.yunhai.bschclassmanage.model.tool.DeleteUser; import com.yunhai.bschclassmanage.model.dto.SendMoveGroupUserDTO;
import com.yunhai.bschclassmanage.model.tool.SyncUserDTO;
import com.yunhai.platform.utils.model.BoaGroup; import com.yunhai.platform.utils.model.BoaGroup;
import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.platform.utils.model.BoaUser;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -35,4 +34,18 @@ public interface ISendUserBaseService {
void deleteTeachers(List<Integer> userIds,Integer groupId,Integer schId); void deleteTeachers(List<Integer> userIds,Integer groupId,Integer schId);
void deleteStudents(List<Integer> userIds,Integer groupId,Integer schId); void deleteStudents(List<Integer> userIds,Integer groupId,Integer schId);
/** 导入失败回滚:删除已推送成功的学生平台账号(syncedList 与 groupIds 按下标一一对应) */
void rollbackSyncedStudents(List<BoaUser> syncedList, List<Integer> groupIds, Integer schId);
/** 导入失败回滚:删除已推送成功的教师平台账号(syncedList 与 groupIds 按下标一一对应) */
void rollbackSyncedTeachers(List<BoaUser> syncedList, List<Integer> groupIds, Integer schId);
void moveTeacherToGroup(List<SendMoveGroupUserDTO> teacherList, Integer schId);
void moveStudentToGroup(List<Integer> stuIds,Integer classId,Integer schId);
List<BoaUser> batchAddStudent(List<SchStudentDTO> stuList);
List<BoaUser> batchAddTeacher(List<SchTeacherDTO> teacherList);
} }
@@ -15,7 +15,6 @@ import com.yunhai.bschclassmanage.utils.UserContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -90,8 +89,8 @@ public class SchStudentService implements ISchStudentService {
@OperationLog(module = LogModule.STUDENT) @OperationLog(module = LogModule.STUDENT)
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void saveStudent(UserContext.UserInfo user, SchStudentDTO entityVO) { public void saveStudent(UserContext.UserInfo user, SchStudentDTO entityDTO) {
infoService.saveStudent(user, entityVO); infoService.saveStudent(user, entityDTO);
} }
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@@ -6,13 +6,13 @@ import com.yunhai.bschclassmanage.enums.NodeType;
import com.yunhai.bschclassmanage.enums.UserRole; import com.yunhai.bschclassmanage.enums.UserRole;
import com.yunhai.bschclassmanage.annotation.SyncOpLog; import com.yunhai.bschclassmanage.annotation.SyncOpLog;
import com.yunhai.bschclassmanage.enums.SyncDataType; import com.yunhai.bschclassmanage.enums.SyncDataType;
import com.yunhai.bschclassmanage.model.dto.SchStudentDTO;
import com.yunhai.bschclassmanage.model.dto.SchTeacherDTO;
import com.yunhai.bschclassmanage.model.dto.SendMoveGroupUserDTO;
import com.yunhai.bschclassmanage.service.IComSchTreeService; import com.yunhai.bschclassmanage.service.IComSchTreeService;
import com.yunhai.bschclassmanage.service.ISendUserBaseService; import com.yunhai.bschclassmanage.service.ISendUserBaseService;
import com.yunhai.platform.openapi.client.YschBaseOpenApi; import com.yunhai.platform.openapi.client.YschBaseOpenApi;
import com.yunhai.platform.openapi.model.SysGroupMoveRequest; import com.yunhai.platform.openapi.model.*;
import com.yunhai.platform.openapi.model.SysGroupRequest;
import com.yunhai.platform.openapi.model.SysUserGroupDeleteItem;
import com.yunhai.platform.openapi.model.SysUserRequest;
import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.model.BoaGroup; import com.yunhai.platform.utils.model.BoaGroup;
import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.platform.utils.model.BoaUser;
@@ -181,23 +181,120 @@ public class SendUserBaseService implements ISendUserBaseService {
} }
} }
// public void moveTeachertToGroup(List<SchTeacherDTO> teacherList){ /**
// for (SchTeacherDTO teacher :teacherList) { * 导入失败回滚:删除已成功推送到平台的学生账号,避免孤儿账号。
// SysUserMoveRequest move=new SysUserMoveRequest(); * syncedList 与 groupIds 按下标一一对应(groupId 取自主组 classId)。
// move.setUserId(); */
// move.setGroupId(); public void rollbackSyncedStudents(List<BoaUser> syncedList, List<Integer> groupIds, Integer schId) {
// move.setSchoolId(); if (syncedList == null || syncedList.isEmpty()) {
// YschBaseOpenApi.moveUserToGroup(teacher.getTeaId(),teacher.getGroupId()); return;
// } }
// } int n = Math.min(syncedList.size(), groupIds == null ? 0 : groupIds.size());
// for (int i = 0; i < n; i++) {
// public void moveStudentToGroup(List<SchTeacherDTO> teacherList){ BoaUser boaUser = syncedList.get(i);
// for (SchTeacherDTO teacher :teacherList) { if (boaUser == null) {
// SysUserMoveRequest move=new SysUserMoveRequest(); continue;
// move.setUserId(); }
// move.setGroupId(); Integer groupId = groupIds.get(i);
// move.setSchoolId(); if (groupId != null) {
// YschBaseOpenApi.moveUserToGroup(teacher.getTeaId(),teacher.getGroupId()); deleteStudents(Collections.singletonList(boaUser.getId()), groupId, schId);
// } }
// } }
}
/**
* 导入失败回滚:删除已成功推送到平台的教师账号,避免孤儿账号。
* syncedList 与 groupIds 按下标一一对应(groupId 取自主组)。
*/
public void rollbackSyncedTeachers(List<BoaUser> syncedList, List<Integer> groupIds, Integer schId) {
if (syncedList == null || syncedList.isEmpty()) {
return;
}
int n = Math.min(syncedList.size(), groupIds == null ? 0 : groupIds.size());
for (int i = 0; i < n; i++) {
BoaUser boaUser = syncedList.get(i);
if (boaUser == null) {
continue;
}
Integer groupId = groupIds.get(i);
if (groupId != null) {
deleteTeachers(Collections.singletonList(boaUser.getId()), groupId, schId);
}
}
}
//移动教师
@SyncOpLog(SyncDataType.SEND_MOVE_TEACHER)
public void moveTeacherToGroup(List<SendMoveGroupUserDTO> teacherList,Integer schId){
for (SendMoveGroupUserDTO teacher :teacherList) {
SysUserMoveRequest move=new SysUserMoveRequest();
move.setUserId(teacher.getUserId());
move.setGroupId(teacher.getGroupId());
YschBaseOpenApi.moveUserToGroup(move);
}
}
//学生调班
@SyncOpLog(SyncDataType.SEND_MOVE_STUDENT)
public void moveStudentToGroup(List<Integer> stuIds,Integer classId,Integer schId){
for (Integer stuId :stuIds) {
SysUserMoveRequest move=new SysUserMoveRequest();
move.setUserId(stuId);
move.setGroupId(classId);
move.setSchoolId(schId);
YschBaseOpenApi.moveUserToGroup(move);
}
}
@SyncOpLog(SyncDataType.SEND_BATCH_STUDENT)
public List<BoaUser> batchAddStudent(List<SchStudentDTO> stuList){
if (stuList == null || stuList.isEmpty()) {
return new ArrayList<>();
}
List<SysUserRequest> requests = new ArrayList<>();
for (SchStudentDTO stu : stuList) {
SysUserRequest sysUser = new SysUserRequest();
sysUser.setAccount(stu.getAccount());
sysUser.setRealName(stu.getName());
sysUser.setGender(stu.getGender());
sysUser.setIdcard(stu.getIdCard());
sysUser.setGroupId(stu.getGroupId());
sysUser.setRoles(Arrays.asList(UserRole.STU_USER.getValue()));
String pwd = stu.getPwd();
if (StringUtils.isNotBlank(pwd)) {
sysUser.setPassword(pwd);
sysUser.setConfirmPassword(pwd);
}
requests.add(sysUser);
}
return YschBaseOpenApi.batchAddUsers(requests);
}
@SyncOpLog(SyncDataType.SEND_BATCH_TEACHER)
public List<BoaUser> batchAddTeacher(List<SchTeacherDTO> teacherList)
{
if (teacherList == null || teacherList.isEmpty()) {
return new ArrayList<>();
}
List<SysUserRequest> requests = new ArrayList<>();
for (SchTeacherDTO teacher : teacherList) {
SysUserRequest sysUser = new SysUserRequest();
sysUser.setAccount(teacher.getAccount());
sysUser.setRealName(teacher.getName());
sysUser.setGender(teacher.getGender());
sysUser.setIdcard(teacher.getIdCard());
sysUser.setPhone(teacher.getTel());
sysUser.setGroupId(teacher.getGroupId());
sysUser.setRoles(Arrays.asList(UserRole.TEACHER_USER.getValue()));
String pwd = teacher.getPwd();
if (StringUtils.isNotBlank(pwd)) {
sysUser.setPassword(pwd);
sysUser.setConfirmPassword(pwd);
}
requests.add(sysUser);
}
return YschBaseOpenApi.batchAddUsers(requests);
}
} }
@@ -12,6 +12,7 @@ import com.yunhai.bschclassmanage.enums.StuAbnormalStatus;
import com.yunhai.bschclassmanage.enums.TransferStatus; import com.yunhai.bschclassmanage.enums.TransferStatus;
import com.yunhai.bschclassmanage.enums.UserGender; import com.yunhai.bschclassmanage.enums.UserGender;
import com.yunhai.bschclassmanage.enums.UserStatus; import com.yunhai.bschclassmanage.enums.UserStatus;
import com.yunhai.bschclassmanage.model.dto.SchStudentDTO;
import com.yunhai.bschclassmanage.model.excel.ErrorExcelColumn; import com.yunhai.bschclassmanage.model.excel.ErrorExcelColumn;
import com.yunhai.bschclassmanage.model.excel.StudentClassNoExcelColumn; import com.yunhai.bschclassmanage.model.excel.StudentClassNoExcelColumn;
import com.yunhai.bschclassmanage.model.excel.StudentExcelColumn; import com.yunhai.bschclassmanage.model.excel.StudentExcelColumn;
@@ -20,10 +21,13 @@ import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService; import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
import com.yunhai.bschclassmanage.service.IComSchTreeService; import com.yunhai.bschclassmanage.service.IComSchTreeService;
import com.yunhai.bschclassmanage.service.ISchClassService; import com.yunhai.bschclassmanage.service.ISchClassService;
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
import com.yunhai.bschclassmanage.utils.EasyPoiUtils; import com.yunhai.bschclassmanage.utils.EasyPoiUtils;
import com.yunhai.bschclassmanage.utils.UserContext; import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.bschclassmanage.utils.ValidateUtil; import com.yunhai.bschclassmanage.utils.ValidateUtil;
import com.yunhai.bschclassmanage.utils.ValidationUtil; import com.yunhai.bschclassmanage.utils.ValidationUtil;
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.exception.ErrorCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -59,6 +63,10 @@ public class SchStudentImportService {
@Autowired @Autowired
private IComSchTreeService comSchTreeService; private IComSchTreeService comSchTreeService;
@Autowired
private ISendUserBaseService sendUserBaseService;
@Autowired @Autowired
private SchStudentCacheService cacheService; private SchStudentCacheService cacheService;
@@ -77,6 +85,9 @@ public class SchStudentImportService {
Integer userId = user.getUserId(); Integer userId = user.getUserId();
Date now = new Date(); Date now = new Date();
// 提到 try 外,供 catch 回滚时访问
List<SchStudentDTO> saveStudentList = new ArrayList<>();
List<BoaUser> syncStudentList = null;
// 使用 try-with-resources 确保流关闭 // 使用 try-with-resources 确保流关闭
try (InputStream is = file.getInputStream()) { try (InputStream is = file.getInputStream()) {
// 1. 加载基础数据(放在 try 内,若加载失败也回滚) // 1. 加载基础数据(放在 try 内,若加载失败也回滚)
@@ -109,7 +120,6 @@ public class SchStudentImportService {
} }
// 4. 业务校验与转换 // 4. 业务校验与转换
List<SchStudent> saveStudentList = new ArrayList<>();
int currentRow = params.getTitleRows() + params.getHeadRows() + 1; // 从第3行开始 int currentRow = params.getTitleRows() + params.getHeadRows() + 1; // 从第3行开始
for (StudentExcelColumn item : list) { for (StudentExcelColumn item : list) {
@@ -122,8 +132,7 @@ public class SchStudentImportService {
continue; continue;
} }
} }
SchStudent stu = new SchStudent(); SchStudentDTO stu = new SchStudentDTO();
// 账号 // 账号
{ {
Optional<SchStudent> accountOpt = schStudentRepository.findByAccount(item.getAccount()); Optional<SchStudent> accountOpt = schStudentRepository.findByAccount(item.getAccount());
@@ -133,10 +142,12 @@ public class SchStudentImportService {
"禁止将" + comSchTreeService.getSchoolName(accountOpt.get().getSchId()) + "正常学生导入!")); "禁止将" + comSchTreeService.getSchoolName(accountOpt.get().getSchId()) + "正常学生导入!"));
continue; continue;
} else { } else {
BeanUtils.copyProperties(accountOpt.get(), stu); errorMsgArr.add(new ErrorExcelColumn(currentRow, item.getStuName(),"学生账号已存在!"));
stu.setUpdateDate(now); continue;
stu.setUpdateUserId(userId); // BeanUtils.copyProperties(accountOpt.get(), stu);
oldClassId = accountOpt.get().getClassId(); // stu.setUpdateDate(now);
// stu.setUpdateUserId(userId);
// oldClassId = accountOpt.get().getClassId();
} }
} else { } else {
stu.setId(0); stu.setId(0);
@@ -190,7 +201,7 @@ public class SchStudentImportService {
} }
// 密码(暂时忽略,或可设置默认密码,但原代码无处理) // 密码(暂时忽略,或可设置默认密码,但原代码无处理)
stu.setPwd(item.getPassword());
// 身份证 // 身份证
if (StringUtils.isNotBlank(item.getIdCard())) { if (StringUtils.isNotBlank(item.getIdCard())) {
if (!ValidateUtil.isValidIdCard(item.getIdCard())) { if (!ValidateUtil.isValidIdCard(item.getIdCard())) {
@@ -250,19 +261,36 @@ public class SchStudentImportService {
throw new DException(ErrorCode.CUSTOM,"导入失败,请检查错误列表",errorMsgArr); throw new DException(ErrorCode.CUSTOM,"导入失败,请检查错误列表",errorMsgArr);
} }
// 6. 批量保存 // 6. 先推送平台用户(返回平台用户ID),顺序与 saveStudentList 一致
schStudentRepository.saveAll(saveStudentList); syncStudentList = sendUserBaseService.batchAddStudent(saveStudentList);
// 按顺序回填平台用户ID(stuId),batchAddUsers 返回顺序与传入一致
for (int i = 0; i < saveStudentList.size(); i++) {
if (i < syncStudentList.size() && syncStudentList.get(i) != null) {
saveStudentList.get(i).setStuId(syncStudentList.get(i).getId());
}
}
// 7. 异步记录导入日志 // 7. DTO 不能直接传给 saveAll(泛型不变性:List<SchStudentDTO> 非 List<SchStudent>),转实体并保留已回填的 stuId
logService.importLogs(user, saveStudentList, gradeList, schClassList); List<SchStudent> studentEntities = saveStudentList.stream().map(dto -> {
SchStudent s = new SchStudent();
BeanUtils.copyProperties(dto, s);
return s;
}).collect(Collectors.toList());
List<SchStudent> savedStudentList = schStudentRepository.saveAll(studentEntities);
// 8. 清除受影响的班级缓存 // 8. 异步记录导入日志
logService.importLogs(user, savedStudentList, gradeList, schClassList);
// 9. 清除受影响的班级缓存
cacheService.clearClassStudentCaches(schId, classIds); cacheService.clearClassStudentCaches(schId, classIds);
} catch (DException e) { } catch (DException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
// 其他所有未预期的异常(包括数据库、IO等) // 导入失败:回滚已成功推送到平台的学生账号,避免孤儿账号
List<Integer> groupIds = saveStudentList.stream()
.map(SchStudentDTO::getGroupId).collect(Collectors.toList());
sendUserBaseService.rollbackSyncedStudents(syncStudentList, groupIds, schId);
log.error("导入班级学生时发生系统异常,操作人:" + user.getUserName(), e); log.error("导入班级学生时发生系统异常,操作人:" + user.getUserName(), e);
throw new DException(ErrorCode.INTERNAL_SERVER_ERROR,"导入失败,请联系管理员",null,e); throw new DException(ErrorCode.INTERNAL_SERVER_ERROR,"导入失败,请联系管理员",null,e);
} }
@@ -1,6 +1,7 @@
package com.yunhai.bschclassmanage.service.impl.student; package com.yunhai.bschclassmanage.service.impl.student;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.yunhai.bschclassmanage.Repository.SchClassRepository; import com.yunhai.bschclassmanage.Repository.SchClassRepository;
import com.yunhai.bschclassmanage.entity.OperationLogRecord; import com.yunhai.bschclassmanage.entity.OperationLogRecord;
import com.yunhai.bschclassmanage.entity.SchClass; import com.yunhai.bschclassmanage.entity.SchClass;
@@ -8,6 +9,7 @@ import com.yunhai.bschclassmanage.entity.SchStudent;
import com.yunhai.bschclassmanage.enums.StuAbnormalStatus; import com.yunhai.bschclassmanage.enums.StuAbnormalStatus;
import com.yunhai.bschclassmanage.enums.TransferStatus; import com.yunhai.bschclassmanage.enums.TransferStatus;
import com.yunhai.bschclassmanage.model.constant.LogModule; import com.yunhai.bschclassmanage.model.constant.LogModule;
import com.yunhai.bschclassmanage.model.dto.SchStudentDTO;
import com.yunhai.bschclassmanage.model.vo.BaseSchClassVO; import com.yunhai.bschclassmanage.model.vo.BaseSchClassVO;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO; import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService; import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
@@ -37,6 +39,13 @@ public class SchStudentLogService {
@Autowired @Autowired
private ILogService logService; private ILogService logService;
/** 学生对象序列化时排除敏感字段(密码),防止明文进入日志内容/调试日志 */
private static final SimplePropertyPreFilter NO_PWD_FILTER = new SimplePropertyPreFilter();
static {
NO_PWD_FILTER.getExcludes().add("pwd");
}
@Autowired @Autowired
private ISchClassService schClassService; private ISchClassService schClassService;
@@ -387,7 +396,7 @@ public class SchStudentLogService {
if (!logRecords.isEmpty()) { if (!logRecords.isEmpty()) {
logService.saveAllLogAsync(logRecords); logService.saveAllLogAsync(logRecords);
} }
log.debug("操作人:{},删除学生日志:{}", user.getUserName(), JSON.toJSONString(students)); log.debug("操作人:{},删除学生日志:{}", user.getUserName(), JSON.toJSONString(students, NO_PWD_FILTER));
} catch (Exception e) { } catch (Exception e) {
log.error("记录批量删除学生日志失败", e); log.error("记录批量删除学生日志失败", e);
} }
@@ -414,10 +423,10 @@ public class SchStudentLogService {
.map(SelectIntVO::getName) .map(SelectIntVO::getName)
.findFirst() .findFirst()
.orElse("未知年级"); .orElse("未知年级");
content = "导入学生[" + stu.getName() + "]成功,用户信息:" + JSON.toJSONString(stu) content = "导入学生[" + stu.getName() + "]成功,用户信息:" + JSON.toJSONString(stu, NO_PWD_FILTER)
+ ",所在年级班级:" + gradeName + schClassOpt.get().getName(); + ",所在年级班级:" + gradeName + schClassOpt.get().getName();
} else { } else {
content = "导入学生[" + stu.getName() + "]成功,用户信息:" + JSON.toJSONString(stu) + ",班级:未知"; content = "导入学生[" + stu.getName() + "]成功,用户信息:" + JSON.toJSONString(stu, NO_PWD_FILTER) + ",班级:未知";
} }
OperationLogRecord log = logService.buildLogRecord(user, MODULE_STUDENT, content, now); OperationLogRecord log = logService.buildLogRecord(user, MODULE_STUDENT, content, now);
logs.add(log); logs.add(log);
@@ -425,7 +434,7 @@ public class SchStudentLogService {
if (!logs.isEmpty()) { if (!logs.isEmpty()) {
logService.saveAllLogAsync(logs); logService.saveAllLogAsync(logs);
} }
log.debug("操作人:{},学生导入日志成功: {}", user.getUserName(), JSON.toJSONString(saveStudentList)); log.debug("操作人:{},学生导入日志成功: {}", user.getUserName(), JSON.toJSONString(saveStudentList, NO_PWD_FILTER));
} catch (Exception e) { } catch (Exception e) {
log.error("操作人:" + user.getUserName() + ",学生导入日志失败: {}", e.getMessage(), e); log.error("操作人:" + user.getUserName() + ",学生导入日志失败: {}", e.getMessage(), e);
} }
@@ -5,6 +5,7 @@ import com.yunhai.bschclassmanage.Repository.SchStudentRepository;
import com.yunhai.bschclassmanage.entity.SchStudent; import com.yunhai.bschclassmanage.entity.SchStudent;
import com.yunhai.bschclassmanage.enums.TransferStatus; import com.yunhai.bschclassmanage.enums.TransferStatus;
import com.yunhai.bschclassmanage.model.dto.StuTransferClassDTO; import com.yunhai.bschclassmanage.model.dto.StuTransferClassDTO;
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
import com.yunhai.bschclassmanage.utils.RedisConstants; import com.yunhai.bschclassmanage.utils.RedisConstants;
import com.yunhai.bschclassmanage.utils.RedisUtil; import com.yunhai.bschclassmanage.utils.RedisUtil;
import com.yunhai.bschclassmanage.utils.UserContext; import com.yunhai.bschclassmanage.utils.UserContext;
@@ -34,6 +35,9 @@ public class SchStudentTransferService {
@Autowired @Autowired
private SchStudentCacheService cacheService; private SchStudentCacheService cacheService;
@Autowired
private ISendUserBaseService sendUserBaseService;
@Autowired @Autowired
private SchStudentLogService logService; private SchStudentLogService logService;
@@ -93,6 +97,8 @@ public class SchStudentTransferService {
// 清除缓存(新旧班级都要清除) // 清除缓存(新旧班级都要清除)
cacheService.clearClassStudentCaches(schId, affectedClassIds); cacheService.clearClassStudentCaches(schId, affectedClassIds);
sendUserBaseService.moveStudentToGroup(model.getStuIds(),model.getTransferClassId(),schId);
// 异步记录操作日志 // 异步记录操作日志
logService.transferClassLogs(user, students, targetClassId, affectedClassIds); logService.transferClassLogs(user, students, targetClassId, affectedClassIds);
} }
@@ -9,15 +9,18 @@ import com.yunhai.bschclassmanage.entity.SchTeacherGroup;
import com.yunhai.bschclassmanage.enums.TransferStatus; import com.yunhai.bschclassmanage.enums.TransferStatus;
import com.yunhai.bschclassmanage.enums.UserGender; import com.yunhai.bschclassmanage.enums.UserGender;
import com.yunhai.bschclassmanage.enums.UserStatus; import com.yunhai.bschclassmanage.enums.UserStatus;
import com.yunhai.bschclassmanage.model.dto.SchTeacherDTO;
import com.yunhai.bschclassmanage.model.excel.ErrorExcelColumn; import com.yunhai.bschclassmanage.model.excel.ErrorExcelColumn;
import com.yunhai.bschclassmanage.model.excel.TeacherExcelColumn; import com.yunhai.bschclassmanage.model.excel.TeacherExcelColumn;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO; import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.service.IComSchTreeService; import com.yunhai.bschclassmanage.service.IComSchTreeService;
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
import com.yunhai.bschclassmanage.utils.UserContext; import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.bschclassmanage.utils.ValidateUtil; import com.yunhai.bschclassmanage.utils.ValidateUtil;
import com.yunhai.bschclassmanage.utils.ValidationUtil; import com.yunhai.bschclassmanage.utils.ValidationUtil;
import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaUser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@@ -44,6 +47,10 @@ public class SchTeacherImportService {
@Autowired @Autowired
private IComSchTreeService comSchTreeService; private IComSchTreeService comSchTreeService;
@Autowired
private ISendUserBaseService sendUserBaseService;
@Autowired @Autowired
private SchTeacherCacheService cacheService; private SchTeacherCacheService cacheService;
@@ -59,6 +66,9 @@ public class SchTeacherImportService {
Integer userId = user.getUserId(); Integer userId = user.getUserId();
Date now = new Date(); Date now = new Date();
// 提到 try 外,供 catch 回滚时访问
List<SchTeacherDTO> teachers = new ArrayList<>();
List<BoaUser> syncTeacherList = null;
try { try {
List<SelectIntVO> teaGroupNameList = comSchTreeService.getTeaGroupNameList(schId, null); List<SelectIntVO> teaGroupNameList = comSchTreeService.getTeaGroupNameList(schId, null);
List<TeacherExcelColumn> list = ExcelImportUtil.importExcel(file.getInputStream(), TeacherExcelColumn.class, params); List<TeacherExcelColumn> list = ExcelImportUtil.importExcel(file.getInputStream(), TeacherExcelColumn.class, params);
@@ -69,10 +79,9 @@ public class SchTeacherImportService {
List<SchTeacher> teachersByAccounts = getTeachersByAccounts(accountList); List<SchTeacher> teachersByAccounts = getTeachersByAccounts(accountList);
List<SchTeacher> schTeacherList = cacheService.getSchTeacherList(schId); List<SchTeacher> schTeacherList = cacheService.getSchTeacherList(schId);
List<SchTeacher> teachers = new ArrayList<>();
int rows = 0; int rows = 0;
for (TeacherExcelColumn item : list) { for (TeacherExcelColumn item : list) {
SchTeacher teacher = new SchTeacher(); SchTeacherDTO teacher = new SchTeacherDTO();
List<SchTeacherGroup> teacherGroupList = new ArrayList<>(); List<SchTeacherGroup> teacherGroupList = new ArrayList<>();
rows += 2; rows += 2;
@@ -85,14 +94,17 @@ public class SchTeacherImportService {
} }
Optional<SchTeacher> schTeacherOpt = teachersByAccounts.stream().filter(s -> s.getAccount().equals(item.getAccount())).findFirst(); Optional<SchTeacher> schTeacherOpt = teachersByAccounts.stream().filter(s -> s.getAccount().equals(item.getAccount())).findFirst();
if (schTeacherOpt.isPresent()) { if (schTeacherOpt.isPresent()) {
BeanUtils.copyProperties(schTeacherOpt.get(), teacher); // BeanUtils.copyProperties(schTeacherOpt.get(), teacher);
teacher.setUpdateDate(now); // teacher.setUpdateDate(now);
teacher.setUpdateUserId(userId); // teacher.setUpdateUserId(userId);
errorMsgArr.add(new ErrorExcelColumn(rows, item.getAccount(), TeacherExcelColumn.ACCOUNT + "教师已存在!"));
continue;
} else { } else {
teacher.setCreateDate(now); teacher.setCreateDate(now);
teacher.setCreateUserId(userId); teacher.setCreateUserId(userId);
teacher.setAccount(item.getAccount()); teacher.setAccount(item.getAccount());
teacher.setStatus(UserStatus.NORMAL.getId()); teacher.setStatus(UserStatus.NORMAL.getId());
teacher.setPwd(item.getPassword());
} }
} }
//姓名 //姓名
@@ -133,6 +145,10 @@ public class SchTeacherImportService {
teacherGroupList.add(teacherGroup); teacherGroupList.add(teacherGroup);
} }
teacher.setGroupList(teacherGroupList); teacher.setGroupList(teacherGroupList);
// 主组取第一个组(DTO.groupId 有 @NotNull/@Min 校验,平台推送 batchAddTeacher 也需要主组)
if (!teacherGroupList.isEmpty()) {
teacher.setGroupId(teacherGroupList.get(0).getGroupId());
}
} }
//身份证 //身份证
{ {
@@ -190,13 +206,30 @@ public class SchTeacherImportService {
throw new DException(ErrorCode.CUSTOM,"导入失败,请检查错误列表",errorMsgArr); throw new DException(ErrorCode.CUSTOM,"导入失败,请检查错误列表",errorMsgArr);
} }
List<SchTeacher> saveList = schTeacherRepository.saveAll(teachers); // 推送平台用户:必须用原 DTO 列表(含 pwd);saveAll 返回的实体不含密码
syncTeacherList = sendUserBaseService.batchAddTeacher(teachers);
// 按顺序回填平台用户ID(batchAddUsers 返回顺序与传入一致)
for (int i = 0; i < teachers.size(); i++) {
if (i < syncTeacherList.size() && syncTeacherList.get(i) != null) {
teachers.get(i).setTeaId(syncTeacherList.get(i).getId());
}
}
// DTO 不能直接传给 saveAll(泛型不变性:List<SchTeacherDTO> 非 List<SchTeacher>),需转实体并保留已回填的 teaId/groupList
List<SchTeacher> teacherEntities = teachers.stream().map(dto -> {
SchTeacher t = new SchTeacher();
BeanUtils.copyProperties(dto, t);
return t;
}).collect(Collectors.toList());
List<SchTeacher> saveList = schTeacherRepository.saveAll(teacherEntities);
List<SchTeacherGroup> schTeacherGroupList = new ArrayList<>(); List<SchTeacherGroup> schTeacherGroupList = new ArrayList<>();
for (SchTeacher teacher : saveList) { for (SchTeacher teacher : saveList) {
teacher.setTeaId(teacher.getId()); // teaId 已是平台推送返回的用户ID(batchAddTeacher 已回填),勿用本地 id 覆盖
for (SchTeacherGroup group : teacher.getGroupList()) { for (SchTeacherGroup group : teacher.getGroupList()) {
group.setTeaId(teacher.getId()); group.setTeaId(teacher.getTeaId());
} }
schTeacherGroupList.addAll(teacher.getGroupList()); schTeacherGroupList.addAll(teacher.getGroupList());
} }
@@ -213,9 +246,16 @@ public class SchTeacherImportService {
// 清除缓存 // 清除缓存
cacheService.clearTeacherCache(schId); cacheService.clearTeacherCache(schId);
} catch (DException e) { } catch (DException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
// 导入失败:回滚已成功推送到平台的教师账号,避免孤儿账号
List<Integer> groupIds = teachers.stream()
.map(SchTeacherDTO::getGroupId).collect(Collectors.toList());
sendUserBaseService.rollbackSyncedTeachers(syncTeacherList, groupIds, schId);
log.error("操作人:" + user.getUserName() + ",导入教师日志失败: {}", e.getMessage(), e); log.error("操作人:" + user.getUserName() + ",导入教师日志失败: {}", e.getMessage(), e);
throw new DException("导入失败,请联系管理员! </br> 错误详情: </br>" + e.getMessage()); throw new DException("导入失败,请联系管理员! </br> 错误详情: </br>" + e.getMessage());
} }
@@ -6,9 +6,11 @@ import com.yunhai.bschclassmanage.Repository.SchTeacherRepository;
import com.yunhai.bschclassmanage.entity.SchTeacher; import com.yunhai.bschclassmanage.entity.SchTeacher;
import com.yunhai.bschclassmanage.entity.SchTeacherGroup; import com.yunhai.bschclassmanage.entity.SchTeacherGroup;
import com.yunhai.bschclassmanage.enums.TransferStatus; import com.yunhai.bschclassmanage.enums.TransferStatus;
import com.yunhai.bschclassmanage.model.dto.SendMoveGroupUserDTO;
import com.yunhai.bschclassmanage.model.dto.TeaTransferGroupDTO; import com.yunhai.bschclassmanage.model.dto.TeaTransferGroupDTO;
import com.yunhai.bschclassmanage.model.dto.TeaTransferInGroupDTO; import com.yunhai.bschclassmanage.model.dto.TeaTransferInGroupDTO;
import com.yunhai.bschclassmanage.service.IComSchTreeService; import com.yunhai.bschclassmanage.service.IComSchTreeService;
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
import com.yunhai.bschclassmanage.utils.OtherUtil; import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.UserContext; import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.exception.DException;
@@ -42,6 +44,9 @@ public class SchTeacherRelationService {
@Autowired @Autowired
private SchTeacherCacheService cacheService; private SchTeacherCacheService cacheService;
@Autowired
private ISendUserBaseService sendUserBaseService;
@Autowired @Autowired
private SchTeacherLogService logService; // 异步日志 private SchTeacherLogService logService; // 异步日志
@@ -256,6 +261,17 @@ public class SchTeacherRelationService {
schTeacherGroupRepository.saveAll(newRelations); schTeacherGroupRepository.saveAll(newRelations);
} }
List<SendMoveGroupUserDTO> teacherList = newRelations.stream()
.map(rel -> {
SendMoveGroupUserDTO dto = new SendMoveGroupUserDTO();
dto.setUserId(rel.getTeaId());
dto.setGroupId(rel.getGroupId());
return dto;
})
.collect(Collectors.toList());
sendUserBaseService.moveTeacherToGroup(teacherList,schId);
// 清除缓存 // 清除缓存
cacheService.clearTeacherCache(schId); cacheService.clearTeacherCache(schId);
} }