package cn.iocoder.yudao.module.mes.service.pro.bagcode; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeGenerateTaskMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateTaskStatusEnum; import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateTaskRegistry; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.RejectedExecutionException; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_GENERATE_TASK_BUSY; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_GENERATE_TASK_NOT_EXISTS; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_GENERATE_TASK_STATUS_INVALID; /** * 袋码批量生成任务 Service 实现 */ @Slf4j @Service @Validated public class MesProBagCodeGenerateTaskServiceImpl implements MesProBagCodeGenerateTaskService { /** * 非终态状态集合 */ private static final List UNFINISHED_STATUSES = List.of( MesProBagCodeGenerateTaskStatusEnum.PENDING.getStatus(), MesProBagCodeGenerateTaskStatusEnum.RUNNING.getStatus(), MesProBagCodeGenerateTaskStatusEnum.CANCELING.getStatus()); /** * 终态状态集合 */ private static final List FINAL_STATUSES = List.of( MesProBagCodeGenerateTaskStatusEnum.FINISHED.getStatus(), MesProBagCodeGenerateTaskStatusEnum.CANCELED.getStatus(), MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus()); @Resource private MesProBagCodeGenerateTaskMapper taskMapper; @Resource private MesProBagCodeGenerateRunner runner; @Resource private MesProBagCodeGenerateCleaner cleaner; @Resource private MesProBagCodeGenerateTaskRegistry registry; @Resource(name = "mesBagCodeGenerateExecutor") private ThreadPoolTaskExecutor executor; @Override public MesProBagCodeGenerateTaskDO submit(MesProBagCodeGenerateSpec spec, String requestId) { // 1. 幂等:网络重试复用同一个 requestId 时直接返回已有任务,不再生成 MesProBagCodeGenerateTaskDO existing = findExisting(requestId); if (existing != null) { return existing; } // 2. 并发闸门:同一时间只跑一个后台生成任务;被挡住时带上当前任务进度 if (!registry.tryAcquire()) { // 并发重复提交时,另一请求可能已抢先落库 existing = findExisting(requestId); if (existing != null) { return existing; } throw exception(PRO_BAG_CODE_GENERATE_TASK_BUSY, resolveRunningProgress()); } // 权限一旦交给执行器,就由执行线程在收尾时归还;此前任何失败都必须在这里归还 boolean handedOver = false; try { MesProBagCodeGenerateTaskDO task = MesProBagCodeGenerateTaskDO.builder() .requestId(requestId) .bizType(spec.bizType().getType()) .uuid(spec.uuid()) .itemId(spec.itemId()) .itemCode(spec.itemCode()) .itemName(spec.itemName()) .unitType(spec.unitType()) .perPallet(spec.perPallet()) .palletCount(spec.palletCount()) .totalCount(spec.totalCount()) .finishedCount(0) .status(MesProBagCodeGenerateTaskStatusEnum.PENDING.getStatus()) .remark(spec.remark()) .build(); try { taskMapper.insert(task); } catch (DuplicateKeyException e) { // 唯一索引兜底并发重复提交 existing = findExisting(requestId); if (existing != null) { return existing; } throw e; } // 3. 先注册运行态再投递:避免「已落库但未注册」的窗口被清理任务误回收 registry.register(task.getId()); handedOver = true; try { executor.execute(() -> runner.run(task.getId(), spec)); } catch (RejectedExecutionException e) { registry.unregister(task.getId()); handedOver = false; taskMapper.updateStatus(task.getId(), MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus(), "生成线程繁忙,请稍后重试", LocalDateTime.now()); throw exception(PRO_BAG_CODE_GENERATE_TASK_BUSY, 0); } return task; } finally { if (!handedOver) { registry.release(); } } } @Override public MesProBagCodeGenerateTaskDO getTask(Long id) { MesProBagCodeGenerateTaskDO task = taskMapper.selectById(id); if (task == null) { throw exception(PRO_BAG_CODE_GENERATE_TASK_NOT_EXISTS); } return task; } @Override public MesProBagCodeGenerateTaskDO getTaskByRequestId(String requestId) { return findExisting(requestId); } @Override public void cancel(Long id) { MesProBagCodeGenerateTaskDO task = getTask(id); if (FINAL_STATUSES.contains(task.getStatus())) { throw exception(PRO_BAG_CODE_GENERATE_TASK_STATUS_INVALID); } if (registry.requestCancel(id)) { // 执行线程会在当前分段结束后停止、清理半成品并置为已取消 taskMapper.updateStatus(id, MesProBagCodeGenerateTaskStatusEnum.CANCELING.getStatus(), null, null); return; } // 不在本进程运行(进程重启后的残留任务):直接清理残留并收尾 cleanupQuietly(task); taskMapper.updateStatus(id, MesProBagCodeGenerateTaskStatusEnum.CANCELED.getStatus(), null, LocalDateTime.now()); } @Override public int recycleStaleTasks() { List list = taskMapper.selectUnfinishedList(UNFINISHED_STATUSES); // 提交线程「落库 → 注册运行态」之间存在极短窗口,跳过刚创建的任务避免误回收 LocalDateTime recent = LocalDateTime.now().minusMinutes(1); int count = 0; for (MesProBagCodeGenerateTaskDO task : list) { if (task.getCreateTime() != null && task.getCreateTime().isAfter(recent)) { continue; } if (registry.isRunning(task.getId())) { continue; } cleanupQuietly(task); taskMapper.updateStatus(task.getId(), MesProBagCodeGenerateTaskStatusEnum.FAILED.getStatus(), "任务因服务重启或异常中断而终止,请重新生成", LocalDateTime.now()); count++; } return count; } // ==================== 私有方法 ==================== private MesProBagCodeGenerateTaskDO findExisting(String requestId) { if (StrUtil.isBlank(requestId)) { return null; } return taskMapper.selectByRequestId(requestId); } /** * 清理半成品:清理失败不能阻断任务终态回写,否则任务会一直挂在非终态 */ private void cleanupQuietly(MesProBagCodeGenerateTaskDO task) { try { cleaner.cleanup(task.getUuid()); } catch (Exception e) { log.error("[cleanupQuietly][生成任务 {} 半成品清理失败,uuid={}]", task.getId(), task.getUuid(), e); } } /** * 正在运行任务的进度百分比,用于「已有任务进行中」的提示 */ private int resolveRunningProgress() { List tasks = taskMapper.selectUnfinishedList(UNFINISHED_STATUSES); return tasks.stream().findFirst().map(this::calcProgress).orElse(0); } private int calcProgress(MesProBagCodeGenerateTaskDO task) { Integer total = task.getTotalCount(); if (total == null || total <= 0) { return 0; } int finished = task.getFinishedCount() == null ? 0 : task.getFinishedCount(); return (int) Math.min(100L, (long) finished * 100 / total); } }