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 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 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); + } +}