package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.designhub.common.ResultCode; import com.designhub.entity.Task; import com.designhub.entity.TaskItem; import com.designhub.entity.Notification; import com.designhub.exception.BusinessException; import com.designhub.mapper.TaskItemMapper; import com.designhub.mapper.TaskMapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 任务子项服务类(流水线任务) */ @Service @RequiredArgsConstructor public class TaskItemService { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TaskItemService.class); private final TaskItemMapper taskItemMapper; private final TaskMapper taskMapper; private final UserService userService; private final NotificationService notificationService; /** * 获取可接单的子任务列表 */ public IPage getAvailableItems(Integer page, Integer pageSize, Long taskTypeId, String keyword) { Page pageParam = new Page<>(page, pageSize); return taskItemMapper.selectAvailableItems(pageParam, taskTypeId, keyword); } /** * 获取进行中的子任务列表 */ public IPage getInProgressItems(Integer page, Integer pageSize, Long receiverId) { Page pageParam = new Page<>(page, pageSize); return taskItemMapper.selectInProgressItems(pageParam, receiverId); } /** * 获取已完成的子任务列表 */ public IPage getCompletedItems(Integer page, Integer pageSize, Long receiverId) { Page pageParam = new Page<>(page, pageSize); return taskItemMapper.selectCompletedItems(pageParam, receiverId); } /** * 根据任务ID获取所有子任务 */ public List getItemsByTaskId(Long taskId) { return taskItemMapper.selectByTaskId(taskId); } /** * 获取子任务详情 */ public TaskItem getItemById(Long itemId) { TaskItem item = taskItemMapper.selectById(itemId); if (item == null) { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } // 加载主任务信息 Task mainTask = taskMapper.selectById(item.getTaskId()); if (mainTask != null) { item.setMainTaskTitle(mainTask.getTitle()); item.setPipelineMode(mainTask.getPipelineMode()); item.setBrandName(mainTask.getBrandName()); item.setCategoryName(mainTask.getCategoryName()); item.setPlatformName(mainTask.getPlatformName()); item.setPublisherName(mainTask.getPublisherName()); item.setSpuInfo(mainTask.getSpuInfo()); item.setTaskSubType(mainTask.getTaskSubType()); item.setAttachmentUrl(mainTask.getAttachmentUrl()); } return item; } /** * 抢单子任务 */ @Transactional(rollbackFor = Exception.class) public void grabItem(Long itemId, Long userId) { log.info("设计师 {} 抢单子任务 {}", userId, itemId); // 获取用户信息 var user = userService.getUserById(userId); if (user == null) { throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在"); } // 获取子任务信息 TaskItem item = taskItemMapper.selectById(itemId); if (item == null) { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } // 验证状态 if (!"pending".equals(item.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务不可接单"); } if (item.getIsLocked() != null && item.getIsLocked() == 1) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务已被锁定,请等待前序任务完成"); } if (item.getReceiverId() != null) { throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "该子任务已被接单"); } // 乐观锁抢单 int result = taskItemMapper.grabItem(itemId, userId, user.getRealName()); if (result == 0) { throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "抢单失败,该子任务可能已被他人接单"); } log.info("设计师 {} 成功抢单子任务 {}", userId, itemId); // 发送通知给任务发布者 Task mainTask = taskMapper.selectById(item.getTaskId()); if (mainTask != null && mainTask.getPublisherId() != null) { Notification notification = new Notification(); notification.setReceiverId(mainTask.getPublisherId()); notification.setNotificationType("task"); notification.setTitle("子任务已被接单"); notification.setContent(user.getRealName() + " 接单了子任务:" + item.getItemNo()); notification.setRelatedId(itemId); notification.setRelatedType("task_item"); notification.setPriority(1); // 普通 notificationService.sendNotification(notification); } } /** * 更新子任务进度 */ @Transactional(rollbackFor = Exception.class) public void updateProgress(Long itemId, Long userId, Integer progress) { log.info("设计师 {} 更新子任务 {} 进度为 {}%", userId, itemId, progress); TaskItem item = taskItemMapper.selectById(itemId); if (item == null) { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } // 验证权限 if (!userId.equals(item.getReceiverId())) { throw new BusinessException(ResultCode.FORBIDDEN, "无权更新该子任务"); } // 验证状态 if (!"in_progress".equals(item.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能更新进行中的子任务"); } // 验证进度值 if (progress < 0 || progress > 100) { throw new BusinessException(ResultCode.PARAM_ERROR, "进度值必须在0-100之间"); } // 更新进度 item.setProgress(progress); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); log.info("子任务 {} 进度已更新为 {}%", itemId, progress); } /** * 完结子任务(关键方法:包含自动解锁逻辑) */ @Transactional(rollbackFor = Exception.class) public Map completeItem(Long itemId, Long userId, String deliveryPath) { log.info("设计师 {} 完结子任务 {}", userId, itemId); // 1. 获取子任务信息 TaskItem item = taskItemMapper.selectById(itemId); if (item == null) { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } // 2. 验证权限 if (!userId.equals(item.getReceiverId())) { throw new BusinessException(ResultCode.FORBIDDEN, "无权完结该子任务"); } // 3. 验证状态 if (!"in_progress".equals(item.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "只能完结进行中的子任务"); } // 4. 更新子任务状态 item.setStatus("completed"); item.setProgress(100); item.setDeliveryPath(deliveryPath); item.setCompleteTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); log.info("子任务 {} 已完成", itemId); // 5. 设计师立即获得积分(如果需要,可以调用积分服务) // scoreService.addScore(userId, item.getTotalScore()); Map result = new HashMap<>(); result.put("completedItem", item); // 6. 🔥 关键步骤:检查是否是顺序流水线,基于group_no解锁下一组 Task mainTask = taskMapper.selectById(item.getTaskId()); if (mainTask != null && "sequential".equals(mainTask.getPipelineMode())) { // 获取当前子任务的分组号 Integer currentGroupNo = item.getGroupNo(); if (currentGroupNo == null) { currentGroupNo = 1; // 兼容旧数据 } // 检查当前组的所有子任务是否都已完成 List currentGroupItems = taskItemMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(TaskItem::getTaskId, item.getTaskId()) .eq(TaskItem::getGroupNo, currentGroupNo) ); boolean allGroupCompleted = currentGroupItems.stream() .allMatch(i -> "completed".equals(i.getStatus())); if (allGroupCompleted) { log.info("✅ 第 {} 组所有子任务已完成,准备解锁下一组", currentGroupNo); // 解锁下一组的所有子任务 Integer nextGroupNo = currentGroupNo + 1; List nextGroupItems = taskItemMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(TaskItem::getTaskId, item.getTaskId()) .eq(TaskItem::getGroupNo, nextGroupNo) .eq(TaskItem::getStatus, "locked") ); if (!nextGroupItems.isEmpty()) { // 批量解锁下一组的所有子任务 for (TaskItem nextItem : nextGroupItems) { nextItem.setStatus("pending"); nextItem.setIsLocked(0); nextItem.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(nextItem); log.info("✅ 解锁子任务: {} (组号: {}, ID: {})", nextItem.getItemNo(), nextGroupNo, nextItem.getId()); } result.put("unlockedGroupNo", nextGroupNo); result.put("unlockedCount", nextGroupItems.size()); // 发送通知:新的子任务组已开放 Notification notification = new Notification(); notification.setNotificationType("task"); notification.setTitle("新子任务组已开放"); notification.setContent("第 " + nextGroupNo + " 组共 " + nextGroupItems.size() + " 个子任务已解锁,可以接单了!"); notification.setRelatedId(item.getTaskId()); notification.setRelatedType("task"); notification.setPriority(1); // 普通 // 可以通知所有接单部门的成员(这里暂时省略,可以扩展) log.info("第 {} 组 {} 个子任务已进入可接任务池", nextGroupNo, nextGroupItems.size()); } else { log.info("没有更多子任务组需要解锁"); } } else { log.info("当前组还有子任务未完成,暂不解锁下一组"); } } // 7. 检查主任务是否所有子任务都已完成 int completedCount = taskItemMapper.countCompletedItems(item.getTaskId()); int totalCount = taskItemMapper.countTotalItems(item.getTaskId()); if (completedCount == totalCount) { // 所有子任务完成,更新主任务状态 mainTask.setStatus("completed"); mainTask.setCompleteTime(LocalDateTime.now()); mainTask.setUpdateTime(LocalDateTime.now()); taskMapper.updateById(mainTask); log.info("🎉 主任务 {} 所有子任务已完成,主任务标记为完成", mainTask.getId()); // 发送通知给发布者 if (mainTask.getPublisherId() != null) { Notification notification = new Notification(); notification.setReceiverId(mainTask.getPublisherId()); notification.setNotificationType("task"); notification.setTitle("流水线任务已完成"); notification.setContent("您的任务《" + mainTask.getTitle() + "》所有子任务已完成!"); notification.setRelatedId(mainTask.getId()); notification.setRelatedType("task"); notification.setPriority(1); // 普通 notificationService.sendNotification(notification); } result.put("mainTaskCompleted", true); } else { result.put("mainTaskCompleted", false); result.put("progress", completedCount + "/" + totalCount); } return result; } /** * 驳回子任务 */ @Transactional(rollbackFor = Exception.class) public void rejectItem(Long itemId, String reason) { TaskItem item = taskItemMapper.selectById(itemId); if (item == null) { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } item.setStatus("rejected"); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); // 发送通知给接单人 if (item.getReceiverId() != null) { Notification notification = new Notification(); notification.setReceiverId(item.getReceiverId()); notification.setNotificationType("task"); notification.setTitle("子任务被驳回"); notification.setContent("您的子任务 " + item.getItemNo() + " 被驳回。原因:" + reason); notification.setRelatedId(itemId); notification.setRelatedType("task_item"); notification.setPriority(1); // 普通 notificationService.sendNotification(notification); } log.info("子任务 {} 已驳回", itemId); } /** * 获取主任务的进度统计 */ public Map getTaskProgress(Long taskId) { int completedCount = taskItemMapper.countCompletedItems(taskId); int totalCount = taskItemMapper.countTotalItems(taskId); Map progress = new HashMap<>(); progress.put("completedCount", completedCount); progress.put("totalCount", totalCount); progress.put("percentage", totalCount > 0 ? (completedCount * 100 / totalCount) : 0); return progress; } }