diff --git a/.DS_Store b/.DS_Store index af3d05f..6164ceb 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index 971c0a1..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: 构建和部署Spring Boot应用程序 - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: 检出代码库 - uses: actions/checkout@v3 - - - name: 设置JDK 17 - uses: actions/setup-java@v3 - with: - java-version: "17" - distribution: "temurin" - - name: 缓存Maven包 - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: 安装依赖 - run: ./mvnw install -DskipTests - - - name: 运行测试 - run: ./mvnw test - - - name: 打包应用程序 - run: ./mvnw package -DskipTests - - - name: 上传构件 - uses: actions/upload-artifact@v3 - with: - name: spring-boot-app - path: | - target/jinduguanli-0.0.1-SNAPSHOT.jar - src/main/resources/application.yml - - deploy: - needs: build - runs-on: ubuntu-latest - - steps: - - name: 下载构件 - uses: actions/download-artifact@v3 - with: - name: spring-boot-app - path: target/ - - - name: 通过scp复制文件 - uses: appleboy/scp-action@v0.1.1 - with: - host: ${{ secrets.REMOTE_HOST }} - username: ${{ secrets.REMOTE_USER }} - key: ${{ secrets.REMOTE_SSH_KEY }} - source: | - target/jinduguanli-0.0.1-SNAPSHOT.jar - src/main/resources/application.yml - target: "/huertian/" - - - name: 通过ssh执行远程命令 - uses: appleboy/ssh-action@v0.1.8 - with: - host: ${{ secrets.REMOTE_HOST }} - username: ${{ secrets.REMOTE_USER }} - key: ${{ secrets.REMOTE_SSH_KEY }} - script: | - cd /huertian/ - java -jar jinduguanli-0.0.1-SNAPSHOT.jar --spring.config.location=application.yml diff --git a/src/main/java/com/huertian/jinduguanli/common/ProgressStatus.java b/src/main/java/com/huertian/jinduguanli/common/ProgressStatus.java new file mode 100644 index 0000000..ff00961 --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/common/ProgressStatus.java @@ -0,0 +1,39 @@ +package com.huertian.jinduguanli.common; + +public enum ProgressStatus { + NOT_STARTED(0, "未开始"), + SCRIPT_CREATION(1, "脚本制作"), + SCRIPT_REVIEW(2, "脚本审核"), + SCRIPT_CONFIRMATION(3, "脚本确认"), + VIDEO_CREATION(4, "视频拍摄与制作"), + VIDEO_CONFIRMATION(5, "视频确认"); + + private final int code; + private final String description; + + ProgressStatus(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static ProgressStatus fromCode(int code) { + for (ProgressStatus status : ProgressStatus.values()) { + if (status.code == code) { + return status; + } + } + throw new IllegalArgumentException("未知的进度状态代码: " + code); + } + + public static String getDescription(int code) { + return fromCode(code).getDescription(); + } +} \ No newline at end of file diff --git a/src/main/java/com/huertian/jinduguanli/controller/ImportController.java b/src/main/java/com/huertian/jinduguanli/controller/ImportController.java index 85412ef..4ed9b91 100644 --- a/src/main/java/com/huertian/jinduguanli/controller/ImportController.java +++ b/src/main/java/com/huertian/jinduguanli/controller/ImportController.java @@ -1,6 +1,7 @@ package com.huertian.jinduguanli.controller; import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.read.listener.PageReadListener; import com.huertian.jinduguanli.common.ApiResponse; import com.huertian.jinduguanli.common.ErrorCode; import com.huertian.jinduguanli.dto.DepartmentImportDTO; @@ -13,12 +14,13 @@ 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.RequestHeader; 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; +import java.util.concurrent.atomic.AtomicInteger; @Slf4j @RestController @@ -34,8 +36,15 @@ public class ImportController { @Autowired private DepartmentService departmentService; + /** + * 每批处理的数据量 + */ + private static final int BATCH_SIZE = 100; + @PostMapping("/users") - public ApiResponse importUsers(@RequestParam("file") MultipartFile file) { + public ApiResponse importUsers( + @RequestParam("file") MultipartFile file, + @RequestHeader("Authorization") String authHeader) { if (file.isEmpty()) { return new ApiResponse<>(ErrorCode.INVALID_PARAM, "文件不能为空", null); } @@ -47,17 +56,29 @@ public class ImportController { try { log.info("开始导入用户数据,文件名:{}", originalFilename); - List userList = EasyExcel.read(file.getInputStream()) - .head(UserImportDTO.class) - .sheet() - .doReadSync(); - log.info("读取到{}条用户数据", userList.size()); + String token = authHeader.substring(7); + AtomicInteger totalCount = new AtomicInteger(0); + AtomicInteger successCount = new AtomicInteger(0); + StringBuilder errorMsg = new StringBuilder(); - for (int i = 0; i < userList.size(); i++) { - log.info("第{}行数据:{}", i + 1, userList.get(i)); + // 使用分批读取,每次处理 BATCH_SIZE 条数据,避免内存溢出 + EasyExcel.read(file.getInputStream(), UserImportDTO.class, new PageReadListener(batch -> { + log.debug("处理用户数据批次,本批数量:{}", batch.size()); + totalCount.addAndGet(batch.size()); + ApiResponse result = userService.batchImportUsers(batch, token); + if (result.getCode() == ErrorCode.SUCCESS) { + successCount.addAndGet(batch.size()); + } else { + errorMsg.append(result.getMessage()).append(";"); + } + }, BATCH_SIZE)).sheet().doRead(); + + log.info("用户导入完成,总计:{}条,成功:{}条", totalCount.get(), successCount.get()); + String resultMsg = String.format("导入完成,总计%d条,成功%d条", totalCount.get(), successCount.get()); + if (errorMsg.length() > 0) { + resultMsg += "。错误信息:" + errorMsg; } - - return userService.batchImportUsers(userList); + return ApiResponse.success(resultMsg); } catch (Exception e) { log.error("导入用户数据失败", e); return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "导入用户数据失败:" + e.getMessage(), null); @@ -77,17 +98,28 @@ public class ImportController { try { log.info("开始导入课程任务数据,文件名:{}", originalFilename); - List taskList = EasyExcel.read(file.getInputStream()) - .head(LessonTaskImportDTO.class) - .sheet() - .doReadSync(); - log.info("读取到{}条课程任务数据", taskList.size()); + AtomicInteger totalCount = new AtomicInteger(0); + AtomicInteger successCount = new AtomicInteger(0); + StringBuilder errorMsg = new StringBuilder(); - for (int i = 0; i < taskList.size(); i++) { - log.info("第{}行数据:{}", i + 1, taskList.get(i)); + // 使用分批读取,每次处理 BATCH_SIZE 条数据,避免内存溢出 + EasyExcel.read(file.getInputStream(), LessonTaskImportDTO.class, new PageReadListener(batch -> { + log.debug("处理课程任务数据批次,本批数量:{}", batch.size()); + totalCount.addAndGet(batch.size()); + ApiResponse result = lessonTaskService.batchImportLessonTasks(batch); + if (result.getCode() == ErrorCode.SUCCESS) { + successCount.addAndGet(batch.size()); + } else { + errorMsg.append(result.getMessage()).append(";"); + } + }, BATCH_SIZE)).sheet().doRead(); + + log.info("课程任务导入完成,总计:{}条,成功:{}条", totalCount.get(), successCount.get()); + String resultMsg = String.format("导入完成,总计%d条,成功%d条", totalCount.get(), successCount.get()); + if (errorMsg.length() > 0) { + resultMsg += "。错误信息:" + errorMsg; } - - return lessonTaskService.batchImportLessonTasks(taskList); + return ApiResponse.success(resultMsg); } catch (Exception e) { log.error("导入课程任务数据失败", e); return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "导入课程任务数据失败:" + e.getMessage(), null); @@ -107,17 +139,28 @@ public class ImportController { try { log.info("开始导入部门数据,文件名:{}", originalFilename); - List departmentList = EasyExcel.read(file.getInputStream()) - .head(DepartmentImportDTO.class) - .sheet() - .doReadSync(); - log.info("读取到{}条部门数据", departmentList.size()); + AtomicInteger totalCount = new AtomicInteger(0); + AtomicInteger successCount = new AtomicInteger(0); + StringBuilder errorMsg = new StringBuilder(); - for (int i = 0; i < departmentList.size(); i++) { - log.info("第{}行数据:{}", i + 1, departmentList.get(i)); + // 使用分批读取,每次处理 BATCH_SIZE 条数据,避免内存溢出 + EasyExcel.read(file.getInputStream(), DepartmentImportDTO.class, new PageReadListener(batch -> { + log.debug("处理部门数据批次,本批数量:{}", batch.size()); + totalCount.addAndGet(batch.size()); + ApiResponse result = departmentService.batchImportDepartments(batch); + if (result.getCode() == ErrorCode.SUCCESS) { + successCount.addAndGet(batch.size()); + } else { + errorMsg.append(result.getMessage()).append(";"); + } + }, BATCH_SIZE)).sheet().doRead(); + + log.info("部门导入完成,总计:{}条,成功:{}条", totalCount.get(), successCount.get()); + String resultMsg = String.format("导入完成,总计%d条,成功%d条", totalCount.get(), successCount.get()); + if (errorMsg.length() > 0) { + resultMsg += "。错误信息:" + errorMsg; } - - return departmentService.batchImportDepartments(departmentList); + return ApiResponse.success(resultMsg); } catch (Exception e) { log.error("导入部门数据失败", e); return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "导入部门数据失败:" + e.getMessage(), null); diff --git a/src/main/java/com/huertian/jinduguanli/controller/LessonTaskController.java b/src/main/java/com/huertian/jinduguanli/controller/LessonTaskController.java index 4787111..b0feb23 100644 --- a/src/main/java/com/huertian/jinduguanli/controller/LessonTaskController.java +++ b/src/main/java/com/huertian/jinduguanli/controller/LessonTaskController.java @@ -9,7 +9,6 @@ import com.huertian.jinduguanli.service.LessonTaskService; import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; @@ -20,7 +19,6 @@ public class LessonTaskController { private static final Logger logger = LoggerFactory.getLogger(LessonTaskController.class); private final LessonTaskService lessonTaskService; - @Autowired public LessonTaskController(LessonTaskService lessonTaskService) { this.lessonTaskService = lessonTaskService; } diff --git a/src/main/java/com/huertian/jinduguanli/dto/LessonTaskDTO.java b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskDTO.java index 60ea6bd..5c6eb9b 100644 --- a/src/main/java/com/huertian/jinduguanli/dto/LessonTaskDTO.java +++ b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskDTO.java @@ -9,7 +9,7 @@ public class LessonTaskDTO { private String courseName; private String microLessonName; private Long userId; - private String username; // 添加用户名字段 + private String username; // 添加用户名字段 private Integer progressStatus; private Long scriptCreateTime; private Long scriptReviewTime; @@ -17,7 +17,9 @@ public class LessonTaskDTO { private Long videoCreateTime; private Long videoConfirmTime; private Long finishTime; + private Integer expediteStatus; private String advise; + private String msg; private Long createdAt; private Long updatedAt; @@ -36,7 +38,9 @@ public class LessonTaskDTO { dto.setVideoCreateTime(lessonTask.getVideoCreateTime()); dto.setVideoConfirmTime(lessonTask.getVideoConfirmTime()); dto.setFinishTime(lessonTask.getFinishTime()); + dto.setExpediteStatus(lessonTask.getExpediteStatus()); dto.setAdvise(lessonTask.getAdvise()); + dto.setMsg(lessonTask.getMsg()); dto.setCreatedAt(lessonTask.getCreatedAt()); dto.setUpdatedAt(lessonTask.getUpdatedAt()); return dto; @@ -146,6 +150,22 @@ public class LessonTaskDTO { this.advise = advise; } + public Integer getExpediteStatus() { + return expediteStatus; + } + + public void setExpediteStatus(Integer expediteStatus) { + this.expediteStatus = expediteStatus; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + public Long getCreatedAt() { return createdAt; } diff --git a/src/main/java/com/huertian/jinduguanli/dto/LessonTaskRequest.java b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskRequest.java index 9de1795..5120dc8 100644 --- a/src/main/java/com/huertian/jinduguanli/dto/LessonTaskRequest.java +++ b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskRequest.java @@ -16,7 +16,9 @@ public class LessonTaskRequest { private Long videoCreateTime; private Long videoConfirmTime; private Long finishTime; + private Integer expediteStatus; private String advise; + private String msg; public String getCourseName() { return courseName; @@ -105,4 +107,20 @@ public class LessonTaskRequest { public void setAdvise(String advise) { this.advise = advise; } + + public Integer getExpediteStatus() { + return expediteStatus; + } + + public void setExpediteStatus(Integer expediteStatus) { + this.expediteStatus = expediteStatus; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } } diff --git a/src/main/java/com/huertian/jinduguanli/dto/LessonTaskSimpleDTO.java b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskSimpleDTO.java new file mode 100644 index 0000000..7b5d12d --- /dev/null +++ b/src/main/java/com/huertian/jinduguanli/dto/LessonTaskSimpleDTO.java @@ -0,0 +1,44 @@ +package com.huertian.jinduguanli.dto; + +import com.huertian.jinduguanli.entity.LessonTask; +import lombok.Data; + +@Data +public class LessonTaskSimpleDTO { + private Long id; + private Integer progressStatus; + private String msg; + + // 从 LessonTask 实体转换为简化 DTO + public static LessonTaskSimpleDTO fromEntity(LessonTask lessonTask) { + LessonTaskSimpleDTO dto = new LessonTaskSimpleDTO(); + dto.setId(lessonTask.getId()); + dto.setProgressStatus(lessonTask.getProgressStatus()); + dto.setMsg(lessonTask.getMsg()); + return dto; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Integer getProgressStatus() { + return progressStatus; + } + + public void setProgressStatus(Integer progressStatus) { + this.progressStatus = progressStatus; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} \ No newline at end of file diff --git a/src/main/java/com/huertian/jinduguanli/entity/LessonTask.java b/src/main/java/com/huertian/jinduguanli/entity/LessonTask.java index f59f66b..9b5d1eb 100644 --- a/src/main/java/com/huertian/jinduguanli/entity/LessonTask.java +++ b/src/main/java/com/huertian/jinduguanli/entity/LessonTask.java @@ -38,9 +38,14 @@ public class LessonTask implements Serializable { private Long videoCreateTime; private Long videoConfirmTime; private Long finishTime; - + + @Column(nullable = false) + private Integer expediteStatus; + private String advise; + private String msg; + @Column(nullable = false) private Long createdAt; @@ -49,20 +54,21 @@ public class LessonTask implements Serializable { @PrePersist protected void onCreate() { - logger.info("创建新课程任务 - 课程名称: {}, 微课名称: {}, 用户ID: {}", - this.courseName, this.microLessonName, this.userId); - progressStatus = 0; + logger.info("创建新课程任务 - 课程名称: {}, 微课名称: {}, 用户ID: {}", + this.courseName, this.microLessonName, this.userId); + progressStatus = 0; + expediteStatus = 0; // 默认未催办 long now = System.currentTimeMillis() / 1000; createdAt = now; updatedAt = now; - logger.info("创建课程任务成功 - 进度状态: {}, 创建时间: {}, 更新时间: {}", - progressStatus, createdAt, updatedAt); + logger.info("创建课程任务成功 - 进度状态: {}, 催办状态: {}, 创建时间: {}, 更新时间: {}", + progressStatus, expediteStatus, createdAt, updatedAt); } @PreUpdate protected void onUpdate() { - logger.info("更新课程任务 - ID: {}, 课程名称: {}, 微课名称: {}", - this.id, this.courseName, this.microLessonName); + logger.info("更新课程任务 - ID: {}, 课程名称: {}, 微课名称: {}", + this.id, this.courseName, this.microLessonName); updatedAt = System.currentTimeMillis() / 1000; logger.info("更新课程任务成功 - ID: {}, 更新时间: {}", id, updatedAt); } diff --git a/src/main/java/com/huertian/jinduguanli/repository/LessonTaskRepository.java b/src/main/java/com/huertian/jinduguanli/repository/LessonTaskRepository.java index b2e5075..bed50f4 100644 --- a/src/main/java/com/huertian/jinduguanli/repository/LessonTaskRepository.java +++ b/src/main/java/com/huertian/jinduguanli/repository/LessonTaskRepository.java @@ -8,20 +8,12 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; import java.util.Map; @Repository public interface LessonTaskRepository extends JpaRepository { Page findByUserId(Long userId, Pageable pageable); - /** - * 查询指定部门的所有课程任务 - * 通过连接users表查询属于指定部门的所有用户的课程任务 - */ - @Query("SELECT lt FROM LessonTask lt JOIN User u ON lt.userId = u.id WHERE u.departmentId = :departmentId") - List findByDepartmentId(@Param("departmentId") Long departmentId); - /** * 分页查询指定部门的所有课程任务 */ diff --git a/src/main/java/com/huertian/jinduguanli/security/JwtAuthenticationFilter.java b/src/main/java/com/huertian/jinduguanli/security/JwtAuthenticationFilter.java index 8348b40..085544c 100644 --- a/src/main/java/com/huertian/jinduguanli/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/huertian/jinduguanli/security/JwtAuthenticationFilter.java @@ -66,6 +66,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } final String jwt = authHeader.substring(7); + logger.debug("Received JWT token: {}", jwt); final String username = jwtService.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { diff --git a/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java b/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java index c2611d4..842b3a2 100644 --- a/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java +++ b/src/main/java/com/huertian/jinduguanli/service/DepartmentService.java @@ -5,6 +5,8 @@ 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 jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +18,12 @@ import java.util.List; @Service public class DepartmentService { private static final Logger logger = LoggerFactory.getLogger(DepartmentService.class); + private static final int BATCH_FLUSH_SIZE = 50; // 每50条清理一次缓存 + private final DepartmentRepository departmentRepository; + + @PersistenceContext + private EntityManager entityManager; @Autowired public DepartmentService(DepartmentRepository departmentRepository) { @@ -55,11 +62,24 @@ public class DepartmentService { departmentRepository.save(department); successCount++; + + // 每 BATCH_FLUSH_SIZE 条数据清理一次 Hibernate 一级缓存,避免内存溢出 + if (successCount % BATCH_FLUSH_SIZE == 0) { + entityManager.flush(); + entityManager.clear(); + logger.debug("已处理 {} 条部门数据,清理缓存", successCount); + } } catch (Exception e) { logger.error("导入第{}行数据失败", i + 2, e); errorMsg.append(String.format("第%d行导入失败;", i + 2)); } } + + // 处理剩余数据的缓存清理 + if (successCount % BATCH_FLUSH_SIZE != 0) { + entityManager.flush(); + entityManager.clear(); + } String resultMsg = String.format("成功导入%d条数据", successCount); if (errorMsg.length() > 0) { diff --git a/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java b/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java index 83240a3..026bb64 100644 --- a/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java +++ b/src/main/java/com/huertian/jinduguanli/service/LessonTaskService.java @@ -8,7 +8,9 @@ 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.EntityManager; import jakarta.persistence.EntityNotFoundException; +import jakarta.persistence.PersistenceContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.CacheEvict; @@ -26,8 +28,13 @@ import org.apache.commons.lang3.StringUtils; @Service public class LessonTaskService { private static final Logger logger = LoggerFactory.getLogger(LessonTaskService.class); + private static final int BATCH_FLUSH_SIZE = 50; // 每50条清理一次缓存 + private final LessonTaskRepository lessonTaskRepository; - private final UserService userService; // 添加 UserService 的依赖 + private final UserService userService; + + @PersistenceContext + private EntityManager entityManager; public LessonTaskService(LessonTaskRepository lessonTaskRepository, UserService userService) { this.lessonTaskRepository = lessonTaskRepository; @@ -129,6 +136,12 @@ public class LessonTaskService { if (request.getAdvise() != null) { task.setAdvise(request.getAdvise()); } + if (request.getMsg() != null) { + task.setMsg(request.getMsg()); + } + if (request.getExpediteStatus() != null) { + task.setExpediteStatus(request.getExpediteStatus()); + } if (request.getCourseName() != null) { task.setCourseName(request.getCourseName()); } @@ -247,6 +260,13 @@ public class LessonTaskService { try { lessonTaskRepository.save(task); successCount++; + + // 每 BATCH_FLUSH_SIZE 条数据清理一次 Hibernate 一级缓存,避免内存溢出 + if (successCount % BATCH_FLUSH_SIZE == 0) { + entityManager.flush(); + entityManager.clear(); + logger.debug("已处理 {} 条数据,清理缓存", successCount); + } } catch (Exception e) { logger.error("保存课程任务失败", e); errorMsg.append(String.format("第%d行保存失败: %s;", i + 2, e.getMessage())); @@ -256,6 +276,12 @@ public class LessonTaskService { errorMsg.append(String.format("第%d行导入失败: %s;", i + 2, e.getMessage())); } } + + // 处理剩余数据的缓存清理 + if (successCount % BATCH_FLUSH_SIZE != 0) { + entityManager.flush(); + entityManager.clear(); + } String resultMsg = String.format("成功导入%d条数据", successCount); if (errorMsg.length() > 0) { diff --git a/src/main/java/com/huertian/jinduguanli/service/UserService.java b/src/main/java/com/huertian/jinduguanli/service/UserService.java index 13a2011..2ba1a0c 100644 --- a/src/main/java/com/huertian/jinduguanli/service/UserService.java +++ b/src/main/java/com/huertian/jinduguanli/service/UserService.java @@ -8,6 +8,8 @@ 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 jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,11 +27,16 @@ import java.util.Optional; @Service public class UserService implements UserDetailsService { + private static final int BATCH_FLUSH_SIZE = 50; // 每50条清理一次缓存 + 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); + + @PersistenceContext + private EntityManager entityManager; @Autowired public UserService(JwtService jwtService, PasswordEncoder passwordEncoder, UserRepository userRepository, @@ -193,7 +200,7 @@ public class UserService implements UserDetailsService { return ApiResponse.success(user); } - public ApiResponse batchImportUsers(List userList) { + public ApiResponse batchImportUsers(List userList, String token) { if (userList == null || userList.isEmpty()) { return new ApiResponse<>(ErrorCode.PARAM_ERROR, "导入数据不能为空", null); } @@ -234,8 +241,29 @@ public class UserService implements UserDetailsService { user.setEmail(dto.getEmail()); user.setPassword(passwordEncoder.encode(dto.getPassword())); user.setDepartmentId(department.getId()); - user.setRoles(0); // 默认角色 - user.setJobs(0); // 默认工作 + + // 处理角色和岗位 + String roleAndJob = dto.getRoleAndJob(); + if (!StringUtils.isBlank(roleAndJob)) { + try { + user.setRoles(Integer.parseInt(roleAndJob)); + user.setJobs(Integer.parseInt(roleAndJob)); + } catch (NumberFormatException e) { + logger.warn("角色/岗位不是有效的数字: {}", roleAndJob); + errorMsg.append(String.format("第%d行角色/岗位不是有效的数字;", i + 2)); + continue; + } + } else { + logger.warn("角色/岗位不能为空"); + errorMsg.append(String.format("第%d行角色/岗位不能为空;", i + 2)); + continue; + } + + // 设置创建者ID + String email = jwtService.extractUsername(token); + Optional creator = userRepository.findByEmail(email); + user.setCreatorId(creator.map(User::getId).orElse(0L)); + user.setStatus(1); // 设置状态为正常 user.setCreatedAt(System.currentTimeMillis() / 1000); user.setUpdatedAt(System.currentTimeMillis() / 1000); @@ -243,6 +271,13 @@ public class UserService implements UserDetailsService { try { userRepository.save(user); successCount++; + + // 每 BATCH_FLUSH_SIZE 条数据清理一次 Hibernate 一级缓存,避免内存溢出 + if (successCount % BATCH_FLUSH_SIZE == 0) { + entityManager.flush(); + entityManager.clear(); + logger.debug("已处理 {} 条用户数据,清理缓存", successCount); + } } catch (Exception e) { logger.error("保存用户失败", e); errorMsg.append(String.format("第%d行保存失败: %s;", i + 2, e.getMessage())); @@ -252,6 +287,12 @@ public class UserService implements UserDetailsService { errorMsg.append(String.format("第%d行导入失败;", i + 2)); } } + + // 处理剩余数据的缓存清理 + if (successCount % BATCH_FLUSH_SIZE != 0) { + entityManager.flush(); + entityManager.clear(); + } String resultMsg = String.format("成功导入%d条数据", successCount); if (errorMsg.length() > 0) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 15c802d..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,23 +0,0 @@ -# 数据库配置 -spring.datasource.url=jdbc:mysql://172.16.215.132:3306/fenshenzhike?useSSL=false&serverTimezone=UTC -spring.datasource.username=root -spring.datasource.password=123 -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver - -server.port=1218 - -# JPA配置 -spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=update -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect -spring.jpa.properties.hibernate.format_sql=true - -# JWT配置 -jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 -jwt.expiration=604800000 - -# 日志配置 -logging.level.org.springframework.security=DEBUG -logging.level.com.huertian.jinduguanli=DEBUG - -spring.devtools.restart.enabled=false \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2e15aba..b8d8c10 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,14 +1,31 @@ server: port: 1218 + ssl: + enabled: true + key-store: /root/jinduguanli/key-store/service5.fenshenzhike.com.pfx + key-store-password: "q4a9hpm0" + key-store-type: PKCS12 spring: main: banner-mode: console + datasource: - url: jdbc:mysql://172.16.215.132:3306/fenshenzhike?useSSL=false&serverTimezone=UTC - username: root - password: 123 + url: jdbc:mysql://8.137.89.177:3306/jinduguanli?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true + username: jinduguanli + password: root041218 driver-class-name: com.mysql.cj.jdbc.Driver + + hikari: + maximum-pool-size: 10 + minimum-idle: 2 + idle-timeout: 600000 # 10 min + max-lifetime: 1800000 # 30 min + connection-timeout: 30000 # 30 s + validation-timeout: 5000 # 5 s + keepalive-time: 300000 # 5 min + connection-test-query: "SELECT 1" + jpa: show-sql: true hibernate: @@ -17,12 +34,15 @@ spring: hibernate: format_sql: true use_sql_comments: true + # 新版可自动选择方言,保留注释以免误导 + # dialect: org.hibernate.dialect.MySQLDialect + redis: - host: 172.16.215.132 + host: 8.137.89.177 port: 6379 database: 0 timeout: 10000 - password: # 如果Redis设置了密码,需要在这里添加 + password: #如有密码在此填入 lettuce: pool: max-active: 8 @@ -37,7 +57,7 @@ spring: jwt: secret: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 - expiration: 86400000 # 24小时 + expiration: 2592000000 # 30天(毫秒) logging: level: @@ -45,4 +65,4 @@ logging: org.hibernate.type.descriptor.sql.BasicBinder: TRACE com.huertian.jinduguanli: DEBUG org.springframework.data.redis: DEBUG - io.lettuce.core: DEBUG + io.lettuce.core: DEBUG \ No newline at end of file