| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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; |
| | | |
| | |
| | | @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 |
| | |
| | | private IXpSchedulingDetailService xpSchedulingDetailService; |
| | | |
| | | @Resource |
| | | private XpFleetClient xpFleetClient; |
| | | |
| | | @Resource |
| | | private XpFleetOperationsClient fleetOperationsClient; |
| | | @Resource |
| | | private XpOperationUserNotificationClient xpOperationUserNotificationClient; |
| | | @Resource |
| | | private XpAliPushMessageClient xpAliPushMessageClient; |
| | | @Resource |
| | | private RedissonClient redissonClient; |
| | | |
| | | |
| | | |
| | |
| | | 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 |