366 lines
15 KiB
Java
366 lines
15 KiB
Java
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<TaskItem> getAvailableItems(Integer page, Integer pageSize, Long taskTypeId, String keyword) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectAvailableItems(pageParam, taskTypeId, keyword);
|
||
}
|
||
|
||
/**
|
||
* 获取进行中的子任务列表
|
||
*/
|
||
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectInProgressItems(pageParam, receiverId);
|
||
}
|
||
|
||
/**
|
||
* 获取已完成的子任务列表
|
||
*/
|
||
public IPage<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId) {
|
||
Page<TaskItem> pageParam = new Page<>(page, pageSize);
|
||
return taskItemMapper.selectCompletedItems(pageParam, receiverId);
|
||
}
|
||
|
||
/**
|
||
* 根据任务ID获取所有子任务
|
||
*/
|
||
public List<TaskItem> 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<String, Object> 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<String, Object> 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<TaskItem> currentGroupItems = taskItemMapper.selectList(
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
|
||
.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<TaskItem> nextGroupItems = taskItemMapper.selectList(
|
||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TaskItem>()
|
||
.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<String, Object> getTaskProgress(Long taskId) {
|
||
int completedCount = taskItemMapper.countCompletedItems(taskId);
|
||
int totalCount = taskItemMapper.countTotalItems(taskId);
|
||
|
||
Map<String, Object> progress = new HashMap<>();
|
||
progress.put("completedCount", completedCount);
|
||
progress.put("totalCount", totalCount);
|
||
progress.put("percentage", totalCount > 0 ? (completedCount * 100 / totalCount) : 0);
|
||
|
||
return progress;
|
||
}
|
||
}
|