first commit

This commit is contained in:
dengqichen
2025-05-28 09:50:20 +08:00
commit da23b31a1e
13 changed files with 1578 additions and 0 deletions
@@ -0,0 +1,24 @@
package com.zeodao.reminder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* Zeodao任务提醒系统主启动类
*
* @author Zeodao
* @version 1.0.0
*/
@SpringBootApplication
@EnableScheduling
public class ZeodaoTaskReminderApplication {
public static void main(String[] args) {
SpringApplication.run(ZeodaoTaskReminderApplication.class, args);
System.out.println("=================================");
System.out.println("Zeodao任务提醒系统启动成功!");
System.out.println("系统将在工作日早上9:00和下午17:30发送任务提醒");
System.out.println("=================================");
}
}
@@ -0,0 +1,160 @@
package com.zeodao.reminder.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 多群任务提醒配置类
*
* @author Zeodao
* @version 2.0.0
*/
@Configuration
@ConfigurationProperties(prefix = "task.reminder")
public class TaskReminderConfig {
private Global global = new Global();
private List<Group> groups = new ArrayList<>();
public Global getGlobal() {
return global;
}
public void setGlobal(Global global) {
this.global = global;
}
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
/**
* 根据群组ID获取群组配置
*/
public Group getGroupById(String groupId) {
return groups.stream()
.filter(group -> group.getId().equals(groupId))
.findFirst()
.orElse(null);
}
/**
* 获取所有启用的群组
*/
public List<Group> getEnabledGroups() {
return groups.stream()
.filter(Group::isEnabled)
.toList();
}
public static class Global {
private int timeout = 5000;
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
}
public static class Group {
private String id;
private String name;
private Webhook webhook = new Webhook();
private String taskSystem;
private Map<String, Schedule> schedules = new HashMap<>();
private boolean enabled = true;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Webhook getWebhook() {
return webhook;
}
public void setWebhook(Webhook webhook) {
this.webhook = webhook;
}
public String getTaskSystem() {
return taskSystem;
}
public void setTaskSystem(String taskSystem) {
this.taskSystem = taskSystem;
}
public Map<String, Schedule> getSchedules() {
return schedules;
}
public void setSchedules(Map<String, Schedule> schedules) {
this.schedules = schedules;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public static class Webhook {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
public static class Schedule {
private String time;
private String message;
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
}
@@ -0,0 +1,123 @@
package com.zeodao.reminder.controller;
import com.zeodao.reminder.service.TaskReminderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 任务提醒控制器
* 提供手动触发提醒和系统状态查询的API
*
* @author Zeodao
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/reminder")
public class TaskReminderController {
private static final Logger logger = LoggerFactory.getLogger(TaskReminderController.class);
@Autowired
private TaskReminderService taskReminderService;
/**
* 系统健康检查
*/
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> response = new HashMap<>();
response.put("status", "UP");
response.put("service", "Zeodao Task Reminder");
response.put("version", "1.0.0");
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.ok(response);
}
/**
* 获取下次提醒信息
*/
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> getReminderInfo() {
Map<String, Object> response = new HashMap<>();
try {
String info = taskReminderService.getNextReminderInfo();
response.put("success", true);
response.put("info", info);
response.put("message", "获取提醒信息成功");
} catch (Exception e) {
logger.error("获取提醒信息失败", e);
response.put("success", false);
response.put("message", "获取提醒信息失败:" + e.getMessage());
}
return ResponseEntity.ok(response);
}
/**
* 手动发送测试消息
*/
@PostMapping("/test")
public ResponseEntity<Map<String, Object>> sendTestMessage() {
Map<String, Object> response = new HashMap<>();
try {
boolean success = taskReminderService.sendTestMessage();
response.put("success", success);
response.put("message", success ? "测试消息发送成功" : "测试消息发送失败");
} catch (Exception e) {
logger.error("发送测试消息失败", e);
response.put("success", false);
response.put("message", "发送测试消息失败:" + e.getMessage());
}
return ResponseEntity.ok(response);
}
/**
* 手动触发早上提醒
*/
@PostMapping("/morning")
public ResponseEntity<Map<String, Object>> triggerMorningReminder() {
Map<String, Object> response = new HashMap<>();
try {
taskReminderService.sendMorningReminder();
response.put("success", true);
response.put("message", "早上提醒已触发");
} catch (Exception e) {
logger.error("触发早上提醒失败", e);
response.put("success", false);
response.put("message", "触发早上提醒失败:" + e.getMessage());
}
return ResponseEntity.ok(response);
}
/**
* 手动触发晚上提醒
*/
@PostMapping("/evening")
public ResponseEntity<Map<String, Object>> triggerEveningReminder() {
Map<String, Object> response = new HashMap<>();
try {
taskReminderService.sendEveningReminder();
response.put("success", true);
response.put("message", "晚上提醒已触发");
} catch (Exception e) {
logger.error("触发晚上提醒失败", e);
response.put("success", false);
response.put("message", "触发晚上提醒失败:" + e.getMessage());
}
return ResponseEntity.ok(response);
}
}
@@ -0,0 +1,74 @@
package com.zeodao.reminder.scheduler;
import com.zeodao.reminder.service.TaskReminderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 任务提醒定时调度器
*
* @author Zeodao
* @version 1.0.0
*/
@Component
public class TaskReminderScheduler {
private static final Logger logger = LoggerFactory.getLogger(TaskReminderScheduler.class);
@Autowired
private TaskReminderService taskReminderService;
/**
* 早上9点任务提醒
* Cron表达式:0 0 9 * * MON-FRI
* 含义:每个工作日(周一到周五)的早上9点0分0秒执行
*/
@Scheduled(cron = "${task.reminder.morning.time}")
public void morningReminder() {
logger.info("=== 早上任务提醒定时任务开始执行 ===");
try {
taskReminderService.sendMorningReminder();
} catch (Exception e) {
logger.error("早上任务提醒执行失败", e);
}
logger.info("=== 早上任务提醒定时任务执行完成 ===");
}
/**
* 下午5:30任务提醒
* Cron表达式:0 30 17 * * MON-FRI
* 含义:每个工作日(周一到周五)的下午5点30分0秒执行
*/
@Scheduled(cron = "${task.reminder.evening.time}")
public void eveningReminder() {
logger.info("=== 晚上任务提醒定时任务开始执行 ===");
try {
taskReminderService.sendEveningReminder();
} catch (Exception e) {
logger.error("晚上任务提醒执行失败", e);
}
logger.info("=== 晚上任务提醒定时任务执行完成 ===");
}
/**
* 系统健康检查(每小时执行一次)
* 用于记录系统运行状态
*/
@Scheduled(cron = "0 0 * * * *")
public void healthCheck() {
logger.debug("系统健康检查 - 任务提醒系统运行正常");
}
/**
* 每天早上8点记录当天的提醒计划
*/
@Scheduled(cron = "0 0 8 * * *")
public void logDailyPlan() {
logger.info("=== 今日提醒计划 ===");
String info = taskReminderService.getNextReminderInfo();
logger.info(info);
}
}
@@ -0,0 +1,137 @@
package com.zeodao.reminder.service;
import com.zeodao.reminder.config.TaskReminderConfig;
import com.zeodao.reminder.util.HolidayUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 任务提醒服务
*
* @author Zeodao
* @version 1.0.0
*/
@Service
public class TaskReminderService {
private static final Logger logger = LoggerFactory.getLogger(TaskReminderService.class);
@Autowired
private WechatWebhookService wechatWebhookService;
@Autowired
private HolidayUtil holidayUtil;
@Value("${task.reminder.morning.message}")
private String morningMessage;
@Value("${task.reminder.evening.message}")
private String eveningMessage;
/**
* 发送早上任务提醒
*/
public void sendMorningReminder() {
if (!shouldSendReminder()) {
logger.info("今天是节假日或周末,跳过早上任务提醒");
return;
}
logger.info("开始发送早上任务提醒");
String message = wechatWebhookService.createTaskReminderMessage(morningMessage, "早上提醒");
boolean success = wechatWebhookService.sendMarkdownMessage(message);
if (success) {
logger.info("早上任务提醒发送成功");
} else {
logger.error("早上任务提醒发送失败");
}
}
/**
* 发送晚上任务提醒
*/
public void sendEveningReminder() {
if (!shouldSendReminder()) {
logger.info("今天是节假日或周末,跳过晚上任务提醒");
return;
}
logger.info("开始发送晚上任务提醒");
String message = wechatWebhookService.createTaskReminderMessage(eveningMessage, "下班提醒");
boolean success = wechatWebhookService.sendMarkdownMessage(message);
if (success) {
logger.info("晚上任务提醒发送成功");
} else {
logger.error("晚上任务提醒发送失败");
}
}
/**
* 判断是否应该发送提醒
* 排除周末和节假日
*/
private boolean shouldSendReminder() {
LocalDate today = LocalDate.now();
// 检查是否是周末
if (holidayUtil.isWeekend(today)) {
logger.debug("今天是周末,不发送提醒");
return false;
}
// 检查是否是节假日
if (holidayUtil.isHoliday(today)) {
logger.debug("今天是节假日,不发送提醒");
return false;
}
return true;
}
/**
* 手动发送测试消息
*/
public boolean sendTestMessage() {
logger.info("发送测试消息");
String testMessage = wechatWebhookService.createTaskReminderMessage(
"这是一条测试消息,用于验证企业微信webhook是否正常工作。",
"测试消息"
);
boolean success = wechatWebhookService.sendMarkdownMessage(testMessage);
if (success) {
logger.info("测试消息发送成功");
} else {
logger.error("测试消息发送失败");
}
return success;
}
/**
* 获取下次提醒时间信息
*/
public String getNextReminderInfo() {
LocalDateTime now = LocalDateTime.now();
StringBuilder info = new StringBuilder();
info.append("当前时间:").append(now.toString()).append("\n");
info.append("今天是否工作日:").append(shouldSendReminder() ? "" : "").append("\n");
info.append("早上提醒时间:每个工作日 09:00\n");
info.append("晚上提醒时间:每个工作日 17:30\n");
return info.toString();
}
}
@@ -0,0 +1,298 @@
package com.zeodao.reminder.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zeodao.reminder.config.TaskReminderConfig;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* 企业微信Webhook服务
*
* @author Zeodao
* @version 1.0.0
*/
@Service
public class WechatWebhookService {
private static final Logger logger = LoggerFactory.getLogger(WechatWebhookService.class);
@Autowired
private TaskReminderConfig taskReminderConfig;
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 发送文本消息到指定群组
*
* @param groupId 群组ID
* @param message 消息内容
* @return 是否发送成功
*/
public boolean sendTextMessage(String groupId, String message) {
try {
TaskReminderConfig.Group group = taskReminderConfig.getGroupById(groupId);
if (group == null) {
logger.error("未找到群组配置: {}", groupId);
return false;
}
Map<String, Object> messageBody = createTextMessage(message);
return sendMessage(group.getWebhook().getUrl(), messageBody);
} catch (Exception e) {
logger.error("发送企业微信消息失败, 群组: {}", groupId, e);
return false;
}
}
/**
* 发送Markdown格式消息到指定群组
*
* @param groupId 群组ID
* @param content Markdown内容
* @return 是否发送成功
*/
public boolean sendMarkdownMessage(String groupId, String content) {
try {
TaskReminderConfig.Group group = taskReminderConfig.getGroupById(groupId);
if (group == null) {
logger.error("未找到群组配置: {}", groupId);
return false;
}
Map<String, Object> messageBody = createMarkdownMessage(content);
return sendMessage(group.getWebhook().getUrl(), messageBody);
} catch (Exception e) {
logger.error("发送企业微信Markdown消息失败, 群组: {}", groupId, e);
return false;
}
}
/**
* 兼容旧版本的方法 - 发送到第一个启用的群组
*/
@Deprecated
public boolean sendMarkdownMessage(String content) {
List<TaskReminderConfig.Group> enabledGroups = taskReminderConfig.getEnabledGroups();
if (enabledGroups.isEmpty()) {
logger.error("没有启用的群组配置");
return false;
}
return sendMarkdownMessage(enabledGroups.get(0).getId(), content);
}
/**
* 创建文本消息体
*/
private Map<String, Object> createTextMessage(String content) {
Map<String, Object> message = new HashMap<>();
message.put("msgtype", "text");
Map<String, Object> text = new HashMap<>();
text.put("content", content);
message.put("text", text);
return message;
}
/**
* 创建Markdown消息体
*/
private Map<String, Object> createMarkdownMessage(String content) {
Map<String, Object> message = new HashMap<>();
message.put("msgtype", "markdown");
Map<String, Object> markdown = new HashMap<>();
markdown.put("content", content);
message.put("markdown", markdown);
return message;
}
/**
* 发送消息到企业微信
*/
private boolean sendMessage(String webhookUrl, Map<String, Object> messageBody) throws IOException {
int timeout = taskReminderConfig.getGlobal().getTimeout();
logger.info("准备发送消息到企业微信,URL: {}", webhookUrl);
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(webhookUrl);
// 设置请求配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
httpPost.setConfig(requestConfig);
// 设置请求头
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
// 设置请求体
String jsonBody = objectMapper.writeValueAsString(messageBody);
StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8);
httpPost.setEntity(entity);
logger.debug("发送消息内容: {}", jsonBody);
// 执行请求
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (statusCode == 200) {
logger.info("企业微信消息发送成功,响应: {}", responseBody);
return true;
} else {
logger.error("企业微信消息发送失败,状态码: {}, 响应: {}", statusCode, responseBody);
return false;
}
}
}
}
/**
* 创建带时间戳的任务提醒消息
*/
public String createTaskReminderMessage(String groupId, String baseMessage, String timeType) {
TaskReminderConfig.Group group = taskReminderConfig.getGroupById(groupId);
if (group == null) {
logger.error("未找到群组配置: {}", groupId);
return baseMessage;
}
LocalDateTime now = LocalDateTime.now();
String timestamp = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String dayOfWeek = getDayOfWeekInChinese(now.getDayOfWeek().getValue());
StringBuilder message = new StringBuilder();
// 根据任务管理系统类型生成不同的标题和操作指引
String systemName = getTaskSystemDisplayName(group.getTaskSystem());
String systemIcon = getTaskSystemIcon(group.getTaskSystem());
message.append("## ").append(systemIcon).append(" ").append(systemName).append("任务状态提醒\n\n");
message.append("**⏰ 时间:** ").append(timestamp).append(" (").append(dayOfWeek).append(")\n");
message.append("**📝 提醒类型:** ").append(timeType).append("\n");
message.append("**👥 群组:** ").append(group.getName()).append("\n\n");
message.append("### 📢 重要提醒\n");
message.append(baseMessage).append("\n\n");
message.append("### 🔗 操作指引\n");
message.append(getTaskSystemInstructions(group.getTaskSystem()));
message.append("---\n");
message.append("💡 **温馨提示:** 及时更新任务状态有助于团队协作和项目管理\n\n");
message.append("*Zeodao任务提醒系统 v2.0*");
return message.toString();
}
/**
* 兼容旧版本的方法
*/
@Deprecated
public String createTaskReminderMessage(String baseMessage, String timeType) {
List<TaskReminderConfig.Group> enabledGroups = taskReminderConfig.getEnabledGroups();
if (enabledGroups.isEmpty()) {
return baseMessage;
}
return createTaskReminderMessage(enabledGroups.get(0).getId(), baseMessage, timeType);
}
/**
* 获取任务管理系统的显示名称
*/
private String getTaskSystemDisplayName(String taskSystem) {
switch (taskSystem.toLowerCase()) {
case "zentao": return "禅道";
case "smartsheet": return "智能表格";
case "jira": return "Jira";
case "trello": return "Trello";
case "asana": return "Asana";
case "notion": return "Notion";
default: return "任务管理系统";
}
}
/**
* 获取任务管理系统的图标
*/
private String getTaskSystemIcon(String taskSystem) {
switch (taskSystem.toLowerCase()) {
case "zentao": return "📋";
case "smartsheet": return "📊";
case "jira": return "🎯";
case "trello": return "📌";
case "asana": return "";
case "notion": return "📝";
default: return "📋";
}
}
/**
* 获取任务管理系统的操作指引
*/
private String getTaskSystemInstructions(String taskSystem) {
switch (taskSystem.toLowerCase()) {
case "zentao":
return "1. 登录禅道系统\n" +
"2. 查看分配给自己的任务\n" +
"3. 更新任务状态和进度\n" +
"4. 添加必要的工作日志\n\n";
case "smartsheet":
return "1. 打开智能表格\n" +
"2. 找到自己负责的任务行\n" +
"3. 更新任务状态和完成百分比\n" +
"4. 添加备注说明进展情况\n\n";
case "jira":
return "1. 登录Jira系统\n" +
"2. 查看分配给自己的Issue\n" +
"3. 更新Issue状态\n" +
"4. 记录工作日志和时间\n\n";
case "trello":
return "1. 打开Trello看板\n" +
"2. 找到自己的任务卡片\n" +
"3. 移动卡片到对应状态列\n" +
"4. 添加评论记录进展\n\n";
default:
return "1. 登录任务管理系统\n" +
"2. 查看分配给自己的任务\n" +
"3. 更新任务状态和进度\n" +
"4. 添加必要的工作记录\n\n";
}
}
/**
* 获取中文星期
*/
private String getDayOfWeekInChinese(int dayOfWeek) {
switch (dayOfWeek) {
case 1: return "星期一";
case 2: return "星期二";
case 3: return "星期三";
case 4: return "星期四";
case 5: return "星期五";
case 6: return "星期六";
case 7: return "星期日";
default: return "未知";
}
}
}
@@ -0,0 +1,205 @@
package com.zeodao.reminder.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.util.HashSet;
import java.util.Set;
/**
* 节假日工具类
* 用于判断是否为节假日或周末
*
* @author Zeodao
* @version 1.0.0
*/
@Component
public class HolidayUtil {
private static final Logger logger = LoggerFactory.getLogger(HolidayUtil.class);
// 2024年法定节假日(可根据实际情况调整)
private static final Set<LocalDate> HOLIDAYS_2024 = new HashSet<>();
// 2025年法定节假日(可根据实际情况调整)
private static final Set<LocalDate> HOLIDAYS_2025 = new HashSet<>();
static {
// 初始化2024年节假日
initHolidays2024();
// 初始化2025年节假日
initHolidays2025();
}
/**
* 初始化2024年节假日
*/
private static void initHolidays2024() {
// 元旦:1月1日
HOLIDAYS_2024.add(LocalDate.of(2024, Month.JANUARY, 1));
// 春节:2月10日-17日
for (int day = 10; day <= 17; day++) {
HOLIDAYS_2024.add(LocalDate.of(2024, Month.FEBRUARY, day));
}
// 清明节:4月4日-6日
for (int day = 4; day <= 6; day++) {
HOLIDAYS_2024.add(LocalDate.of(2024, Month.APRIL, day));
}
// 劳动节:5月1日-5日
for (int day = 1; day <= 5; day++) {
HOLIDAYS_2024.add(LocalDate.of(2024, Month.MAY, day));
}
// 端午节:6月10日
HOLIDAYS_2024.add(LocalDate.of(2024, Month.JUNE, 10));
// 中秋节:9月15日-17日
for (int day = 15; day <= 17; day++) {
HOLIDAYS_2024.add(LocalDate.of(2024, Month.SEPTEMBER, day));
}
// 国庆节:10月1日-7日
for (int day = 1; day <= 7; day++) {
HOLIDAYS_2024.add(LocalDate.of(2024, Month.OCTOBER, day));
}
}
/**
* 初始化2025年节假日
*/
private static void initHolidays2025() {
// 元旦:1月1日
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JANUARY, 1));
// 春节:1月28日-2月3日(预估,实际以国务院公布为准)
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JANUARY, 28));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JANUARY, 29));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JANUARY, 30));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JANUARY, 31));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.FEBRUARY, 1));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.FEBRUARY, 2));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.FEBRUARY, 3));
// 清明节:4月5日-7日(预估)
for (int day = 5; day <= 7; day++) {
HOLIDAYS_2025.add(LocalDate.of(2025, Month.APRIL, day));
}
// 劳动节:5月1日-5日(预估)
for (int day = 1; day <= 5; day++) {
HOLIDAYS_2025.add(LocalDate.of(2025, Month.MAY, day));
}
// 端午节:5月31日-6月2日(预估)
HOLIDAYS_2025.add(LocalDate.of(2025, Month.MAY, 31));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JUNE, 1));
HOLIDAYS_2025.add(LocalDate.of(2025, Month.JUNE, 2));
// 中秋节:10月6日(预估)
HOLIDAYS_2025.add(LocalDate.of(2025, Month.OCTOBER, 6));
// 国庆节:10月1日-7日
for (int day = 1; day <= 7; day++) {
HOLIDAYS_2025.add(LocalDate.of(2025, Month.OCTOBER, day));
}
}
/**
* 判断指定日期是否为周末
*
* @param date 日期
* @return 是否为周末
*/
public boolean isWeekend(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
boolean isWeekend = dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
if (isWeekend) {
logger.debug("日期 {} 是周末", date);
}
return isWeekend;
}
/**
* 判断指定日期是否为法定节假日
*
* @param date 日期
* @return 是否为节假日
*/
public boolean isHoliday(LocalDate date) {
boolean isHoliday = false;
int year = date.getYear();
switch (year) {
case 2024:
isHoliday = HOLIDAYS_2024.contains(date);
break;
case 2025:
isHoliday = HOLIDAYS_2025.contains(date);
break;
default:
// 对于其他年份,可以考虑调用外部API或使用默认规则
logger.warn("未配置年份 {} 的节假日信息,默认不是节假日", year);
break;
}
if (isHoliday) {
logger.debug("日期 {} 是法定节假日", date);
}
return isHoliday;
}
/**
* 判断指定日期是否为工作日
*
* @param date 日期
* @return 是否为工作日
*/
public boolean isWorkday(LocalDate date) {
return !isWeekend(date) && !isHoliday(date);
}
/**
* 获取下一个工作日
*
* @param date 起始日期
* @return 下一个工作日
*/
public LocalDate getNextWorkday(LocalDate date) {
LocalDate nextDay = date.plusDays(1);
while (!isWorkday(nextDay)) {
nextDay = nextDay.plusDays(1);
}
return nextDay;
}
/**
* 添加自定义节假日
*
* @param date 节假日日期
*/
public void addCustomHoliday(LocalDate date) {
int year = date.getYear();
switch (year) {
case 2024:
HOLIDAYS_2024.add(date);
break;
case 2025:
HOLIDAYS_2025.add(date);
break;
default:
logger.warn("暂不支持添加年份 {} 的自定义节假日", year);
break;
}
logger.info("添加自定义节假日:{}", date);
}
}
+58
View File
@@ -0,0 +1,58 @@
server:
port: 8080
spring:
application:
name: zeodao-task-reminder
# 多群任务提醒配置
task:
reminder:
# 全局默认配置
global:
timeout: 5000 # 超时时间(毫秒)
# 群组配置列表
groups:
# 禅道团队群
- id: "zentao-team"
name: "禅道开发团队"
webhook:
url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=614b110b-8957-4be8-95b9-4eca84c15028"
task-system: "zentao" # 任务管理系统类型:zentao, smartsheet, jira, trello 等
schedules:
morning:
time: "0 0 9 * * MON-FRI" # 工作日早上9点
message: "早上好!新的一天开始了,请大家及时登录禅道系统刷新今日的任务状态,确保任务进度准确反映当前工作情况。"
evening:
time: "0 30 17 * * MON-FRI" # 工作日下午5:30
message: "下班前提醒:请大家登录禅道系统,及时更新今日任务的完成状态和进度,为明天的工作安排做好准备!"
enabled: true
# 智能表格团队群
- id: "smartsheet-team"
name: "智能表格团队"
webhook:
url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=496db13a-d92f-4686-98fa-5ff05f49616e"
task-system: "smartsheet"
schedules:
morning:
time: "0 0 8 * * MON-FRI" # 工作日早上8点
message: "早上好!请大家及时更新智能表格中的任务状态,确保项目进度信息准确无误。"
evening:
time: "0 0 18 * * MON-FRI" # 工作日下午6点
message: "下班前提醒:请在智能表格中更新今日工作完成情况,为明天做好准备!"
enabled: true
# 日志配置
logging:
level:
com.zeodao.reminder: DEBUG
org.springframework.web: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/task-reminder.log
max-size: 10MB
max-history: 30
@@ -0,0 +1,130 @@
package com.zeodao.reminder.service;
import com.zeodao.reminder.util.HolidayUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.LocalDate;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
/**
* 任务提醒服务测试类
*
* @author Zeodao
* @version 1.0.0
*/
@ExtendWith(MockitoExtension.class)
class TaskReminderServiceTest {
@Mock
private WechatWebhookService wechatWebhookService;
@Mock
private HolidayUtil holidayUtil;
@InjectMocks
private TaskReminderService taskReminderService;
@BeforeEach
void setUp() {
// 设置测试用的消息内容
ReflectionTestUtils.setField(taskReminderService, "morningMessage", "测试早上消息");
ReflectionTestUtils.setField(taskReminderService, "eveningMessage", "测试晚上消息");
}
@Test
void testSendMorningReminderOnWorkday() {
// 模拟工作日
when(holidayUtil.isWeekend(any(LocalDate.class))).thenReturn(false);
when(holidayUtil.isHoliday(any(LocalDate.class))).thenReturn(false);
when(wechatWebhookService.createTaskReminderMessage(anyString(), anyString())).thenReturn("测试消息");
when(wechatWebhookService.sendMarkdownMessage(anyString())).thenReturn(true);
// 执行测试
taskReminderService.sendMorningReminder();
// 验证
verify(wechatWebhookService, times(1)).sendMarkdownMessage(anyString());
}
@Test
void testSendMorningReminderOnWeekend() {
// 模拟周末
when(holidayUtil.isWeekend(any(LocalDate.class))).thenReturn(true);
// 注意:当isWeekend返回true时,不会调用isHoliday方法,所以不需要mock
// 执行测试
taskReminderService.sendMorningReminder();
// 验证不发送消息
verify(wechatWebhookService, never()).sendMarkdownMessage(anyString());
}
@Test
void testSendMorningReminderOnHoliday() {
// 模拟节假日(不是周末,但是节假日)
when(holidayUtil.isWeekend(any(LocalDate.class))).thenReturn(false);
when(holidayUtil.isHoliday(any(LocalDate.class))).thenReturn(true);
// 执行测试
taskReminderService.sendMorningReminder();
// 验证不发送消息
verify(wechatWebhookService, never()).sendMarkdownMessage(anyString());
// 验证两个方法都被调用了
verify(holidayUtil, times(1)).isWeekend(any(LocalDate.class));
verify(holidayUtil, times(1)).isHoliday(any(LocalDate.class));
}
@Test
void testSendEveningReminderOnWorkday() {
// 模拟工作日
when(holidayUtil.isWeekend(any(LocalDate.class))).thenReturn(false);
when(holidayUtil.isHoliday(any(LocalDate.class))).thenReturn(false);
when(wechatWebhookService.createTaskReminderMessage(anyString(), anyString())).thenReturn("测试消息");
when(wechatWebhookService.sendMarkdownMessage(anyString())).thenReturn(true);
// 执行测试
taskReminderService.sendEveningReminder();
// 验证
verify(wechatWebhookService, times(1)).sendMarkdownMessage(anyString());
}
@Test
void testSendTestMessage() {
// 模拟成功发送
when(wechatWebhookService.createTaskReminderMessage(anyString(), anyString())).thenReturn("测试消息");
when(wechatWebhookService.sendMarkdownMessage(anyString())).thenReturn(true);
// 执行测试
boolean result = taskReminderService.sendTestMessage();
// 验证
assert result;
verify(wechatWebhookService, times(1)).sendMarkdownMessage(anyString());
}
@Test
void testGetNextReminderInfo() {
// 模拟工作日
when(holidayUtil.isWeekend(any(LocalDate.class))).thenReturn(false);
when(holidayUtil.isHoliday(any(LocalDate.class))).thenReturn(false);
// 执行测试
String info = taskReminderService.getNextReminderInfo();
// 验证
assert info != null;
assert info.contains("当前时间");
assert info.contains("今天是否工作日:是");
}
}