From 579ce861b61b9f8a3f2d7a01a1725cb4e7d1d6e5 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Mon, 21 Sep 2026 21:21:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=80=83=E5=8B=A4=EF=BC=9A?= =?UTF-8?q?=E5=8E=9F=E5=AD=90=E7=AD=BE=E9=80=80=E9=98=B2=E6=AD=A2=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E8=A6=86=E7=9B=96=E9=A6=96=E6=AC=A1=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/TrainStudentCheckinMapper.java | 15 +++++++++ .../impl/AttendanceAppServiceImpl.java | 33 ++++++++++++++++--- .../train/TrainStudentCheckinMapper.xml | 12 +++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentCheckinMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentCheckinMapper.java index bd8b5c005..20cc7623f 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentCheckinMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentCheckinMapper.java @@ -64,4 +64,19 @@ public interface TrainStudentCheckinMapper * 查询指定课次的学员考勤打卡流水 */ public List 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); } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java index 087985c03..275cfcbb3 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/AttendanceAppServiceImpl.java @@ -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 diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentCheckinMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentCheckinMapper.xml index 7142143f7..65d72c6b1 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentCheckinMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentCheckinMapper.xml @@ -102,4 +102,16 @@ where a.lesson_id = #{lessonId} order by s.s_name asc, a.checkin_id asc + + + + 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 +