From e19ca74df6f68abcab12fc2505ebab7bdf7af262 Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期四, 09 四月 2026 16:31:22 +0800
Subject: [PATCH] fix:运维APP下班打卡逻辑修改

---
 xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java |  147 ++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 127 insertions(+), 20 deletions(-)

diff --git a/xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java b/xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java
index 95d472a..6f76521 100644
--- a/xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java
+++ b/xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java
@@ -11,6 +11,7 @@
 import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
 import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
 import com.xiaopeng.api.feignClient.pushMessage.XpAliPushMessageClient;
+import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient;
 import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetOperationsClient;
 import com.xiaopeng.api.model.account.SysRole;
 import com.xiaopeng.api.model.account.XpOperationsRole;import com.xiaopeng.api.model.account.XpOperationsUser;
@@ -19,6 +20,7 @@
 import com.xiaopeng.api.model.maintenance.XpClockIn;
 import com.xiaopeng.api.model.maintenance.XpSchedulingDetail;
 import com.xiaopeng.api.model.maintenance.XpShift;
+import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
 import com.xiaopeng.api.vo.account.XpOperationsUserVO;
 import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;import com.xiaopeng.common.core.constant.Constants;
 import com.xiaopeng.common.core.domain.R;
@@ -37,6 +39,8 @@
 import com.xiaopeng.maintenance.vo.ClockInStatisticsDetailsVo;
 import com.xiaopeng.maintenance.vo.ClockInStatisticsVo;
 import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -72,6 +76,10 @@
 @Service
 public class XpClockInServiceImpl extends ServiceImpl<XpClockInMapper, XpClockIn> implements IXpClockInService {
 
+    private static final String CLOCK_ATTENDANCE_LOCK_PREFIX = "CLOCK_ATTENDANCE_";
+    private static final long CLOCK_ATTENDANCE_LOCK_WAIT_SECONDS = 1L;
+    private static final long CLOCK_ATTENDANCE_LOCK_LEASE_SECONDS = 10L;
+
     @Resource
     private XpCheckLocationMapper xpCheckLocationMapper;
     @Resource
@@ -86,11 +94,16 @@
     private IXpSchedulingDetailService xpSchedulingDetailService;
 
     @Resource
+    private XpFleetClient xpFleetClient;
+
+    @Resource
     private XpFleetOperationsClient fleetOperationsClient;
     @Resource
     private XpOperationUserNotificationClient xpOperationUserNotificationClient;
     @Resource
     private XpAliPushMessageClient xpAliPushMessageClient;
+    @Resource
+    private RedissonClient redissonClient;
 
 
 
@@ -183,38 +196,132 @@
             return R.fail("打卡失败");
         }
 
-        // 查询上班打卡信息
-        XpClockIn xpClockIn = xpClockInMapper.selectOne(Wrappers.lambdaQuery(XpClockIn.class)
-                .eq(XpClockIn::getAppUserId, dto.getOperationsUserId())
-                .likeRight(XpClockIn::getClockInTime, LocalDate.now())
+        LocalDate attendanceDate = LocalDate.now();
+        RLock lock = redissonClient.getLock(buildAttendanceLockKey(dto.getOperationsUserId(), attendanceDate));
+        try {
+            boolean isLocked = lock.tryLock(CLOCK_ATTENDANCE_LOCK_WAIT_SECONDS,
+                    CLOCK_ATTENDANCE_LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
+            if (!isLocked) {
+                return R.fail("系统繁忙,请稍后重试");
+            }
+
+            LocalDateTime now = LocalDateTime.now();
+            XpClockIn xpClockIn = findTodayClockInRecord(dto.getOperationsUserId(), attendanceDate);
+            boolean isNewRecord = false;
+            if (Objects.isNull(xpClockIn)) {
+                Long shiftId = resolveClockOutShiftId(dto.getOperationsUserId(), dto.getShiftId(), attendanceDate);
+                if (Objects.isNull(shiftId)) {
+                    return R.fail("班次不存在");
+                }
+                xpClockIn = buildMissingClockInRecord(dto.getOperationsUserId(), shiftId, now);
+                isNewRecord = true;
+            }
+
+            XpShift xpShift = xpShiftMapper.selectById(xpClockIn.getShiftId());
+            if (null == xpShift) {
+                return R.fail("班次不存在");
+            }
+
+            fillClockOutInfo(xpClockIn, dto, xpShift, now, attendanceDate);
+            if (isNewRecord) {
+                this.save(xpClockIn);
+            } else {
+                this.updateById(xpClockIn);
+            }
+            sendAgreementUpdateNotification(now);
+            return R.ok();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("下班打卡获取考勤锁被中断, appUserId={}", dto.getOperationsUserId(), e);
+            return R.fail("系统繁忙,请稍后重试");
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    @Override
+    public boolean createMissingClockInIfAbsent(Long appUserId, Long shiftId, LocalDate attendanceDate) {
+        if (Objects.isNull(appUserId) || Objects.isNull(shiftId) || Objects.isNull(attendanceDate)) {
+            return false;
+        }
+        RLock lock = redissonClient.getLock(buildAttendanceLockKey(appUserId, attendanceDate));
+        try {
+            boolean isLocked = lock.tryLock(CLOCK_ATTENDANCE_LOCK_WAIT_SECONDS,
+                    CLOCK_ATTENDANCE_LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
+            if (!isLocked) {
+                log.info("补上班缺卡获取锁失败, appUserId={}, attendanceDate={}", appUserId, attendanceDate);
+                return false;
+            }
+            XpClockIn existingClockIn = findTodayClockInRecord(appUserId, attendanceDate);
+            if (Objects.nonNull(existingClockIn)) {
+                return false;
+            }
+            XpClockIn missingClockIn = buildMissingClockInRecord(appUserId, shiftId, LocalDateTime.now());
+            return this.save(missingClockIn);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("补上班缺卡获取考勤锁被中断, appUserId={}, attendanceDate={}", appUserId, attendanceDate, e);
+            return false;
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    private XpClockIn findTodayClockInRecord(Long appUserId, LocalDate attendanceDate) {
+        LocalDateTime startTime = attendanceDate.atStartOfDay();
+        LocalDateTime endTime = attendanceDate.plusDays(1).atStartOfDay();
+        return xpClockInMapper.selectOne(Wrappers.lambdaQuery(XpClockIn.class)
+                .eq(XpClockIn::getAppUserId, appUserId)
+                .ge(XpClockIn::getClockInTime, startTime)
+                .lt(XpClockIn::getClockInTime, endTime)
+                .orderByDesc(XpClockIn::getClockInTime)
                 .last("LIMIT 1"));
-        if (Objects.isNull(xpClockIn)) {
-            return R.fail("请先上班打卡");
-        }
+    }
 
-        // 查询班次
-        XpShift xpShift = xpShiftMapper.selectById(xpClockIn.getShiftId());
-        if (null == xpShift) {
-            return R.fail("班次不存在");
+    private Long resolveClockOutShiftId(Long appUserId, Long fallbackShiftId, LocalDate attendanceDate) {
+        R<XpFleet> fleetResult = xpFleetClient.getFleetByOperationsUserId(appUserId);
+        XpFleet xpFleet = Objects.isNull(fleetResult) ? null : fleetResult.getData();
+        if (Objects.nonNull(xpFleet)) {
+            XpSchedulingDetail schedulingDetail = xpSchedulingDetailService.getOne(Wrappers.lambdaQuery(XpSchedulingDetail.class)
+                    .eq(XpSchedulingDetail::getFleetId, xpFleet.getId())
+                    .eq(XpSchedulingDetail::getPlanTime, attendanceDate)
+                    .last("LIMIT 1"));
+            if (Objects.nonNull(schedulingDetail) && Objects.nonNull(schedulingDetail.getShiftId())) {
+                return schedulingDetail.getShiftId();
+            }
         }
-        // 开始打卡时间
-        String endTime = xpShift.getEndTime();
-        LocalDateTime endLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + endTime + ":00");
+        return fallbackShiftId;
+    }
 
-        LocalDateTime now = LocalDateTime.now();
+    private XpClockIn buildMissingClockInRecord(Long appUserId, Long shiftId, LocalDateTime now) {
+        XpClockIn xpClockIn = new XpClockIn();
+        xpClockIn.setAppUserId(appUserId);
+        xpClockIn.setClockInTime(now);
+        xpClockIn.setClockInResult(3);
+        xpClockIn.setShiftId(shiftId);
+        return xpClockIn;
+    }
+
+    private void fillClockOutInfo(XpClockIn xpClockIn, ClockDTO dto, XpShift xpShift,
+                                  LocalDateTime now, LocalDate attendanceDate) {
+        LocalDateTime endLocalDateTime = DateUtils.stringToLocalDateTime(attendanceDate + " " + xpShift.getEndTime() + ":00");
         xpClockIn.setClockOutTime(now);
         xpClockIn.setClockOutLon(dto.getUserLon());
         xpClockIn.setClockOutLat(dto.getUserLat());
         xpClockIn.setClockOutAddress(dto.getClockOutAddress());
-        if (xpClockIn.getClockOutTime().isBefore(endLocalDateTime)) {
+        if (now.isBefore(endLocalDateTime)) {
             xpClockIn.setClockOutResult(2);
         } else {
             xpClockIn.setClockOutResult(1);
         }
-        xpClockIn.setAppUserId(null);
-        this.updateById(xpClockIn);
-        sendAgreementUpdateNotification(now);
-        return R.ok();
+    }
+
+    private String buildAttendanceLockKey(Long appUserId, LocalDate attendanceDate) {
+        return CLOCK_ATTENDANCE_LOCK_PREFIX + appUserId + "_" + attendanceDate;
     }
 
     @Override

--
Gitblit v1.7.1