From a7fe99f94d1a3c9c7254ba0d599d96294ba304dc Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Mon, 21 Sep 2026 21:21:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=82=E8=AE=AD=EF=BC=9A?= =?UTF-8?q?=E4=B8=B2=E8=A1=8C=E5=8C=96=E6=8A=A5=E5=88=B0=E4=B8=8E=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../train/mapper/TrainEnrollmentMapper.java | 17 ++++++++++++ .../service/ITrainEnrollmentService.java | 4 +++ .../impl/EnrollmentAppServiceImpl.java | 26 ++++++++++++++----- .../impl/TrainEnrollmentServiceImpl.java | 12 +++++++++ .../mapper/train/TrainEnrollmentMapper.xml | 13 ++++++++++ 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainEnrollmentMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainEnrollmentMapper.java index 0293a8df5..6e83efa74 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainEnrollmentMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainEnrollmentMapper.java @@ -32,6 +32,23 @@ public interface TrainEnrollmentMapper */ TrainEnrollment selectTrainEnrollmentById(@Param("enrollmentId") Long enrollmentId); + /** + * 锁定指定参训学员行 (SELECT ... FOR UPDATE),用于报到与状态流转并发控制。 + * + * @param enrollmentId 参训档案ID + * @return 参训记录 + */ + TrainEnrollment selectTrainEnrollmentByIdForUpdate(@Param("enrollmentId") Long enrollmentId); + + /** + * 按 enrollment_id 稳定升序锁定指定开班计划下的全部学员记录 (SELECT ... FOR UPDATE), + * 用于结训预检与结训归档流程防并发竞态。 + * + * @param planId 开班计划ID + * @return 该计划学员记录列表 + */ + List selectEnrollmentsForUpdateByPlanId(@Param("planId") String planId); + /** * 根据计划ID与学员ID查询参训记录(用于唯一性校验)。 * diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainEnrollmentService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainEnrollmentService.java index 24ae0f276..d84dfaaf9 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainEnrollmentService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainEnrollmentService.java @@ -17,6 +17,10 @@ public interface ITrainEnrollmentService { TrainEnrollment selectTrainEnrollmentById(Long enrollmentId); + TrainEnrollment selectTrainEnrollmentByIdForUpdate(Long enrollmentId); + + List selectEnrollmentsForUpdateByPlanId(String planId); + TrainEnrollment selectTrainEnrollmentByPlanIdAndStudentId(String planId, String studentId); List selectEnrollmentItemList(EnrollmentQuery query); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentAppServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentAppServiceImpl.java index e05cd1f75..edd1c493e 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentAppServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/EnrollmentAppServiceImpl.java @@ -377,7 +377,8 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService throw new ServiceException("普通状态接口禁止直接设置为已完成状态,结训必须走开班计划结训流程"); } - TrainEnrollment enrollment = requireEnrollment(enrollmentId); + // 锁定参训学员记录行 (SELECT ... FOR UPDATE),保证状态流转并发串行化并消除 TOCTOU + TrainEnrollment enrollment = requireEnrollmentForUpdate(enrollmentId); // 计划已结训或取消时,禁止状态变更 TrainClassPlan plan = requirePlan(enrollment.getPlanId()); checkPlanStatusNotLocked(plan); @@ -568,6 +569,22 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService return enrollment; } + private TrainEnrollment requireEnrollmentForUpdate(Long enrollmentId) + { + if (enrollmentId == null) + { + throw new ServiceException(EnrollmentErrorCode.ENROLLMENT_NOT_FOUND_MESSAGE, EnrollmentErrorCode.ENROLLMENT_NOT_FOUND); + } + TrainEnrollment enrollment = trainEnrollmentService != null + ? trainEnrollmentService.selectTrainEnrollmentByIdForUpdate(enrollmentId) + : (trainEnrollmentMapper != null ? trainEnrollmentMapper.selectTrainEnrollmentByIdForUpdate(enrollmentId) : null); + if (enrollment == null) + { + throw new ServiceException(EnrollmentErrorCode.ENROLLMENT_NOT_FOUND_MESSAGE, EnrollmentErrorCode.ENROLLMENT_NOT_FOUND); + } + return enrollment; + } + private void checkPlanStatusNotLocked(TrainClassPlan plan) { if (Integer.valueOf(2).equals(plan.getPlanStatus()) || Integer.valueOf(3).equals(plan.getPlanStatus())) @@ -719,11 +736,8 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService dto = new ReportConfirmDTO(); } - TrainEnrollment enrollment = trainEnrollmentMapper.selectTrainEnrollmentById(enrollmentId); - if (enrollment == null) - { - throw new ServiceException(EnrollmentErrorCode.ENROLLMENT_NOT_FOUND_MESSAGE, EnrollmentErrorCode.ENROLLMENT_NOT_FOUND); - } + // 锁定参训学员记录行 (SELECT ... FOR UPDATE),保证报到确认与状态流转串行化并消除 TOCTOU + TrainEnrollment enrollment = requireEnrollmentForUpdate(enrollmentId); // 幂等与查重:若已报到(status==1)或已有报到记录 TrainClassReport existingReport = trainClassReportMapper.selectTrainClassReportByEnrollmentId(enrollmentId); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainEnrollmentServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainEnrollmentServiceImpl.java index 60254e586..dd21f9e14 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainEnrollmentServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainEnrollmentServiceImpl.java @@ -28,6 +28,18 @@ public class TrainEnrollmentServiceImpl implements ITrainEnrollmentService return trainEnrollmentMapper.selectTrainEnrollmentById(enrollmentId); } + @Override + public TrainEnrollment selectTrainEnrollmentByIdForUpdate(Long enrollmentId) + { + return trainEnrollmentMapper.selectTrainEnrollmentByIdForUpdate(enrollmentId); + } + + @Override + public List selectEnrollmentsForUpdateByPlanId(String planId) + { + return trainEnrollmentMapper.selectEnrollmentsForUpdateByPlanId(planId); + } + @Override public TrainEnrollment selectTrainEnrollmentByPlanIdAndStudentId(String planId, String studentId) { diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainEnrollmentMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainEnrollmentMapper.xml index 765457f62..77e028dea 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainEnrollmentMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainEnrollmentMapper.xml @@ -45,6 +45,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where enrollment_id = #{enrollmentId} + + + +