From dfdde28aa2e4ee77f93ba114e99c3549d271b59f Mon Sep 17 00:00:00 2001 From: zhoujinhua Date: Tue, 21 Oct 2025 09:05:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=92=89=E9=92=89=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/AppFeedbackController.java | 58 ++++++- .../com/corewing/app/util/DingTalkUtil.java | 164 ++++++++++++++++++ src/main/resources/application.properties | 4 + 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/corewing/app/util/DingTalkUtil.java diff --git a/src/main/java/com/corewing/app/controller/AppFeedbackController.java b/src/main/java/com/corewing/app/controller/AppFeedbackController.java index 62af9b7..54f603c 100644 --- a/src/main/java/com/corewing/app/controller/AppFeedbackController.java +++ b/src/main/java/com/corewing/app/controller/AppFeedbackController.java @@ -7,8 +7,11 @@ import com.corewing.app.common.Result; import com.corewing.app.dto.FeedbackRequest; import com.corewing.app.entity.AppFeedback; import com.corewing.app.service.AppFeedbackService; +import com.corewing.app.util.DingTalkUtil; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; /** @@ -19,9 +22,11 @@ import java.util.List; public class AppFeedbackController { private final AppFeedbackService feedbackService; + private final DingTalkUtil dingTalkUtil; - public AppFeedbackController(AppFeedbackService feedbackService) { + public AppFeedbackController(AppFeedbackService feedbackService, DingTalkUtil dingTalkUtil) { this.feedbackService = feedbackService; + this.dingTalkUtil = dingTalkUtil; } /** @@ -47,6 +52,8 @@ public class AppFeedbackController { boolean success = feedbackService.createFeedback(feedback); if (success) { + // 推送到钉钉 + sendFeedbackToDingTalk(feedback); return Result.success("反馈提交成功"); } return Result.error("反馈提交失败"); @@ -142,4 +149,53 @@ public class AppFeedbackController { return Result.error(e.getMessage()); } } + + /** + * 发送反馈信息到钉钉 + */ + private void sendFeedbackToDingTalk(AppFeedback feedback) { + try { + String title = "📢 新的用户反馈"; + StringBuilder text = new StringBuilder(); + text.append("### 📢 新的用户反馈\n\n"); + text.append("---\n\n"); + + // 用户信息 + if (feedback.getUserId() != null) { + text.append("**用户ID:** ").append(feedback.getUserId()).append("\n\n"); + } else { + text.append("**用户ID:** 匿名用户\n\n"); + } + + // 反馈类型 + text.append("**问题类型:** ").append(feedback.getFeedbackType()).append("\n\n"); + + // 反馈标题 + text.append("**问题标题:** ").append(feedback.getTitle()).append("\n\n"); + + // 反馈内容 + if (feedback.getContent() != null && !feedback.getContent().trim().isEmpty()) { + text.append("**问题描述:** \n\n").append(feedback.getContent()).append("\n\n"); + } + + // 联系方式 + if (feedback.getContact() != null && !feedback.getContact().trim().isEmpty()) { + text.append("**联系方式:** ").append(feedback.getContact()).append("\n\n"); + } + + // 提交时间 + text.append("**提交时间:** ").append( + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ).append("\n\n"); + + text.append("---\n\n"); + text.append("> 请及时处理用户反馈!"); + + // 异步发送钉钉消息(避免阻塞用户请求) + new Thread(() -> dingTalkUtil.sendMarkdownMessage(title, text.toString())).start(); + } catch (Exception e) { + // 钉钉推送失败不影响反馈提交,只记录日志 + // log.error("推送反馈到钉钉失败: {}", e.getMessage(), e); + } + } } diff --git a/src/main/java/com/corewing/app/util/DingTalkUtil.java b/src/main/java/com/corewing/app/util/DingTalkUtil.java new file mode 100644 index 0000000..b5e74c4 --- /dev/null +++ b/src/main/java/com/corewing/app/util/DingTalkUtil.java @@ -0,0 +1,164 @@ +package com.corewing.app.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * 钉钉机器人推送工具类 + */ +@Slf4j +@Component +public class DingTalkUtil { + + @Value("${dingtalk.webhook:}") + private String webhook; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 发送文本消息到钉钉 + * + * @param content 消息内容 + * @return 是否发送成功 + */ + public boolean sendTextMessage(String content) { + return sendTextMessage(content, null); + } + + /** + * 发送文本消息到钉钉 + * + * @param content 消息内容 + * @param atMobiles 需要@的手机号列表 + * @return 是否发送成功 + */ + public boolean sendTextMessage(String content, String[] atMobiles) { + if (webhook == null || webhook.trim().isEmpty()) { + log.warn("钉钉 Webhook 未配置,跳过消息推送"); + return false; + } + + try { + Map message = new HashMap<>(); + message.put("msgtype", "text"); + + Map text = new HashMap<>(); + text.put("content", content); + message.put("text", text); + + // @人员配置 + if (atMobiles != null && atMobiles.length > 0) { + Map at = new HashMap<>(); + at.put("atMobiles", atMobiles); + at.put("isAtAll", false); + message.put("at", at); + } + + return sendRequest(message); + } catch (Exception e) { + log.error("发送钉钉文本消息失败: {}", e.getMessage(), e); + return false; + } + } + + /** + * 发送 Markdown 消息到钉钉 + * + * @param title 消息标题 + * @param text Markdown 内容 + * @return 是否发送成功 + */ + public boolean sendMarkdownMessage(String title, String text) { + return sendMarkdownMessage(title, text, null); + } + + /** + * 发送 Markdown 消息到钉钉 + * + * @param title 消息标题 + * @param text Markdown 内容 + * @param atMobiles 需要@的手机号列表 + * @return 是否发送成功 + */ + public boolean sendMarkdownMessage(String title, String text, String[] atMobiles) { + if (webhook == null || webhook.trim().isEmpty()) { + log.warn("钉钉 Webhook 未配置,跳过消息推送"); + return false; + } + + try { + Map message = new HashMap<>(); + message.put("msgtype", "markdown"); + + Map markdown = new HashMap<>(); + markdown.put("title", title); + markdown.put("text", text); + message.put("markdown", markdown); + + // @人员配置 + if (atMobiles != null && atMobiles.length > 0) { + Map at = new HashMap<>(); + at.put("atMobiles", atMobiles); + at.put("isAtAll", false); + message.put("at", at); + } + + return sendRequest(message); + } catch (Exception e) { + log.error("发送钉钉 Markdown 消息失败: {}", e.getMessage(), e); + return false; + } + } + + /** + * 发送 HTTP 请求到钉钉机器人 + * + * @param message 消息体 + * @return 是否发送成功 + */ + private boolean sendRequest(Map message) { + HttpURLConnection connection = null; + try { + URL url = new URL(webhook); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + connection.setDoOutput(true); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + // 发送请求 + String jsonMessage = objectMapper.writeValueAsString(message); + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonMessage.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + // 读取响应 + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + log.info("钉钉消息推送成功"); + return true; + } else { + log.error("钉钉消息推送失败, HTTP 状态码: {}", responseCode); + return false; + } + } catch (Exception e) { + log.error("钉钉消息推送异常: {}", e.getMessage(), e); + return false; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a9a7896..3ff91fe 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -79,3 +79,7 @@ spring.mail.properties.mail.smtp.ssl.required=true spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory spring.mail.properties.mail.smtp.socketFactory.port=465 spring.mail.properties.mail.smtp.socketFactory.fallback=false + +# 钉钉机器人配置 +# 请前往钉钉群设置 -> 智能群助手 -> 添加机器人 -> 自定义机器人,获取 Webhook 地址 +dingtalk.webhook=https://oapi.dingtalk.com/robot/send?access_token=7eed4b3483303c9ec71ef37a08c347bb597fd4c64211a96a8f55f72405ff6444