From 628d8fd8c409c2276a1dfdbc20ef20b47416efd8 Mon Sep 17 00:00:00 2001 From: lzh Date: Fri, 9 Jan 2026 17:39:34 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E3=80=90ops=E3=80=91=E4=BF=9D?= =?UTF-8?q?=E6=B4=81=E5=91=98=E4=B8=9A=E5=8A=A1=E9=A2=86=E5=9F=9F=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/cleaner/CleanerDomainService.java | 370 ++++++++++++++++++ .../cleaner/CleanerDomainServiceImpl.java | 325 +++++++++++++++ 2 files changed, 695 insertions(+) create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainService.java create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainServiceImpl.java diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainService.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainService.java new file mode 100644 index 0000000..0641685 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainService.java @@ -0,0 +1,370 @@ +package com.viewsh.module.ops.environment.domain.cleaner; + +import com.viewsh.module.ops.core.dispatch.model.AssigneeStatus; +import com.viewsh.module.ops.environment.integration.adapter.CleanerAssigneeStatusAdapter; +import com.viewsh.module.ops.enums.PriorityEnum; + +import java.util.List; + +/** + * 保洁员领域服务接口 + *

+ * 职责: + * 1. 保洁员状态管理 + * 2. 保洁员可用性查询 + * 3. 保洁员工作量统计 + *

+ * 设计说明: + * - 业务层领域服务,处理保洁业务特定逻辑 + * - 通过 {@link AssigneeStatus} 接口与通用调度引擎解耦 + * + * @author lzh + */ +public interface CleanerDomainService { + + // ==================== 状态查询 ==================== + + /** + * 获取保洁员状态(通用接口) + * + * @param userId 保洁员ID + * @return 通用执行人状态接口 + */ + AssigneeStatus getAssigneeStatus(Long userId); + + /** + * 获取保洁员当前工作量 + * + * @param userId 保洁员ID + * @return 工作量信息 + */ + CleanerWorkload getWorkload(Long userId); + + /** + * 检查保洁员是否可用(可接新单) + * + * @param userId 保洁员ID + * @return 是否可用 + */ + boolean isAvailable(Long userId); + + /** + * 检查保洁员是否在线 + * + * @param userId 保洁员ID + * @return 是否在线 + */ + boolean isOnline(Long userId); + + // ==================== 保洁员查询 ==================== + + /** + * 查询指定区域的保洁员列表 + * + * @param areaId 区域ID + * @return 保洁员列表(通用状态接口) + */ + List listByArea(Long areaId); + + /** + * 查询指定区域的可用保洁员 + * + * @param areaId 区域ID + * @return 可用保洁员列表 + */ + List listAvailableByArea(Long areaId); + + /** + * 查询附近的保洁员 + * + * @param areaId 中心区域ID + * @param radius 搜索半径 + * @return 附近保洁员列表 + */ + List listNearby(Long areaId, Integer radius); + + /** + * 根据条件推荐保洁员 + * + * @param areaId 区域ID + * @param priority 工单优先级 + * @param limit 最多返回数量 + * @return 推荐的保洁员列表 + */ + List recommend(Long areaId, PriorityEnum priority, int limit); + + /** + * 获取最佳保洁员 + * + * @param areaId 区域ID + * @param priority 工单优先级 + * @return 推荐结果,无合适保洁员返回null + */ + CleanerRecommendation getBestCleaner(Long areaId, PriorityEnum priority); + + // ==================== 工作量统计 ==================== + + /** + * 统计保洁员今日完成任务数 + * + * @param userId 保洁员ID + * @return 完成任务数 + */ + int countCompletedToday(Long userId); + + /** + * 统计保洁员今日工作时长(分钟) + * + * @param userId 保洁员ID + * @return 工作时长 + */ + int getWorkMinutesToday(Long userId); + + /** + * 获取保洁员当前等待任务数 + * + * @param userId 保洁员ID + * @return 等待任务数 + */ + int getWaitingTaskCount(Long userId); + + // ==================== 状态更新(仅业务层调用)==================== + + /** + * 设置保洁员当前工单 + * + * @param userId 保洁员ID + * @param orderId 工单ID + * @param orderCode 工单编号 + */ + void setCurrentWorkOrder(Long userId, Long orderId, String orderCode); + + /** + * 清理保洁员当前工单 + * + * @param userId 保洁员ID + */ + void clearCurrentWorkOrder(Long userId); + + /** + * 更新保洁员当前位置 + * + * @param userId 保洁员ID + * @param areaId 区域ID + * @param areaName 区域名称 + */ + void updateCurrentArea(Long userId, Long areaId, String areaName); + + // ==================== 内部类 ==================== + + /** + * 保洁员工作量信息 + */ + class CleanerWorkload { + /** + * 保洁员ID + */ + private Long userId; + + /** + * 保洁员姓名 + */ + private String userName; + + /** + * 当前任务数 + */ + private int currentTaskCount; + + /** + * 等待任务数 + */ + private int waitingTaskCount; + + /** + * 今日完成任务数 + */ + private int completedToday; + + /** + * 今日工作时长(分钟) + */ + private int workMinutesToday; + + public CleanerWorkload() { + } + + public CleanerWorkload(Long userId, String userName, int currentTaskCount, + int waitingTaskCount, int completedToday, int workMinutesToday) { + this.userId = userId; + this.userName = userName; + this.currentTaskCount = currentTaskCount; + this.waitingTaskCount = waitingTaskCount; + this.completedToday = completedToday; + this.workMinutesToday = workMinutesToday; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getCurrentTaskCount() { + return currentTaskCount; + } + + public void setCurrentTaskCount(int currentTaskCount) { + this.currentTaskCount = currentTaskCount; + } + + public int getWaitingTaskCount() { + return waitingTaskCount; + } + + public void setWaitingTaskCount(int waitingTaskCount) { + this.waitingTaskCount = waitingTaskCount; + } + + public int getCompletedToday() { + return completedToday; + } + + public void setCompletedToday(int completedToday) { + this.completedToday = completedToday; + } + + public int getWorkMinutesToday() { + return workMinutesToday; + } + + public void setWorkMinutesToday(int workMinutesToday) { + this.workMinutesToday = workMinutesToday; + } + + /** + * 获取总任务数(当前+等待) + */ + public int getTotalTaskCount() { + return currentTaskCount + waitingTaskCount; + } + + /** + * 是否忙碌 + */ + public boolean isBusy() { + return currentTaskCount > 0; + } + } + + /** + * 保洁员推荐结果 + */ + class CleanerRecommendation { + /** + * 保洁员ID + */ + private Long userId; + + /** + * 保洁员姓名 + */ + private String userName; + + /** + * 推荐分数(0-100) + */ + private int score; + + /** + * 推荐理由 + */ + private String reason; + + /** + * 电量 + */ + private Integer batteryLevel; + + /** + * 状态 + */ + private String status; + + public CleanerRecommendation() { + } + + public CleanerRecommendation(Long userId, String userName, int score, String reason) { + this.userId = userId; + this.userName = userName; + this.score = score; + this.reason = reason; + } + + public static CleanerRecommendation of(Long userId, String userName, int score, String reason) { + return new CleanerRecommendation(userId, userName, score, reason); + } + + public static CleanerRecommendation none() { + return null; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public Integer getBatteryLevel() { + return batteryLevel; + } + + public void setBatteryLevel(Integer batteryLevel) { + this.batteryLevel = batteryLevel; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainServiceImpl.java new file mode 100644 index 0000000..e770283 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/domain/cleaner/CleanerDomainServiceImpl.java @@ -0,0 +1,325 @@ +package com.viewsh.module.ops.environment.domain.cleaner; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.viewsh.module.ops.api.queue.OrderQueueDTO; +import com.viewsh.module.ops.api.queue.OrderQueueService; +import com.viewsh.module.ops.core.dispatch.model.AssigneeStatus; +import com.viewsh.module.ops.environment.dal.dataobject.cleaner.OpsCleanerStatusDO; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import com.viewsh.module.ops.enums.CleanerStatusEnum; +import com.viewsh.module.ops.enums.PriorityEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import com.viewsh.module.ops.environment.integration.adapter.CleanerAssigneeStatusAdapter; +import com.viewsh.module.ops.environment.service.cleaner.CleanerStatusService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 保洁员领域服务实现 + *

+ * 职责: + * 1. 保洁员状态管理 + * 2. 保洁员可用性查询 + * 3. 保洁员工作量统计 + *

+ * 设计说明: + * - 业务层领域服务,处理保洁业务特定逻辑 + * - 通过 {@link AssigneeStatus} 接口与通用调度引擎解耦 + * - 使用 {@link CleanerAssigneeStatusAdapter} 适配保洁员状态到通用接口 + * + * @author lzh + */ +@Slf4j +@Service +public class CleanerDomainServiceImpl implements CleanerDomainService { + + @Resource + private CleanerStatusService cleanerStatusService; + + @Resource + private OrderQueueService orderQueueService; + + @Resource + private OpsOrderMapper orderMapper; + + // ==================== 状态查询 ==================== + + @Override + public AssigneeStatus getAssigneeStatus(Long userId) { + OpsCleanerStatusDO cleanerStatus = cleanerStatusService.getStatus(userId); + if (cleanerStatus == null) { + return null; + } + + // 获取等待任务数 + int waitingCount = getWaitingTaskCount(userId); + + return new CleanerAssigneeStatusAdapter(cleanerStatus, (long) waitingCount); + } + + @Override + public CleanerWorkload getWorkload(Long userId) { + OpsCleanerStatusDO cleanerStatus = cleanerStatusService.getStatus(userId); + if (cleanerStatus == null) { + return new CleanerWorkload(); + } + + // 获取等待任务数 + int waitingCount = getWaitingTaskCount(userId); + + // 统计今日完成任务数 + int completedToday = countCompletedToday(userId); + + // 统计今日工作时长 + int workMinutes = getWorkMinutesToday(userId); + + return new CleanerWorkload( + userId, + cleanerStatus.getUserName(), + cleanerStatus.getCurrentOpsOrderId() != null ? 1 : 0, + waitingCount, + completedToday, + workMinutes + ); + } + + @Override + public boolean isAvailable(Long userId) { + OpsCleanerStatusDO cleanerStatus = cleanerStatusService.getStatus(userId); + if (cleanerStatus == null) { + return false; + } + + CleanerStatusEnum statusEnum = cleanerStatus.getStatusEnum(); + return statusEnum != null && statusEnum.isCanAcceptNewOrder(); + } + + @Override + public boolean isOnline(Long userId) { + return cleanerStatusService.isOnline(userId); + } + + // ==================== 保洁员查询 ==================== + + @Override + public List listByArea(Long areaId) { + List cleaners = cleanerStatusService.listCleanersByArea(areaId); + + return cleaners.stream() + .map(cleaner -> { + int waitingCount = getWaitingTaskCount(cleaner.getUserId()); + return new CleanerAssigneeStatusAdapter(cleaner, (long) waitingCount); + }) + .collect(Collectors.toList()); + } + + @Override + public List listAvailableByArea(Long areaId) { + List cleaners = cleanerStatusService.listAvailableCleaners(areaId); + + return cleaners.stream() + .map(cleaner -> { + int waitingCount = getWaitingTaskCount(cleaner.getUserId()); + return new CleanerAssigneeStatusAdapter(cleaner, (long) waitingCount); + }) + .collect(Collectors.toList()); + } + + @Override + public List listNearby(Long areaId, Integer radius) { + // TODO: 实现基于地理位置的附近查询 + // 目前简化为按区域查询 + return listByArea(areaId); + } + + @Override + public List recommend(Long areaId, PriorityEnum priority, int limit) { + List cleaners = cleanerStatusService.listCleanersByArea(areaId); + if (cleaners.isEmpty()) { + return new ArrayList<>(); + } + + // 过滤在线的保洁员 + List onlineCleaners = cleaners.stream() + .filter(c -> CleanerStatusEnum.OFFLINE != c.getStatusEnum()) + .collect(Collectors.toList()); + + // 按分数排序 + return onlineCleaners.stream() + .limit(limit) + .map(cleaner -> { + int score = calculateScore(cleaner); + String reason = buildRecommendationReason(cleaner); + return CleanerRecommendation.of( + cleaner.getUserId(), + cleaner.getUserName(), + score, + reason + ); + }) + .sorted((a, b) -> Integer.compare(b.getScore(), a.getScore())) + .collect(Collectors.toList()); + } + + @Override + public CleanerRecommendation getBestCleaner(Long areaId, PriorityEnum priority) { + List recommendations = recommend(areaId, priority, 1); + return recommendations.isEmpty() ? null : recommendations.get(0); + } + + // ==================== 工作量统计 ==================== + + @Override + public int countCompletedToday(Long userId) { + LocalDate today = LocalDate.now(); + LocalDateTime startOfDay = today.atStartOfDay(); + LocalDateTime endOfDay = today.plusDays(1).atStartOfDay(); + + // 查询今日完成的保洁工单 +// LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); +// queryWrapper.eq(OpsOrderDO::getAssigneeId, userId) +// .eq(OpsOrderDO::getOrderType, "CLEAN") +// .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.COMPLETED.getStatus()) +// .ge(OpsOrderDO::getCompletionTime, startOfDay) +// .lt(OpsOrderDO::getCompletionTime, endOfDay); + +// return orderMapper.selectCount(queryWrapper); + return 0; + } + + @Override + public int getWorkMinutesToday(Long userId) { + LocalDate today = LocalDate.now(); + LocalDateTime startOfDay = today.atStartOfDay(); + LocalDateTime endOfDay = today.plusDays(1).atStartOfDay(); + + // 查询今日完成的保洁工单 +// LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); +// queryWrapper.eq(OpsOrderDO::getAssigneeId, userId) +// .eq(OpsOrderDO::getOrderType, "CLEAN") +// .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.COMPLETED.getStatus()) +// .ge(OpsOrderDO::getCompletionTime, startOfDay) +// .lt(OpsOrderDO::getCompletionTime, endOfDay) +// .isNotNull(OpsOrderDO::getCompletionTime) +// .isNotNull(OpsOrderDO::getDispatchTime); + +// List orders = orderMapper.selectList(queryWrapper); + + // 计算总工作时长(分钟) + int totalMinutes = 0; +// for (OpsOrderDO order : orders) { +// if (order.getDispatchTime() != null && order.getCompletionTime() != null) { +// long minutes = Duration.between(order.getDispatchTime(), order.getCompletionTime()).toMinutes(); +// totalMinutes += minutes; +// } +// } + + return totalMinutes; + } + + @Override + public int getWaitingTaskCount(Long userId) { + try { + List waitingTasks = orderQueueService.getWaitingTasksByUserId(userId); + return waitingTasks != null ? waitingTasks.size() : 0; + } catch (Exception e) { + log.warn("查询等待任务数量失败: userId={}", userId, e); + return 0; + } + } + + // ==================== 状态更新 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public void setCurrentWorkOrder(Long userId, Long orderId, String orderCode) { + cleanerStatusService.setCurrentWorkOrder(userId, orderId, orderCode); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void clearCurrentWorkOrder(Long userId) { + cleanerStatusService.clearCurrentWorkOrder(userId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateCurrentArea(Long userId, Long areaId, String areaName) { + cleanerStatusService.updateCurrentArea(userId, areaId, areaName); + } + + // ==================== 私有方法 ==================== + + /** + * 计算推荐分数(0-100) + */ + private int calculateScore(OpsCleanerStatusDO cleaner) { + int score = 50; // 基础分 + + CleanerStatusEnum statusEnum = cleaner.getStatusEnum(); + + // 状态分数 + if (statusEnum == CleanerStatusEnum.IDLE) { + score += 30; + } else if (statusEnum == CleanerStatusEnum.BUSY) { + score += 10; + } + + // 电量分数 + if (cleaner.getBatteryLevel() != null) { + if (cleaner.getBatteryLevel() > 80) { + score += 15; + } else if (cleaner.getBatteryLevel() > 50) { + score += 10; + } else if (cleaner.getBatteryLevel() > 20) { + score += 5; + } + } + + // 心跳分数 + if (cleaner.getLastHeartbeatTime() != null) { + long minutesSinceHeartbeat = Duration.between( + cleaner.getLastHeartbeatTime(), + LocalDateTime.now() + ).toMinutes(); + if (minutesSinceHeartbeat < 5) { + score += 5; + } + } + + return Math.min(score, 100); + } + + /** + * 构建推荐理由 + */ + private String buildRecommendationReason(OpsCleanerStatusDO cleaner) { + StringBuilder reason = new StringBuilder(); + + CleanerStatusEnum statusEnum = cleaner.getStatusEnum(); + if (statusEnum != null) { + reason.append("状态=").append(statusEnum.getDescription()); + } + + if (cleaner.getBatteryLevel() != null) { + reason.append("、电量").append(cleaner.getBatteryLevel()).append("%"); + } + + if (cleaner.getCurrentAreaName() != null) { + reason.append("、位置=").append(cleaner.getCurrentAreaName()); + } + + return reason.toString(); + } +}