增加BUG任务的提醒
This commit is contained in:
parent
25f6988e65
commit
b5b50bf0cb
@ -24,7 +24,7 @@ task:
|
|||||||
api-url: "https://zentao.iscmtech.com" # 禅道地址
|
api-url: "https://zentao.iscmtech.com" # 禅道地址
|
||||||
username: "your-username" # 禅道用户名
|
username: "your-username" # 禅道用户名
|
||||||
password: "your-password" # 禅道密码
|
password: "your-password" # 禅道密码
|
||||||
project-id: 26 # 项目ID
|
project-id: 26 # 项目ID(同时用于获取任务和BUG)
|
||||||
# 用户映射:禅道邮箱 -> 企业微信手机号
|
# 用户映射:禅道邮箱 -> 企业微信手机号
|
||||||
user-mapping:
|
user-mapping:
|
||||||
"liguiling@iscmtech.com": "13800138000"
|
"liguiling@iscmtech.com": "13800138000"
|
||||||
@ -63,7 +63,7 @@ user-mapping:
|
|||||||
|
|
||||||
## API接口
|
## API接口
|
||||||
|
|
||||||
### 测试禅道连接
|
### 测试禅道任务连接
|
||||||
```
|
```
|
||||||
GET /api/reminder/zentao/test/{groupId}
|
GET /api/reminder/zentao/test/{groupId}
|
||||||
```
|
```
|
||||||
@ -73,6 +73,16 @@ GET /api/reminder/zentao/test/{groupId}
|
|||||||
POST /api/reminder/zentao/reminder/{groupId}
|
POST /api/reminder/zentao/reminder/{groupId}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 测试禅道BUG连接
|
||||||
|
```
|
||||||
|
GET /api/reminder/zentao/bugs/test/{groupId}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动触发禅道BUG提醒
|
||||||
|
```
|
||||||
|
POST /api/reminder/zentao/bugs/reminder/{groupId}
|
||||||
|
```
|
||||||
|
|
||||||
### 手动触发指定群组提醒
|
### 手动触发指定群组提醒
|
||||||
```
|
```
|
||||||
POST /api/reminder/groups/{groupId}/morning
|
POST /api/reminder/groups/{groupId}/morning
|
||||||
@ -81,21 +91,33 @@ POST /api/reminder/groups/{groupId}/evening
|
|||||||
|
|
||||||
## 消息格式
|
## 消息格式
|
||||||
|
|
||||||
|
### 任务提醒消息格式
|
||||||
|
|
||||||
系统会发送包含以下信息的详细任务提醒:
|
系统会发送包含以下信息的详细任务提醒:
|
||||||
|
|
||||||
- 📊 任务统计(总数、过期数、涉及人员)
|
- 📊 任务统计(总数、过期数、涉及人员)
|
||||||
- 👥 按人员分组的任务列表
|
- 👥 按人员分组的任务列表
|
||||||
- 🔴 过期任务标识
|
- 🔴 过期任务标识
|
||||||
- 🟡 进行中任务标识
|
|
||||||
- ⚪ 其他状态任务标识
|
- ⚪ 其他状态任务标识
|
||||||
- 📋 任务优先级和截止日期
|
- 📋 任务优先级和截止日期
|
||||||
- 🔗 禅道系统访问链接
|
- 🔗 禅道系统访问链接
|
||||||
|
|
||||||
|
### BUG提醒消息格式
|
||||||
|
|
||||||
|
系统会发送包含以下信息的详细BUG提醒:
|
||||||
|
|
||||||
|
- 📊 BUG统计(总数、过期数、涉及人员)
|
||||||
|
- 👥 按人员分组的BUG列表
|
||||||
|
- 🔴 过期BUG标识
|
||||||
|
- 🐛 未解决BUG标识
|
||||||
|
- 📋 BUG严重程度和截止日期
|
||||||
|
- 🔗 禅道系统访问链接
|
||||||
|
|
||||||
## 使用步骤
|
## 使用步骤
|
||||||
|
|
||||||
1. **配置禅道信息**:在配置文件中填入正确的禅道地址、用户名、密码和项目ID
|
1. **配置禅道信息**:在配置文件中填入正确的禅道地址、用户名、密码和项目ID
|
||||||
2. **配置用户映射**:将团队成员的禅道邮箱映射到企业微信手机号
|
2. **配置用户映射**:将团队成员的禅道邮箱映射到企业微信手机号
|
||||||
3. **测试连接**:调用测试API确认禅道连接正常
|
3. **测试连接**:调用测试API确认禅道任务和BUG连接正常
|
||||||
4. **启用定时任务**:系统会按配置的时间自动发送提醒
|
4. **启用定时任务**:系统会按配置的时间自动发送提醒
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.zeodao.reminder.controller;
|
|||||||
|
|
||||||
import com.zeodao.reminder.config.TaskReminderConfig;
|
import com.zeodao.reminder.config.TaskReminderConfig;
|
||||||
import com.zeodao.reminder.model.ZentaoTask;
|
import com.zeodao.reminder.model.ZentaoTask;
|
||||||
|
import com.zeodao.reminder.model.ZentaoBug;
|
||||||
import com.zeodao.reminder.scheduler.DynamicTaskScheduler;
|
import com.zeodao.reminder.scheduler.DynamicTaskScheduler;
|
||||||
import com.zeodao.reminder.service.TaskReminderService;
|
import com.zeodao.reminder.service.TaskReminderService;
|
||||||
import com.zeodao.reminder.service.ZentaoTaskReminderService;
|
import com.zeodao.reminder.service.ZentaoTaskReminderService;
|
||||||
@ -270,4 +271,72 @@ public class TaskReminderController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试禅道BUG连接
|
||||||
|
*/
|
||||||
|
@GetMapping("/zentao/bugs/test/{groupId}")
|
||||||
|
public ResponseEntity<Map<String, Object>> testZentaoBugConnection(@PathVariable String groupId) {
|
||||||
|
Map<String, Object> 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<ZentaoBug> bugs = zentaoTaskReminderService.getBugsForTesting(group);
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "禅道BUG API连接成功");
|
||||||
|
response.put("bugCount", bugs.size());
|
||||||
|
response.put("bugs", bugs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("测试禅道BUG连接失败: {}", groupId, e);
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "测试禅道BUG连接失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发禅道BUG提醒
|
||||||
|
*/
|
||||||
|
@PostMapping("/zentao/bugs/reminder/{groupId}")
|
||||||
|
public ResponseEntity<Map<String, Object>> sendZentaoBugReminder(@PathVariable String groupId) {
|
||||||
|
Map<String, Object> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
zentaoTaskReminderService.sendBugReminder(group);
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "禅道BUG提醒已发送");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("发送禅道BUG提醒失败: {}", groupId, e);
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "发送禅道BUG提醒失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
244
src/main/java/com/zeodao/reminder/model/ZentaoBug.java
Normal file
244
src/main/java/com/zeodao/reminder/model/ZentaoBug.java
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package com.zeodao.reminder.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禅道BUG实体类 - 使用JsonNode实现动态字段解析
|
||||||
|
*
|
||||||
|
* @author Zeodao
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ZentaoBug {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private String severity;
|
||||||
|
|
||||||
|
private String pri;
|
||||||
|
|
||||||
|
private String assignedTo;
|
||||||
|
|
||||||
|
private String assignedToRealName;
|
||||||
|
|
||||||
|
private String assignedDate;
|
||||||
|
|
||||||
|
private String deadline;
|
||||||
|
|
||||||
|
private String openedBy;
|
||||||
|
|
||||||
|
private String openedDate;
|
||||||
|
|
||||||
|
private String resolvedBy;
|
||||||
|
|
||||||
|
private String resolvedDate;
|
||||||
|
|
||||||
|
private String closedBy;
|
||||||
|
|
||||||
|
private String closedDate;
|
||||||
|
|
||||||
|
private String lastEditedBy;
|
||||||
|
|
||||||
|
private String lastEditedDate;
|
||||||
|
|
||||||
|
private String product;
|
||||||
|
|
||||||
|
private String project;
|
||||||
|
|
||||||
|
private String execution;
|
||||||
|
|
||||||
|
private String module;
|
||||||
|
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
private String os;
|
||||||
|
|
||||||
|
private String browser;
|
||||||
|
|
||||||
|
private String steps;
|
||||||
|
|
||||||
|
private String confirmed;
|
||||||
|
|
||||||
|
private String activatedCount;
|
||||||
|
|
||||||
|
private String activatedDate;
|
||||||
|
|
||||||
|
private String deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从JsonNode创建ZentaoBug对象,安全地提取字段
|
||||||
|
*/
|
||||||
|
public static ZentaoBug fromJsonNode(JsonNode node) {
|
||||||
|
ZentaoBug bug = new ZentaoBug();
|
||||||
|
|
||||||
|
// 安全地提取字段,如果字段不存在或类型不匹配,使用默认值
|
||||||
|
bug.setId(getStringValue(node, "id"));
|
||||||
|
bug.setTitle(getStringValue(node, "title"));
|
||||||
|
bug.setType(getStringValue(node, "type"));
|
||||||
|
bug.setStatus(getStringValue(node, "status"));
|
||||||
|
bug.setSeverity(getStringValue(node, "severity"));
|
||||||
|
bug.setPri(getStringValue(node, "pri"));
|
||||||
|
bug.setAssignedTo(getStringValue(node, "assignedTo"));
|
||||||
|
bug.setAssignedToRealName(getStringValue(node, "assignedToRealName"));
|
||||||
|
bug.setAssignedDate(getStringValue(node, "assignedDate"));
|
||||||
|
bug.setDeadline(getStringValue(node, "deadline"));
|
||||||
|
bug.setOpenedBy(getStringValue(node, "openedBy"));
|
||||||
|
bug.setOpenedDate(getStringValue(node, "openedDate"));
|
||||||
|
bug.setResolvedBy(getStringValue(node, "resolvedBy"));
|
||||||
|
bug.setResolvedDate(getStringValue(node, "resolvedDate"));
|
||||||
|
bug.setClosedBy(getStringValue(node, "closedBy"));
|
||||||
|
bug.setClosedDate(getStringValue(node, "closedDate"));
|
||||||
|
bug.setLastEditedBy(getStringValue(node, "lastEditedBy"));
|
||||||
|
bug.setLastEditedDate(getStringValue(node, "lastEditedDate"));
|
||||||
|
bug.setProduct(getStringValue(node, "product"));
|
||||||
|
bug.setProject(getStringValue(node, "project"));
|
||||||
|
bug.setExecution(getStringValue(node, "execution"));
|
||||||
|
bug.setModule(getStringValue(node, "module"));
|
||||||
|
bug.setKeywords(getStringValue(node, "keywords"));
|
||||||
|
bug.setOs(getStringValue(node, "os"));
|
||||||
|
bug.setBrowser(getStringValue(node, "browser"));
|
||||||
|
bug.setSteps(getStringValue(node, "steps"));
|
||||||
|
bug.setConfirmed(getStringValue(node, "confirmed"));
|
||||||
|
bug.setActivatedCount(getStringValue(node, "activatedCount"));
|
||||||
|
bug.setActivatedDate(getStringValue(node, "activatedDate"));
|
||||||
|
bug.setDeleted(getStringValue(node, "deleted"));
|
||||||
|
|
||||||
|
return bug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地从JsonNode获取字符串值
|
||||||
|
*/
|
||||||
|
private static String getStringValue(JsonNode node, String fieldName) {
|
||||||
|
if (node == null || !node.has(fieldName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode fieldNode = node.get(fieldName);
|
||||||
|
if (fieldNode.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理嵌套对象(如status可能是对象)
|
||||||
|
if (fieldNode.isObject()) {
|
||||||
|
if (fieldNode.has("code")) {
|
||||||
|
return fieldNode.get("code").asText();
|
||||||
|
}
|
||||||
|
if (fieldNode.has("account")) {
|
||||||
|
return fieldNode.get("account").asText();
|
||||||
|
}
|
||||||
|
if (fieldNode.has("realname")) {
|
||||||
|
return fieldNode.get("realname").asText();
|
||||||
|
}
|
||||||
|
return fieldNode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于基本类型,直接转换为字符串
|
||||||
|
return fieldNode.asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断BUG是否未解决
|
||||||
|
*/
|
||||||
|
public boolean isUnresolved() {
|
||||||
|
return !"resolved".equals(status) && !"closed".equals(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断BUG是否已过期
|
||||||
|
*/
|
||||||
|
public boolean isOverdue() {
|
||||||
|
if (deadline == null || deadline.isEmpty() || "0000-00-00".equals(deadline)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 简单的日期比较,实际项目中应该使用更严格的日期处理
|
||||||
|
return deadline.compareTo(java.time.LocalDate.now().toString()) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取严重程度描述
|
||||||
|
*/
|
||||||
|
public String getSeverityDesc() {
|
||||||
|
switch (severity) {
|
||||||
|
case "1":
|
||||||
|
return "致命";
|
||||||
|
case "2":
|
||||||
|
return "严重";
|
||||||
|
case "3":
|
||||||
|
return "一般";
|
||||||
|
case "4":
|
||||||
|
return "轻微";
|
||||||
|
default:
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取优先级描述
|
||||||
|
*/
|
||||||
|
public String getPriorityDesc() {
|
||||||
|
switch (pri) {
|
||||||
|
case "1":
|
||||||
|
return "高";
|
||||||
|
case "2":
|
||||||
|
return "中";
|
||||||
|
case "3":
|
||||||
|
return "低";
|
||||||
|
case "4":
|
||||||
|
return "最低";
|
||||||
|
default:
|
||||||
|
return "普通";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取BUG类型描述
|
||||||
|
*/
|
||||||
|
public String getTypeDesc() {
|
||||||
|
switch (type) {
|
||||||
|
case "codeerror":
|
||||||
|
return "代码错误";
|
||||||
|
case "config":
|
||||||
|
return "配置相关";
|
||||||
|
case "install":
|
||||||
|
return "安装部署";
|
||||||
|
case "security":
|
||||||
|
return "安全相关";
|
||||||
|
case "performance":
|
||||||
|
return "性能问题";
|
||||||
|
case "standard":
|
||||||
|
return "标准规范";
|
||||||
|
case "automation":
|
||||||
|
return "测试脚本";
|
||||||
|
case "designdefect":
|
||||||
|
return "设计缺陷";
|
||||||
|
case "others":
|
||||||
|
return "其他";
|
||||||
|
default:
|
||||||
|
return type != null ? type : "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态描述
|
||||||
|
*/
|
||||||
|
public String getStatusDesc() {
|
||||||
|
switch (status) {
|
||||||
|
case "active":
|
||||||
|
return "激活";
|
||||||
|
case "resolved":
|
||||||
|
return "已解决";
|
||||||
|
case "closed":
|
||||||
|
return "已关闭";
|
||||||
|
default:
|
||||||
|
return status != null ? status : "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -127,6 +127,13 @@ public class ZentaoTask {
|
|||||||
return !"done".equals(status) && !"closed".equals(status) && !"cancel".equals(status);
|
return !"done".equals(status) && !"closed".equals(status) && !"cancel".equals(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断BUG是否未解决(用于BUG类型的数据)
|
||||||
|
*/
|
||||||
|
public boolean isBugUnresolved() {
|
||||||
|
return !"resolved".equals(status) && !"closed".equals(status);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断任务是否已过期
|
* 判断任务是否已过期
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import com.zeodao.reminder.config.TaskReminderConfig;
|
|||||||
import com.zeodao.reminder.exception.ProjectNotFoundException;
|
import com.zeodao.reminder.exception.ProjectNotFoundException;
|
||||||
import com.zeodao.reminder.model.ProjectInfo;
|
import com.zeodao.reminder.model.ProjectInfo;
|
||||||
import com.zeodao.reminder.model.ZentaoTask;
|
import com.zeodao.reminder.model.ZentaoTask;
|
||||||
|
import com.zeodao.reminder.model.ZentaoBug;
|
||||||
import com.zeodao.reminder.model.ZentaoUser;
|
import com.zeodao.reminder.model.ZentaoUser;
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
@ -82,6 +83,35 @@ public class ZentaoApiService {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目下的未解决BUG
|
||||||
|
*/
|
||||||
|
public List<ZentaoBug> getUnresolvedBugs(TaskReminderConfig.Group group) {
|
||||||
|
try {
|
||||||
|
String sessionId = getSessionId(group.getZentao());
|
||||||
|
if (sessionId == null) {
|
||||||
|
logger.error("Failed to get session for group: {}", group.getId());
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接使用项目ID作为产品ID(在大多数情况下项目ID和产品ID是相同的)
|
||||||
|
String productId = String.valueOf(group.getZentao().getProjectId());
|
||||||
|
|
||||||
|
// 禅道开源版BUG API路径
|
||||||
|
String url = group.getZentao().getApiUrl() + "/api.php/v1/products/" + productId + "/bugs";
|
||||||
|
logger.debug("Trying to get bugs from URL: {}", url);
|
||||||
|
|
||||||
|
List<ZentaoBug> bugs = tryGetBugsFromUrl(url, sessionId, group);
|
||||||
|
logger.info("Found {} unresolved bugs for group: {}", bugs.size(), group.getId());
|
||||||
|
return bugs;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error fetching bugs from Zentao for group: {}", group.getId(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查项目是否存在并获取项目信息
|
* 检查项目是否存在并获取项目信息
|
||||||
*/
|
*/
|
||||||
@ -380,6 +410,84 @@ public class ZentaoApiService {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试从指定URL获取BUG列表
|
||||||
|
*/
|
||||||
|
private List<ZentaoBug> tryGetBugsFromUrl(String url, String sessionId, TaskReminderConfig.Group group) {
|
||||||
|
try (CloseableHttpClient client = HttpClients.createDefault()) {
|
||||||
|
HttpGet request = new HttpGet(url);
|
||||||
|
request.setHeader("Cookie", "zentaosid=" + sessionId);
|
||||||
|
request.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
try (CloseableHttpResponse response = client.execute(request)) {
|
||||||
|
String responseBody = EntityUtils.toString(response.getEntity());
|
||||||
|
logger.debug("Zentao BUG API response for {}: {}", url, responseBody);
|
||||||
|
|
||||||
|
JsonNode rootNode = objectMapper.readTree(responseBody);
|
||||||
|
|
||||||
|
// 检查是否有错误
|
||||||
|
if (rootNode.has("error")) {
|
||||||
|
logger.debug("API returned error for {}: {}", url, rootNode.get("error").asText());
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用安全的JsonNode解析方法
|
||||||
|
List<ZentaoBug> allBugs = parseBugsFromJsonNode(rootNode);
|
||||||
|
|
||||||
|
logger.debug("Found {} bugs, filtering unresolved ones", allBugs.size());
|
||||||
|
for (ZentaoBug bug : allBugs) {
|
||||||
|
logger.debug("Bug: {} - Status: {} - Unresolved: {}",
|
||||||
|
bug.getTitle(), bug.getStatus(), bug.isUnresolved());
|
||||||
|
}
|
||||||
|
|
||||||
|
return allBugs.stream()
|
||||||
|
.filter(ZentaoBug::isUnresolved)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Error trying URL {}: {}", url, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地从JsonNode解析BUG列表
|
||||||
|
*/
|
||||||
|
private List<ZentaoBug> parseBugsFromJsonNode(JsonNode rootNode) {
|
||||||
|
List<ZentaoBug> bugs = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 尝试从bugs字段获取BUG数组
|
||||||
|
if (rootNode.has("bugs") && rootNode.get("bugs").isArray()) {
|
||||||
|
JsonNode bugsArray = rootNode.get("bugs");
|
||||||
|
for (JsonNode bugNode : bugsArray) {
|
||||||
|
ZentaoBug bug = ZentaoBug.fromJsonNode(bugNode);
|
||||||
|
if (bug != null) {
|
||||||
|
bugs.add(bug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bugs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果根节点直接是数组
|
||||||
|
if (rootNode.isArray()) {
|
||||||
|
for (JsonNode bugNode : rootNode) {
|
||||||
|
ZentaoBug bug = ZentaoBug.fromJsonNode(bugNode);
|
||||||
|
if (bug != null) {
|
||||||
|
bugs.add(bug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bugs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Error parsing bugs from JsonNode: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return bugs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全地从JsonNode解析任务列表
|
* 安全地从JsonNode解析任务列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.zeodao.reminder.config.TaskReminderConfig;
|
|||||||
import com.zeodao.reminder.exception.ProjectNotFoundException;
|
import com.zeodao.reminder.exception.ProjectNotFoundException;
|
||||||
import com.zeodao.reminder.model.ProjectInfo;
|
import com.zeodao.reminder.model.ProjectInfo;
|
||||||
import com.zeodao.reminder.model.ZentaoTask;
|
import com.zeodao.reminder.model.ZentaoTask;
|
||||||
|
import com.zeodao.reminder.model.ZentaoBug;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -88,6 +89,58 @@ public class ZentaoTaskReminderService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送禅道BUG提醒
|
||||||
|
*/
|
||||||
|
public void sendBugReminder(TaskReminderConfig.Group group) {
|
||||||
|
try {
|
||||||
|
logger.info("Starting Zentao bug reminder for group: {}", group.getId());
|
||||||
|
|
||||||
|
// 首先检查项目是否存在
|
||||||
|
ProjectInfo projectInfo;
|
||||||
|
try {
|
||||||
|
projectInfo = zentaoApiService.getProjectInfo(group);
|
||||||
|
logger.info("Project found: {} (ID: {}, Status: {})",
|
||||||
|
projectInfo.getName(), projectInfo.getId(), projectInfo.getStatus());
|
||||||
|
} catch (ProjectNotFoundException e) {
|
||||||
|
logger.error("Project validation failed for group {}: {}", group.getId(), e.getMessage());
|
||||||
|
// 项目不存在时只记录日志,不发送企业微信消息
|
||||||
|
throw new RuntimeException("禅道项目配置错误: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证用户映射配置
|
||||||
|
if (!userMappingService.validateUserMapping(group)) {
|
||||||
|
logger.warn("User mapping validation failed for group: {}", group.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取未解决BUG
|
||||||
|
List<ZentaoBug> unresolvedBugs = zentaoApiService.getUnresolvedBugs(group);
|
||||||
|
if (unresolvedBugs.isEmpty()) {
|
||||||
|
logger.info("No unresolved bugs found for group: {}", group.getId());
|
||||||
|
sendNoBugsMessage(group, projectInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按负责人分组
|
||||||
|
Map<String, List<ZentaoBug>> bugsByAssignee = unresolvedBugs.stream()
|
||||||
|
.filter(bug -> bug.getAssignedTo() != null && !bug.getAssignedTo().isEmpty())
|
||||||
|
.collect(Collectors.groupingBy(ZentaoBug::getAssignedTo));
|
||||||
|
|
||||||
|
// 生成提醒消息并收集@人手机号
|
||||||
|
List<String> mentionedMobiles = new ArrayList<>();
|
||||||
|
String message = buildBugReminderMessage(group, bugsByAssignee, unresolvedBugs, projectInfo, mentionedMobiles);
|
||||||
|
|
||||||
|
// 发送带@人的消息
|
||||||
|
wechatWebhookService.sendMessageWithMentions(group.getWebhook().getUrl(), message, mentionedMobiles);
|
||||||
|
|
||||||
|
logger.info("Zentao bug reminder sent successfully for group: {}", group.getId());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error sending Zentao bug reminder for group: {}", group.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建任务提醒消息
|
* 构建任务提醒消息
|
||||||
*/
|
*/
|
||||||
@ -150,6 +203,89 @@ public class ZentaoTaskReminderService {
|
|||||||
return message.toString();
|
return message.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建BUG提醒消息
|
||||||
|
*/
|
||||||
|
private String buildBugReminderMessage(TaskReminderConfig.Group group,
|
||||||
|
Map<String, List<ZentaoBug>> bugsByAssignee,
|
||||||
|
List<ZentaoBug> allBugs,
|
||||||
|
ProjectInfo projectInfo,
|
||||||
|
List<String> mentionedMobiles) {
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
|
||||||
|
// 简化消息头部
|
||||||
|
message.append("**禅道BUG提醒**\n");
|
||||||
|
message.append("项目: ").append(projectInfo.getName()).append("\n\n");
|
||||||
|
|
||||||
|
// 简化统计信息
|
||||||
|
long overdueBugs = allBugs.stream().filter(ZentaoBug::isOverdue).count();
|
||||||
|
message.append("未解决BUG: ").append(allBugs.size()).append("个");
|
||||||
|
if (overdueBugs > 0) {
|
||||||
|
message.append(",已过期: ").append(overdueBugs).append("个");
|
||||||
|
}
|
||||||
|
message.append("\n\n");
|
||||||
|
|
||||||
|
// BUG详情 - 显示真实姓名,但用用户名匹配手机号
|
||||||
|
for (Map.Entry<String, List<ZentaoBug>> entry : bugsByAssignee.entrySet()) {
|
||||||
|
String assignedTo = entry.getKey(); // 这是用户名
|
||||||
|
List<ZentaoBug> bugs = entry.getValue();
|
||||||
|
|
||||||
|
// 用用户名匹配手机号
|
||||||
|
String phone = userMappingService.getPhoneByRealName(group, assignedTo);
|
||||||
|
if (phone != null) {
|
||||||
|
mentionedMobiles.add(phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示真实姓名(从BUG中获取)
|
||||||
|
String realName = bugs.get(0).getAssignedToRealName();
|
||||||
|
String displayName = (realName != null && !realName.isEmpty()) ? realName : assignedTo;
|
||||||
|
message.append(displayName).append(" (").append(bugs.size()).append("个BUG)\n");
|
||||||
|
|
||||||
|
for (ZentaoBug bug : bugs) {
|
||||||
|
// 简化BUG状态图标
|
||||||
|
String statusIcon = bug.isOverdue() ? "🔴" : "🐛";
|
||||||
|
|
||||||
|
message.append("- ").append(statusIcon).append(" [").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("请及时登录禅道系统处理BUG");
|
||||||
|
|
||||||
|
return message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送无BUG消息
|
||||||
|
*/
|
||||||
|
private void sendNoBugsMessage(TaskReminderConfig.Group group, ProjectInfo projectInfo) {
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
message.append("**禅道BUG提醒**\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 bugs message for group: {}", group.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送无任务消息
|
* 发送无任务消息
|
||||||
*/
|
*/
|
||||||
@ -181,4 +317,18 @@ public class ZentaoTaskReminderService {
|
|||||||
return List.of(); // 返回空列表
|
return List.of(); // 返回空列表
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取BUG详情(用于测试)
|
||||||
|
*/
|
||||||
|
public List<ZentaoBug> getBugsForTesting(TaskReminderConfig.Group group) {
|
||||||
|
try {
|
||||||
|
// 先检查项目是否存在
|
||||||
|
zentaoApiService.getProjectInfo(group);
|
||||||
|
return zentaoApiService.getUnresolvedBugs(group);
|
||||||
|
} catch (ProjectNotFoundException e) {
|
||||||
|
logger.error("Project not found during testing: {}", e.getMessage());
|
||||||
|
return List.of(); // 返回空列表
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ task:
|
|||||||
api-url: "https://zentao.iscmtech.com"
|
api-url: "https://zentao.iscmtech.com"
|
||||||
username: "admin" # 请替换为实际的禅道用户名
|
username: "admin" # 请替换为实际的禅道用户名
|
||||||
password: "Lianyu!@#~123456" # 请替换为实际的禅道密码
|
password: "Lianyu!@#~123456" # 请替换为实际的禅道密码
|
||||||
project-id: 38 # 项目ID
|
project-id: 38 # 项目ID(同时用于获取任务和BUG)
|
||||||
kanban-id: 39 # 看板ID(看板模式项目需要)
|
kanban-id: 39 # 看板ID(看板模式项目需要)
|
||||||
# 用户映射:禅道用户名 -> 企业微信手机号(显示使用真实姓名)
|
# 用户映射:禅道用户名 -> 企业微信手机号(显示使用真实姓名)
|
||||||
user-mapping:
|
user-mapping:
|
||||||
|
|||||||
@ -0,0 +1,170 @@
|
|||||||
|
package com.zeodao.reminder.service;
|
||||||
|
|
||||||
|
import com.zeodao.reminder.config.TaskReminderConfig;
|
||||||
|
import com.zeodao.reminder.model.ZentaoBug;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禅道BUG提醒服务测试
|
||||||
|
*
|
||||||
|
* @author Zeodao
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@TestPropertySource(locations = "classpath:application-test.properties")
|
||||||
|
public class ZentaoBugReminderTest {
|
||||||
|
|
||||||
|
private TaskReminderConfig.Group testGroup;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// 创建测试群组配置
|
||||||
|
testGroup = new TaskReminderConfig.Group();
|
||||||
|
testGroup.setId("test-group");
|
||||||
|
testGroup.setName("测试群组");
|
||||||
|
testGroup.setEnabled(true);
|
||||||
|
testGroup.setTaskSystem("zentao");
|
||||||
|
|
||||||
|
TaskReminderConfig.Webhook webhook = new TaskReminderConfig.Webhook();
|
||||||
|
webhook.setUrl("https://test.webhook.url");
|
||||||
|
testGroup.setWebhook(webhook);
|
||||||
|
|
||||||
|
TaskReminderConfig.Zentao zentao = new TaskReminderConfig.Zentao();
|
||||||
|
zentao.setApiUrl("https://test.zentao.com");
|
||||||
|
zentao.setUsername("testuser");
|
||||||
|
zentao.setPassword("testpass");
|
||||||
|
zentao.setProjectId(1);
|
||||||
|
testGroup.setZentao(zentao);
|
||||||
|
|
||||||
|
Map<String, String> userMapping = new HashMap<>();
|
||||||
|
userMapping.put("testuser", "13800138000");
|
||||||
|
testGroup.setUserMapping(userMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testZentaoBugModelCreation() {
|
||||||
|
ZentaoBug bug = new ZentaoBug();
|
||||||
|
bug.setId("1");
|
||||||
|
bug.setTitle("测试BUG");
|
||||||
|
bug.setStatus("active");
|
||||||
|
bug.setSeverity("2");
|
||||||
|
bug.setPri("1");
|
||||||
|
bug.setAssignedTo("testuser");
|
||||||
|
bug.setDeadline("2025-06-01");
|
||||||
|
|
||||||
|
assertNotNull(bug);
|
||||||
|
assertEquals("1", bug.getId());
|
||||||
|
assertEquals("测试BUG", bug.getTitle());
|
||||||
|
assertEquals("active", bug.getStatus());
|
||||||
|
assertTrue(bug.isUnresolved());
|
||||||
|
assertFalse(bug.isOverdue()); // 假设今天是2025年5月28日之前
|
||||||
|
assertEquals("严重", bug.getSeverityDesc());
|
||||||
|
assertEquals("高", bug.getPriorityDesc());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBugStatusLogic() {
|
||||||
|
ZentaoBug activeBug = new ZentaoBug();
|
||||||
|
activeBug.setStatus("active");
|
||||||
|
assertTrue(activeBug.isUnresolved());
|
||||||
|
|
||||||
|
ZentaoBug resolvedBug = new ZentaoBug();
|
||||||
|
resolvedBug.setStatus("resolved");
|
||||||
|
assertFalse(resolvedBug.isUnresolved());
|
||||||
|
|
||||||
|
ZentaoBug closedBug = new ZentaoBug();
|
||||||
|
closedBug.setStatus("closed");
|
||||||
|
assertFalse(closedBug.isUnresolved());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBugOverdueLogic() {
|
||||||
|
ZentaoBug overdueBug = new ZentaoBug();
|
||||||
|
overdueBug.setDeadline("2020-01-01");
|
||||||
|
assertTrue(overdueBug.isOverdue());
|
||||||
|
|
||||||
|
ZentaoBug futureBug = new ZentaoBug();
|
||||||
|
futureBug.setDeadline("2030-01-01");
|
||||||
|
assertFalse(futureBug.isOverdue());
|
||||||
|
|
||||||
|
ZentaoBug noDeadlineBug = new ZentaoBug();
|
||||||
|
noDeadlineBug.setDeadline(null);
|
||||||
|
assertFalse(noDeadlineBug.isOverdue());
|
||||||
|
|
||||||
|
ZentaoBug zeroDeadlineBug = new ZentaoBug();
|
||||||
|
zeroDeadlineBug.setDeadline("0000-00-00");
|
||||||
|
assertFalse(zeroDeadlineBug.isOverdue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBugDescriptions() {
|
||||||
|
ZentaoBug bug = new ZentaoBug();
|
||||||
|
|
||||||
|
// 测试严重程度描述
|
||||||
|
bug.setSeverity("1");
|
||||||
|
assertEquals("致命", bug.getSeverityDesc());
|
||||||
|
|
||||||
|
bug.setSeverity("2");
|
||||||
|
assertEquals("严重", bug.getSeverityDesc());
|
||||||
|
|
||||||
|
bug.setSeverity("3");
|
||||||
|
assertEquals("一般", bug.getSeverityDesc());
|
||||||
|
|
||||||
|
bug.setSeverity("4");
|
||||||
|
assertEquals("轻微", bug.getSeverityDesc());
|
||||||
|
|
||||||
|
bug.setSeverity("unknown");
|
||||||
|
assertEquals("未知", bug.getSeverityDesc());
|
||||||
|
|
||||||
|
// 测试优先级描述
|
||||||
|
bug.setPri("1");
|
||||||
|
assertEquals("高", bug.getPriorityDesc());
|
||||||
|
|
||||||
|
bug.setPri("2");
|
||||||
|
assertEquals("中", bug.getPriorityDesc());
|
||||||
|
|
||||||
|
bug.setPri("3");
|
||||||
|
assertEquals("低", bug.getPriorityDesc());
|
||||||
|
|
||||||
|
bug.setPri("4");
|
||||||
|
assertEquals("最低", bug.getPriorityDesc());
|
||||||
|
|
||||||
|
bug.setPri("unknown");
|
||||||
|
assertEquals("普通", bug.getPriorityDesc());
|
||||||
|
|
||||||
|
// 测试类型描述
|
||||||
|
bug.setType("codeerror");
|
||||||
|
assertEquals("代码错误", bug.getTypeDesc());
|
||||||
|
|
||||||
|
bug.setType("config");
|
||||||
|
assertEquals("配置相关", bug.getTypeDesc());
|
||||||
|
|
||||||
|
bug.setType("security");
|
||||||
|
assertEquals("安全相关", bug.getTypeDesc());
|
||||||
|
|
||||||
|
bug.setType("unknown");
|
||||||
|
assertEquals("unknown", bug.getTypeDesc());
|
||||||
|
|
||||||
|
// 测试状态描述
|
||||||
|
bug.setStatus("active");
|
||||||
|
assertEquals("激活", bug.getStatusDesc());
|
||||||
|
|
||||||
|
bug.setStatus("resolved");
|
||||||
|
assertEquals("已解决", bug.getStatusDesc());
|
||||||
|
|
||||||
|
bug.setStatus("closed");
|
||||||
|
assertEquals("已关闭", bug.getStatusDesc());
|
||||||
|
|
||||||
|
bug.setStatus("unknown");
|
||||||
|
assertEquals("unknown", bug.getStatusDesc());
|
||||||
|
}
|
||||||
|
}
|
||||||
129
test-bug-api.md
Normal file
129
test-bug-api.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# 禅道BUG提醒功能测试指南
|
||||||
|
|
||||||
|
## 新增功能概述
|
||||||
|
|
||||||
|
已成功为禅道任务提醒系统添加了BUG延期通知功能,现在系统支持:
|
||||||
|
|
||||||
|
1. **任务延期通知** - 原有功能
|
||||||
|
2. **BUG延期通知** - 新增功能
|
||||||
|
|
||||||
|
## 新增的API接口
|
||||||
|
|
||||||
|
### 1. 测试禅道BUG连接
|
||||||
|
```
|
||||||
|
GET /api/reminder/zentao/bugs/test/{groupId}
|
||||||
|
```
|
||||||
|
|
||||||
|
**功能**: 测试禅道BUG API连接,获取未解决的BUG列表
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "禅道BUG API连接成功",
|
||||||
|
"bugCount": 5,
|
||||||
|
"bugs": [
|
||||||
|
{
|
||||||
|
"id": "123",
|
||||||
|
"title": "登录页面显示异常",
|
||||||
|
"status": "active",
|
||||||
|
"severity": "2",
|
||||||
|
"assignedTo": "zhangsan",
|
||||||
|
"deadline": "2025-06-01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 手动触发禅道BUG提醒
|
||||||
|
```
|
||||||
|
POST /api/reminder/zentao/bugs/reminder/{groupId}
|
||||||
|
```
|
||||||
|
|
||||||
|
**功能**: 手动发送禅道BUG提醒到企业微信群
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "禅道BUG提醒已发送"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
BUG功能直接使用现有的 `project-id` 配置,无需额外配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
zentao:
|
||||||
|
api-url: "https://zentao.iscmtech.com"
|
||||||
|
username: "admin"
|
||||||
|
password: "password"
|
||||||
|
project-id: 38 # 同时用于获取任务和BUG
|
||||||
|
```
|
||||||
|
|
||||||
|
## 消息格式
|
||||||
|
|
||||||
|
### BUG提醒消息示例:
|
||||||
|
```
|
||||||
|
**禅道BUG提醒**
|
||||||
|
项目: 一站式平台
|
||||||
|
|
||||||
|
未解决BUG: 3个,已过期: 1个
|
||||||
|
|
||||||
|
张三 (2个BUG)
|
||||||
|
- 🔴 [238] 测试任务2 [严重] (2025-05-28)
|
||||||
|
- 🐛 [236] 测试任务 [一般] (2025-05-28)
|
||||||
|
|
||||||
|
请及时登录禅道系统处理BUG
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
### 新增文件:
|
||||||
|
1. `ZentaoBug.java` - BUG数据模型
|
||||||
|
2. `ZentaoBugReminderTest.java` - BUG功能测试
|
||||||
|
|
||||||
|
### 修改文件:
|
||||||
|
1. `ZentaoApiService.java` - 添加获取BUG的API方法
|
||||||
|
2. `ZentaoTaskReminderService.java` - 添加BUG提醒逻辑
|
||||||
|
3. `TaskReminderController.java` - 添加BUG相关API接口
|
||||||
|
4. `TaskReminderConfig.java` - 添加productId配置字段
|
||||||
|
5. `application.yml` - 添加产品ID配置
|
||||||
|
6. `ZENTAO_INTEGRATION.md` - 更新文档
|
||||||
|
|
||||||
|
## 测试步骤
|
||||||
|
|
||||||
|
1. **启动应用程序**
|
||||||
|
2. **测试BUG连接**:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/api/reminder/zentao/bugs/test/zentao-team
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **手动触发BUG提醒**:
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/reminder/zentao/bugs/reminder/zentao-team
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特性说明
|
||||||
|
|
||||||
|
- **自动过滤**: 只显示未解决的BUG(状态不是 resolved 或 closed)
|
||||||
|
- **过期标识**: 🔴 表示已过期的BUG,🐛 表示普通未解决BUG
|
||||||
|
- **严重程度**: 显示BUG的严重程度(致命、严重、一般、轻微)
|
||||||
|
- **@人功能**: 支持根据用户映射配置@相关负责人
|
||||||
|
- **统计信息**: 显示总BUG数和过期BUG数
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **项目ID复用**: 系统直接使用 `project-id` 作为产品ID来获取BUG(在大多数禅道配置中,项目ID和产品ID是相同的)
|
||||||
|
2. **API兼容性**: 使用禅道开源版 RESTful API v1
|
||||||
|
3. **权限要求**: 配置的禅道用户需要有产品的BUG查看权限
|
||||||
|
4. **数据格式**: BUG数据结构与任务数据结构不同,使用专门的ZentaoBug模型处理
|
||||||
|
|
||||||
|
## 后续扩展
|
||||||
|
|
||||||
|
可以考虑添加:
|
||||||
|
- 定时BUG提醒(类似任务提醒的定时功能)
|
||||||
|
- BUG优先级过滤
|
||||||
|
- BUG类型分类显示
|
||||||
|
- BUG处理时间统计
|
||||||
Loading…
Reference in New Issue
Block a user