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 @@ <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> + <!-- EasyExcel --> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>easyexcel</artifactId> + <version>3.3.2</version> + </dependency> + + <!-- Apache Commons Lang3 --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.12.0</version> + </dependency> </dependencies> <build> 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<String> 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<UserImportDTO> 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<String> 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<LessonTaskImportDTO> 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<String> 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<DepartmentImportDTO> 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, Long> { + 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<String> batchImportDepartments(List<DepartmentImportDTO> 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<String> batchImportLessonTasks(List<LessonTaskImportDTO> 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<User> 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<String> batchImportUsers(List<UserImportDTO> 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 0000000..f3e5070 Binary files /dev/null and b/批量添加用户模版.xlsx differ diff --git a/批量添加课程模版.xlsx b/批量添加课程模版.xlsx new file mode 100644 index 0000000..cd30c30 Binary files /dev/null and b/批量添加课程模版.xlsx differ diff --git a/批量添加部门模版.xlsx b/批量添加部门模版.xlsx new file mode 100644 index 0000000..438ca25 Binary files /dev/null and b/批量添加部门模版.xlsx differ