Skip to content

Commit 2173b05

Browse files
authored
Merge pull request #1465 from miahemu/notification
Add Webhook
2 parents 298060b + cd68edc commit 2173b05

File tree

9 files changed

+402
-0
lines changed

9 files changed

+402
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ai.chat2db.server.domain.api.param.message;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
import lombok.experimental.SuperBuilder;
7+
8+
/**
9+
* @author Juechen
10+
* @version : MessageCreateParam.java
11+
*/
12+
@Data
13+
@SuperBuilder
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
public class MessageCreateParam {
17+
18+
/**
19+
* 平台类型
20+
* @see ai.chat2db.server.domain.core.enums.ExternalNotificationTypeEnum
21+
*/
22+
private String platformType;
23+
24+
/**
25+
* 服务URL
26+
*/
27+
private String serviceUrl;
28+
29+
/**
30+
* 密钥
31+
*/
32+
private String secretKey;
33+
34+
/**
35+
* 消息模版
36+
*/
37+
private String textTemplate;
38+
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ai.chat2db.server.domain.api.service;
2+
3+
import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
4+
5+
/**
6+
* @author Juechen
7+
* @version : WebhookSender.java
8+
*/
9+
public interface WebhookSender {
10+
11+
void sendMessage(MessageCreateParam param);
12+
13+
}

chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml

+10
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,15 @@
121121
<artifactId>chat2db-sqlserver</artifactId>
122122
<version>${revision}</version>
123123
</dependency>
124+
<dependency>
125+
<groupId>commons-codec</groupId>
126+
<artifactId>commons-codec</artifactId>
127+
<version>1.11</version>
128+
</dependency>
129+
<dependency>
130+
<groupId>com.squareup.okhttp3</groupId>
131+
<artifactId>okhttp</artifactId>
132+
<version>4.9.1</version>
133+
</dependency>
124134
</dependencies>
125135
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package ai.chat2db.server.domain.core.enums;
2+
3+
import ai.chat2db.server.domain.api.service.WebhookSender;
4+
import ai.chat2db.server.domain.core.notification.DingTalkWebhookSender;
5+
import ai.chat2db.server.domain.core.notification.LarkWebhookSender;
6+
import ai.chat2db.server.domain.core.notification.WeComWebhookSender;
7+
import ai.chat2db.server.tools.base.enums.BaseEnum;
8+
import lombok.Getter;
9+
10+
/**
11+
* @author Juechen
12+
* @version : ExternalNotificationTypeEnum.java
13+
*/
14+
@Getter
15+
public enum ExternalNotificationTypeEnum implements BaseEnum<String> {
16+
17+
/**
18+
* 企业微信
19+
*/
20+
WECOM("WeCom", WeComWebhookSender.class),
21+
22+
/**
23+
* 钉钉
24+
*/
25+
DINGTALK("DingTalk", DingTalkWebhookSender.class),
26+
27+
/**
28+
* 飞书
29+
*/
30+
LARK("Lark", LarkWebhookSender.class),
31+
32+
;
33+
34+
final String description;
35+
36+
final Class<? extends WebhookSender> webhookSender;
37+
38+
39+
@Override
40+
public String getCode() {
41+
return this.name();
42+
}
43+
44+
public static WebhookSender getWebhookSender(String platformType) {
45+
String lowerCasePlatformType = platformType.toLowerCase();
46+
switch (lowerCasePlatformType) {
47+
case "wecom":
48+
return new WeComWebhookSender();
49+
case "dingtalk":
50+
return new DingTalkWebhookSender();
51+
case "lark":
52+
return new LarkWebhookSender();
53+
default:
54+
return null;
55+
}
56+
}
57+
58+
/**
59+
* Get enum by name
60+
*
61+
* @param name
62+
* @return
63+
*/
64+
public static ExternalNotificationTypeEnum getByName(String name) {
65+
for (ExternalNotificationTypeEnum dbTypeEnum : ExternalNotificationTypeEnum.values()) {
66+
if (dbTypeEnum.name().equalsIgnoreCase(name)) {
67+
return dbTypeEnum;
68+
}
69+
}
70+
return null;
71+
}
72+
73+
ExternalNotificationTypeEnum(String description, Class<? extends WebhookSender> webhookSender) {
74+
this.description = description;
75+
this.webhookSender = webhookSender;
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ai.chat2db.server.domain.core.notification;
2+
3+
import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
4+
import ai.chat2db.server.domain.api.service.WebhookSender;
5+
import ai.chat2db.server.domain.core.enums.ExternalNotificationTypeEnum;
6+
import org.springframework.stereotype.Service;
7+
8+
@Service
9+
public class BaseWebhookSender implements WebhookSender {
10+
11+
/**
12+
* Sends a message through the specified webhook platform.
13+
*
14+
* @param param The parameter object containing message details and platform type.
15+
* @throws IllegalArgumentException if the provided param is null or invalid.
16+
* @throws RuntimeException if an error occurs while attempting to send the message.
17+
*/
18+
public void sendMessage(MessageCreateParam param) throws IllegalArgumentException {
19+
// Validate the input parameter to ensure it's not null and meets the necessary criteria.
20+
if (param == null || param.getPlatformType() == null) {
21+
throw new IllegalArgumentException("MessageCreateParam or its platform type cannot be null.");
22+
}
23+
24+
try {
25+
// Attempt to retrieve the appropriate WebhookSender based on the platform type.
26+
ExternalNotificationTypeEnum extern = ExternalNotificationTypeEnum.getByName(param.getPlatformType());
27+
WebhookSender sender = extern.getWebhookSender(param.getPlatformType());
28+
29+
// Guard clause for null sender. Ideally, getWebhookSender should prevent this, but it's good to be cautious.
30+
if (sender == null) {
31+
throw new RuntimeException("Failed to retrieve WebhookSender for platform type: " + param.getPlatformType());
32+
}
33+
34+
// Send the message. Any exceptions thrown by sendMessage should be caught and handled here.
35+
sender.sendMessage(param);
36+
} catch (Exception e) {
37+
// Wrap and re-throw any runtime exceptions as a checked exception specific to webhook sending.
38+
throw new RuntimeException("An error occurred while sending the message.", e);
39+
}
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package ai.chat2db.server.domain.core.notification;
2+
3+
import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
4+
import okhttp3.*;
5+
6+
import java.io.IOException;
7+
import java.io.UnsupportedEncodingException;
8+
import java.net.URLEncoder;
9+
import java.security.InvalidKeyException;
10+
import java.security.NoSuchAlgorithmException;
11+
import javax.crypto.Mac;
12+
import javax.crypto.spec.SecretKeySpec;
13+
14+
import org.apache.commons.codec.binary.Base64;
15+
import org.springframework.stereotype.Service;
16+
17+
/**
18+
* @author Juechen
19+
* @version : DingTalkWebhookSender.java
20+
*/
21+
@Service
22+
public class DingTalkWebhookSender extends BaseWebhookSender {
23+
24+
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
25+
26+
@Override
27+
public void sendMessage(MessageCreateParam param) {
28+
try {
29+
OkHttpClient client = new OkHttpClient();
30+
String secret = param.getSecretKey();
31+
Long timestamp = System.currentTimeMillis();
32+
33+
String sign = generateSign(secret, timestamp);
34+
35+
String webhookUrl = param.getServiceUrl() + "&sign=" + sign + "&timestamp=" + timestamp;
36+
37+
38+
String payload = "{\"msgtype\": \"text\",\"text\": {\"content\": \"" + param.getTextTemplate() + "\"}}";
39+
RequestBody requestBody = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"));
40+
41+
Request request = new Request.Builder()
42+
.url(webhookUrl)
43+
.post(requestBody)
44+
.header("Content-Type", "application/json")
45+
.build();
46+
47+
48+
Response response = client.newCall(request).execute();
49+
if (!response.isSuccessful()) {
50+
throw new RuntimeException("Failed to send message: " + response.code());
51+
}
52+
System.out.println(response.body().string());
53+
} catch (IOException e) {
54+
throw new RuntimeException(e);
55+
} catch (NoSuchAlgorithmException e) {
56+
throw new RuntimeException(e);
57+
} catch (InvalidKeyException e) {
58+
throw new RuntimeException(e);
59+
}
60+
61+
}
62+
63+
private static String generateSign(String secret, Long timestamp) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
64+
String stringToSign = timestamp + "\n" + secret;
65+
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
66+
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
67+
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
68+
return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package ai.chat2db.server.domain.core.notification;
2+
3+
import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
4+
import okhttp3.*;
5+
6+
import javax.crypto.Mac;
7+
import javax.crypto.spec.SecretKeySpec;
8+
import java.io.IOException;
9+
import java.nio.charset.StandardCharsets;
10+
import java.security.InvalidKeyException;
11+
import java.security.NoSuchAlgorithmException;
12+
13+
import org.apache.commons.codec.binary.Base64;
14+
import org.springframework.stereotype.Service;
15+
16+
/**
17+
* @author Juechen
18+
* @version : LarkWebhookSender.java
19+
*/
20+
@Service
21+
public class LarkWebhookSender extends BaseWebhookSender {
22+
23+
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
24+
25+
@Override
26+
public void sendMessage(MessageCreateParam param) {
27+
try {
28+
OkHttpClient client = new OkHttpClient();
29+
String webhookUrl = param.getServiceUrl();
30+
String secret = param.getSecretKey();
31+
int timestamp = (int) (System.currentTimeMillis() / 1000);
32+
33+
String signature = GenSign(secret, timestamp);
34+
35+
String payload = "{\"timestamp\": \"" + timestamp
36+
+ "\",\"sign\": \"" + signature
37+
+ "\",\"msg_type\":\"text\",\"content\":{\"text\":\""+ param.getTextTemplate() +"\"}}";
38+
RequestBody body = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"));
39+
40+
41+
Request request = new Request.Builder()
42+
.url(webhookUrl)
43+
.post(body)
44+
.addHeader("Content-Type", "application/json")
45+
.build();
46+
47+
Response response = client.newCall(request).execute();
48+
if (!response.isSuccessful()) {
49+
throw new RuntimeException("Failed to send message: " + response.code());
50+
}
51+
System.out.println(response.body().string());
52+
} catch (NoSuchAlgorithmException e) {
53+
throw new RuntimeException(e);
54+
} catch (InvalidKeyException e) {
55+
throw new RuntimeException(e);
56+
} catch (IOException e) {
57+
throw new RuntimeException(e);
58+
}
59+
60+
}
61+
62+
private static String GenSign(String secret, int timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
63+
String stringToSign = timestamp + "\n" + secret;
64+
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
65+
mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM));
66+
byte[] signData = mac.doFinal(new byte[]{});
67+
return new String(Base64.encodeBase64(signData));
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package ai.chat2db.server.domain.core.notification;
2+
3+
import ai.chat2db.server.domain.api.param.message.MessageCreateParam;
4+
import okhttp3.*;
5+
import org.springframework.stereotype.Service;
6+
7+
import java.io.IOException;
8+
9+
/**
10+
* @author Juechen
11+
* @version : WeComWebhookSender.java
12+
*/
13+
@Service
14+
public class WeComWebhookSender extends BaseWebhookSender {
15+
16+
@Override
17+
public void sendMessage(MessageCreateParam param) {
18+
try {
19+
OkHttpClient client = new OkHttpClient();
20+
String webhookUrl = param.getServiceUrl();
21+
String text = param.getTextTemplate();
22+
23+
String payload = "{\"msgtype\": \"text\",\"text\": {\"content\": \"" + text + "\"}}";
24+
25+
RequestBody requestBody = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"));
26+
27+
Request request = new Request.Builder()
28+
.url(webhookUrl)
29+
.post(requestBody)
30+
.addHeader("Content-Type", "application/json")
31+
.build();
32+
33+
Response response = client.newCall(request).execute();
34+
if (!response.isSuccessful()) {
35+
throw new RuntimeException("Failed to send message: " + response.code());
36+
}
37+
System.out.println(response.body().string());
38+
} catch (IOException e) {
39+
throw new RuntimeException(e);
40+
}
41+
42+
}
43+
}

0 commit comments

Comments
 (0)