package cn.iocoder.yudao.module.mes.service.pro.task;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskAssigneeDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskAssigneeMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
|
import cn.iocoder.yudao.module.mes.dal.redis.pro.task.MesProTaskWaitRemindRedisDAO;
|
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
|
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
|
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
|
import java.math.BigDecimal;
|
import java.time.Duration;
|
import java.time.LocalDateTime;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.stream.Collectors;
|
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
|
|
/**
|
* MES 生产任务报工倒计时提醒 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Slf4j
|
@Service
|
public class MesProTaskWaitRemindServiceImpl implements MesProTaskWaitRemindService {
|
|
/**
|
* 三个提醒等级对应的站内信模板编号
|
*/
|
private static final String[] REMIND_TEMPLATE_CODES = {
|
"mes_task_wait_remind_10", // 等级 1:剩余 10 分钟
|
"mes_task_wait_remind_5", // 等级 2:剩余 5 分钟
|
"mes_task_wait_remind_end" // 等级 3:倒计时结束
|
};
|
|
@Resource
|
private MesProTaskMapper taskMapper;
|
@Resource
|
private MesProTaskAssigneeMapper taskAssigneeMapper;
|
@Resource
|
private MesProProcessService processService;
|
@Resource
|
private MesProTaskWaitRemindRedisDAO waitRemindRedisDAO;
|
@Resource
|
private NotifyMessageSendApi notifyMessageSendApi;
|
|
@Override
|
public void sendWaitRemind() {
|
try {
|
// 1. 抢扫描门闩,避免多实例/多次并发重复扫描
|
if (!waitRemindRedisDAO.tryAcquireScanLock()) {
|
return;
|
}
|
// 2. 查询已开始倒计时且未终态/未停工的任务
|
List<MesProTaskDO> taskList = taskMapper.selectListByWaitStartTimeNotNull();
|
if (CollUtil.isEmpty(taskList)) {
|
return;
|
}
|
// 3. 按任务编号批量查询报工人员
|
Map<Long, List<MesProTaskAssigneeDO>> assigneeMap = convertMultiMap(
|
taskAssigneeMapper.selectListByTaskIds(
|
taskList.stream().map(MesProTaskDO::getId).collect(Collectors.toSet())),
|
MesProTaskAssigneeDO::getTaskId);
|
// 4. 逐任务按三个等级发送提醒
|
for (MesProTaskDO task : taskList) {
|
try {
|
processTaskRemind(task, assigneeMap.get(task.getId()));
|
} catch (Exception ex) {
|
// best-effort:单个任务提醒失败不影响其余任务
|
log.error("[sendWaitRemind][任务 {} 发送倒计时提醒失败]", task.getId(), ex);
|
}
|
}
|
} catch (Exception ex) {
|
// best-effort:提醒失败不影响其他流程
|
log.error("[sendWaitRemind][发送报工倒计时提醒失败]", ex);
|
}
|
}
|
|
private void processTaskRemind(MesProTaskDO task, List<MesProTaskAssigneeDO> assigneeList) {
|
// 1. 取工序等待时长,未配置则无需倒计时提醒
|
MesProProcessDO process = processService.getProcess(task.getProcessId());
|
if (process == null || process.getWaitDuration() == null
|
|| process.getWaitDuration().compareTo(BigDecimal.ZERO) <= 0) {
|
return;
|
}
|
// 2. 计算倒计时结束时间与剩余分钟
|
LocalDateTime waitStartTime = task.getWaitStartTime();
|
LocalDateTime readyTime = waitStartTime
|
.plusMinutes((long) (process.getWaitDuration().doubleValue() * 60));
|
long remainingMinutes = Duration.between(LocalDateTime.now(), readyTime).toMinutes(); // 可为负
|
// 3. 三个等级独立判定,Redis 去重(首次标记成功才发送)
|
sendRemindIfDue(task, process, assigneeList, waitStartTime, 1, remainingMinutes <= 10);
|
sendRemindIfDue(task, process, assigneeList, waitStartTime, 2, remainingMinutes <= 5);
|
sendRemindIfDue(task, process, assigneeList, waitStartTime, 3, remainingMinutes <= 0);
|
}
|
|
private void sendRemindIfDue(MesProTaskDO task, MesProProcessDO process,
|
List<MesProTaskAssigneeDO> assigneeList, LocalDateTime waitStartTime,
|
int level, boolean due) {
|
if (!due) {
|
return;
|
}
|
if (!waitRemindRedisDAO.tryMarkReminded(task.getId(), level, waitStartTime)) {
|
return; // 该等级已提醒过
|
}
|
// 组装模板参数
|
Map<String, Object> params = new HashMap<>();
|
params.put("taskCode", task.getCode());
|
params.put("taskName", task.getName());
|
params.put("processName", process.getName());
|
// 接收人:优先报工人员;未指定则用任务创建人兜底
|
List<Long> receiverUserIds = assigneeList == null ? null
|
: assigneeList.stream().map(MesProTaskAssigneeDO::getUserId).collect(Collectors.toList());
|
if (CollUtil.isEmpty(receiverUserIds)) {
|
Long creatorUserId = parseCreatorUserId(task.getCreator());
|
if (creatorUserId == null) {
|
return;
|
}
|
receiverUserIds = List.of(creatorUserId);
|
}
|
for (Long userId : receiverUserIds) {
|
NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
|
.setUserId(userId)
|
.setTemplateCode(REMIND_TEMPLATE_CODES[level - 1])
|
.setTemplateParams(params);
|
notifyMessageSendApi.sendSingleMessageToAdmin(reqDTO);
|
}
|
}
|
|
private Long parseCreatorUserId(String creator) {
|
if (StrUtil.isBlank(creator)) {
|
return null;
|
}
|
try {
|
return Long.valueOf(creator.trim());
|
} catch (NumberFormatException ex) {
|
return null;
|
}
|
}
|
|
}
|