chore: 【ops】保洁员业务领域调整

This commit is contained in:
lzh
2026-01-09 17:39:34 +08:00
parent 9dd2dd35f1
commit 628d8fd8c4
2 changed files with 695 additions and 0 deletions

View File

@@ -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;
/**
* 保洁员领域服务接口
* <p>
* 职责:
* 1. 保洁员状态管理
* 2. 保洁员可用性查询
* 3. 保洁员工作量统计
* <p>
* 设计说明:
* - 业务层领域服务,处理保洁业务特定逻辑
* - 通过 {@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<AssigneeStatus> listByArea(Long areaId);
/**
* 查询指定区域的可用保洁员
*
* @param areaId 区域ID
* @return 可用保洁员列表
*/
List<AssigneeStatus> listAvailableByArea(Long areaId);
/**
* 查询附近的保洁员
*
* @param areaId 中心区域ID
* @param radius 搜索半径
* @return 附近保洁员列表
*/
List<AssigneeStatus> listNearby(Long areaId, Integer radius);
/**
* 根据条件推荐保洁员
*
* @param areaId 区域ID
* @param priority 工单优先级
* @param limit 最多返回数量
* @return 推荐的保洁员列表
*/
List<CleanerRecommendation> 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;
}
}
}

View File

@@ -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;
/**
* 保洁员领域服务实现
* <p>
* 职责:
* 1. 保洁员状态管理
* 2. 保洁员可用性查询
* 3. 保洁员工作量统计
* <p>
* 设计说明:
* - 业务层领域服务,处理保洁业务特定逻辑
* - 通过 {@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<AssigneeStatus> listByArea(Long areaId) {
List<OpsCleanerStatusDO> 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<AssigneeStatus> listAvailableByArea(Long areaId) {
List<OpsCleanerStatusDO> 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<AssigneeStatus> listNearby(Long areaId, Integer radius) {
// TODO: 实现基于地理位置的附近查询
// 目前简化为按区域查询
return listByArea(areaId);
}
@Override
public List<CleanerRecommendation> recommend(Long areaId, PriorityEnum priority, int limit) {
List<OpsCleanerStatusDO> cleaners = cleanerStatusService.listCleanersByArea(areaId);
if (cleaners.isEmpty()) {
return new ArrayList<>();
}
// 过滤在线的保洁员
List<OpsCleanerStatusDO> 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<CleanerRecommendation> 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<OpsOrderDO> 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<OpsOrderDO> 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<OpsOrderDO> 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<OrderQueueDTO> 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();
}
}