From 5c632a724fea4b974dd81d1eb1cadb6e7735ac61 Mon Sep 17 00:00:00 2001 From: huertian Date: Sat, 4 Jan 2025 15:07:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 添加用户、课程任务和部门的批量导入功能 2. 优化错误处理逻辑,提供更详细的错误信息 3. 更新API文档,添加批量导入接口说明 --- API文档.md | 92 +++++++++++++ pom.xml | 13 ++ .../jinduguanli/common/ErrorCode.java | 56 +++++++- .../controller/ImportController.java | 130 ++++++++++++++++++ .../jinduguanli/dto/DepartmentImportDTO.java | 30 ++++ .../jinduguanli/dto/LessonTaskImportDTO.java | 39 ++++++ .../jinduguanli/dto/UserImportDTO.java | 61 ++++++++ .../jinduguanli/entity/Department.java | 22 +++ .../repository/DepartmentRepository.java | 8 ++ .../service/DepartmentService.java | 76 ++++++++++ .../service/LessonTaskService.java | 81 ++++++++++- .../jinduguanli/service/UserService.java | 100 +++++++++++--- 批量添加用户模版.xlsx | Bin 0 -> 10380 bytes 批量添加课程模版.xlsx | Bin 0 -> 10197 bytes 批量添加部门模版.xlsx | Bin 0 -> 9560 bytes 15 files changed, 688 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/huertian/jinduguanli/controller/ImportController.java create mode 100644 src/main/java/com/huertian/jinduguanli/dto/DepartmentImportDTO.java create mode 100644 src/main/java/com/huertian/jinduguanli/dto/LessonTaskImportDTO.java create mode 100644 src/main/java/com/huertian/jinduguanli/dto/UserImportDTO.java create mode 100644 src/main/java/com/huertian/jinduguanli/entity/Department.java create mode 100644 src/main/java/com/huertian/jinduguanli/repository/DepartmentRepository.java create mode 100644 src/main/java/com/huertian/jinduguanli/service/DepartmentService.java create mode 100644 批量添加用户模版.xlsx create mode 100644 批量添加课程模版.xlsx create mode 100644 批量添加部门模版.xlsx diff --git a/API文档.md b/API文档.md index 475207d..78005d9 100644 --- a/API文档.md +++ b/API文档.md @@ -545,9 +545,101 @@ } ``` +## 批量导入接口 + +### 1. 批量导入用户 + +- **接口**:`POST /api/import/users` +- **描述**:通过 Excel 文件批量导入用户 +- **认证**:需要 +- **请求体**: + - `Content-Type`: `multipart/form-data` + - `file`: Excel 文件(.xlsx) +- **Excel 文件格式**: + | 用户名 | 邮箱 | 密码 | 部门名称 | + |--------|------|------|----------| + | 张三 | zhangsan@example.com | 123456 | 技术部 | +- **成功响应**: + ```json + { + "code": 10000, + "message": "成功", + "data": "成功导入2条数据" + } + ``` +- **错误响应**: + ```json + { + "code": 10012, + "message": "成功导入0条数据。错误信息:第2行邮箱已存在;第3行部门不存在;", + "data": null + } + ``` + +### 2. 批量导入课程任务 + +- **接口**:`POST /api/import/lesson-tasks` +- **描述**:通过 Excel 文件批量导入课程任务 +- **认证**:需要 +- **请求体**: + - `Content-Type`: `multipart/form-data` + - `file`: Excel 文件(.xlsx) +- **Excel 文件格式**: + | 课程名称 | 微课名称 | 教师邮箱 | + |----------|----------|-----------| + | 数学课程 | 第一章 | teacher@example.com | +- **成功响应**: + ```json + { + "code": 10000, + "message": "成功", + "data": "成功导入3条数据" + } + ``` +- **错误响应**: + ```json + { + "code": 10012, + "message": "成功导入0条数据。错误信息:第2行未找到教师用户(teacher@example.com);", + "data": null + } + ``` + +### 3. 批量导入部门 + +- **接口**:`POST /api/import/departments` +- **描述**:通过 Excel 文件批量导入部门 +- **认证**:需要 +- **请求体**: + - `Content-Type`: `multipart/form-data` + - `file`: Excel 文件(.xlsx) +- **Excel 文件格式**: + | 部门名称 | 部门描述 | + |----------|----------| + | 技术部 | 负责技术研发 | +- **成功响应**: + ```json + { + "code": 10000, + "message": "成功", + "data": "成功导入2条数据" + } + ``` +- **错误响应**: + ```json + { + "code": 10012, + "message": "成功导入0条数据。错误信息:第2行部门名称已存在;", + "data": null + } + ``` + ## 注意事项 1. 所有时间戳字段均使用秒级时间戳(10 位) 2. 课程任务状态变更时会自动记录相应的时间戳 3. 部门课程任务列表会额外返回用户名信息 4. 分页参数中的页码从 1 开始 +5. 批量导入时,Excel 文件必须严格按照模板格式填写 +6. 批量导入时,如果某行数据导入失败,会在返回信息中说明具体原因 +7. 批量导入时,如果全部数据导入失败,会返回错误状态码(10012) diff --git a/pom.xml b/pom.xml index 3360fa9..fad92c4 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,19 @@ org.apache.commons commons-pool2 + + + com.alibaba + easyexcel + 3.3.2 + + + + + org.apache.commons + commons-lang3 + 3.12.0 + diff --git a/src/main/java/com/huertian/jinduguanli/common/ErrorCode.java b/src/main/java/com/huertian/jinduguanli/common/ErrorCode.java index e0e1486..3894d0c 100644 --- a/src/main/java/com/huertian/jinduguanli/common/ErrorCode.java +++ b/src/main/java/com/huertian/jinduguanli/common/ErrorCode.java @@ -1,14 +1,68 @@ package com.huertian.jinduguanli.common; public class ErrorCode { + /** + * 操作成功 + */ public static final int SUCCESS = 10000; + + /** + * 系统内部错误 + */ public static final int SYSTEM_ERROR = 10001; + + /** + * 参数校验错误 + */ public static final int INVALID_PARAM = 10002; + + /** + * 用户不存在 + */ public static final int USER_NOT_FOUND = 10003; + + /** + * 用户不存在或已被禁用 + */ public static final int USER_NOT_FOUND_OR_DISABLED = 10004; + + /** + * 邮箱已存在 + */ public static final int EMAIL_EXISTS = 10005; - public static final int PASSWORD_INCORRECT = 10006; + + /** + * 业务逻辑错误 + */ + public static final int BUSINESS_ERROR = 10006; + + /** + * 无效的令牌 + */ public static final int INVALID_TOKEN = 10007; + + /** + * 未授权访问 + */ public static final int UNAUTHORIZED = 10008; + + /** + * 用户名已存在 + */ public static final int USERNAME_EXISTS = 10009; + + /** + * 密码错误 + */ + public static final int PASSWORD_INCORRECT = 10010; + + /** + * 参数为空 + */ + public static final int PARAM_IS_BLANK = 10011; + + /** + * 参数错误 + */ + public static final int PARAM_ERROR = 10012; } diff --git a/src/main/java/com/huertian/jinduguanli/controller/ImportController.java b/src/main/java/com/huertian/jinduguanli/controller/ImportController.java new file mode 100644 index 0000000..85412ef --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/controller/ImportController.java @@ -0,0 +1,130 @@ +package com.huertian.jinduguanli.controller; + +import com.alibaba.excel.EasyExcel; +import com.huertian.jinduguanli.common.ApiResponse; +import com.huertian.jinduguanli.common.ErrorCode; +import com.huertian.jinduguanli.dto.DepartmentImportDTO; +import com.huertian.jinduguanli.dto.LessonTaskImportDTO; +import com.huertian.jinduguanli.dto.UserImportDTO; +import com.huertian.jinduguanli.service.DepartmentService; +import com.huertian.jinduguanli.service.LessonTaskService; +import com.huertian.jinduguanli.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/api/import") +public class ImportController { + + @Autowired + private UserService userService; + + @Autowired + private LessonTaskService lessonTaskService; + + @Autowired + private DepartmentService departmentService; + + @PostMapping("/users") + public ApiResponse importUsers(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return new ApiResponse<>(ErrorCode.INVALID_PARAM, "文件不能为空", null); + } + + String originalFilename = file.getOriginalFilename(); + if (!isValidExcelFile(originalFilename)) { + return new ApiResponse<>(ErrorCode.INVALID_PARAM, "只支持.xlsx格式的文件", null); + } + + try { + log.info("开始导入用户数据,文件名:{}", originalFilename); + List userList = EasyExcel.read(file.getInputStream()) + .head(UserImportDTO.class) + .sheet() + .doReadSync(); + log.info("读取到{}条用户数据", userList.size()); + + for (int i = 0; i < userList.size(); i++) { + log.info("第{}行数据:{}", i + 1, userList.get(i)); + } + + return userService.batchImportUsers(userList); + } catch (Exception e) { + log.error("导入用户数据失败", e); + return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "导入用户数据失败:" + e.getMessage(), null); + } + } + + @PostMapping("/lesson-tasks") + public ApiResponse importLessonTasks(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return new ApiResponse<>(ErrorCode.INVALID_PARAM, "文件不能为空", null); + } + + String originalFilename = file.getOriginalFilename(); + if (!isValidExcelFile(originalFilename)) { + return new ApiResponse<>(ErrorCode.INVALID_PARAM, "只支持.xlsx格式的文件", null); + } + + try { + log.info("开始导入课程任务数据,文件名:{}", originalFilename); + List taskList = EasyExcel.read(file.getInputStream()) + .head(LessonTaskImportDTO.class) + .sheet() + .doReadSync(); + log.info("读取到{}条课程任务数据", taskList.size()); + + for (int i = 0; i < taskList.size(); i++) { + log.info("第{}行数据:{}", i + 1, taskList.get(i)); + } + + return lessonTaskService.batchImportLessonTasks(taskList); + } catch (Exception e) { + log.error("导入课程任务数据失败", e); + return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "导入课程任务数据失败:" + e.getMessage(), null); + } + } + + @PostMapping("/departments") + public ApiResponse importDepartments(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return new ApiResponse<>(ErrorCode.INVALID_PARAM, "文件不能为空", null); + } + + String originalFilename = file.getOriginalFilename(); + if (!isValidExcelFile(originalFilename)) { + return new ApiResponse<>(ErrorCode.INVALID_PARAM, "只支持.xlsx格式的文件", null); + } + + try { + log.info("开始导入部门数据,文件名:{}", originalFilename); + List departmentList = EasyExcel.read(file.getInputStream()) + .head(DepartmentImportDTO.class) + .sheet() + .doReadSync(); + log.info("读取到{}条部门数据", departmentList.size()); + + for (int i = 0; i < departmentList.size(); i++) { + log.info("第{}行数据:{}", i + 1, departmentList.get(i)); + } + + return departmentService.batchImportDepartments(departmentList); + } catch (Exception e) { + log.error("导入部门数据失败", e); + return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "导入部门数据失败:" + e.getMessage(), null); + } + } + + private boolean isValidExcelFile(String filename) { + return StringUtils.isNotBlank(filename) && filename.toLowerCase().endsWith(".xlsx"); + } +} diff --git a/src/main/java/com/huertian/jinduguanli/dto/DepartmentImportDTO.java b/src/main/java/com/huertian/jinduguanli/dto/DepartmentImportDTO.java new file mode 100644 index 0000000..fa0be18 --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/dto/DepartmentImportDTO.java @@ -0,0 +1,30 @@ +package com.huertian.jinduguanli.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + + +public class DepartmentImportDTO { + @ExcelProperty("部门名称") + private String departmentName; + + @ExcelProperty("部门描述") + private String description; + + public String getDepartmentName() { + return departmentName; + } + + public void setDepartmentName(String departmentName) { + this.departmentName = departmentName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/src/main/java/com/huertian/jinduguanli/dto/LessonTaskImportDTO.java b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskImportDTO.java new file mode 100644 index 0000000..0e69dfe --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskImportDTO.java @@ -0,0 +1,39 @@ +package com.huertian.jinduguanli.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +public class LessonTaskImportDTO { + @ExcelProperty("课程名称") + private String lessonName; + + @ExcelProperty("微课名称") + private String microLessonName; + + @ExcelProperty("教师账号") + private String teacherEmail; + + public String getLessonName() { + return lessonName; + } + + public void setLessonName(String lessonName) { + this.lessonName = lessonName; + } + + public String getMicroLessonName() { + return microLessonName; + } + + public void setMicroLessonName(String microLessonName) { + this.microLessonName = microLessonName; + } + + public String getTeacherEmail() { + return teacherEmail; + } + + public void setTeacherEmail(String teacherEmail) { + this.teacherEmail = teacherEmail; + } +} diff --git a/src/main/java/com/huertian/jinduguanli/dto/UserImportDTO.java b/src/main/java/com/huertian/jinduguanli/dto/UserImportDTO.java new file mode 100644 index 0000000..3bc17f9 --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/dto/UserImportDTO.java @@ -0,0 +1,61 @@ +package com.huertian.jinduguanli.dto; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +public class UserImportDTO { + @ExcelProperty(index = 0) + private String username; + + @ExcelProperty(index = 1) + private String email; + + @ExcelProperty(index = 2) + private String password; + + @ExcelProperty(index = 3) + private String departmentName; + + @ExcelProperty(index = 4) + private String roleAndJob; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDepartmentName() { + return departmentName; + } + + public void setDepartmentName(String departmentName) { + this.departmentName = departmentName; + } + + public String getRoleAndJob() { + return roleAndJob; + } + + public void setRoleAndJob(String roleAndJob) { + this.roleAndJob = roleAndJob; + } +} diff --git a/src/main/java/com/huertian/jinduguanli/entity/Department.java b/src/main/java/com/huertian/jinduguanli/entity/Department.java new file mode 100644 index 0000000..7e68baf --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/entity/Department.java @@ -0,0 +1,22 @@ +package com.huertian.jinduguanli.entity; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "departments") +public class Department { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String name; + + private String description; + + private Long createdAt; + + private Long updatedAt; +} diff --git a/src/main/java/com/huertian/jinduguanli/repository/DepartmentRepository.java b/src/main/java/com/huertian/jinduguanli/repository/DepartmentRepository.java new file mode 100644 index 0000000..c4fb4d7 --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/repository/DepartmentRepository.java @@ -0,0 +1,8 @@ +package com.huertian.jinduguanli.repository; + +import com.huertian.jinduguanli.entity.Department; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DepartmentRepository extends JpaRepository { + Department findByName(String name); +} diff --git a/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java b/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java new file mode 100644 index 0000000..c2611d4 --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java @@ -0,0 +1,76 @@ +package com.huertian.jinduguanli.service; + +import com.huertian.jinduguanli.common.ApiResponse; +import com.huertian.jinduguanli.common.ErrorCode; +import com.huertian.jinduguanli.dto.DepartmentImportDTO; +import com.huertian.jinduguanli.entity.Department; +import com.huertian.jinduguanli.repository.DepartmentRepository; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class DepartmentService { + private static final Logger logger = LoggerFactory.getLogger(DepartmentService.class); + private final DepartmentRepository departmentRepository; + + @Autowired + public DepartmentService(DepartmentRepository departmentRepository) { + this.departmentRepository = departmentRepository; + } + + public ApiResponse batchImportDepartments(List departmentList) { + if (departmentList == null || departmentList.isEmpty()) { + return new ApiResponse<>(ErrorCode.PARAM_ERROR, "导入数据不能为空", null); + } + + StringBuilder errorMsg = new StringBuilder(); + int successCount = 0; + + for (int i = 0; i < departmentList.size(); i++) { + DepartmentImportDTO dto = departmentList.get(i); + try { + // 基本数据验证 + if (StringUtils.isBlank(dto.getDepartmentName())) { + errorMsg.append(String.format("第%d行部门名称不能为空;", i + 2)); + continue; + } + + // 检查部门名称是否已存在 + if (departmentRepository.findByName(dto.getDepartmentName()) != null) { + errorMsg.append(String.format("第%d行部门名称已存在;", i + 2)); + continue; + } + + // 创建部门 + Department department = new Department(); + department.setName(dto.getDepartmentName()); + department.setDescription(dto.getDescription()); + department.setCreatedAt(System.currentTimeMillis() / 1000); + department.setUpdatedAt(System.currentTimeMillis() / 1000); + + departmentRepository.save(department); + successCount++; + } catch (Exception e) { + logger.error("导入第{}行数据失败", i + 2, e); + errorMsg.append(String.format("第%d行导入失败;", i + 2)); + } + } + + String resultMsg = String.format("成功导入%d条数据", successCount); + if (errorMsg.length() > 0) { + resultMsg += "。错误信息:" + errorMsg; + } + + // 如果没有成功导入任何数据,返回错误状态 + if (successCount == 0) { + return new ApiResponse<>(ErrorCode.PARAM_ERROR, resultMsg, null); + } + + return ApiResponse.success(resultMsg); + } +} diff --git a/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java b/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java index 368152d..25af6a6 100644 --- a/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java +++ b/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java @@ -1,8 +1,12 @@ package com.huertian.jinduguanli.service; +import com.huertian.jinduguanli.common.ApiResponse; +import com.huertian.jinduguanli.common.ErrorCode; import com.huertian.jinduguanli.dto.LessonTaskDTO; +import com.huertian.jinduguanli.dto.LessonTaskImportDTO; import com.huertian.jinduguanli.dto.LessonTaskRequest; import com.huertian.jinduguanli.entity.LessonTask; +import com.huertian.jinduguanli.entity.User; import com.huertian.jinduguanli.repository.LessonTaskRepository; import jakarta.persistence.EntityNotFoundException; import org.slf4j.Logger; @@ -15,13 +19,20 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.beans.BeanUtils; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + @Service public class LessonTaskService { private static final Logger logger = LoggerFactory.getLogger(LessonTaskService.class); private final LessonTaskRepository lessonTaskRepository; + private final UserService userService; // 添加 UserService 的依赖 - public LessonTaskService(LessonTaskRepository lessonTaskRepository) { + public LessonTaskService(LessonTaskRepository lessonTaskRepository, UserService userService) { this.lessonTaskRepository = lessonTaskRepository; + this.userService = userService; } @Cacheable(value = "lessonTasks", key = "#userId != null ? 'user:' + #userId + ':page:' + #pageable.pageNumber : 'all:page:' + #pageable.pageNumber") @@ -192,6 +203,74 @@ public class LessonTaskService { return lessonTaskRepository.findByDepartmentIdAndUserStatus(departmentId, userStatus, pageable); } + @Transactional + public ApiResponse batchImportLessonTasks(List taskList) { + if (taskList == null || taskList.isEmpty()) { + logger.error("导入任务列表为空"); + return new ApiResponse<>(ErrorCode.PARAM_IS_BLANK, "导入任务列表为空", null); + } + + StringBuilder errorMsg = new StringBuilder(); + int successCount = 0; + + for (int i = 0; i < taskList.size(); i++) { + LessonTaskImportDTO dto = taskList.get(i); + try { + // 基本数据验证 + if (StringUtils.isBlank(dto.getLessonName())) { + errorMsg.append(String.format("第%d行课程名称不能为空;", i + 2)); + continue; + } + if (StringUtils.isBlank(dto.getMicroLessonName())) { + errorMsg.append(String.format("第%d行微课名称不能为空;", i + 2)); + continue; + } + if (StringUtils.isBlank(dto.getTeacherEmail())) { + errorMsg.append(String.format("第%d行教师账号不能为空;", i + 2)); + continue; + } + + // 查找教师用户ID + ApiResponse teacherResponse = userService.findTeacherByEmail(dto.getTeacherEmail()); + if (teacherResponse.getCode() != ErrorCode.SUCCESS) { + errorMsg.append(String.format("第%d行未找到教师用户(%s);", i + 2, dto.getTeacherEmail())); + continue; + } + + LessonTask task = new LessonTask(); + task.setCourseName(dto.getLessonName()); + task.setMicroLessonName(dto.getMicroLessonName()); + task.setUserId(teacherResponse.getData().getId()); + task.setProgressStatus(0); // 初始状态:未开始 + task.setCreatedAt(System.currentTimeMillis() / 1000); + task.setUpdatedAt(System.currentTimeMillis() / 1000); + + try { + lessonTaskRepository.save(task); + successCount++; + } catch (Exception e) { + logger.error("保存课程任务失败", e); + errorMsg.append(String.format("第%d行保存失败: %s;", i + 2, e.getMessage())); + } + } catch (Exception e) { + logger.error("导入第{}行数据失败", i + 2, e); + errorMsg.append(String.format("第%d行导入失败: %s;", i + 2, e.getMessage())); + } + } + + String resultMsg = String.format("成功导入%d条数据", successCount); + if (errorMsg.length() > 0) { + resultMsg += "。错误信息:" + errorMsg; + } + + // 如果没有成功导入任何数据,返回错误状态 + if (successCount == 0) { + return new ApiResponse<>(ErrorCode.PARAM_ERROR, resultMsg, null); + } + + return ApiResponse.success(resultMsg); + } + private void validateRequest(LessonTaskRequest request) { if (request.getCourseName() == null || request.getCourseName().trim().isEmpty()) { throw new IllegalArgumentException("课程名称不能为空"); diff --git a/src/main/java/com/huertian/jinduguanli/service/UserService.java b/src/main/java/com/huertian/jinduguanli/service/UserService.java index faa8d3c..13a2011 100644 --- a/src/main/java/com/huertian/jinduguanli/service/UserService.java +++ b/src/main/java/com/huertian/jinduguanli/service/UserService.java @@ -2,13 +2,13 @@ package com.huertian.jinduguanli.service; import com.huertian.jinduguanli.common.ApiResponse; import com.huertian.jinduguanli.common.ErrorCode; -import com.huertian.jinduguanli.dto.CreateUserRequest; -import com.huertian.jinduguanli.dto.LoginResponse; -import com.huertian.jinduguanli.dto.UserLoginRequest; -import com.huertian.jinduguanli.dto.UserPageResponse; +import com.huertian.jinduguanli.dto.*; +import com.huertian.jinduguanli.entity.Department; import com.huertian.jinduguanli.entity.User; +import com.huertian.jinduguanli.repository.DepartmentRepository; import com.huertian.jinduguanli.repository.UserRepository; import com.huertian.jinduguanli.security.service.JwtService; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -28,13 +28,16 @@ public class UserService implements UserDetailsService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtService jwtService; + private final DepartmentRepository departmentRepository; private static final Logger logger = LoggerFactory.getLogger(UserService.class); @Autowired - public UserService(JwtService jwtService, PasswordEncoder passwordEncoder, UserRepository userRepository) { + public UserService(JwtService jwtService, PasswordEncoder passwordEncoder, UserRepository userRepository, + DepartmentRepository departmentRepository) { this.jwtService = jwtService; this.passwordEncoder = passwordEncoder; this.userRepository = userRepository; + this.departmentRepository = departmentRepository; } @Override @@ -77,7 +80,7 @@ public class UserService implements UserDetailsService { user.setCreatorId(request.getCreatorId()); user.setStatus(1); // 时间戳由 @PrePersist 处理,这里不需要手动设置 - + userRepository.save(user); return ApiResponse.success(); @@ -183,22 +186,83 @@ public class UserService implements UserDetailsService { return new ApiResponse<>(ErrorCode.USER_NOT_FOUND, "用户不存在", null); } - // 验证用户是否为教师角色且为课程制作教师岗位 - if (user.getRoles() != 1) { - return new ApiResponse<>(ErrorCode.INVALID_PARAM, "非教师用户", null); - } - - if (user.getJobs() != 1) { - return new ApiResponse<>(ErrorCode.INVALID_PARAM, "非课程制作教师", null); - } - if (user.getStatus() != 1) { return new ApiResponse<>(ErrorCode.INVALID_PARAM, "用户状态异常", null); } - // 清除敏感信息 - user.setPassword(null); - return ApiResponse.success(user); } + + public ApiResponse batchImportUsers(List userList) { + if (userList == null || userList.isEmpty()) { + return new ApiResponse<>(ErrorCode.PARAM_ERROR, "导入数据不能为空", null); + } + + StringBuilder errorMsg = new StringBuilder(); + int successCount = 0; + + for (int i = 0; i < userList.size(); i++) { + UserImportDTO dto = userList.get(i); + try { + // 基本数据验证 + if (StringUtils.isBlank(dto.getUsername()) || + StringUtils.isBlank(dto.getEmail()) || + StringUtils.isBlank(dto.getPassword()) || + StringUtils.isBlank(dto.getDepartmentName()) || + StringUtils.isBlank(dto.getRoleAndJob())) { + errorMsg.append(String.format("第%d行数据不完整;", i + 2)); + continue; + } + + // 检查邮箱是否已存在 + if (userRepository.findByEmail(dto.getEmail()).isPresent()) { + errorMsg.append(String.format("第%d行邮箱已存在;", i + 2)); + continue; + } + + // 查找部门ID + Department department = departmentRepository.findByName(dto.getDepartmentName()); + logger.info("查找部门:{},结果:{}", dto.getDepartmentName(), department); + if (department == null) { + errorMsg.append(String.format("第%d行部门不存在;", i + 2)); + continue; + } + + // 创建新用户 + User user = new User(); + user.setUsername(dto.getUsername()); + user.setEmail(dto.getEmail()); + user.setPassword(passwordEncoder.encode(dto.getPassword())); + user.setDepartmentId(department.getId()); + user.setRoles(0); // 默认角色 + user.setJobs(0); // 默认工作 + user.setStatus(1); // 设置状态为正常 + user.setCreatedAt(System.currentTimeMillis() / 1000); + user.setUpdatedAt(System.currentTimeMillis() / 1000); + + try { + userRepository.save(user); + successCount++; + } catch (Exception e) { + logger.error("保存用户失败", e); + errorMsg.append(String.format("第%d行保存失败: %s;", i + 2, e.getMessage())); + } + } catch (Exception e) { + logger.error("导入第{}行数据失败", i + 2, e); + errorMsg.append(String.format("第%d行导入失败;", i + 2)); + } + } + + String resultMsg = String.format("成功导入%d条数据", successCount); + if (errorMsg.length() > 0) { + resultMsg += "。错误信息:" + errorMsg; + } + + // 如果没有成功导入任何数据,返回错误状态 + if (successCount == 0) { + return new ApiResponse<>(ErrorCode.PARAM_ERROR, resultMsg, null); + } + + return ApiResponse.success(resultMsg); + } } diff --git a/批量添加用户模版.xlsx b/批量添加用户模版.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f3e5070cf1ee22f797e5fa8db053762835c2a0d9 GIT binary patch literal 10380 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 E0A>2)*Z=?k literal 0 HcmV?d00001 diff --git a/批量添加课程模版.xlsx b/批量添加课程模版.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..cd30c307e77b93e8e294bd607c8c58164b93835a GIT binary patch literal 10197 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 literal 0 HcmV?d00001 diff --git a/批量添加部门模版.xlsx b/批量添加部门模版.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..438ca251714ffcabf22fae5a16149c71ee770982 GIT binary patch literal 9560 zcma)ibyyus@;1TUEd+OWcL@Y{JGeU>oC6`a2lwE?9TME#-2#C?a0%`XA0)f?-emW; z&)0vFK_lWu#$i_gb%u3@I!Uv<}Lcj)` zZ2mJG$u~Y_y)jW&%@Es+aw1DGTBpB;asOWo*@X zS~N2vBdOu2-Jk;6h z{q^zwIV0l={mo8UQrEWPE(FUCgwh^#lV3Mlgl=bEFJ7m+nq-tWm_ksyfLNy-_j?Yo z{B*I_ht>KW4X)d3yJM)|*;OeEP*;Cq*Z7HDw12W|40JSorZ*y1M7o<9H}KNWjrwby z6knvY1z#2`9Sr0fOfK|T@BAK02B|b&>pDGA`!y{7D8D{tTXQQI-TL82z1HT-IM4|- z0uX9`Jx^K@7j@ltHLrh$RYc>o$UB_h_yc+=H5R!A613dpeM_=Nd=eg`Gr+f&2X`zc z{$sPWqM5BY3nxFsfb25P#Z(#SuRM|+%M}z=@ z?QAI6$d6!fgLwGRBoD`0=Q?NtqjnKJW-VsqV0Z0Q$CE1jw+NQ%1L8k?=d zeZ(gC#4v6s0%Anphqp=Co!I&688#X6+Bh>nkS7^Kv@ZbNaiO`zsV#I4X`QmeJ0!;) z^g1U*V(oe#;_2x2`Kkj_h!k?BDyd^-T)iSum7dGLn z)#G!83lg1a+?f`HR#-p}YGjZ0RWMAp3Now-L zKRV%fQL90&=^PRaE1YiRSg48v;-lkF0M2?N0Ic4Zc3! zc~hZgTs}%m31$8vvChtT`_cF8=`bCn#A(N@V?$d!q2q4+q8bt2@FV4-mr*F?=4>;Z zsg12)Lyv=rdcr#c6~3w5Gb4TlvTl2p4$;M-JX8DMoeabiwIG|H?&ZG&kbeQ(fR5Hq z7N(}of4ZV{$az_#CmZoWfq~)v2ll53%g;3vKVY}OjQV7(8)V4C2_v2*13pQ7NQQB2 zk*N(?6|(oRG{xk$6z(_3QZg=hM2<22FGu^lu`S)tbIEypK8bSCrBJ=37`N9AGpoxM z^8ThfO#La6gCefc7wa9gOvM)>A$mSrLR+L|SaiIm>>8(iQaOeS5th_#Y`inodFoAj zYu6%Mq)Jvunh%kmZDMID@$ldfP6>m`X**WbPh0$Y#Y0{U;Qy$rv9MKmOZ&Zt4r!Np zR&Ok{dtv(}==v%`kfMGDfqrY}PAwy>?L|6GqS2gt`6Eb0)jYXoWpeoZ;v%hVhVI;w znItp~4-pan$Wv3l3Z0ICfHhoWoeWWc6oI3No^ll*0)jq55DGKx76BZ&-9bb9$&J z;EmVp@vwfkQgs?Elp*Bd{BQ)5Gla(fdZ!!P^LnJf+w)xM=ys|?U-02u6b|P@(xK49 zb-~!3Fm-mGI8!U=WVecnsr7EX4oLKN83rH829!yz3CVQ?uzydnoQfQ7%|m!J8@JsT z!i`Q1y+}Ai@RG0nmDB5Y^^~suZoy)YM=vTy4)|&>j0EGl*-p>79en!2Cn(pJz;ra6 z0B8qelcC;@f>jQ6sJ!PRi=stw^P}|s-166m*GEL=Eu>KQ#Uk zaEzBC6@R6Ap(MKRLqziOHhgd7dc4;qAV5}u*;K?lAn<75+xl`v!rAV);GLyV{%NRwgEG7H7+vm+T9+E!XmQZx2~MR#RFnoQYuIz06)I?IkG(hG@?(iKrW4Z|yA7umN70u*cH zSg`mC#&!mHG=>TmF{&mGhdSlx8E417kU{V_AVY1x_=uLcD?um^6S5k^%92!hUzw>t zRR9V?$YhK!vB!Ljs?W3d6ludhqy{UZ3Z;~r(=vr z!R7uAqt!&GpA-GlKx2L^fB>D81%+A)Z>4-7Mha1mLv4w6?LmHm8?wa0i-B8bdaq59 zi$YjcZ0PWlhB+0U(p`F1De)GEhxDD?` z`ZR)$h8mswIa4UUJ>?<#%S)s9j)bnn$UqBhGBMp`BQaAP${lviFMcI@8r2on84mpU zsIc`s0d$=Z8zCvE2Pkij9BI7ym2B1D)*RK9rsCx|m511LH0bH8ltKQ;PTT-R;NOv>EbGO-j@B57mOV#pB)8 zQ?d?1dNtd(FFN?D{xVi#!Zg8b3Jof@&&DK3#$$2Nx;$B%Z*#bEo1U(SVJ8`DXvK(S zvD0;^SUkk=ysnHj4RXF66fVJiDZ*jD20?!;Lqs}L)+|_W-8@m8z~~C@8m|Ffn>NfM z2WW=PHJx{y=CEBMw(mfyu%55o?~^9CLa3p0xZSmVF{hYYJlBBPVtqOawXP085L@fH zP^c1rEHik7YKOl3I8?nd=pvEFOd#xk@d)|*dDQUHlJV+kgJ6F;lRkflJ#7?D7KVLW~BQm231H&?PYVb%7Q#Fdp)QnQY zGE&0>V=^^r#j*_QpX=5(KGQHVDuMkNp{rFJq^6;%oSbEnAC(-DqyI9*Bsl^xn7e_q zL752FAii_c10`M*O4IYQsEDG0=SBh=+U%ww^n<%J{J?ddJ4LOunKXQ!`-i~1qEXb= z(Aac2hx~N8(8ZxmET)1A5bwv!>k}l-8wyv>8)65O2J`!SrVHc2noGgOnTu@8sjAT> zoCpC0*I^G%7s}{clfLXljjXmFj58#@k#Sttspm6u;U zNxzUmi6UERWn+>WV>R!ciS%)C+H!TrbL+6R>2_34Khql83$>_l8STuB5i(#aJL?X! zLm_w=AdwmJjka6R$YsWM3lBdGGa!SJs>qzxgXDZ zZ4g5_g1lZ4i=@%a;-FEZus6QTmn2?=`!!%PEteE%-FYrd3fDDXT|qlfjawlz*zb@G zyPV(9SF>|FOl+XJV=KZzLt4*VzTI!#9xPldH>qr9=O4%yOzrjZL{LYe}`Xuaw?48;wqhCYSaPu(0!*`c_U}o}o%4Ws;Xfm}d^B#{v zNW1qW;(%i$PceJCN@v<4`flW+8S6(C+?O);C{jHXn~xD@N!v1&_~1zskF=-arufuzP!T!IKKVv={5lM$q>4kV_wBfyo%|OhF{(YlFMa z+UCR+%G&qejSZE(bjpkf1{S3Y28Q&nW#Q~@WBPMFEb3TCz0!F35ZUdG%j1UvYhKJW)M%SYM0 zJ+D=!qYSnrr^u~g(C1^*Z>5+~eOoO;TLPC=?>0couyyofb&G~2r22`pejIy^0&hWc z#h#ZzPp%A7h&fnb${|?2#L?(FfsRlAez2A(jr+?v*$_OW0n3?oLJl#mZD{s)fzQzS z-d_m-Vpn;G9uRCk6-yttqxgsRu0t%AL&C^sdVDQW=tzRgMCxGUe0T0a{Kc+|9sP)V zk^6HUaGPpq(2HfWD8c3CuK0@Qb5q8nN6AJ4l}#DDP4~;JmFSCJ-w(8CkTx2(0d+R( z1TCMd1n^P9MV1kC-S`o;uo&Da0@|9F{F5BTVI%CC1QgfukC0X-xtMfg?cgguZV{>P z0QJ3%_n<4(x=?ghwYdVraNlQN(^h$kWk0?8d3!GbF)B$4%)Acwc}e@v;gK(Mm}cT5S`QlPV4PBKe3j^(abQJ>n*; zN}eL~m4z8f9*r@h&gjHUib8UV7+_Rs#kb?{ir!*d>IS=-V>p=k6=26w;2>ql$>e0A zLsb}NwTY4<{2@KfXOW8Rh+c#otV$=rg(&>?CqQeNp{ zdz~o0qD6h=F2R6}UEj1UHrDN$mMd0VTv!1Pbglj9gs-_Y?!zQ{=DB79DUd*Nwx*PT z(dFnQP2{l|TDr`chwFRD>xyFOFxPvZ!(At=2CR$uCV%EHum|YFc23@{S0Go0$5CTc zQEw{SrHjqOJ*vY4G;y3gUH5N%Cj>Pk=!GSWT|OgUoSCjM7w_QC$^^V6m4LNj$4_A7 za@GY7o|{z_Sr<2ZTysQMl*qM|``}G}{+Lp$4|N=&RR)N25syns2PeIzW35sL0F!C1 zt>M@xycrC8(=#?^H*Zq9WC!$Rem-_G9zPp<7(nyfhn|o##|J znMF5~e$eV{&SnIoXD{U#j=!r3_z?RTX4va5i~Ge@rvj!#S+si64_m)egKr^n2E_n> z=WFnrZ8rNi7OFeYFX-3yY%Xn1#|cu9`NLSm??R~!IS{bRwGx(Lu`^cPmo1_j3$lc! z!9ofx$a9FHzQB~IM1x^Qoij?J;qiQzVD{Nc*BosB@pVna$a69RSvTmeo*hk!V`5xV z+}c7}tc2!#d`o=f{r+-bv}`#J5{bAHeE_CN2dXmW`^p$uM4XzKY-%$yam8_ouvJsr zy#`~=O1Y!tl!r|KPZFi=mJWJko|9$RJa2*Y$p$WTY14|khomV^QPQ`R&gsi*PPU%Y z>5xeV^272C?pdXjdoQ7ZWjyJXzIRq4uWIqKdgQCkA_)z%Sv?S^T&_&b7o7LHR@(78 z(Tg{rTyYrMT~|u5%2Nn0&F8E@p&o?#9rzJx7!d2mJ1bm@zMczR45-(wuXaWBbWgvv z|9}SVLTXWXS*jgdT}l+fGu+)7By<&l9(yFX)w;BKIYrfqKa)P5&{^X?0;6CEhS-i; ziIg?M)ifBTjn_9vCX=N@viPM!mfxlovw9BaR#A}C4;D}l*(E9{eO{!GFdTI1Vh{St zSKbS@%KweE!LXq|Fti}n8rF$Y2kB#PLU%9#Pt=3DbOpXqL$y|PF{$vt|CN+rU|Mv} zv11IiiWK1)v9w5yex|bO?kIJQx`GlCH+L`yesbi7KVb%3G-yC+P}8v7A8Q%%0*rH& ztFYZvDaLAzIOTQEozCaFyN;I$0w%=(|LsIosbW+AtLNK}PnwQ0MbQ^Qy8Vx2$YZafXF4h< zW@5wYwVqI4J~!K<1j@MV+eS%N^at;rPER&_eoSy*seeS5In#uQma;`QsDmcQgWm?3 zmaw|tob4U++4~5^k~;U+LZt4AeyB_*6}NvJJvG0iYP-6=e%lr?fXWXG0I72*jp=7^eZ>Jv#nB0e%j54w;B%p`~`CrqU z77~j4YjKGByU`U`JUCt6b!<)P3x#cDaR!YCY-O5%W4>NrtD0gOCCX?89i~?SJmT-+ z1eR2I=$vjU+MXf>T-YJ#EF?ZWnDWh|8F!{9scr>rZCvjjfh+{l5tzE5*DexMJ201{ zLU&@azFX$h=;gO#gx-*YazG)oybcyGEuDz>QHp6`;$q~hQi#&TTvljkSu~_ZETm3d z*k~dXAt8am!x7rAA;rl^v58h*CVw?ZfA?VE$Rz@kvY6Y9oP(@a?X$jA5Q>S72G6Bv zUf}KJaKlVu$nxUeAu@jFONx zQtHwJkREy15Wll$dVQiQmp_#QlaZVuG@kIyyE2JPtzKn4rLI zA3^)o`asty%6FmLjXua)`+&Glq1vHXJJzCV{ia<;P#$n;)K;foE3cFqu8PLYyNuxH z0g;^ePO%bAc|&LLhFz=j8>@^{bOA{GO6P!$E|!P{ z@?)P@xX?PDW*&i+hAbtHEU7zKok8N|gW`7i8nCah8}M)%@Jq=*sF|`L%-%I4sy{ue zs)P#fqI~Y=z0TZOzhT@?LI6^|FLF)qNF;uRux#fA^rGu)U%53SPK;*|AmaU3*$Pxn zdPfdMN46HxIUjF}ocjI^5EL=CJCx=GVtw++^XQCfdwXntY5fVt>#Czdkk;lAv13!=M>y1dK)! zM7x)V#Bdx+qN#Ad^YrD}?~LErTNz`-KU@}a6u4y&4%t~JLYK56*)m9yOjax?vv|~H z)(IWYQ@!W5`ADbNaW|LLBYYo*`Y?G#Ud21n-qAo;wVpznT%;gQ&fj@j9$z! zeuz9CSa;Q~%UXcER+KTr)hBOHcKu0>WNeln!Hz-=c;Gq#;Kl70&#U-a_YJJdO0C~X zQ%g#EiZsXl4F3yRH%2C8n7mUL2-0qS5pTi%?2wA%op;5O>ikSfzO~xE!DCjVc2bI( zl+`W_hZw7~_`6rV=-vGgFy%Udr#>%Q$M#0ZWWiRD-pEc!YMyYgkU>UfyyG)(lAXBCa!!s>RYTNXBi&H8UPAb&H85Hy!0R zdqX2;Wi6>VbIw@?`FL7f*^CGQs=4dm+RfZQTX>r5&};?0L}(H`$aNWxs0F$#8b--* z39Z6XtRbI}>TJqI`x$3!bSV;NqAM7gphXUg4OKD}xXtWoMrMX|sZcqEwwm)A*K6{Z zvw-F$$W<+ll1*6p-M%>3&jjzjTvow-%Sj&N-xAd7Z9qMfUp-rDRjgL>qo(CuKh1v2 z{X4NF%%XcvAB;uSRu_6}oF*ilNpXSZPxzzwL-UD;?ddCe@t_>h~_FUPo>(Z?76cbWqF>C-~r(bVS8?894b*XGBk|GqzEAQ1k-Fg_#8V#l76 zbf~?Deut==t#WQyXkVR~NcZGS-uWcUK8{mj!~*tPTSS!TryF6SK3s36e)lwKdbLQb z$C4k}?B}B7Ck*U&ZB;t*1Zgh%NtU+EK)9>gcS1J(I5D~n@ZaQ*pJybG(e+dwXAS0~ ziL;#8QB@Sq3v0sk*{<*d4xQ3EdnmqdnJ6VnrwrPpnwNfy2c_(b<6 zhBCm7ob}aJ*poOA=e`0$0-;+jM($GAh7WT#;&vS_7Ix8*8Z4lzC1E8G-_Lu%?g zUPiGi+1+2#0$boAJf(@Qg@`sbQ2Jb8gXBKxkIbb@&l zgzCs>)((8z!pGPmv#k6<3yBk-zeAN?mNrAPLN+@c0B590DtMd;Z zWji=5wM~w>Z67WZ|Kpqh*G;$f+JlJfP%RM_Y?c;y8P56>NBnof@lw zcnC6Rs^hh#!U?|QB$P-(8ILA1L@SP0j5)P8Myud)bxCX}c!_{b*{eZ&o7a%%8Kmiw zd$N6~BGfiuu^clQ7YBv-?_17ezCb@L3S$e1$U7JLYysPxzyO<&M?-zr1(&Czm0i^| z=Y#P3qsvAVjXCe062>pUF@FZ*epN;MX_+VEfV}`Ce!Aum z{Z~8v>fpcUJ)b?!ul8JEfk^PT|M^|%pC0I0=9g0Dzhr){nE$2hc}f0i&&r2`|L{+L4)jk)_N?a@OWyw!`wzGFXFq>NegEz!5c*Px$k2Y$6@3kUtO@U!q=l?1