| | |
| | | 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; |
| | |
| | | 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; |
| | | |
| | |
| | | |
| | | @Resource |
| | | private XpFleetOperationsClient fleetOperationsClient; |
| | | @Resource |
| | | private XpOperationUserNotificationClient xpOperationUserNotificationClient; |
| | | @Resource |
| | | private XpAliPushMessageClient xpAliPushMessageClient; |
| | | |
| | | |
| | | |
| | |
| | | |
| | | 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); |
| | | sendAgreementUpdateNotification(now); |
| | | return R.ok(); |
| | | } |
| | | |
| | |
| | | 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()); |
| | |
| | | } |
| | | xpClockIn.setAppUserId(null); |
| | | this.updateById(xpClockIn); |
| | | sendAgreementUpdateNotification(now); |
| | | return R.ok(); |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |