113 行
4.7 KiB
Java
113 行
4.7 KiB
Java
|
|
package com.xuqm.im.service;
|
||
|
|
|
||
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
||
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
|
import com.xuqm.im.entity.WebhookConfigEntity;
|
||
|
|
import com.xuqm.im.model.WebhookCallbackEnvelope;
|
||
|
|
import com.xuqm.im.repository.WebhookConfigRepository;
|
||
|
|
import org.springframework.beans.factory.annotation.Value;
|
||
|
|
import org.springframework.stereotype.Component;
|
||
|
|
|
||
|
|
import java.net.URI;
|
||
|
|
import java.net.http.HttpRequest;
|
||
|
|
import java.net.http.HttpResponse;
|
||
|
|
import java.net.http.HttpClient;
|
||
|
|
import java.time.Duration;
|
||
|
|
import java.nio.charset.StandardCharsets;
|
||
|
|
import java.security.MessageDigest;
|
||
|
|
import java.security.NoSuchAlgorithmException;
|
||
|
|
import java.util.HexFormat;
|
||
|
|
import java.util.List;
|
||
|
|
import java.util.UUID;
|
||
|
|
import javax.crypto.Mac;
|
||
|
|
import javax.crypto.spec.SecretKeySpec;
|
||
|
|
|
||
|
|
@Component
|
||
|
|
public class WebhookDispatchService {
|
||
|
|
|
||
|
|
private final WebhookConfigRepository webhookRepository;
|
||
|
|
private final ImAppSecretClient appSecretClient;
|
||
|
|
private final ObjectMapper objectMapper;
|
||
|
|
|
||
|
|
@Value("${im.webhook-timeout-ms:3000}")
|
||
|
|
private int webhookTimeoutMs;
|
||
|
|
|
||
|
|
public WebhookDispatchService(WebhookConfigRepository webhookRepository,
|
||
|
|
ImAppSecretClient appSecretClient,
|
||
|
|
ObjectMapper objectMapper) {
|
||
|
|
this.webhookRepository = webhookRepository;
|
||
|
|
this.appSecretClient = appSecretClient;
|
||
|
|
this.objectMapper = objectMapper;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void dispatch(String appId, String callbackType, String callbackEvent, Object payload) {
|
||
|
|
List<WebhookConfigEntity> webhooks = webhookRepository.findByAppIdAndEnabledTrue(appId);
|
||
|
|
if (webhooks.isEmpty()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
String appSecret = appSecretClient.getAppSecret(appId);
|
||
|
|
long requestTime = System.currentTimeMillis();
|
||
|
|
String nonce = UUID.randomUUID().toString().replace("-", "");
|
||
|
|
String callbackId = UUID.randomUUID().toString();
|
||
|
|
WebhookCallbackEnvelope envelope = new WebhookCallbackEnvelope(
|
||
|
|
callbackId,
|
||
|
|
callbackType,
|
||
|
|
callbackEvent,
|
||
|
|
requestTime,
|
||
|
|
objectMapper.valueToTree(payload),
|
||
|
|
null,
|
||
|
|
appId
|
||
|
|
);
|
||
|
|
String body = objectMapper.writeValueAsString(envelope);
|
||
|
|
String signature = signWebhook(appId, appSecret, requestTime, nonce, body);
|
||
|
|
HttpClient client = HttpClient.newBuilder()
|
||
|
|
.connectTimeout(Duration.ofMillis(webhookTimeoutMs))
|
||
|
|
.build();
|
||
|
|
for (WebhookConfigEntity webhook : webhooks) {
|
||
|
|
try {
|
||
|
|
HttpRequest request = HttpRequest.newBuilder()
|
||
|
|
.uri(URI.create(webhook.getUrl()))
|
||
|
|
.timeout(Duration.ofMillis(webhookTimeoutMs))
|
||
|
|
.header("Content-Type", "application/json")
|
||
|
|
.header("X-App-Id", appId)
|
||
|
|
.header("X-App-Timestamp", String.valueOf(requestTime))
|
||
|
|
.header("X-App-Nonce", nonce)
|
||
|
|
.header("X-App-Signature", signature)
|
||
|
|
.POST(HttpRequest.BodyPublishers.ofString(body))
|
||
|
|
.build();
|
||
|
|
client.send(request, HttpResponse.BodyHandlers.ofString());
|
||
|
|
} catch (Exception e) {
|
||
|
|
// 回调失败不影响主流程
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (Exception e) {
|
||
|
|
// 准备失败不影响主流程
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private String signWebhook(String appId, String appSecret, long requestTime, String nonce, String body) {
|
||
|
|
String payload = appId + "\n" + requestTime + "\n" + nonce + "\n" + sha256Hex(body);
|
||
|
|
return hmacSha256Hex(appSecret, payload);
|
||
|
|
}
|
||
|
|
|
||
|
|
private String sha256Hex(String value) {
|
||
|
|
try {
|
||
|
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||
|
|
return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8)));
|
||
|
|
} catch (NoSuchAlgorithmException e) {
|
||
|
|
throw new IllegalStateException("Failed to hash webhook body", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private String hmacSha256Hex(String secret, String payload) {
|
||
|
|
try {
|
||
|
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||
|
|
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||
|
|
return HexFormat.of().formatHex(mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));
|
||
|
|
} catch (Exception e) {
|
||
|
|
throw new IllegalStateException("Failed to sign webhook body", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|