package com.jeesite.modules.mes.dingtalk; import com.alibaba.fastjson.JSONObject; import com.jeesite.common.lang.StringUtils; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @Component public class DingTalkRobotClient { private static final Logger logger = LoggerFactory.getLogger(DingTalkRobotClient.class); private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private final DingTalkRobotProperties properties; private final OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); public DingTalkRobotClient(DingTalkRobotProperties properties) { this.properties = properties; } public boolean sendMarkdown(String title, String markdownText) { return sendMarkdown(title, markdownText, getConfiguredAtMobiles(), properties.isAtAll()); } /** * 按指定手机号 @ 发送,不使用 yml 全局 at-all。 */ public boolean sendMarkdown(String title, String markdownText, List atMobiles) { return sendMarkdown(title, markdownText, atMobiles, false); } private boolean sendMarkdown(String title, String markdownText, List atMobiles, boolean atAll) { if (!properties.isEnabled() || StringUtils.isBlank(properties.getUrl())) { logger.warn("DingTalk robot is disabled or webhook url is empty."); return false; } JSONObject body = new JSONObject(); body.put("msgtype", "markdown"); JSONObject markdown = new JSONObject(); markdown.put("title", title); markdown.put("text", appendAtText(markdownText, atMobiles)); body.put("markdown", markdown); body.put("at", buildAt(atMobiles, atAll)); return post(body); } private JSONObject buildAt(List atMobiles, boolean atAll) { JSONObject at = new JSONObject(); at.put("isAtAll", atAll); List mobiles = atMobiles == null ? java.util.Collections.emptyList() : atMobiles; if (!mobiles.isEmpty()) { at.put("atMobiles", mobiles); } return at; } private String appendAtText(String markdownText, List atMobiles) { if (atMobiles == null || atMobiles.isEmpty()) { return markdownText; } String atText = atMobiles.stream() .map(mobile -> "@" + mobile) .collect(Collectors.joining(" ")); return markdownText + "\n\n" + atText; } private List getConfiguredAtMobiles() { if (StringUtils.isBlank(properties.getAtMobiles())) { return java.util.Collections.emptyList(); } return Stream.of(properties.getAtMobiles().split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); } private boolean post(JSONObject body) { String requestUrl = buildSignedUrl(); Request request = new Request.Builder() .url(requestUrl) .post(RequestBody.create(JSON, body.toJSONString())) .build(); try (Response response = httpClient.newCall(request).execute()) { String responseBody = response.body() == null ? "" : response.body().string(); if (!response.isSuccessful()) { logger.warn("DingTalk robot send failed, httpStatus={}, body={}", response.code(), responseBody); return false; } JSONObject result = JSONObject.parseObject(responseBody); boolean success = result != null && result.getIntValue("errcode") == 0; if (!success) { logger.warn("DingTalk robot returned failure: {}", responseBody); } return success; } catch (Exception ex) { logger.warn("DingTalk robot send exception: {}", ex.getMessage(), ex); return false; } } private String buildSignedUrl() { String url = properties.getUrl(); if (StringUtils.isBlank(properties.getSecret())) { return url; } long timestamp = System.currentTimeMillis(); String sign = sign(timestamp, properties.getSecret()); String separator = url.contains("?") ? "&" : "?"; return url + separator + "timestamp=" + timestamp + "&sign=" + sign; } private String sign(long timestamp, String secret) { try { String stringToSign = timestamp + "\n" + secret; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); String base64 = Base64.getEncoder().encodeToString(signData); return URLEncoder.encode(base64, StandardCharsets.UTF_8.name()); } catch (Exception ex) { throw new IllegalStateException("Build DingTalk sign failed", ex); } } }