diff --git a/ZENTAO_INTEGRATION.md b/ZENTAO_INTEGRATION.md index bd515b2..d62d799 100644 --- a/ZENTAO_INTEGRATION.md +++ b/ZENTAO_INTEGRATION.md @@ -63,22 +63,35 @@ user-mapping: ## API接口 -### 测试禅道任务连接 +### 测试禅道项目状态(推荐) +``` +GET /api/reminder/zentao/status/test/{groupId} +``` +**功能**: 测试禅道连接,同时获取任务和BUG数据 + +### 手动触发禅道项目状态提醒(推荐) +``` +POST /api/reminder/zentao/status/reminder/{groupId} +``` +**功能**: 发送统一的项目状态提醒,包含任务和BUG + +### 测试禅道任务连接(兼容性) ``` GET /api/reminder/zentao/test/{groupId} ``` -### 手动触发禅道任务提醒 +### 手动触发禅道任务提醒(兼容性) ``` POST /api/reminder/zentao/reminder/{groupId} ``` +**注意**: 现在也会包含BUG信息 -### 测试禅道BUG连接 +### 测试禅道BUG连接(单独测试) ``` GET /api/reminder/zentao/bugs/test/{groupId} ``` -### 手动触发禅道BUG提醒 +### 手动触发禅道BUG提醒(单独发送) ``` POST /api/reminder/zentao/bugs/reminder/{groupId} ``` @@ -91,27 +104,38 @@ POST /api/reminder/groups/{groupId}/evening ## 消息格式 -### 任务提醒消息格式 +### 项目状态提醒消息格式(推荐) -系统会发送包含以下信息的详细任务提醒: +系统会发送包含以下信息的统一项目状态提醒: -- 📊 任务统计(总数、过期数、涉及人员) -- 👥 按人员分组的任务列表 -- 🔴 过期任务标识 -- ⚪ 其他状态任务标识 -- 📋 任务优先级和截止日期 -- 🔗 禅道系统访问链接 - -### BUG提醒消息格式 - -系统会发送包含以下信息的详细BUG提醒: - -- 📊 BUG统计(总数、过期数、涉及人员) -- 👥 按人员分组的BUG列表 -- 🔴 过期BUG标识 +- 📊 项目概况(任务和BUG的总数、过期数) +- 👥 按人员分组的工作项列表(任务+BUG) +- 🔴 过期事项标识 +- ⚪ 未完成任务标识 - 🐛 未解决BUG标识 -- 📋 BUG严重程度和截止日期 -- 🔗 禅道系统访问链接 +- 📋 任务优先级和BUG严重程度 +- 📅 截止日期信息 + +**示例消息**: +``` +**禅道项目状态提醒** +项目: [2025-06-08]一站式平台 + +📊 项目概况 +未完成任务: 2个,已过期: 2个 +未解决BUG: 1个 + +邓启辰 (3个事项) +- 🔴 [任务238] 测试任务2 (2025-05-28) +- 🔴 [任务236] 测试任务 (2025-05-28) +- 🐛 [BUG911] 测试BUG [一般] (2025-05-29) + +请及时登录禅道系统更新状态 +``` + +### 单独提醒消息格式(兼容性) + +如果使用单独的任务或BUG提醒接口,消息格式保持原样。 ## 使用步骤 diff --git a/src/main/java/com/zeodao/reminder/controller/TaskReminderController.java b/src/main/java/com/zeodao/reminder/controller/TaskReminderController.java index 31e5379..10dedf9 100644 --- a/src/main/java/com/zeodao/reminder/controller/TaskReminderController.java +++ b/src/main/java/com/zeodao/reminder/controller/TaskReminderController.java @@ -339,4 +339,77 @@ public class TaskReminderController { return ResponseEntity.ok(response); } + + /** + * 测试禅道项目状态(任务+BUG) + */ + @GetMapping("/zentao/status/test/{groupId}") + public ResponseEntity> testZentaoProjectStatus(@PathVariable String groupId) { + Map response = new HashMap<>(); + + try { + TaskReminderConfig.Group group = taskReminderConfig.getGroupById(groupId); + if (group == null) { + response.put("success", false); + response.put("message", "群组不存在:" + groupId); + return ResponseEntity.ok(response); + } + + if (!"zentao".equals(group.getTaskSystem())) { + response.put("success", false); + response.put("message", "该群组不是禅道类型"); + return ResponseEntity.ok(response); + } + + List tasks = zentaoTaskReminderService.getTasksForTesting(group); + List bugs = zentaoTaskReminderService.getBugsForTesting(group); + + response.put("success", true); + response.put("message", "禅道项目状态API连接成功"); + response.put("taskCount", tasks.size()); + response.put("bugCount", bugs.size()); + response.put("tasks", tasks); + response.put("bugs", bugs); + } catch (Exception e) { + logger.error("测试禅道项目状态失败: {}", groupId, e); + response.put("success", false); + response.put("message", "测试禅道项目状态失败:" + e.getMessage()); + } + + return ResponseEntity.ok(response); + } + + /** + * 手动触发禅道项目状态提醒(任务+BUG统一通知) + */ + @PostMapping("/zentao/status/reminder/{groupId}") + public ResponseEntity> sendZentaoProjectStatusReminder(@PathVariable String groupId) { + Map response = new HashMap<>(); + + try { + TaskReminderConfig.Group group = taskReminderConfig.getGroupById(groupId); + if (group == null) { + response.put("success", false); + response.put("message", "群组不存在:" + groupId); + return ResponseEntity.ok(response); + } + + if (!"zentao".equals(group.getTaskSystem())) { + response.put("success", false); + response.put("message", "该群组不是禅道类型"); + return ResponseEntity.ok(response); + } + + // 使用现有的sendTaskReminder方法,它现在已经包含了任务和BUG + zentaoTaskReminderService.sendTaskReminder(group); + response.put("success", true); + response.put("message", "禅道项目状态提醒已发送(包含任务和BUG)"); + } catch (Exception e) { + logger.error("发送禅道项目状态提醒失败: {}", groupId, e); + response.put("success", false); + response.put("message", "发送禅道项目状态提醒失败:" + e.getMessage()); + } + + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/zeodao/reminder/service/ZentaoTaskReminderService.java b/src/main/java/com/zeodao/reminder/service/ZentaoTaskReminderService.java index a0c5cb2..98fff8e 100644 --- a/src/main/java/com/zeodao/reminder/service/ZentaoTaskReminderService.java +++ b/src/main/java/com/zeodao/reminder/service/ZentaoTaskReminderService.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,11 +39,11 @@ public class ZentaoTaskReminderService { private WechatWebhookService wechatWebhookService; /** - * 发送禅道任务提醒 + * 发送禅道项目状态提醒(包含任务和BUG) */ public void sendTaskReminder(TaskReminderConfig.Group group) { try { - logger.info("Starting Zentao task reminder for group: {}", group.getId()); + logger.info("Starting Zentao project status reminder for group: {}", group.getId()); // 首先检查项目是否存在 ProjectInfo projectInfo; @@ -62,30 +63,28 @@ public class ZentaoTaskReminderService { return; } - // 获取未完成任务 + // 获取未完成任务和未解决BUG List incompleteTasks = zentaoApiService.getIncompleteTasks(group); - if (incompleteTasks.isEmpty()) { - logger.info("No incomplete tasks found for group: {}", group.getId()); - sendNoTasksMessage(group, projectInfo); + List unresolvedBugs = zentaoApiService.getUnresolvedBugs(group); + + // 如果任务和BUG都为空,发送无事项消息 + if (incompleteTasks.isEmpty() && unresolvedBugs.isEmpty()) { + logger.info("No incomplete tasks or unresolved bugs found for group: {}", group.getId()); + sendNoItemsMessage(group, projectInfo); return; } - // 按负责人分组 - Map> tasksByAssignee = incompleteTasks.stream() - .filter(task -> task.getAssignedTo() != null && !task.getAssignedTo().isEmpty()) - .collect(Collectors.groupingBy(ZentaoTask::getAssignedTo)); - - // 生成提醒消息并收集@人手机号 + // 生成统一的项目状态提醒消息 List mentionedMobiles = new ArrayList<>(); - String message = buildTaskReminderMessage(group, tasksByAssignee, incompleteTasks, projectInfo, mentionedMobiles); + String message = buildProjectStatusMessage(group, incompleteTasks, unresolvedBugs, projectInfo, mentionedMobiles); // 发送带@人的消息 wechatWebhookService.sendMessageWithMentions(group.getWebhook().getUrl(), message, mentionedMobiles); - logger.info("Zentao task reminder sent successfully for group: {}", group.getId()); + logger.info("Zentao project status reminder sent successfully for group: {}", group.getId()); } catch (Exception e) { - logger.error("Error sending Zentao task reminder for group: {}", group.getId(), e); + logger.error("Error sending Zentao project status reminder for group: {}", group.getId(), e); } } @@ -142,7 +141,147 @@ public class ZentaoTaskReminderService { } /** - * 构建任务提醒消息 + * 构建统一的项目状态提醒消息(包含任务和BUG) + */ + private String buildProjectStatusMessage(TaskReminderConfig.Group group, + List incompleteTasks, + List unresolvedBugs, + ProjectInfo projectInfo, + List mentionedMobiles) { + StringBuilder message = new StringBuilder(); + + // 消息头部 + message.append("**禅道项目状态提醒**\n"); + message.append("项目: ").append(projectInfo.getName()).append("\n\n"); + + // 统计信息 + long overdueTasks = incompleteTasks.stream().filter(ZentaoTask::isOverdue).count(); + long overdueBugs = unresolvedBugs.stream().filter(ZentaoBug::isOverdue).count(); + + message.append("📊 **项目概况**\n"); + message.append("未完成任务: ").append(incompleteTasks.size()).append("个"); + if (overdueTasks > 0) { + message.append(",已过期: ").append(overdueTasks).append("个"); + } + message.append("\n"); + + message.append("未解决BUG: ").append(unresolvedBugs.size()).append("个"); + if (overdueBugs > 0) { + message.append(",已过期: ").append(overdueBugs).append("个"); + } + message.append("\n\n"); + + // 按负责人合并任务和BUG + Map itemsByAssignee = mergeTasksAndBugs(incompleteTasks, unresolvedBugs); + + // 生成每个人的工作项详情 + for (Map.Entry entry : itemsByAssignee.entrySet()) { + String assignedTo = entry.getKey(); + ProjectItems items = entry.getValue(); + + // 用用户名匹配手机号 + String phone = userMappingService.getPhoneByRealName(group, assignedTo); + if (phone != null) { + mentionedMobiles.add(phone); + } + + // 显示真实姓名 + String displayName = getDisplayName(items, assignedTo); + int totalItems = items.tasks.size() + items.bugs.size(); + message.append(displayName).append(" (").append(totalItems).append("个事项)\n"); + + // 显示任务 + for (ZentaoTask task : items.tasks) { + String statusIcon = task.isOverdue() ? "🔴" : "⚪"; + message.append("- ").append(statusIcon).append(" [任务").append(task.getId()).append("] ") + .append(task.getName()); + if (task.getDeadline() != null && !task.getDeadline().isEmpty() && + !"0000-00-00".equals(task.getDeadline())) { + message.append(" (").append(task.getDeadline()).append(")"); + } + message.append("\n"); + } + + // 显示BUG + for (ZentaoBug bug : items.bugs) { + String statusIcon = bug.isOverdue() ? "🔴" : "🐛"; + message.append("- ").append(statusIcon).append(" [BUG").append(bug.getId()).append("] ") + .append(bug.getTitle()); + if (bug.getSeverity() != null && !bug.getSeverity().isEmpty()) { + message.append(" [").append(bug.getSeverityDesc()).append("]"); + } + if (bug.getDeadline() != null && !bug.getDeadline().isEmpty() && + !"0000-00-00".equals(bug.getDeadline())) { + message.append(" (").append(bug.getDeadline()).append(")"); + } + message.append("\n"); + } + message.append("\n"); + } + + // 提示信息 + message.append("请及时登录禅道系统更新状态"); + + return message.toString(); + } + + /** + * 合并任务和BUG按负责人分组 + */ + private Map mergeTasksAndBugs(List tasks, List bugs) { + Map result = new HashMap<>(); + + // 添加任务 + for (ZentaoTask task : tasks) { + if (task.getAssignedTo() != null && !task.getAssignedTo().isEmpty()) { + result.computeIfAbsent(task.getAssignedTo(), k -> new ProjectItems()).tasks.add(task); + } + } + + // 添加BUG + for (ZentaoBug bug : bugs) { + if (bug.getAssignedTo() != null && !bug.getAssignedTo().isEmpty()) { + result.computeIfAbsent(bug.getAssignedTo(), k -> new ProjectItems()).bugs.add(bug); + } + } + + return result; + } + + /** + * 获取显示名称(优先使用真实姓名) + */ + private String getDisplayName(ProjectItems items, String assignedTo) { + // 从任务中获取真实姓名 + if (!items.tasks.isEmpty()) { + String realName = items.tasks.get(0).getAssignedToRealName(); + if (realName != null && !realName.isEmpty()) { + return realName; + } + } + + // 从BUG中获取真实姓名 + if (!items.bugs.isEmpty()) { + String realName = items.bugs.get(0).getAssignedToRealName(); + if (realName != null && !realName.isEmpty()) { + return realName; + } + } + + // 默认返回用户名 + return assignedTo; + } + + /** + * 项目工作项容器类 + */ + private static class ProjectItems { + List tasks = new ArrayList<>(); + List bugs = new ArrayList<>(); + } + + /** + * 构建任务提醒消息(保留原方法以兼容性) */ private String buildTaskReminderMessage(TaskReminderConfig.Group group, Map> tasksByAssignee, @@ -287,7 +426,23 @@ public class ZentaoTaskReminderService { } /** - * 发送无任务消息 + * 发送无事项消息(既没有任务也没有BUG) + */ + private void sendNoItemsMessage(TaskReminderConfig.Group group, ProjectInfo projectInfo) { + StringBuilder message = new StringBuilder(); + message.append("**禅道项目状态提醒**\n"); + message.append("项目: ").append(projectInfo.getName()).append("\n\n"); + message.append("🎉 恭喜!当前项目没有未完成的任务和未解决的BUG。"); + + try { + wechatWebhookService.sendMessage(group.getWebhook().getUrl(), message.toString()); + } catch (Exception e) { + logger.error("Error sending no items message for group: {}", group.getId(), e); + } + } + + /** + * 发送无任务消息(保留原方法以兼容性) */ private void sendNoTasksMessage(TaskReminderConfig.Group group, ProjectInfo projectInfo) { StringBuilder message = new StringBuilder(); diff --git a/test-bug-api.md b/test-bug-api.md index 650b7ce..bf956bc 100644 --- a/test-bug-api.md +++ b/test-bug-api.md @@ -1,11 +1,12 @@ -# 禅道BUG提醒功能测试指南 +# 禅道统一项目状态提醒功能测试指南 -## 新增功能概述 +## 功能概述 -已成功为禅道任务提醒系统添加了BUG延期通知功能,现在系统支持: +已成功实现禅道统一项目状态提醒功能,现在系统支持: -1. **任务延期通知** - 原有功能 -2. **BUG延期通知** - 新增功能 +1. **统一项目状态提醒** - 一个通知包含任务和BUG(推荐) +2. **单独任务提醒** - 仅任务通知(兼容性) +3. **单独BUG提醒** - 仅BUG通知(兼容性) ## 新增的API接口