DingTalkRobotClient.java 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package com.jeesite.modules.mes.dingtalk;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.jeesite.common.lang.StringUtils;
  4. import okhttp3.MediaType;
  5. import okhttp3.OkHttpClient;
  6. import okhttp3.Request;
  7. import okhttp3.RequestBody;
  8. import okhttp3.Response;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.springframework.stereotype.Component;
  12. import javax.crypto.Mac;
  13. import javax.crypto.spec.SecretKeySpec;
  14. import java.net.URLEncoder;
  15. import java.nio.charset.StandardCharsets;
  16. import java.util.Base64;
  17. import java.util.List;
  18. import java.util.concurrent.TimeUnit;
  19. import java.util.stream.Collectors;
  20. import java.util.stream.Stream;
  21. @Component
  22. public class DingTalkRobotClient {
  23. private static final Logger logger = LoggerFactory.getLogger(DingTalkRobotClient.class);
  24. private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
  25. private final DingTalkRobotProperties properties;
  26. private final OkHttpClient httpClient = new OkHttpClient.Builder()
  27. .connectTimeout(5, TimeUnit.SECONDS)
  28. .readTimeout(10, TimeUnit.SECONDS)
  29. .build();
  30. public DingTalkRobotClient(DingTalkRobotProperties properties) {
  31. this.properties = properties;
  32. }
  33. public boolean sendMarkdown(String title, String markdownText) {
  34. return sendMarkdown(title, markdownText, getConfiguredAtMobiles(), properties.isAtAll());
  35. }
  36. /**
  37. * 按指定手机号 @ 发送,不使用 yml 全局 at-all。
  38. */
  39. public boolean sendMarkdown(String title, String markdownText, List<String> atMobiles) {
  40. return sendMarkdown(title, markdownText, atMobiles, false);
  41. }
  42. private boolean sendMarkdown(String title, String markdownText, List<String> atMobiles, boolean atAll) {
  43. if (!properties.isEnabled() || StringUtils.isBlank(properties.getUrl())) {
  44. logger.warn("DingTalk robot is disabled or webhook url is empty.");
  45. return false;
  46. }
  47. JSONObject body = new JSONObject();
  48. body.put("msgtype", "markdown");
  49. JSONObject markdown = new JSONObject();
  50. markdown.put("title", title);
  51. markdown.put("text", appendAtText(markdownText, atMobiles));
  52. body.put("markdown", markdown);
  53. body.put("at", buildAt(atMobiles, atAll));
  54. return post(body);
  55. }
  56. private JSONObject buildAt(List<String> atMobiles, boolean atAll) {
  57. JSONObject at = new JSONObject();
  58. at.put("isAtAll", atAll);
  59. List<String> mobiles = atMobiles == null ? java.util.Collections.emptyList() : atMobiles;
  60. if (!mobiles.isEmpty()) {
  61. at.put("atMobiles", mobiles);
  62. }
  63. return at;
  64. }
  65. private String appendAtText(String markdownText, List<String> atMobiles) {
  66. if (atMobiles == null || atMobiles.isEmpty()) {
  67. return markdownText;
  68. }
  69. String atText = atMobiles.stream()
  70. .map(mobile -> "@" + mobile)
  71. .collect(Collectors.joining(" "));
  72. return markdownText + "\n\n" + atText;
  73. }
  74. private List<String> getConfiguredAtMobiles() {
  75. if (StringUtils.isBlank(properties.getAtMobiles())) {
  76. return java.util.Collections.emptyList();
  77. }
  78. return Stream.of(properties.getAtMobiles().split(","))
  79. .map(String::trim)
  80. .filter(StringUtils::isNotBlank)
  81. .collect(Collectors.toList());
  82. }
  83. private boolean post(JSONObject body) {
  84. String requestUrl = buildSignedUrl();
  85. Request request = new Request.Builder()
  86. .url(requestUrl)
  87. .post(RequestBody.create(JSON, body.toJSONString()))
  88. .build();
  89. try (Response response = httpClient.newCall(request).execute()) {
  90. String responseBody = response.body() == null ? "" : response.body().string();
  91. if (!response.isSuccessful()) {
  92. logger.warn("DingTalk robot send failed, httpStatus={}, body={}", response.code(), responseBody);
  93. return false;
  94. }
  95. JSONObject result = JSONObject.parseObject(responseBody);
  96. boolean success = result != null && result.getIntValue("errcode") == 0;
  97. if (!success) {
  98. logger.warn("DingTalk robot returned failure: {}", responseBody);
  99. }
  100. return success;
  101. } catch (Exception ex) {
  102. logger.warn("DingTalk robot send exception: {}", ex.getMessage(), ex);
  103. return false;
  104. }
  105. }
  106. private String buildSignedUrl() {
  107. String url = properties.getUrl();
  108. if (StringUtils.isBlank(properties.getSecret())) {
  109. return url;
  110. }
  111. long timestamp = System.currentTimeMillis();
  112. String sign = sign(timestamp, properties.getSecret());
  113. String separator = url.contains("?") ? "&" : "?";
  114. return url + separator + "timestamp=" + timestamp + "&sign=" + sign;
  115. }
  116. private String sign(long timestamp, String secret) {
  117. try {
  118. String stringToSign = timestamp + "\n" + secret;
  119. Mac mac = Mac.getInstance("HmacSHA256");
  120. mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
  121. byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
  122. String base64 = Base64.getEncoder().encodeToString(signData);
  123. return URLEncoder.encode(base64, StandardCharsets.UTF_8.name());
  124. } catch (Exception ex) {
  125. throw new IllegalStateException("Build DingTalk sign failed", ex);
  126. }
  127. }
  128. }