格式化

This commit is contained in:
dengqichen 2025-05-30 13:06:45 +08:00
parent c5233719cb
commit 65e9dc7b16
17 changed files with 197 additions and 96 deletions

View File

@ -20,6 +20,7 @@ import java.util.Map;
public class TaskReminderConfig { public class TaskReminderConfig {
private Global global = new Global(); private Global global = new Global();
private List<Group> groups = new ArrayList<>(); private List<Group> groups = new ArrayList<>();
public Global getGlobal() { public Global getGlobal() {
@ -43,13 +44,12 @@ public class TaskReminderConfig {
*/ */
public Group getGroupById(String groupId) { public Group getGroupById(String groupId) {
return groups.stream() return groups.stream()
.filter(group -> group.getId().equals(groupId)) .filter(group -> group.getId().equals(groupId))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
public static class Global { public static class Global {
private int timeout = 5000; private int timeout = 5000;
@ -64,12 +64,17 @@ public class TaskReminderConfig {
public static class Group { public static class Group {
private String id; private String id;
private String name; private String name;
private Webhook webhook = new Webhook(); private Webhook webhook = new Webhook();
private TaskSystemType taskSystem; private TaskSystemType taskSystem;
private Map<String, Schedule> schedules = new HashMap<>(); private Map<String, Schedule> schedules = new HashMap<>();
private Zentao zentao = new Zentao(); private Zentao zentao = new Zentao();
private Map<String, String> userMapping = new HashMap<>(); private Map<String, String> userMapping = new HashMap<>();
public String getId() { public String getId() {
@ -127,7 +132,6 @@ public class TaskReminderConfig {
} }
public Zentao getZentao() { public Zentao getZentao() {
return zentao; return zentao;
} }
@ -159,7 +163,9 @@ public class TaskReminderConfig {
public static class Schedule { public static class Schedule {
private String time; private String time;
private String message; private String message;
private boolean enabled = true; // 默认启用 private boolean enabled = true; // 默认启用
public String getTime() { public String getTime() {
@ -189,10 +195,15 @@ public class TaskReminderConfig {
public static class Zentao { public static class Zentao {
private String apiUrl; private String apiUrl;
private String token; private String token;
private String username; private String username;
private String password; private String password;
private Integer projectId; private Integer projectId;
private Integer kanbanId; private Integer kanbanId;
public String getApiUrl() { public String getApiUrl() {

View File

@ -187,8 +187,8 @@ public class TaskReminderController {
*/ */
@PostMapping("/groups/{groupId}/{scheduleType}") @PostMapping("/groups/{groupId}/{scheduleType}")
public ResponseEntity<Map<String, Object>> triggerGroupReminder( public ResponseEntity<Map<String, Object>> triggerGroupReminder(
@PathVariable String groupId, @PathVariable String groupId,
@PathVariable String scheduleType) { @PathVariable String scheduleType) {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {

View File

@ -24,6 +24,7 @@ public enum ScheduleType {
OVERDUE_REMINDER("overdue-reminder", "逾期任务提醒"); OVERDUE_REMINDER("overdue-reminder", "逾期任务提醒");
private final String code; private final String code;
private final String description; private final String description;
ScheduleType(String code, String description) { ScheduleType(String code, String description) {

View File

@ -39,6 +39,7 @@ public enum TaskSystemType {
NOTION("notion", "Notion"); NOTION("notion", "Notion");
private final String code; private final String code;
private final String description; private final String description;
TaskSystemType(String code, String description) { TaskSystemType(String code, String description) {

View File

@ -16,8 +16,11 @@ import lombok.NoArgsConstructor;
public class ProjectInfo { public class ProjectInfo {
private Integer id; private Integer id;
private String name; private String name;
private String status; private String status;
private boolean exists; private boolean exists;
/** /**
@ -32,11 +35,16 @@ public class ProjectInfo {
*/ */
public String getStatusDescription() { public String getStatusDescription() {
switch (status) { switch (status) {
case "wait": return "未开始"; case "wait":
case "doing": return "进行中"; return "未开始";
case "suspended": return "已挂起"; case "doing":
case "closed": return "已关闭"; return "进行中";
default: return "未知状态"; case "suspended":
return "已挂起";
case "closed":
return "已关闭";
default:
return "未知状态";
} }
} }

View File

@ -13,37 +13,69 @@ import lombok.Data;
public class ZentaoTask { public class ZentaoTask {
private String id; private String id;
private String name; private String name;
private String type; private String type;
private String status; private String status;
private String pri; private String pri;
private String assignedTo; private String assignedTo;
private String assignedToRealName; private String assignedToRealName;
private String assignedDate; private String assignedDate;
private String deadline; private String deadline;
private String estStarted; private String estStarted;
private String realStarted; private String realStarted;
private String finishedBy; private String finishedBy;
private String finishedDate; private String finishedDate;
private String canceledBy; private String canceledBy;
private String canceledDate; private String canceledDate;
private String closedBy; private String closedBy;
private String closedDate; private String closedDate;
private String closedReason; private String closedReason;
private String lastEditedBy; private String lastEditedBy;
private String lastEditedDate; private String lastEditedDate;
private String openedBy; private String openedBy;
private String openedDate; private String openedDate;
private String desc; private String desc;
private String project; private String project;
private String story; private String story;
private String storyTitle; private String storyTitle;
private String module; private String module;
private String estimate; private String estimate;
private String consumed; private String consumed;
private String left; private String left;
private String progress; private String progress;
private String color; private String color;
private String deleted; private String deleted;
/** /**
@ -150,11 +182,16 @@ public class ZentaoTask {
*/ */
public String getPriorityDesc() { public String getPriorityDesc() {
switch (pri) { switch (pri) {
case "1": return ""; case "1":
case "2": return ""; return "";
case "3": return ""; case "2":
case "4": return "最低"; return "";
default: return "普通"; case "3":
return "";
case "4":
return "最低";
default:
return "普通";
} }
} }
} }

View File

@ -14,36 +14,68 @@ import lombok.Data;
public class ZentaoUser { public class ZentaoUser {
private String id; private String id;
private String account; private String account;
private String realname; private String realname;
private String nickname; private String nickname;
private String avatar; private String avatar;
private String birthday; private String birthday;
private String gender; private String gender;
private String email; private String email;
private String skype; private String skype;
private String qq; private String qq;
private String yahoo; private String yahoo;
private String gtalk; private String gtalk;
private String wangwang; private String wangwang;
private String mobile; private String mobile;
private String phone; private String phone;
private String address; private String address;
private String zipcode; private String zipcode;
private String join; private String join;
private String visits; private String visits;
private String ip; private String ip;
private String last; private String last;
private String fails; private String fails;
private String locked; private String locked;
private String feedback; private String feedback;
private String mail; private String mail;
private String clientStatus; private String clientStatus;
private String clientLang; private String clientLang;
private String dept; private String dept;
private String role; private String role;
private String type; private String type;
private String groups; private String groups;
private String view; private String view;
private String deleted; private String deleted;
} }

View File

@ -131,8 +131,8 @@ public class DynamicTaskScheduler {
*/ */
public int getActiveTaskCount() { public int getActiveTaskCount() {
return (int) scheduledTasks.values().stream() return (int) scheduledTasks.values().stream()
.filter(future -> future != null && !future.isCancelled()) .filter(future -> future != null && !future.isCancelled())
.count(); .count();
} }
/** /**

View File

@ -28,7 +28,7 @@ public class UserMappingService {
/** /**
* 根据禅道用户名获取企业微信@标识 * 根据禅道用户名获取企业微信@标识
* *
* @param group 群组配置 * @param group 群组配置
* @param zentaoUsername 禅道用户名 * @param zentaoUsername 禅道用户名
* @return 企业微信@标识 @13800138000 null * @return 企业微信@标识 @13800138000 null
*/ */
@ -106,7 +106,7 @@ public class UserMappingService {
/** /**
* 批量获取企业微信@标识 * 批量获取企业微信@标识
* *
* @param group 群组配置 * @param group 群组配置
* @param zentaoUsernames 禅道用户名列表 * @param zentaoUsernames 禅道用户名列表
* @return 企业微信@标识列表 * @return 企业微信@标识列表
*/ */

View File

@ -185,10 +185,10 @@ public class WechatWebhookService {
// 设置请求配置 // 设置请求配置
RequestConfig requestConfig = RequestConfig.custom() RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout) .setConnectTimeout(timeout)
.setSocketTimeout(timeout) .setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout) .setConnectionRequestTimeout(timeout)
.build(); .build();
httpPost.setConfig(requestConfig); httpPost.setConfig(requestConfig);
// 设置请求头 // 设置请求头
@ -261,7 +261,6 @@ public class WechatWebhookService {
} }
/** /**
* 获取任务管理系统的图标 * 获取任务管理系统的图标
*/ */
@ -271,13 +270,20 @@ public class WechatWebhookService {
} }
switch (taskSystemType) { switch (taskSystemType) {
case ZENTAO: return "📋"; case ZENTAO:
case SMARTSHEET: return "📊"; return "📋";
case JIRA: return "🎯"; case SMARTSHEET:
case TRELLO: return "📌"; return "📊";
case ASANA: return ""; case JIRA:
case NOTION: return "📝"; return "🎯";
default: return "📋"; case TRELLO:
return "📌";
case ASANA:
return "";
case NOTION:
return "📝";
default:
return "📋";
} }
} }
@ -287,37 +293,37 @@ public class WechatWebhookService {
private String getTaskSystemInstructions(TaskSystemType taskSystemType) { private String getTaskSystemInstructions(TaskSystemType taskSystemType) {
if (taskSystemType == null) { if (taskSystemType == null) {
return "1. 登录任务管理系统\n" + return "1. 登录任务管理系统\n" +
"2. 查看分配给自己的任务\n" + "2. 查看分配给自己的任务\n" +
"3. 更新任务状态和进度\n" + "3. 更新任务状态和进度\n" +
"4. 添加必要的工作记录\n\n"; "4. 添加必要的工作记录\n\n";
} }
switch (taskSystemType) { switch (taskSystemType) {
case ZENTAO: case ZENTAO:
return "1. 登录禅道系统\n" + return "1. 登录禅道系统\n" +
"2. 查看分配给自己的任务\n" + "2. 查看分配给自己的任务\n" +
"3. 更新任务状态和进度\n" + "3. 更新任务状态和进度\n" +
"4. 添加必要的工作日志\n\n"; "4. 添加必要的工作日志\n\n";
case SMARTSHEET: case SMARTSHEET:
return "1. 打开智能表格\n" + return "1. 打开智能表格\n" +
"2. 找到自己负责的任务行\n" + "2. 找到自己负责的任务行\n" +
"3. 更新任务状态和完成百分比\n" + "3. 更新任务状态和完成百分比\n" +
"4. 添加备注说明进展情况\n\n"; "4. 添加备注说明进展情况\n\n";
case JIRA: case JIRA:
return "1. 登录Jira系统\n" + return "1. 登录Jira系统\n" +
"2. 查看分配给自己的Issue\n" + "2. 查看分配给自己的Issue\n" +
"3. 更新Issue状态\n" + "3. 更新Issue状态\n" +
"4. 记录工作日志和时间\n\n"; "4. 记录工作日志和时间\n\n";
case TRELLO: case TRELLO:
return "1. 打开Trello看板\n" + return "1. 打开Trello看板\n" +
"2. 找到自己的任务卡片\n" + "2. 找到自己的任务卡片\n" +
"3. 移动卡片到对应状态列\n" + "3. 移动卡片到对应状态列\n" +
"4. 添加评论记录进展\n\n"; "4. 添加评论记录进展\n\n";
default: default:
return "1. 登录任务管理系统\n" + return "1. 登录任务管理系统\n" +
"2. 查看分配给自己的任务\n" + "2. 查看分配给自己的任务\n" +
"3. 更新任务状态和进度\n" + "3. 更新任务状态和进度\n" +
"4. 添加必要的工作记录\n\n"; "4. 添加必要的工作记录\n\n";
} }
} }
@ -326,14 +332,22 @@ public class WechatWebhookService {
*/ */
private String getDayOfWeekInChinese(int dayOfWeek) { private String getDayOfWeekInChinese(int dayOfWeek) {
switch (dayOfWeek) { switch (dayOfWeek) {
case 1: return "星期一"; case 1:
case 2: return "星期二"; return "星期一";
case 3: return "星期三"; case 2:
case 4: return "星期四"; return "星期二";
case 5: return "星期五"; case 3:
case 6: return "星期六"; return "星期三";
case 7: return "星期日"; case 4:
default: return "未知"; return "星期四";
case 5:
return "星期五";
case 6:
return "星期六";
case 7:
return "星期日";
default:
return "未知";
} }
} }
} }

View File

@ -39,6 +39,7 @@ public class ZentaoApiService {
private static final Logger logger = LoggerFactory.getLogger(ZentaoApiService.class); private static final Logger logger = LoggerFactory.getLogger(ZentaoApiService.class);
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
private final Map<String, String> sessionCache = new HashMap<>(); private final Map<String, String> sessionCache = new HashMap<>();
/** /**
@ -211,7 +212,8 @@ public class ZentaoApiService {
if (rootNode.has("data")) { if (rootNode.has("data")) {
JsonNode dataNode = rootNode.get("data"); JsonNode dataNode = rootNode.get("data");
return objectMapper.convertValue( return objectMapper.convertValue(
dataNode, new TypeReference<List<ZentaoUser>>() {}); dataNode, new TypeReference<List<ZentaoUser>>() {
});
} }
} }
} }

View File

@ -144,10 +144,10 @@ public class ZentaoTaskReminderService {
* 构建统一的项目状态提醒消息包含任务和BUG * 构建统一的项目状态提醒消息包含任务和BUG
*/ */
private String buildProjectStatusMessage(TaskReminderConfig.Group group, private String buildProjectStatusMessage(TaskReminderConfig.Group group,
List<ZentaoTask> incompleteTasks, List<ZentaoTask> incompleteTasks,
List<ZentaoBug> unresolvedBugs, List<ZentaoBug> unresolvedBugs,
ProjectInfo projectInfo, ProjectInfo projectInfo,
List<String> mentionedMobiles) { List<String> mentionedMobiles) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
// 消息头部 // 消息头部
@ -194,7 +194,7 @@ public class ZentaoTaskReminderService {
for (ZentaoTask task : items.tasks) { for (ZentaoTask task : items.tasks) {
String statusIcon = task.isOverdue() ? "🔴" : ""; String statusIcon = task.isOverdue() ? "🔴" : "";
message.append("- ").append(statusIcon).append(" [任务").append(task.getId()).append("] ") message.append("- ").append(statusIcon).append(" [任务").append(task.getId()).append("] ")
.append(task.getName()); .append(task.getName());
if (task.getDeadline() != null && !task.getDeadline().isEmpty() && if (task.getDeadline() != null && !task.getDeadline().isEmpty() &&
!"0000-00-00".equals(task.getDeadline())) { !"0000-00-00".equals(task.getDeadline())) {
message.append(" (").append(task.getDeadline()).append(")"); message.append(" (").append(task.getDeadline()).append(")");
@ -206,7 +206,7 @@ public class ZentaoTaskReminderService {
for (ZentaoBug bug : items.bugs) { for (ZentaoBug bug : items.bugs) {
String statusIcon = bug.isOverdue() ? "🔴" : "🐛"; String statusIcon = bug.isOverdue() ? "🔴" : "🐛";
message.append("- ").append(statusIcon).append(" [BUG").append(bug.getId()).append("] ") message.append("- ").append(statusIcon).append(" [BUG").append(bug.getId()).append("] ")
.append(bug.getTitle()); .append(bug.getTitle());
if (bug.getSeverity() != null && !bug.getSeverity().isEmpty()) { if (bug.getSeverity() != null && !bug.getSeverity().isEmpty()) {
message.append(" [").append(bug.getSeverityDesc()).append("]"); message.append(" [").append(bug.getSeverityDesc()).append("]");
} }
@ -277,6 +277,7 @@ public class ZentaoTaskReminderService {
*/ */
private static class ProjectItems { private static class ProjectItems {
List<ZentaoTask> tasks = new ArrayList<>(); List<ZentaoTask> tasks = new ArrayList<>();
List<ZentaoBug> bugs = new ArrayList<>(); List<ZentaoBug> bugs = new ArrayList<>();
} }
@ -284,10 +285,10 @@ public class ZentaoTaskReminderService {
* 构建任务提醒消息保留原方法以兼容性 * 构建任务提醒消息保留原方法以兼容性
*/ */
private String buildTaskReminderMessage(TaskReminderConfig.Group group, private String buildTaskReminderMessage(TaskReminderConfig.Group group,
Map<String, List<ZentaoTask>> tasksByAssignee, Map<String, List<ZentaoTask>> tasksByAssignee,
List<ZentaoTask> allTasks, List<ZentaoTask> allTasks,
ProjectInfo projectInfo, ProjectInfo projectInfo,
List<String> mentionedMobiles) { List<String> mentionedMobiles) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
// 简化消息头部 // 简化消息头部
@ -323,7 +324,7 @@ public class ZentaoTaskReminderService {
String statusIcon = task.isOverdue() ? "🔴" : ""; String statusIcon = task.isOverdue() ? "🔴" : "";
message.append("- ").append(statusIcon).append(" [").append(task.getId()).append("] ") message.append("- ").append(statusIcon).append(" [").append(task.getId()).append("] ")
.append(task.getName()); .append(task.getName());
// 截止日期 // 截止日期
if (task.getDeadline() != null && !task.getDeadline().isEmpty() && if (task.getDeadline() != null && !task.getDeadline().isEmpty() &&
@ -346,10 +347,10 @@ public class ZentaoTaskReminderService {
* 构建BUG提醒消息 * 构建BUG提醒消息
*/ */
private String buildBugReminderMessage(TaskReminderConfig.Group group, private String buildBugReminderMessage(TaskReminderConfig.Group group,
Map<String, List<ZentaoBug>> bugsByAssignee, Map<String, List<ZentaoBug>> bugsByAssignee,
List<ZentaoBug> allBugs, List<ZentaoBug> allBugs,
ProjectInfo projectInfo, ProjectInfo projectInfo,
List<String> mentionedMobiles) { List<String> mentionedMobiles) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
// 简化消息头部 // 简化消息头部
@ -385,7 +386,7 @@ public class ZentaoTaskReminderService {
String statusIcon = bug.isOverdue() ? "🔴" : "🐛"; String statusIcon = bug.isOverdue() ? "🔴" : "🐛";
message.append("- ").append(statusIcon).append(" [").append(bug.getId()).append("] ") message.append("- ").append(statusIcon).append(" [").append(bug.getId()).append("] ")
.append(bug.getTitle()); .append(bug.getTitle());
// 严重程度 // 严重程度
if (bug.getSeverity() != null && !bug.getSeverity().isEmpty()) { if (bug.getSeverity() != null && !bug.getSeverity().isEmpty()) {
@ -458,7 +459,6 @@ public class ZentaoTaskReminderService {
} }
/** /**
* 获取任务详情用于测试 * 获取任务详情用于测试
*/ */

View File

@ -16,7 +16,7 @@ public interface ReminderHandler {
/** /**
* 判断是否支持处理指定的提醒类型 * 判断是否支持处理指定的提醒类型
* *
* @param scheduleType 提醒类型 * @param scheduleType 提醒类型
* @param taskSystemType 任务系统类型 * @param taskSystemType 任务系统类型
* @return 是否支持 * @return 是否支持
*/ */

View File

@ -27,7 +27,7 @@ public class ReminderHandlerFactory {
/** /**
* 获取合适的提醒处理器 * 获取合适的提醒处理器
* *
* @param scheduleType 提醒类型 * @param scheduleType 提醒类型
* @param taskSystemType 任务系统类型 * @param taskSystemType 任务系统类型
* @return 提醒处理器如果没有找到则返回null * @return 提醒处理器如果没有找到则返回null
*/ */
@ -49,7 +49,7 @@ public class ReminderHandlerFactory {
* 获取合适的提醒处理器字符串参数版本用于兼容 * 获取合适的提醒处理器字符串参数版本用于兼容
* *
* @param scheduleTypeCode 提醒类型代码 * @param scheduleTypeCode 提醒类型代码
* @param taskSystemCode 任务系统类型代码 * @param taskSystemCode 任务系统类型代码
* @return 提醒处理器如果没有找到则返回null * @return 提醒处理器如果没有找到则返回null
*/ */
public ReminderHandler getHandler(String scheduleTypeCode, String taskSystemCode) { public ReminderHandler getHandler(String scheduleTypeCode, String taskSystemCode) {

View File

@ -35,11 +35,7 @@ public class TextReminderHandler implements ReminderHandler {
logger.info("处理文本提醒 - 群组: {}, 类型: {}", group.getName(), scheduleType.getDescription()); logger.info("处理文本提醒 - 群组: {}, 类型: {}", group.getName(), scheduleType.getDescription());
// 生成带格式的提醒消息 // 生成带格式的提醒消息
String message = wechatWebhookService.createTaskReminderMessage( String message = wechatWebhookService.createTaskReminderMessage(group.getId(), schedule.getMessage(), scheduleType.getDescription());
group.getId(),
schedule.getMessage(),
scheduleType.getDescription()
);
// 发送消息 // 发送消息
boolean success = wechatWebhookService.sendMarkdownMessage(group.getId(), message); boolean success = wechatWebhookService.sendMarkdownMessage(group.getId(), message);

View File

@ -31,7 +31,6 @@ public class HolidayUtil {
} }
/** /**
* 初始化2025年节假日 * 初始化2025年节假日
* 根据国务院办公厅2024年11月12日发布的关于2025年部分节假日安排的通知 * 根据国务院办公厅2024年11月12日发布的关于2025年部分节假日安排的通知