From 2128179bbec4bc1a9b8f6957c2a9e0662d9ad92b Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期日, 15 三月 2026 15:52:05 +0800
Subject: [PATCH] Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi

---
 xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java |  175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 172 insertions(+), 3 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 54fdd82..b05f363 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
@@ -1,21 +1,28 @@
 package com.xiaopeng.maintenance.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.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.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.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;
@@ -36,6 +43,7 @@
 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;
@@ -46,6 +54,9 @@
 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;
 
@@ -76,6 +87,10 @@
 
     @Resource
     private XpFleetOperationsClient fleetOperationsClient;
+    @Resource
+    private XpOperationUserNotificationClient xpOperationUserNotificationClient;
+    @Resource
+    private XpAliPushMessageClient xpAliPushMessageClient;
 
 
 
@@ -132,7 +147,8 @@
 
         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());
@@ -143,6 +159,7 @@
             xpClockIn.setClockInResult(1);
         }
         this.save(xpClockIn);
+        sendAgreementUpdateNotification(now);
         return R.ok();
     }
 
@@ -184,7 +201,8 @@
         String endTime = xpShift.getEndTime();
         LocalDateTime endLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + endTime + ":00");
 
-        xpClockIn.setClockOutTime(LocalDateTime.now());
+        LocalDateTime now = LocalDateTime.now();
+        xpClockIn.setClockOutTime(now);
         xpClockIn.setClockOutLon(dto.getUserLon());
         xpClockIn.setClockOutLat(dto.getUserLat());
         xpClockIn.setClockOutAddress(dto.getClockOutAddress());
@@ -195,6 +213,7 @@
         }
         xpClockIn.setAppUserId(null);
         this.updateById(xpClockIn);
+        sendAgreementUpdateNotification(now);
         return R.ok();
     }
 
@@ -441,4 +460,154 @@
         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 message = String.format("你已于%s打卡成功!", clockTime);
+                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("NOTICE");
+        return dto;
+    }
+
 }

--
Gitblit v1.7.1