搭建消息告警模块以及跨网推送服务,支持企微,飞书,钉钉通知
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using Common.Notify;
|
||||
using Model.Entity.Inspection;
|
||||
using Service.Interface;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Service.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// 钉钉群机器人通知发送器(自定义机器人 Webhook,支持加签校验)
|
||||
/// 报文:msgtype=markdown,at.atMobiles 支持 @手机号;
|
||||
/// 加签:URL 追加 timestamp(毫秒)+sign(算法见 ImWebhookSigner.DingTalkSign)
|
||||
/// </summary>
|
||||
public class DingTalkNotifier : IAlertNotifier
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<DingTalkNotifier> _logger;
|
||||
|
||||
public DingTalkNotifier(HttpClient httpClient, ILogger<DingTalkNotifier> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public NotifyChannelTypeEnum ChannelType => NotifyChannelTypeEnum.DingTalk;
|
||||
|
||||
public async Task<NotifyResult> SendAsync(AlertNotifyMessage message, string webhookUrl, string? secret, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = webhookUrl;
|
||||
// 加签:URL 追加 timestamp 与 sign 参数
|
||||
if (!string.IsNullOrWhiteSpace(secret))
|
||||
{
|
||||
long timestampMs = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
string sign = ImWebhookSigner.DingTalkSign(timestampMs, secret);
|
||||
url += $"{(url.Contains('?') ? "&" : "?")}timestamp={timestampMs}&sign={sign}";
|
||||
}
|
||||
|
||||
// markdown 正文中 @ 手机号需同时出现在 atMobiles 与正文里才会真正提醒
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine($"### {message.Title}");
|
||||
text.AppendLine(message.Content);
|
||||
foreach (var mobile in message.AtMobiles)
|
||||
{
|
||||
text.Append($"@{mobile} ");
|
||||
}
|
||||
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
["msgtype"] = "markdown",
|
||||
["markdown"] = new Dictionary<string, object>
|
||||
{
|
||||
["title"] = message.Title,
|
||||
["text"] = text.ToString()
|
||||
},
|
||||
["at"] = new Dictionary<string, object>
|
||||
{
|
||||
["atMobiles"] = message.AtMobiles,
|
||||
["isAtAll"] = false
|
||||
}
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(body);
|
||||
_logger.LogInformation("钉钉推送 atMobiles=[{At}]", string.Join(",", message.AtMobiles));
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using var response = await _httpClient.PostAsync(url, content, ct);
|
||||
string respText = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return NotifyResult.Fail($"HTTP {(int)response.StatusCode}: {Truncate(respText)}");
|
||||
}
|
||||
|
||||
// 钉钉返回 {"errcode":0,"errmsg":"ok"}
|
||||
using var doc = JsonDocument.Parse(respText);
|
||||
bool ok = doc.RootElement.TryGetProperty("errcode", out var errcode) && errcode.GetInt32() == 0;
|
||||
return ok ? NotifyResult.Ok() : NotifyResult.Fail(Truncate(respText));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return NotifyResult.Fail($"钉钉推送异常: {NotifyError.Describe(ex)}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string Truncate(string s, int max = 200) =>
|
||||
string.IsNullOrEmpty(s) || s.Length <= max ? s : s.Substring(0, max) + "...";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user