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.List; 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 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 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 enabledGroups = taskReminderConfig.getEnabledGroups(); if (enabledGroups.isEmpty()) { logger.error("没有启用的群组配置"); return false; } return sendMarkdownMessage(enabledGroups.get(0).getId(), content); } /** * 创建文本消息体 */ private Map createTextMessage(String content) { Map message = new HashMap<>(); message.put("msgtype", "text"); Map text = new HashMap<>(); text.put("content", content); message.put("text", text); return message; } /** * 创建Markdown消息体 */ private Map createMarkdownMessage(String content) { Map message = new HashMap<>(); message.put("msgtype", "markdown"); Map markdown = new HashMap<>(); markdown.put("content", content); message.put("markdown", markdown); return message; } /** * 创建带@人的Markdown消息体 */ private Map createMarkdownMessageWithMentions(String content, List mobileList) { Map message = new HashMap<>(); message.put("msgtype", "markdown"); Map markdown = new HashMap<>(); markdown.put("content", content); message.put("markdown", markdown); // 添加@人列表 if (mobileList != null && !mobileList.isEmpty()) { message.put("mentioned_mobile_list", mobileList); } return message; } /** * 发送Markdown消息到指定URL */ public boolean sendMessage(String webhookUrl, String markdownContent) { try { Map messageBody = createMarkdownMessage(markdownContent); return sendMessage(webhookUrl, messageBody); } catch (Exception e) { logger.error("发送企业微信消息失败, URL: {}", webhookUrl, e); return false; } } /** * 发送带@人的Markdown消息到指定URL */ public boolean sendMessageWithMentions(String webhookUrl, String markdownContent, List mobileList) { try { Map messageBody = createMarkdownMessageWithMentions(markdownContent, mobileList); return sendMessage(webhookUrl, messageBody); } catch (Exception e) { logger.error("发送企业微信@人消息失败, URL: {}", webhookUrl, e); return false; } } /** * 发送消息到企业微信 */ private boolean sendMessage(String webhookUrl, Map 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\n"); message.append("📢 ").append(baseMessage).append("\n\n"); message.append("🔗 **操作指引:**\n"); message.append(getTaskSystemInstructions(group.getTaskSystem())); message.append("💡 及时更新任务状态有助于团队协作和项目管理"); return message.toString(); } /** * 兼容旧版本的方法 */ @Deprecated public String createTaskReminderMessage(String baseMessage, String timeType) { List 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 "未知"; } } }