修复考勤:原子签退防止并发覆盖首次时间

This commit is contained in:
wangzhibiao committed 2026-09-21 21:43:52 +08:00
1 parent 78ea5ce387
commit 579ce861b6
3 files changed
+55 -5

No files matched your search

@@ -64,4 +64,19 @@ public interface TrainStudentCheckinMapper
* 查询指定课次的学员考勤打卡流水
*/
public List<TrainStudentCheckin> selectCheckinsByLessonId(@org.apache.ibatis.annotations.Param("lessonId") Long lessonId);
/**
* 原子条件更新签退时间 (防并发覆盖首次签退事实)
* 仅当记录存在、sign_in_time 不为空且 sign_out_time 为空时更新成功
*
* @param checkinId 打卡记录ID
* @param signOutTime 签退时间
* @param operator 操作人
* @param updateTime 更新时间
* @return 影响行数 (1: 成功; 0: 条件不满足或已被签退)
*/
public int signOutIfUnsigned(@org.apache.ibatis.annotations.Param("checkinId") Long checkinId,
@org.apache.ibatis.annotations.Param("signOutTime") java.util.Date signOutTime,
@org.apache.ibatis.annotations.Param("operator") String operator,
@org.apache.ibatis.annotations.Param("updateTime") java.util.Date updateTime);
}
@@ -169,12 +169,35 @@ public class AttendanceAppServiceImpl implements AttendanceAppService
AttendanceErrorCode.ATTENDANCE_ALREADY_SIGNED_OUT);
}
existing.setSignOutTime(signOutTime);
existing.setUpdateBy(operator);
existing.setUpdateTime(new Date());
trainStudentCheckinMapper.updateTrainStudentCheckin(existing);
Date now = new Date();
int affectedRows = trainStudentCheckinMapper.signOutIfUnsigned(existing.getCheckinId(), signOutTime, operator, now);
if (affectedRows == 1)
{
existing.setSignOutTime(signOutTime);
existing.setUpdateBy(operator);
existing.setUpdateTime(now);
return existing;
}
return existing;
// affectedRows == 0: 重新读取记录并分类报错
TrainStudentCheckin latest = trainStudentCheckinMapper.selectTrainStudentCheckinByCheckinId(existing.getCheckinId());
if (latest == null)
{
throw new ServiceException(AttendanceErrorCode.ATTENDANCE_NOT_FOUND_MESSAGE, AttendanceErrorCode.ATTENDANCE_NOT_FOUND);
}
if (latest.getSignInTime() == null)
{
throw new ServiceException(AttendanceErrorCode.ATTENDANCE_SIGN_IN_REQUIRED_BEFORE_SIGN_OUT_MESSAGE,
AttendanceErrorCode.ATTENDANCE_SIGN_IN_REQUIRED_BEFORE_SIGN_OUT);
}
if (latest.getSignOutTime() != null)
{
throw new ServiceException(AttendanceErrorCode.ATTENDANCE_ALREADY_SIGNED_OUT_MESSAGE,
AttendanceErrorCode.ATTENDANCE_ALREADY_SIGNED_OUT);
}
throw new ServiceException(AttendanceErrorCode.ATTENDANCE_ALREADY_SIGNED_OUT_MESSAGE,
AttendanceErrorCode.ATTENDANCE_ALREADY_SIGNED_OUT);
}
@Override
@@ -102,4 +102,16 @@
where a.lesson_id = #{lessonId}
order by s.s_name asc, a.checkin_id asc
</select>
<!-- 原子条件更新签退时间 (首次签退事实不可覆盖) -->
<update id="signOutIfUnsigned">
update train_student_checkin
set
sign_out_time = #{signOutTime},
update_by = #{operator},
update_time = #{updateTime}
where checkin_id = #{checkinId}
and sign_in_time is not null
and sign_out_time is null
</update>
</mapper>