| | |
| | | package com.xiaopeng.maintenance.service.impl; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.xiaopeng.api.dto.pushMessage.AliPushMessageDTO; |
| | | 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.XpCheckLocation; |
| | | import com.xiaopeng.api.model.account.XpOperationsRole;import com.xiaopeng.api.model.account.XpOperationsUser; |
| | | import com.xiaopeng.api.model.account.XpOperationsUserNotification; |
| | | import com.xiaopeng.api.model.maintenance.XpCheckLocation; |
| | | 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.common.core.enums.NotificationTypeEnum; |
| | | import com.xiaopeng.common.core.utils.DateUtils; |
| | | import com.xiaopeng.common.core.utils.GeodesyUtil; |
| | | import com.xiaopeng.common.core.utils.StringUtils; |
| | | import com.xiaopeng.common.core.utils.poi.ExcelUtil; |
| | | import com.xiaopeng.common.core.web.page.PageInfo; |
| | | import com.xiaopeng.maintenance.dto.ClockDTO; |
| | | import com.xiaopeng.maintenance.dto.ClockInStatisticsDetailsDto; |
| | | import com.xiaopeng.maintenance.dto.ClockInStatisticsDto; |
| | | import com.xiaopeng.maintenance.dto.VerifyRangeDTO; |
| | | import com.xiaopeng.maintenance.dto.*; |
| | | import com.xiaopeng.maintenance.mapper.XpCheckLocationMapper; |
| | | import com.xiaopeng.maintenance.mapper.XpClockInMapper; |
| | | import com.xiaopeng.maintenance.mapper.XpShiftMapper; |
| | | import com.xiaopeng.maintenance.service.IXpClockInService; |
| | | import com.xiaopeng.maintenance.service.IXpSchedulingDetailService; |
| | | 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; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | import java.time.LocalDateTime; |
| | | import java.time.LocalTime; |
| | | import java.util.List; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.ThreadPoolExecutor; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | @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 |
| | |
| | | |
| | | @Resource |
| | | private XpOperationUserClient xpOperationUserClient; |
| | | |
| | | @Resource |
| | | private IXpSchedulingDetailService xpSchedulingDetailService; |
| | | |
| | | @Resource |
| | | private XpFleetClient xpFleetClient; |
| | | |
| | | @Resource |
| | | private XpFleetOperationsClient fleetOperationsClient; |
| | | @Resource |
| | | private XpOperationUserNotificationClient xpOperationUserNotificationClient; |
| | | @Resource |
| | | private XpAliPushMessageClient xpAliPushMessageClient; |
| | | @Resource |
| | | private RedissonClient redissonClient; |
| | | |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public R<String> clockIn(ClockDTO dto) { |
| | | public R<Integer> clockIn(ClockDTO dto) { |
| | | |
| | | XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId()); |
| | | if (null == xpCheckLocation) { |
| | | return R.fail("打卡地址不存在"); |
| | | } |
| | | if (xpCheckLocation.getStatus() == 0) { |
| | | return R.fail("打卡地址已禁用"); |
| | | } |
| | | |
| | | Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat()); |
| | | if (null != distance) { |
| | |
| | | |
| | | XpClockIn xpClockIn = new XpClockIn(); |
| | | xpClockIn.setAppUserId(dto.getOperationsUserId()); |
| | | xpClockIn.setClockInTime(LocalDateTime.now()); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | xpClockIn.setClockInTime(now); |
| | | xpClockIn.setClockInLon(dto.getUserLon()); |
| | | xpClockIn.setClockInLat(dto.getUserLat()); |
| | | xpClockIn.setShiftId(dto.getShiftId()); |
| | |
| | | xpClockIn.setClockInResult(1); |
| | | } |
| | | this.save(xpClockIn); |
| | | return R.ok(); |
| | | sendAgreementUpdateNotification(now); |
| | | return R.ok(xpClockIn.getClockInResult()); |
| | | } |
| | | |
| | | @Override |
| | | public R<String> clockOut(ClockDTO dto) { |
| | | public R<Integer> clockOut(ClockDTO dto) { |
| | | |
| | | XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId()); |
| | | if (null == xpCheckLocation) { |
| | | return R.fail("打卡地址不存在"); |
| | | } |
| | | if (xpCheckLocation.getStatus() == 0) { |
| | | return R.fail("打卡地址已禁用"); |
| | | } |
| | | Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat()); |
| | | if (null != distance) { |
| | | Double distance1 = distance.get("WGS84"); |
| | |
| | | return R.fail("打卡失败"); |
| | | } |
| | | |
| | | // 查询上班打卡信息 |
| | | XpClockIn xpClockIn = xpClockInMapper.selectOne(Wrappers.lambdaQuery(XpClockIn.class) |
| | | .eq(XpClockIn::getAppUserId, dto.getOperationsUserId()) |
| | | .likeRight(XpClockIn::getClockInTime, LocalDate.now()) |
| | | .last("LIMIT 1")); |
| | | if (Objects.isNull(xpClockIn)) { |
| | | return R.fail("请先上班打卡"); |
| | | } |
| | | 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("系统繁忙,请稍后重试"); |
| | | } |
| | | |
| | | // 查询班次 |
| | | XpShift xpShift = xpShiftMapper.selectById(xpClockIn.getShiftId()); |
| | | if (null == xpShift) { |
| | | return R.fail("班次不存在"); |
| | | } |
| | | // 开始打卡时间 |
| | | String endTime = xpShift.getEndTime(); |
| | | LocalDateTime endLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + endTime + ":00"); |
| | | 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; |
| | | } |
| | | |
| | | xpClockIn.setClockOutTime(LocalDateTime.now()); |
| | | xpClockIn.setClockOutLon(dto.getUserLon()); |
| | | xpClockIn.setClockOutLat(dto.getUserLat()); |
| | | xpClockIn.setClockOutAddress(dto.getClockOutAddress()); |
| | | if (xpClockIn.getClockOutTime().isBefore(endLocalDateTime)) { |
| | | xpClockIn.setClockInResult(2); |
| | | } else { |
| | | xpClockIn.setClockInResult(1); |
| | | 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(xpClockIn.getClockOutResult()); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.error("下班打卡获取考勤锁被中断, appUserId={}", dto.getOperationsUserId(), e); |
| | | return R.fail("系统繁忙,请稍后重试"); |
| | | } finally { |
| | | if (lock.isHeldByCurrentThread()) { |
| | | lock.unlock(); |
| | | } |
| | | } |
| | | xpClockIn.setAppUserId(null); |
| | | this.updateById(xpClockIn); |
| | | return R.ok(); |
| | | } |
| | | |
| | | @Override |
| | | public Boolean checkWorkStatus(Long appUserId) { |
| | | 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")); |
| | | } |
| | | |
| | | 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(); |
| | | } |
| | | } |
| | | return fallbackShiftId; |
| | | } |
| | | |
| | | 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 (now.isBefore(endLocalDateTime)) { |
| | | xpClockIn.setClockOutResult(2); |
| | | } else { |
| | | xpClockIn.setClockOutResult(1); |
| | | } |
| | | xpClockIn.setAppUserId(null); |
| | | this.updateById(xpClockIn); |
| | | sendAgreementUpdateNotification(now); |
| | | } |
| | | |
| | | private String buildAttendanceLockKey(Long appUserId, LocalDate attendanceDate) { |
| | | return CLOCK_ATTENDANCE_LOCK_PREFIX + appUserId + "_" + attendanceDate; |
| | | } |
| | | |
| | | @Override |
| | | public Integer checkWorkStatus(Long appUserId) { |
| | | if (appUserId == null) { |
| | | log.warn("检查上班状态失败: appUserId 为空"); |
| | | return false; |
| | | return 1; |
| | | } |
| | | |
| | | try { |
| | |
| | | LocalDateTime endTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); // 23:59:59 |
| | | |
| | | // 2. 查询当天打卡记录 |
| | | List<XpClockIn> records = baseMapper.selectTodayClockInRecords( |
| | | appUserId, startTime, endTime |
| | | ); |
| | | |
| | | XpClockIn xpClockIn = baseMapper.selectOne(new LambdaQueryWrapper<XpClockIn>() |
| | | .eq(XpClockIn::getAppUserId, appUserId) |
| | | .between(XpClockIn::getClockInTime, startTime, endTime) |
| | | .last("limit 1")); |
| | | // 3. 判断上班状态 |
| | | if (records == null || records.isEmpty()) { |
| | | // 无打卡记录 = 未上班 |
| | | log.debug("用户 {} 今日无打卡记录, workFlag=false", appUserId); |
| | | return false; |
| | | } else if (records.size() == 1) { |
| | | // 1条记录 = 已上班未下班 |
| | | log.debug("用户 {} 今日打卡1次, workFlag=true", appUserId); |
| | | return true; |
| | | // 有上班打卡状态,无下班打卡状态 |
| | | if (Objects.isNull(xpClockIn)) { |
| | | return 1; |
| | | } else if (Objects.nonNull(xpClockIn.getClockInResult()) && Objects.isNull(xpClockIn.getClockOutResult())) { |
| | | return 2; |
| | | } else { |
| | | // 2条及以上记录 = 已下班 |
| | | log.debug("用户 {} 今日打卡{}次, workFlag=false", appUserId, records.size()); |
| | | return false; |
| | | return 3; |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("检查用户上班状态异常, appUserId={}", appUserId, e); |
| | | // 异常情况返回 false |
| | | return false; |
| | | return 1; |
| | | } |
| | | } |
| | | |
| | |
| | | @Override |
| | | public PageInfo<ClockInStatisticsDto> getClockInStatistics(ClockInStatisticsVo query) { |
| | | PageInfo<ClockInStatisticsDto> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); |
| | | List<Long> appUserIds = new ArrayList<>(); |
| | | List<Long> appUserIds1 = new ArrayList<>(); |
| | | List<Long> appUserIds2 = new ArrayList<>(); |
| | | List<Long> appUserIds3 = new ArrayList<>(); |
| | | //处理查询条件 |
| | | if(StringUtils.isNotEmpty(query.getName())){ |
| | | R<List<XpOperationsUser>> listR = xpOperationUserClient.listByName(query.getName()); |
| | | if(listR.getCode() == Constants.SUCCESS){ |
| | | List<Long> collect = listR.getData().stream().map(XpOperationsUser::getId).collect(Collectors.toList()); |
| | | appUserIds.addAll(collect); |
| | | appUserIds1.addAll(collect); |
| | | } |
| | | appUserIds.add(0L); |
| | | appUserIds1.add(0L); |
| | | } |
| | | if(null != query.getRoleId()){ |
| | | R<List<XpOperationsRole>> roleByRoleId = xpOperationUserClient.getXpOperationsRoleByRoleId(query.getRoleId()); |
| | | if(roleByRoleId.getCode() == Constants.SUCCESS){ |
| | | List<Long> collect = roleByRoleId.getData().stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toList()); |
| | | appUserIds.addAll(collect); |
| | | appUserIds2.addAll(collect); |
| | | } |
| | | appUserIds.add(0L); |
| | | appUserIds2.add(0L); |
| | | } |
| | | if(StringUtils.isNotEmpty(query.getName()) && null != query.getRoleId()){ |
| | | for (Long l : appUserIds1) { |
| | | if(appUserIds2.contains(l)){ |
| | | appUserIds3.add(l); |
| | | } |
| | | } |
| | | }else{ |
| | | appUserIds3.addAll(appUserIds1); |
| | | appUserIds3.addAll(appUserIds2); |
| | | } |
| | | |
| | | String startTime = ""; |
| | | String endTime = ""; |
| | | if(StringUtils.isNotEmpty(query.getStatisticDate())){ |
| | | String statisticDate = query.getStatisticDate(); |
| | | startTime = statisticDate.split(" - ")[0]; |
| | | endTime = statisticDate.split(" - ")[1]; |
| | | startTime = statisticDate.split(" - ")[0].split(" ")[0]; |
| | | endTime = statisticDate.split(" - ")[1].split(" ")[0]; |
| | | } |
| | | //查询数据 |
| | | List<ClockInStatisticsDto> clockInStatistics = this.baseMapper.getClockInStatistics(pageInfo, appUserIds, startTime, endTime); |
| | | List<ClockInStatisticsDto> clockInStatistics = this.baseMapper.getClockInStatistics(pageInfo, appUserIds3, startTime, endTime); |
| | | List<String> appUserIdList = clockInStatistics.stream().map(ClockInStatisticsDto::getAppUserId).collect(Collectors.toList()); |
| | | List<Long> ids = appUserIdList.stream().map(Long::parseLong).collect(Collectors.toList()); |
| | | List<XpOperationsUserWithRoleVO> data = xpOperationUserClient.listByIdsWithRole(ids).getData(); |
| | |
| | | clockInStatistic.setRoleName(roleName); |
| | | } |
| | | } |
| | | Integer shouldClockInDays = clockInStatistic.getShouldClockInDays(); |
| | | Integer clockInDays = clockInStatistic.getClockInDays(); |
| | | clockInStatistic.setNotClockInDays(shouldClockInDays - clockInDays); |
| | | } |
| | | pageInfo.setRecords(clockInStatistics); |
| | | return pageInfo; |
| | |
| | | @Override |
| | | public void exportClockInStatistics(ClockInStatisticsVo query, HttpServletResponse response) { |
| | | PageInfo<ClockInStatisticsDto> pageInfo = new PageInfo<>(0, 10000); |
| | | List<Long> appUserIds = new ArrayList<>(); |
| | | List<Long> appUserIds1 = new ArrayList<>(); |
| | | List<Long> appUserIds2 = new ArrayList<>(); |
| | | List<Long> appUserIds3 = new ArrayList<>(); |
| | | //处理查询条件 |
| | | if(StringUtils.isNotEmpty(query.getName())){ |
| | | R<List<XpOperationsUser>> listR = xpOperationUserClient.listByName(query.getName()); |
| | | if(listR.getCode() == Constants.SUCCESS){ |
| | | List<Long> collect = listR.getData().stream().map(XpOperationsUser::getId).collect(Collectors.toList()); |
| | | appUserIds.addAll(collect); |
| | | appUserIds1.addAll(collect); |
| | | } |
| | | appUserIds.add(0L); |
| | | appUserIds1.add(0L); |
| | | } |
| | | if(null != query.getRoleId()){ |
| | | R<List<XpOperationsRole>> roleByRoleId = xpOperationUserClient.getXpOperationsRoleByRoleId(query.getRoleId()); |
| | | if(roleByRoleId.getCode() == Constants.SUCCESS){ |
| | | List<Long> collect = roleByRoleId.getData().stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toList()); |
| | | appUserIds.addAll(collect); |
| | | appUserIds2.addAll(collect); |
| | | } |
| | | appUserIds.add(0L); |
| | | appUserIds2.add(0L); |
| | | } |
| | | if(StringUtils.isNotEmpty(query.getName()) && null != query.getRoleId()){ |
| | | for (Long l : appUserIds1) { |
| | | if(appUserIds2.contains(l)){ |
| | | appUserIds3.add(l); |
| | | } |
| | | } |
| | | }else{ |
| | | appUserIds3.addAll(appUserIds1); |
| | | appUserIds3.addAll(appUserIds2); |
| | | } |
| | | String startTime = ""; |
| | | String endTime = ""; |
| | | if(StringUtils.isNotEmpty(query.getStatisticDate())){ |
| | | String statisticDate = query.getStatisticDate(); |
| | | startTime = statisticDate.split(" - ")[0]; |
| | | endTime = statisticDate.split(" - ")[1]; |
| | | startTime = statisticDate.split(" - ")[0].split(" ")[0]; |
| | | endTime = statisticDate.split(" - ")[1].split(" ")[0]; |
| | | } |
| | | //查询数据 |
| | | List<ClockInStatisticsDto> clockInStatistics = this.baseMapper.getClockInStatistics(pageInfo, appUserIds, startTime, endTime); |
| | | List<ClockInStatisticsDto> clockInStatistics = this.baseMapper.getClockInStatistics(pageInfo, appUserIds3, startTime, endTime); |
| | | List<String> appUserIdList = clockInStatistics.stream().map(ClockInStatisticsDto::getAppUserId).collect(Collectors.toList()); |
| | | List<Long> ids = appUserIdList.stream().map(Long::parseLong).collect(Collectors.toList()); |
| | | List<XpOperationsUserWithRoleVO> data = xpOperationUserClient.listByIdsWithRole(ids).getData(); |
| | |
| | | clockInStatistic.setRoleName(roleName); |
| | | } |
| | | } |
| | | Integer shouldClockInDays = clockInStatistic.getShouldClockInDays(); |
| | | Integer clockInDays = clockInStatistic.getClockInDays(); |
| | | clockInStatistic.setNotClockInDays(shouldClockInDays - clockInDays); |
| | | } |
| | | |
| | | ExcelUtil<ClockInStatisticsDto> excel = new ExcelUtil<ClockInStatisticsDto>(ClockInStatisticsDto.class); |
| | |
| | | * @return |
| | | */ |
| | | @Override |
| | | public PageInfo<ClockInStatisticsDetailsDto> getClockInStatisticsDetails(ClockInStatisticsDetailsVo query) { |
| | | public GetClockInStatisticsDetailsDto getClockInStatisticsDetails(ClockInStatisticsDetailsVo query) { |
| | | GetClockInStatisticsDetailsDto dto = new GetClockInStatisticsDetailsDto(); |
| | | PageInfo<ClockInStatisticsDetailsDto> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); |
| | | |
| | | String startTime = ""; |
| | | String endTime = ""; |
| | | if(StringUtils.isNotEmpty(query.getStatisticDate())){ |
| | | String statisticDate = query.getStatisticDate(); |
| | | startTime = statisticDate.split(" - ")[0]; |
| | | endTime = statisticDate.split(" - ")[1]; |
| | | startTime = statisticDate.split(" - ")[0].split(" ")[0]; |
| | | endTime = statisticDate.split(" - ")[1].split(" ")[0]; |
| | | } |
| | | List<ClockInStatisticsDetailsDto> clockInStatisticsDetails = this.baseMapper.getClockInStatisticsDetails(pageInfo, query.getAppUserId(), startTime, endTime); |
| | | List<XpOperationsUserWithRoleVO> data = xpOperationUserClient.listByIdsWithRole(Arrays.asList(query.getAppUserId())).getData(); |
| | |
| | | clockInStatistic.setRoleName(xpOperationsUserWithRoleVO.getRoles().stream().map(SysRole::getRoleName).collect(Collectors.joining(";"))); |
| | | } |
| | | pageInfo.setRecords(clockInStatisticsDetails); |
| | | return pageInfo; |
| | | dto.setList(pageInfo); |
| | | XpOperationsUser xpOperationsUser = xpOperationUserClient.queryById(query.getAppUserId()).getData(); |
| | | List<Long> longList = fleetOperationsClient.getFleetIdsByUserId(xpOperationsUser.getId()).getData(); |
| | | List<XpSchedulingDetail> list = xpSchedulingDetailService.list(new QueryWrapper<XpSchedulingDetail>().eq("fleet_id", longList.get(0)).between(StringUtils.isNotEmpty(startTime), "plan_time", startTime, endTime)); |
| | | dto.setShouldClockInCount(list.size() * 2); |
| | | dto.setNormalClockInCount(this.baseMapper.getClockInStatisticsDetailsCount(1, query.getAppUserId(), startTime, endTime)); |
| | | dto.setLateClockInCount(this.baseMapper.getClockInStatisticsDetailsCount(2, query.getAppUserId(), startTime, endTime)); |
| | | dto.setMissingClockInCount(this.baseMapper.getClockInStatisticsDetailsCount(3, query.getAppUserId(), startTime, endTime)); |
| | | return dto; |
| | | } |
| | | |
| | | |
| | |
| | | String endTime = ""; |
| | | if(StringUtils.isNotEmpty(query.getStatisticDate())){ |
| | | String statisticDate = query.getStatisticDate(); |
| | | startTime = statisticDate.split(" - ")[0]; |
| | | endTime = statisticDate.split(" - ")[1]; |
| | | startTime = statisticDate.split(" - ")[0].split(" ")[0]; |
| | | endTime = statisticDate.split(" - ")[1].split(" ")[0]; |
| | | } |
| | | List<ClockInStatisticsDetailsDto> clockInStatisticsDetails = this.baseMapper.getClockInStatisticsDetails(pageInfo, query.getAppUserId(), startTime, endTime); |
| | | List<XpOperationsUserWithRoleVO> data = xpOperationUserClient.listByIdsWithRole(Arrays.asList(query.getAppUserId())).getData(); |
| | |
| | | ExcelUtil<ClockInStatisticsDetailsDto> excel = new ExcelUtil<ClockInStatisticsDetailsDto>(ClockInStatisticsDetailsDto.class); |
| | | excel.exportExcel(response, clockInStatisticsDetails, "打卡统计明细"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 发送协议更新通知 |
| | | * @param clockTime 打卡时间 |
| | | */ |
| | | private void sendAgreementUpdateNotification(LocalDateTime clockTime) { |
| | | // 异步处理,避免阻塞主流程 |
| | | ThreadPoolExecutor executor = new ThreadPoolExecutor( |
| | | 1, 1, 0L, TimeUnit.MILLISECONDS, |
| | | new LinkedBlockingQueue<Runnable>() |
| | | ); |
| | | |
| | | executor.execute(() -> { |
| | | try { |
| | | // 1. 获取所有启用状态的运维用户 |
| | | R<List<XpOperationsUser>> result = xpOperationUserClient.listAllEnabled(); |
| | | if (result == null || result.getCode() != 200 || result.getData() == null || result.getData().isEmpty()) { |
| | | log.warn("没有需要通知的启用状态用户"); |
| | | return; |
| | | } |
| | | |
| | | List<XpOperationsUser> enabledUsers = result.getData(); |
| | | log.info("打卡成功通知,共需通知 {} 位用户", enabledUsers.size()); |
| | | |
| | | // 2. 构建通知内容 |
| | | String format = clockTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); |
| | | String message = String.format("你已于%s打卡成功!", format); |
| | | String bodyJson = buildAgreementPushBodyJson(message); |
| | | |
| | | // 3. 构建并保存通知记录 |
| | | List<XpOperationsUserNotification> notifications = enabledUsers.stream() |
| | | .map(user -> buildAgreementNotification(message, bodyJson, user)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 批量保存通知记录 |
| | | xpOperationUserNotificationClient.addNotificationBatch(notifications); |
| | | log.info("协议更新通知记录保存成功,共 {} 条", notifications.size()); |
| | | |
| | | // 4. 按设备类型分组并批量推送 |
| | | pushMessageByDeviceType(enabledUsers, message); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("发送协议更新通知失败", e); |
| | | } |
| | | }); |
| | | |
| | | executor.shutdown(); |
| | | } |
| | | |
| | | /** |
| | | * 构建推送 body JSON |
| | | * @param message 消息内容 |
| | | * @return JSON 字符串 |
| | | */ |
| | | private String buildAgreementPushBodyJson(String message) { |
| | | JSONObject body = new JSONObject(); |
| | | body.put("content", message); |
| | | body.put("strategy", NotificationTypeEnum.CLOCK_IN_SUCCESS.getCode()); // 11 |
| | | body.put("type", 0); |
| | | body.put("id", 0); |
| | | return body.toJSONString(); |
| | | } |
| | | |
| | | /** |
| | | * 构建通知实体 |
| | | * @param message 消息内容 |
| | | * @param bodyJson 推送body内容 |
| | | * @param user 运维用户 |
| | | * @return 通知实体 |
| | | */ |
| | | private XpOperationsUserNotification buildAgreementNotification(String message, String bodyJson, XpOperationsUser user) { |
| | | XpOperationsUserNotification notification = new XpOperationsUserNotification(); |
| | | notification.setNotificationType(NotificationTypeEnum.CLOCK_IN_SUCCESS.getCode()); // 11 |
| | | notification.setNotificationContent(message); |
| | | notification.setOperationsUserId(user.getId()); |
| | | notification.setDataId(0L); |
| | | notification.setReadStatus(0); // 未读 |
| | | notification.setNotificationExt(bodyJson); |
| | | return notification; |
| | | } |
| | | |
| | | /** |
| | | * 按设备类型分组推送消息 |
| | | * @param users 用户列表 |
| | | * @param message 消息内容 |
| | | */ |
| | | private void pushMessageByDeviceType(List<XpOperationsUser> users, String message) { |
| | | // 按设备类型分组 (1=Android, 2=iOS) |
| | | Map<Integer, List<XpOperationsUser>> usersByDeviceType = users.stream() |
| | | .filter(u -> u.getDeviceType() != null && u.getPhoneNumber() != null) |
| | | .collect(Collectors.groupingBy(XpOperationsUser::getDeviceType)); |
| | | |
| | | // 分别推送 Android 和 iOS |
| | | for (Map.Entry<Integer, List<XpOperationsUser>> entry : usersByDeviceType.entrySet()) { |
| | | Integer deviceType = entry.getKey(); |
| | | List<XpOperationsUser> deviceUsers = entry.getValue(); |
| | | |
| | | // 按 1000 个手机号一批进行推送 |
| | | List<List<XpOperationsUser>> batches = partition(deviceUsers, 1000); |
| | | |
| | | for (List<XpOperationsUser> batch : batches) { |
| | | String accounts = batch.stream() |
| | | .map(XpOperationsUser::getPhoneNumber) |
| | | .collect(Collectors.joining(",")); |
| | | |
| | | AliPushMessageDTO pushDTO = buildPushMessageDTO(deviceType, accounts, message); |
| | | |
| | | try { |
| | | xpAliPushMessageClient.pushMessage(pushDTO); |
| | | log.info("打卡成功推送成功,设备类型={},用户数={}", |
| | | deviceType == 1 ? "Android" : "iOS", batch.size()); |
| | | } catch (Exception e) { |
| | | log.error("打卡成功推送失败,设备类型={}", deviceType, e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 将列表按指定大小分批 |
| | | * @param list 原始列表 |
| | | * @param batchSize 批次大小 |
| | | * @return 分批后的列表 |
| | | */ |
| | | private <T> List<List<T>> partition(List<T> list, int batchSize) { |
| | | List<List<T>> batches = new ArrayList<>(); |
| | | for (int i = 0; i < list.size(); i += batchSize) { |
| | | batches.add(list.subList(i, Math.min(i + batchSize, list.size()))); |
| | | } |
| | | return batches; |
| | | } |
| | | |
| | | /** |
| | | * 构建推送消息 DTO |
| | | * @param deviceType 设备类型 |
| | | * @param accounts 手机号(逗号分隔) |
| | | * @param message 消息内容 |
| | | * @return 推送 DTO |
| | | */ |
| | | private AliPushMessageDTO buildPushMessageDTO(Integer deviceType, String accounts, String message) { |
| | | AliPushMessageDTO dto = new AliPushMessageDTO(); |
| | | dto.setDeviceType(deviceType); |
| | | dto.setAccount(accounts); |
| | | dto.setTitle("打卡成功通知"); |
| | | dto.setBody(buildAgreementPushBodyJson(message)); |
| | | dto.setPushType("MESSAGE"); |
| | | return dto; |
| | | } |
| | | |
| | | } |