工具包
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
/**
* @author 张林
* @version 1.0
* @date 2022/1/10 4:06 下午
*/
public class DingMessageUtils {
public DingMessageUtils(String sendUrl, String secret, String active) {
DingMessageUtils.sendUrl = sendUrl;
DingMessageUtils.secret = secret;
DingMessageUtils.active = active;
}
// 钉钉自定义机器人下的 url + token
private static String sendUrl;
// 密钥
private static String secret;
// 当前环境作推送限制
private static String active;
public void sendDingDingErrorMessage(Throwable e) {
sendDingDingErrorMessage(getStackInfo(e));
}
public boolean sendDingDingErrorMessage(String errorMessage) {
return sendDingDingMessage(errorMessage);
}
public boolean sendDingDingErrorMessage(Throwable e, String tranceId) {
// 组装信息格式
return sendDingDingMessage(String.format(
"错误信息: \n" +
"%s \n" +
"TranceId : \n" +
"%s \n" +
"Stack_Info : \n" +
"%s",
e.getMessage(), tranceId, getStackInfo(e)
));
// 如果有elk 这里就不推送堆栈信息了
// return sendDingDingMessage(String.format("%s \ntranceId : %s \n",
// e.getMessage(), tranceId));
}
public boolean sendDingDingMessage(String message) {
// 特定环境就不推了
if ("dev".equals(active) || Objects.isNull(secret) || Objects.isNull(sendUrl)) {
return true;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("msgtype", "text");
JSONObject context = new JSONObject();
context.put("content", message);
jsonObject.put("text", context);
String endUrl = sendUrl + genSign();
String post = HttpUtil.post(endUrl, jsonObject.toString());
return 0 == JSONObject.parseObject(post).getInteger("errcode");
}
// 签名
private static String genSign() {
Long timestamp = System.currentTimeMillis();
String stringToSign = timestamp + "\n" + secret;
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return "×tamp=" + timestamp + "&sign=" + URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
} catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
return "加密失败" + e.getMessage();
}
}
// 堆栈信息
private static String getStackInfo(Throwable e) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
e.printStackTrace(printWriter);
return stringWriter.toString();
}
}
获取链路的traceId
import brave.Tracer;
public class sendMessageUtil() {
// 如果你的项目中接入了 ELK&链路追踪 , 这里可以获取traceId, 并且在推送消息的时候仅推送traceId,
// 这样在钉钉聊天窗中, 就不需要推送堆栈信息, 看着更舒服
@Autowired
private Tracer tracer;
private void sendDingMessage(Throwable e) {
dingMessageUtils.sendDingDingErrorMessage(e, tracer.currentSpan().context().traceIdString());
}
}
在多环境使用中, 钉钉的配置信息可能需要yml 配置文件中获取, 但是不想把这些东西放在工具包中:
下边将DingMessageUtils 加入spring容器中管理
import util.DingMessageUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 张林
* @version 1.0
* @date 2022/1/10 5:58 下午
*/
@Configuration
public class DingMessageConfig {
@Value("${spring.profiles.active}")
private String active;
@Value("${dingding.message.sendUrl}")
private String sendUrl;
@Value("${dingding.message.secret}")
private String secret;
@Bean
public DingMessageUtils dingMessageUtil() {
return new DingMessageUtils(sendUrl, secret, active);
}
}