From bd72c228385acd5548e7cc059c040905a96a5561 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Tue, 23 Jun 2026 09:31:09 +0800 Subject: [PATCH] feat: add school admin student import --- .../schooladmin/SchoolAdminController.java | 211 ++++++++++++++++++ .../SchoolAdminStudentImportDTO.java | 40 ++++ .../SchoolAdminControllerTest.java | 21 ++ 3 files changed, 272 insertions(+) create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/importDto/SchoolAdminStudentImportDTO.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminControllerTest.java diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminController.java b/src/main/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminController.java index bbf56df..456a5e0 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminController.java @@ -1,5 +1,6 @@ package com.sztzjy.linkCommerce.controller.schooladmin; +import com.alibaba.excel.EasyExcel; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.sztzjy.linkCommerce.config.security.JwtUser; @@ -8,6 +9,7 @@ import com.sztzjy.linkCommerce.entity.*; import com.sztzjy.linkCommerce.entity.dto.SchoolClassDto; import com.sztzjy.linkCommerce.entity.dto.SchoolFacultyDto; import com.sztzjy.linkCommerce.entity.dto.SchoolMajorDto; +import com.sztzjy.linkCommerce.entity.importDto.SchoolAdminStudentImportDTO; import com.sztzjy.linkCommerce.mapper.*; import com.sztzjy.linkCommerce.service.SchoolAdminService; import com.sztzjy.linkCommerce.service.SchoolService; @@ -18,10 +20,18 @@ import io.swagger.annotations.ApiOperation; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; +import java.io.InputStream; +import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.UUID; @Api(tags = "学校管理员管理") @@ -322,6 +332,45 @@ public class SchoolAdminController { return writeResult(count, "新增成功", "新增失败"); } + @PostMapping("/students/import") + @ApiOperation("学校管理员-批量导入学生") + @Transactional(rollbackFor = Exception.class) + public ResultEntity importStudents(@RequestParam("file") MultipartFile file, HttpServletRequest request) { + String schoolId = currentSchoolId(request); + ResultEntity fileError = validateImportFile(file); + if (fileError != null) { + return fileError; + } + + try (InputStream inputStream = file.getInputStream()) { + List rows = EasyExcel.read(inputStream) + .head(SchoolAdminStudentImportDTO.class) + .sheet() + .doReadSync(); + List errors = new ArrayList<>(); + List students = buildImportStudents(rows, schoolId, errors); + if (!errors.isEmpty()) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "数据校验失败", errors); + } + if (students.isEmpty()) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "未读取到可导入的学生数据"); + } + + for (Userinfo student : students) { + int count = userinfoMapper.insertSelective(student); + if (count <= 0) { + throw new IllegalStateException("导入失败"); + } + } + return new ResultEntity<>(HttpStatus.OK, "导入成功,共 " + students.size() + " 条"); + } catch (IllegalArgumentException e) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, e.getMessage()); + } catch (Exception e) { + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + return new ResultEntity<>(HttpStatus.ACCEPTED, "导入失败:" + e.getMessage()); + } + } + @PutMapping("/students/{id}") @ApiOperation("学校管理员-编辑学生") public ResultEntity updateStudent(@PathVariable String id, @@ -359,6 +408,168 @@ public class SchoolAdminController { return new ResultEntity<>(HttpStatus.ACCEPTED, fail); } + private ResultEntity validateImportFile(MultipartFile file) { + if (file == null || file.isEmpty()) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "请选择要导入的Excel文件"); + } + String filename = StringUtils.defaultString(file.getOriginalFilename()); + if (!StringUtils.endsWithIgnoreCase(filename, ".xls") && !StringUtils.endsWithIgnoreCase(filename, ".xlsx")) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "仅支持xls或xlsx格式文件"); + } + return null; + } + + private List buildImportStudents(List rows, String schoolId, List errors) { + List students = new ArrayList<>(); + Set usernames = new HashSet<>(); + if (rows == null) { + return students; + } + for (int i = 0; i < rows.size(); i++) { + SchoolAdminStudentImportDTO row = rows.get(i); + int rowNumber = i + 2; + if (isBlankImportRow(row)) { + continue; + } + + String name = StringUtils.trimToEmpty(row.getName()); + String username = StringUtils.trimToEmpty(row.getUsername()); + String facultyName = StringUtils.trimToEmpty(row.getSchoolFacultyName()); + String majorName = StringUtils.trimToEmpty(row.getSchoolMajorName()); + String className = StringUtils.trimToEmpty(row.getSchoolClassName()); + String sex = normalizeImportSex(row.getSex()); + + if (StringUtils.isBlank(name)) { + errors.add("第 " + rowNumber + " 行:姓名不能为空"); + } + if (StringUtils.isBlank(username)) { + errors.add("第 " + rowNumber + " 行:学号不能为空"); + } else if (!usernames.add(username)) { + errors.add("第 " + rowNumber + " 行:学号在导入文件中重复"); + } else if (userInfoService.existsByUserName(username)) { + errors.add("第 " + rowNumber + " 行:账号已存在"); + } + if (StringUtils.isBlank(row.getSex()) || sex != null) { + // 性别允许为空;填写时仅允许男/女及常见数字写法。 + } else { + errors.add("第 " + rowNumber + " 行:性别请填写男或女"); + } + if (StringUtils.isBlank(facultyName)) { + errors.add("第 " + rowNumber + " 行:所属院系不能为空"); + } + if (StringUtils.isBlank(majorName)) { + errors.add("第 " + rowNumber + " 行:所属专业不能为空"); + } + if (StringUtils.isBlank(className)) { + errors.add("第 " + rowNumber + " 行:所属班级不能为空"); + } + + SchoolFaculty faculty = StringUtils.isBlank(facultyName) ? null : findFacultyByName(schoolId, facultyName); + SchoolMajor major = null; + SchoolClass schoolClass = null; + if (StringUtils.isNotBlank(facultyName) && faculty == null) { + errors.add("第 " + rowNumber + " 行:未找到院系“" + facultyName + "”"); + } + if (faculty != null && StringUtils.isNotBlank(majorName)) { + major = findMajorByName(faculty.getSchoolFacultyId(), majorName); + if (major == null) { + errors.add("第 " + rowNumber + " 行:院系“" + facultyName + "”下未找到专业“" + majorName + "”"); + } + } + if (major != null && StringUtils.isNotBlank(className)) { + schoolClass = findClassByName(schoolId, major.getSchoolMajorId(), className); + if (schoolClass == null) { + errors.add("第 " + rowNumber + " 行:专业“" + majorName + "”下未找到班级“" + className + "”"); + } + } + + if (!hasRowError(errors, rowNumber)) { + Userinfo student = new Userinfo(); + student.setUserId(UUID.randomUUID().toString()); + student.setName(name); + student.setUsername(username); + student.setPassword("123qwe"); + student.setSex(sex); + student.setPhone(StringUtils.trimToEmpty(row.getPhone())); + student.setEmail(StringUtils.trimToEmpty(row.getEmail())); + student.setSchoolFacultyId(faculty.getSchoolFacultyId()); + student.setSchoolMajorId(major.getSchoolMajorId()); + student.setSchoolClassId(schoolClass.getSchoolClassId()); + student.setCreateTime(new Date()); + student.setCodeFrom("Excel导入"); + schoolAdminService.prepareStudentForSave(student, schoolId); + students.add(student); + } + } + return students; + } + + private boolean isBlankImportRow(SchoolAdminStudentImportDTO row) { + if (row == null) { + return true; + } + return StringUtils.isBlank(row.getName()) + && StringUtils.isBlank(row.getUsername()) + && StringUtils.isBlank(row.getSex()) + && StringUtils.isBlank(row.getSchoolFacultyName()) + && StringUtils.isBlank(row.getSchoolMajorName()) + && StringUtils.isBlank(row.getSchoolClassName()) + && StringUtils.isBlank(row.getPhone()) + && StringUtils.isBlank(row.getEmail()); + } + + private String normalizeImportSex(String sex) { + String value = StringUtils.trimToEmpty(sex); + if (StringUtils.isBlank(value)) { + return ""; + } + if ("1".equals(value) || "男".equals(value) || "男性".equals(value)) { + return "男"; + } + if ("2".equals(value) || "女".equals(value) || "女性".equals(value)) { + return "女"; + } + return null; + } + + private boolean hasRowError(List errors, int rowNumber) { + String prefix = "第 " + rowNumber + " 行:"; + for (String error : errors) { + if (error.startsWith(prefix)) { + return true; + } + } + return false; + } + + private SchoolFaculty findFacultyByName(String schoolId, String facultyName) { + SchoolFacultyExample example = new SchoolFacultyExample(); + example.createCriteria() + .andSchoolIdEqualTo(schoolId) + .andSchoolFacultyNameEqualTo(facultyName); + List faculties = schoolFacultyMapper.selectByExample(example); + return faculties.isEmpty() ? null : faculties.get(0); + } + + private SchoolMajor findMajorByName(String facultyId, String majorName) { + SchoolMajorExample example = new SchoolMajorExample(); + example.createCriteria() + .andSchoolFacultyIdEqualTo(facultyId) + .andSchoolMajorNameEqualTo(majorName); + List majors = schoolMajorMapper.selectByExample(example); + return majors.isEmpty() ? null : majors.get(0); + } + + private SchoolClass findClassByName(String schoolId, String majorId, String className) { + SchoolClassExample example = new SchoolClassExample(); + example.createCriteria() + .andSchoolIdEqualTo(schoolId) + .andSchoolMajorIdEqualTo(majorId) + .andClassNameEqualTo(className); + List classes = schoolClassMapper.selectByExample(example); + return classes.isEmpty() ? null : classes.get(0); + } + private SchoolFaculty requireFaculty(String facultyId, String schoolId) { if (StringUtils.isBlank(facultyId)) { throw new IllegalArgumentException("院系ID不能为空"); diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/importDto/SchoolAdminStudentImportDTO.java b/src/main/java/com/sztzjy/linkCommerce/entity/importDto/SchoolAdminStudentImportDTO.java new file mode 100644 index 0000000..1a34b33 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/importDto/SchoolAdminStudentImportDTO.java @@ -0,0 +1,40 @@ +package com.sztzjy.linkCommerce.entity.importDto; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class SchoolAdminStudentImportDTO { + @ExcelProperty("姓名") + @ApiModelProperty("姓名") + private String name; + + @ExcelProperty("学号") + @ApiModelProperty("学号") + private String username; + + @ExcelProperty("性别") + @ApiModelProperty("性别") + private String sex; + + @ExcelProperty("所属院系") + @ApiModelProperty("所属院系") + private String schoolFacultyName; + + @ExcelProperty("所属专业") + @ApiModelProperty("所属专业") + private String schoolMajorName; + + @ExcelProperty("所属班级") + @ApiModelProperty("所属班级") + private String schoolClassName; + + @ExcelProperty("手机") + @ApiModelProperty("手机") + private String phone; + + @ExcelProperty("邮箱") + @ApiModelProperty("邮箱") + private String email; +} diff --git a/src/test/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminControllerTest.java b/src/test/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminControllerTest.java new file mode 100644 index 0000000..d081a48 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/controller/schooladmin/SchoolAdminControllerTest.java @@ -0,0 +1,21 @@ +package com.sztzjy.linkCommerce.controller.schooladmin; + +import org.junit.jupiter.api.Test; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SchoolAdminControllerTest { + + @Test + void exposesDedicatedStudentImportEndpoint() { + boolean exists = Arrays.stream(SchoolAdminController.class.getDeclaredMethods()) + .anyMatch(method -> method.getName().equals("importStudents") + && method.isAnnotationPresent(PostMapping.class) + && Arrays.asList(method.getAnnotation(PostMapping.class).value()).contains("/students/import")); + + assertTrue(exists, "School admin student import must use /api/school-admin/students/import"); + } +}