修复参训:串行化报到与状态迁移
This commit is contained in:
1 parent
579ce861b6
commit
a7fe99f94d
5 files changed
+66
-6
No files matched your search
@@ -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<TrainEnrollment> selectEnrollmentsForUpdateByPlanId(@Param("planId") String planId);
|
||||
|
||||
/**
|
||||
* 根据计划ID与学员ID查询参训记录(用于唯一性校验)。
|
||||
*
|
||||
|
||||
+4
@@ -17,6 +17,10 @@ public interface ITrainEnrollmentService
|
||||
{
|
||||
TrainEnrollment selectTrainEnrollmentById(Long enrollmentId);
|
||||
|
||||
TrainEnrollment selectTrainEnrollmentByIdForUpdate(Long enrollmentId);
|
||||
|
||||
List<TrainEnrollment> selectEnrollmentsForUpdateByPlanId(String planId);
|
||||
|
||||
TrainEnrollment selectTrainEnrollmentByPlanIdAndStudentId(String planId, String studentId);
|
||||
|
||||
List<EnrollmentItemVO> selectEnrollmentItemList(EnrollmentQuery query);
|
||||
|
||||
+20
-6
@@ -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);
|
||||
|
||||
+12
@@ -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<TrainEnrollment> selectEnrollmentsForUpdateByPlanId(String planId)
|
||||
{
|
||||
return trainEnrollmentMapper.selectEnrollmentsForUpdateByPlanId(planId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrainEnrollment selectTrainEnrollmentByPlanIdAndStudentId(String planId, String studentId)
|
||||
{
|
||||
|
||||
@@ -45,6 +45,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where enrollment_id = #{enrollmentId}
|
||||
</select>
|
||||
|
||||
<select id="selectTrainEnrollmentByIdForUpdate" parameterType="Long" resultMap="TrainEnrollmentResult">
|
||||
<include refid="selectTrainEnrollmentDomain" />
|
||||
where enrollment_id = #{enrollmentId}
|
||||
for update
|
||||
</select>
|
||||
|
||||
<select id="selectEnrollmentsForUpdateByPlanId" resultMap="TrainEnrollmentResult">
|
||||
<include refid="selectTrainEnrollmentDomain" />
|
||||
where plan_id = #{planId}
|
||||
order by enrollment_id asc
|
||||
for update
|
||||
</select>
|
||||
|
||||
<select id="selectTrainEnrollmentByPlanIdAndStudentId" resultMap="TrainEnrollmentResult">
|
||||
<include refid="selectTrainEnrollmentDomain" />
|
||||
where plan_id = #{planId} and student_id = #{studentId}
|
||||
|
||||
Reference in new issue
Block a user