diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/message/MessageCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/message/MessageCreateParam.java
new file mode 100644
index 000000000..f15098334
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/message/MessageCreateParam.java
@@ -0,0 +1,40 @@
+package ai.chat2db.server.domain.api.param.message;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * @author Juechen
+ * @version : MessageCreateParam.java
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MessageCreateParam {
+
+ /**
+ * 平台类型
+ * @see ai.chat2db.server.domain.core.enums.ExternalNotificationTypeEnum
+ */
+ private String platformType;
+
+ /**
+ * 服务URL
+ */
+ private String serviceUrl;
+
+ /**
+ * 密钥
+ */
+ private String secretKey;
+
+ /**
+ * 消息模版
+ */
+ private String textTemplate;
+
+
+}
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/WebhookSender.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/WebhookSender.java
new file mode 100644
index 000000000..fabfcf6af
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/WebhookSender.java
@@ -0,0 +1,13 @@
+package ai.chat2db.server.domain.api.service;
+
+import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
+
+/**
+ * @author Juechen
+ * @version : WebhookSender.java
+ */
+public interface WebhookSender {
+
+ void sendMessage(MessageCreateParam param);
+
+}
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml
index 47a86c68a..4916c324b 100644
--- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml
@@ -121,5 +121,15 @@
chat2db-sqlserver
${revision}
+
+ commons-codec
+ commons-codec
+ 1.11
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.9.1
+
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/enums/ExternalNotificationTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/enums/ExternalNotificationTypeEnum.java
new file mode 100644
index 000000000..6afbdc6e8
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/enums/ExternalNotificationTypeEnum.java
@@ -0,0 +1,77 @@
+package ai.chat2db.server.domain.core.enums;
+
+import ai.chat2db.server.domain.api.service.WebhookSender;
+import ai.chat2db.server.domain.core.notification.DingTalkWebhookSender;
+import ai.chat2db.server.domain.core.notification.LarkWebhookSender;
+import ai.chat2db.server.domain.core.notification.WeComWebhookSender;
+import ai.chat2db.server.tools.base.enums.BaseEnum;
+import lombok.Getter;
+
+/**
+ * @author Juechen
+ * @version : ExternalNotificationTypeEnum.java
+ */
+@Getter
+public enum ExternalNotificationTypeEnum implements BaseEnum {
+
+ /**
+ * 企业微信
+ */
+ WECOM("WeCom", WeComWebhookSender.class),
+
+ /**
+ * 钉钉
+ */
+ DINGTALK("DingTalk", DingTalkWebhookSender.class),
+
+ /**
+ * 飞书
+ */
+ LARK("Lark", LarkWebhookSender.class),
+
+ ;
+
+ final String description;
+
+ final Class extends WebhookSender> webhookSender;
+
+
+ @Override
+ public String getCode() {
+ return this.name();
+ }
+
+ public static WebhookSender getWebhookSender(String platformType) {
+ String lowerCasePlatformType = platformType.toLowerCase();
+ switch (lowerCasePlatformType) {
+ case "wecom":
+ return new WeComWebhookSender();
+ case "dingtalk":
+ return new DingTalkWebhookSender();
+ case "lark":
+ return new LarkWebhookSender();
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Get enum by name
+ *
+ * @param name
+ * @return
+ */
+ public static ExternalNotificationTypeEnum getByName(String name) {
+ for (ExternalNotificationTypeEnum dbTypeEnum : ExternalNotificationTypeEnum.values()) {
+ if (dbTypeEnum.name().equalsIgnoreCase(name)) {
+ return dbTypeEnum;
+ }
+ }
+ return null;
+ }
+
+ ExternalNotificationTypeEnum(String description, Class extends WebhookSender> webhookSender) {
+ this.description = description;
+ this.webhookSender = webhookSender;
+ }
+}
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/BaseWebhookSender.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/BaseWebhookSender.java
new file mode 100644
index 000000000..c769f072a
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/BaseWebhookSender.java
@@ -0,0 +1,42 @@
+package ai.chat2db.server.domain.core.notification;
+
+import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
+import ai.chat2db.server.domain.api.service.WebhookSender;
+import ai.chat2db.server.domain.core.enums.ExternalNotificationTypeEnum;
+import org.springframework.stereotype.Service;
+
+@Service
+public class BaseWebhookSender implements WebhookSender {
+
+ /**
+ * Sends a message through the specified webhook platform.
+ *
+ * @param param The parameter object containing message details and platform type.
+ * @throws IllegalArgumentException if the provided param is null or invalid.
+ * @throws RuntimeException if an error occurs while attempting to send the message.
+ */
+ public void sendMessage(MessageCreateParam param) throws IllegalArgumentException {
+ // Validate the input parameter to ensure it's not null and meets the necessary criteria.
+ if (param == null || param.getPlatformType() == null) {
+ throw new IllegalArgumentException("MessageCreateParam or its platform type cannot be null.");
+ }
+
+ try {
+ // Attempt to retrieve the appropriate WebhookSender based on the platform type.
+ ExternalNotificationTypeEnum extern = ExternalNotificationTypeEnum.getByName(param.getPlatformType());
+ WebhookSender sender = extern.getWebhookSender(param.getPlatformType());
+
+ // Guard clause for null sender. Ideally, getWebhookSender should prevent this, but it's good to be cautious.
+ if (sender == null) {
+ throw new RuntimeException("Failed to retrieve WebhookSender for platform type: " + param.getPlatformType());
+ }
+
+ // Send the message. Any exceptions thrown by sendMessage should be caught and handled here.
+ sender.sendMessage(param);
+ } catch (Exception e) {
+ // Wrap and re-throw any runtime exceptions as a checked exception specific to webhook sending.
+ throw new RuntimeException("An error occurred while sending the message.", e);
+ }
+ }
+
+}
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/DingTalkWebhookSender.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/DingTalkWebhookSender.java
new file mode 100644
index 000000000..c4408fe82
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/DingTalkWebhookSender.java
@@ -0,0 +1,70 @@
+package ai.chat2db.server.domain.core.notification;
+
+import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
+import okhttp3.*;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Juechen
+ * @version : DingTalkWebhookSender.java
+ */
+@Service
+public class DingTalkWebhookSender extends BaseWebhookSender {
+
+ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+ @Override
+ public void sendMessage(MessageCreateParam param) {
+ try {
+ OkHttpClient client = new OkHttpClient();
+ String secret = param.getSecretKey();
+ Long timestamp = System.currentTimeMillis();
+
+ String sign = generateSign(secret, timestamp);
+
+ String webhookUrl = param.getServiceUrl() + "&sign=" + sign + "×tamp=" + timestamp;
+
+
+ String payload = "{\"msgtype\": \"text\",\"text\": {\"content\": \"" + param.getTextTemplate() + "\"}}";
+ RequestBody requestBody = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"));
+
+ Request request = new Request.Builder()
+ .url(webhookUrl)
+ .post(requestBody)
+ .header("Content-Type", "application/json")
+ .build();
+
+
+ Response response = client.newCall(request).execute();
+ if (!response.isSuccessful()) {
+ throw new RuntimeException("Failed to send message: " + response.code());
+ }
+ System.out.println(response.body().string());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private static String generateSign(String secret, Long timestamp) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
+ String stringToSign = timestamp + "\n" + secret;
+ Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+ mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
+ byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
+ return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
+ }
+}
\ No newline at end of file
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/LarkWebhookSender.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/LarkWebhookSender.java
new file mode 100644
index 000000000..477297d33
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/LarkWebhookSender.java
@@ -0,0 +1,69 @@
+package ai.chat2db.server.domain.core.notification;
+
+import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
+import okhttp3.*;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Juechen
+ * @version : LarkWebhookSender.java
+ */
+@Service
+public class LarkWebhookSender extends BaseWebhookSender {
+
+ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+ @Override
+ public void sendMessage(MessageCreateParam param) {
+ try {
+ OkHttpClient client = new OkHttpClient();
+ String webhookUrl = param.getServiceUrl();
+ String secret = param.getSecretKey();
+ int timestamp = (int) (System.currentTimeMillis() / 1000);
+
+ String signature = GenSign(secret, timestamp);
+
+ String payload = "{\"timestamp\": \"" + timestamp
+ + "\",\"sign\": \"" + signature
+ + "\",\"msg_type\":\"text\",\"content\":{\"text\":\""+ param.getTextTemplate() +"\"}}";
+ RequestBody body = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"));
+
+
+ Request request = new Request.Builder()
+ .url(webhookUrl)
+ .post(body)
+ .addHeader("Content-Type", "application/json")
+ .build();
+
+ Response response = client.newCall(request).execute();
+ if (!response.isSuccessful()) {
+ throw new RuntimeException("Failed to send message: " + response.code());
+ }
+ System.out.println(response.body().string());
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private static String GenSign(String secret, int timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
+ String stringToSign = timestamp + "\n" + secret;
+ Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+ mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM));
+ byte[] signData = mac.doFinal(new byte[]{});
+ return new String(Base64.encodeBase64(signData));
+ }
+}
\ No newline at end of file
diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/WeComWebhookSender.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/WeComWebhookSender.java
new file mode 100644
index 000000000..ec4abeabd
--- /dev/null
+++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/WeComWebhookSender.java
@@ -0,0 +1,43 @@
+package ai.chat2db.server.domain.core.notification;
+
+import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
+import okhttp3.*;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+
+/**
+ * @author Juechen
+ * @version : WeComWebhookSender.java
+ */
+@Service
+public class WeComWebhookSender extends BaseWebhookSender {
+
+ @Override
+ public void sendMessage(MessageCreateParam param) {
+ try {
+ OkHttpClient client = new OkHttpClient();
+ String webhookUrl = param.getServiceUrl();
+ String text = param.getTextTemplate();
+
+ String payload = "{\"msgtype\": \"text\",\"text\": {\"content\": \"" + text + "\"}}";
+
+ RequestBody requestBody = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"));
+
+ Request request = new Request.Builder()
+ .url(webhookUrl)
+ .post(requestBody)
+ .addHeader("Content-Type", "application/json")
+ .build();
+
+ Response response = client.newCall(request).execute();
+ if (!response.isSuccessful()) {
+ throw new RuntimeException("Failed to send message: " + response.code());
+ }
+ System.out.println(response.body().string());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/WebhookServiceTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/WebhookServiceTest.java
new file mode 100644
index 000000000..930667e1b
--- /dev/null
+++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/WebhookServiceTest.java
@@ -0,0 +1,38 @@
+package ai.chat2db.server.start.test.core;
+
+import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
+import ai.chat2db.server.domain.core.notification.BaseWebhookSender;
+import ai.chat2db.server.start.test.TestApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * @author Juechen
+ * @version : WebhookServiceTest.java
+ */
+public class WebhookServiceTest extends TestApplication {
+
+ @Autowired
+ private BaseWebhookSender baseWebhookSender;
+
+ @Test
+ public void test() {
+ MessageCreateParam param = new MessageCreateParam();
+// param.setServiceUrl("https://oapi.dingtalk.com/robot/send?access_token=3dc1c8a55a3ba966d38fb37466c93c536ac210895304e2682966252ea8f8a252");
+// param.setSecretKey("SEC5058616c6ea2e5745abeb381d510579538ea5baa7cdd28a386c809289b1f1db9");
+// param.setPlatformType("DingTalk");
+// param.setTextTemplate("你好,钉钉!");
+
+ param.setServiceUrl("https://open.feishu.cn/open-apis/bot/v2/hook/da4c4585-b320-4a72-8fbe-920b48c4a0c9");
+ param.setSecretKey("tm3p2x2IBs8Lh8cBiJo1F");
+ param.setPlatformType("LaRK");
+ param.setTextTemplate("你好,飞书");
+
+// param.setServiceUrl("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=346b7d1e-39bd-4146-89e4-bca5fe05f5b4");
+// param.setSecretKey("");
+// param.setPlatformType("WeCom");
+// param.setTextTemplate("你好,企业微信");
+ baseWebhookSender.sendMessage(param);
+ }
+}