- 新增任务排队解锁功能,确保先发布的任务优先被处理 - 添加系统设置中的任务排队配置(启用开关、每日解锁数、补充解锁数) - 新增 TaskUnlockStatus 实体和 Mapper - 新增 TaskUnlockService 处理排队解锁逻辑 - 新增 TaskStatusNotificationService 发送钉钉通知 - 更新 TaskItemService 和 TaskItemController 支持排队状态 - 前端新增 TaskStatusBadge 组件显示排队/抢单状态 - 更新 TaskCard 和 TaskHall 支持排队状态展示 - 添加数据库迁移脚本 V15
268 lines
9.5 KiB
Java
268 lines
9.5 KiB
Java
package com.designhub.service;
|
||
|
||
import com.designhub.entity.*;
|
||
import com.designhub.mapper.UserMapper;
|
||
import lombok.RequiredArgsConstructor;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.scheduling.annotation.Async;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import java.time.LocalDateTime;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.util.HashSet;
|
||
import java.util.Set;
|
||
|
||
/**
|
||
* 任务状态变更通知服务
|
||
* 负责在任务状态变化时通知需求人
|
||
*/
|
||
@Service
|
||
@Slf4j
|
||
@RequiredArgsConstructor
|
||
public class TaskStatusNotificationService {
|
||
|
||
private final NotificationService notificationService;
|
||
private final UserMapper userMapper;
|
||
|
||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||
|
||
/**
|
||
* 通知需求人:任务已解锁(排队中 → 抢单中)
|
||
*/
|
||
@Async
|
||
public void notifyTaskUnlocked(TaskItem item, Task mainTask) {
|
||
if (mainTask == null) {
|
||
log.warn("通知任务解锁失败:主任务为空");
|
||
return;
|
||
}
|
||
|
||
Set<Long> demandUserIds = getDemandUserIds(mainTask);
|
||
if (demandUserIds.isEmpty()) {
|
||
log.warn("通知任务解锁失败:没有找到需求人");
|
||
return;
|
||
}
|
||
|
||
String content = String.format(
|
||
"您发布的任务《%s》已完成排队,现已开放设计师抢单。\n" +
|
||
"任务编号:%s\n" +
|
||
"发布时间:%s\n" +
|
||
"设计师正在抢单中,请耐心等待...",
|
||
mainTask.getTitle(),
|
||
item.getItemNo(),
|
||
formatTime(mainTask.getCreateTime())
|
||
);
|
||
|
||
for (Long userId : demandUserIds) {
|
||
try {
|
||
Notification notification = new Notification();
|
||
notification.setReceiverId(userId);
|
||
notification.setNotificationType("task_status");
|
||
notification.setTitle("📢 任务已开放抢单");
|
||
notification.setContent(content);
|
||
notification.setRelatedId(item.getId());
|
||
notification.setRelatedType("task_item");
|
||
notification.setPriority(1); // 普通优先级
|
||
notification.setPushDingtalk(1); // 推送钉钉
|
||
notificationService.sendNotification(notification);
|
||
|
||
log.info("已通知需求人 {} 任务 {} 已解锁开放抢单", userId, item.getItemNo());
|
||
} catch (Exception e) {
|
||
log.error("通知需求人 {} 任务解锁失败: {}", userId, e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通知需求人:任务已被接单(抢单中 → 进行中)
|
||
*/
|
||
@Async
|
||
public void notifyTaskGrabbed(TaskItem item, Task mainTask, User designer) {
|
||
if (mainTask == null || designer == null) {
|
||
log.warn("通知任务接单失败:主任务或设计师信息为空");
|
||
return;
|
||
}
|
||
|
||
Set<Long> demandUserIds = getDemandUserIds(mainTask);
|
||
if (demandUserIds.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
String content = String.format(
|
||
"您发布的任务《%s》已被设计师接单。\n" +
|
||
"任务编号:%s\n" +
|
||
"接单设计师:%s\n" +
|
||
"预计完成:%s\n" +
|
||
"如有问题可联系设计师沟通。",
|
||
mainTask.getTitle(),
|
||
item.getItemNo(),
|
||
designer.getRealName(),
|
||
item.getDeadline() != null ? formatTime(item.getDeadline()) : "待定"
|
||
);
|
||
|
||
for (Long userId : demandUserIds) {
|
||
// 不通知自己(如果设计师就是需求人)
|
||
if (userId.equals(designer.getId())) {
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
Notification notification = new Notification();
|
||
notification.setReceiverId(userId);
|
||
notification.setNotificationType("task_status");
|
||
notification.setTitle("🎉 任务已被接单");
|
||
notification.setContent(content);
|
||
notification.setRelatedId(item.getId());
|
||
notification.setRelatedType("task_item");
|
||
notification.setPriority(1);
|
||
notification.setPushDingtalk(1);
|
||
notificationService.sendNotification(notification);
|
||
|
||
log.info("已通知需求人 {} 任务 {} 已被 {} 接单", userId, item.getItemNo(), designer.getRealName());
|
||
} catch (Exception e) {
|
||
log.error("通知需求人 {} 任务接单失败: {}", userId, e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通知需求人:任务已完成
|
||
*/
|
||
@Async
|
||
public void notifyTaskCompleted(TaskItem item, Task mainTask, User designer) {
|
||
if (mainTask == null || designer == null) {
|
||
log.warn("通知任务完成失败:主任务或设计师信息为空");
|
||
return;
|
||
}
|
||
|
||
Set<Long> demandUserIds = getDemandUserIds(mainTask);
|
||
if (demandUserIds.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
String content = String.format(
|
||
"您发布的任务《%s》已完成。\n" +
|
||
"任务编号:%s\n" +
|
||
"完成设计师:%s\n" +
|
||
"完成时间:%s\n" +
|
||
"请及时查收并确认交付物。",
|
||
mainTask.getTitle(),
|
||
item.getItemNo(),
|
||
designer.getRealName(),
|
||
formatTime(item.getCompleteTime() != null ? item.getCompleteTime() : LocalDateTime.now())
|
||
);
|
||
|
||
for (Long userId : demandUserIds) {
|
||
try {
|
||
Notification notification = new Notification();
|
||
notification.setReceiverId(userId);
|
||
notification.setNotificationType("task_status");
|
||
notification.setTitle("✅ 任务已完成");
|
||
notification.setContent(content);
|
||
notification.setRelatedId(item.getId());
|
||
notification.setRelatedType("task_item");
|
||
notification.setPriority(2); // 重要
|
||
notification.setPushDingtalk(1);
|
||
notificationService.sendNotification(notification);
|
||
|
||
log.info("已通知需求人 {} 任务 {} 已完成", userId, item.getItemNo());
|
||
} catch (Exception e) {
|
||
log.error("通知需求人 {} 任务完成失败: {}", userId, e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 批量通知任务解锁
|
||
*/
|
||
@Async
|
||
public void batchNotifyTasksUnlocked(java.util.List<TaskItem> items, java.util.Map<Long, Task> taskMap) {
|
||
if (items == null || items.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// 按主任务分组,避免同一任务的多个子任务重复通知
|
||
java.util.Map<Long, java.util.List<TaskItem>> groupedByTask = items.stream()
|
||
.collect(java.util.stream.Collectors.groupingBy(TaskItem::getTaskId));
|
||
|
||
for (java.util.Map.Entry<Long, java.util.List<TaskItem>> entry : groupedByTask.entrySet()) {
|
||
Long taskId = entry.getKey();
|
||
java.util.List<TaskItem> taskItems = entry.getValue();
|
||
Task mainTask = taskMap.get(taskId);
|
||
|
||
if (mainTask == null) {
|
||
continue;
|
||
}
|
||
|
||
Set<Long> demandUserIds = getDemandUserIds(mainTask);
|
||
if (demandUserIds.isEmpty()) {
|
||
continue;
|
||
}
|
||
|
||
// 构建通知内容
|
||
String itemNos = taskItems.stream()
|
||
.map(TaskItem::getItemNo)
|
||
.collect(java.util.stream.Collectors.joining(", "));
|
||
|
||
String content = String.format(
|
||
"您发布的任务《%s》有 %d 个子任务已完成排队,现已开放设计师抢单。\n" +
|
||
"子任务编号:%s\n" +
|
||
"设计师正在抢单中,请耐心等待...",
|
||
mainTask.getTitle(),
|
||
taskItems.size(),
|
||
itemNos
|
||
);
|
||
|
||
for (Long userId : demandUserIds) {
|
||
try {
|
||
Notification notification = new Notification();
|
||
notification.setReceiverId(userId);
|
||
notification.setNotificationType("task_status");
|
||
notification.setTitle("📢 任务已开放抢单");
|
||
notification.setContent(content);
|
||
notification.setRelatedId(taskId);
|
||
notification.setRelatedType("task");
|
||
notification.setPriority(1);
|
||
notification.setPushDingtalk(1);
|
||
notificationService.sendNotification(notification);
|
||
} catch (Exception e) {
|
||
log.error("批量通知任务解锁失败: {}", e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取任务的所有需求人(发布者 + 详情填写人 + 一级发起人)
|
||
*/
|
||
private Set<Long> getDemandUserIds(Task task) {
|
||
Set<Long> userIds = new HashSet<>();
|
||
|
||
// 发布者
|
||
if (task.getPublisherId() != null) {
|
||
userIds.add(task.getPublisherId());
|
||
}
|
||
|
||
// 详情填写人
|
||
if (task.getDetailCompleterId() != null) {
|
||
userIds.add(task.getDetailCompleterId());
|
||
}
|
||
|
||
// 一级发起人
|
||
if (task.getPrimaryPublisherId() != null) {
|
||
userIds.add(task.getPrimaryPublisherId());
|
||
}
|
||
|
||
return userIds;
|
||
}
|
||
|
||
/**
|
||
* 格式化时间
|
||
*/
|
||
private String formatTime(LocalDateTime time) {
|
||
if (time == null) {
|
||
return "未知";
|
||
}
|
||
return time.format(DATE_TIME_FORMATTER);
|
||
}
|
||
}
|