Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Webhook #1465

Merged
merged 1 commit into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;


}
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,15 @@
<artifactId>chat2db-sqlserver</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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<String> {

/**
* 企业微信
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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 + "&timestamp=" + 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");
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}

}
}
Loading