From 1ae61549a15c48bca403e9560b216aa56f4683ca Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 30 Oct 2025 20:18:14 +0800 Subject: [PATCH] refactor: reimplement dingtalkbot notifier, drop thrird-party deps --- .../providers/dingtalkbot/dingtalkbot.go | 72 +++++++++++++++---- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go b/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go index 7cdbfc49..081251a6 100644 --- a/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go +++ b/pkg/core/notifier/providers/dingtalkbot/dingtalkbot.go @@ -2,12 +2,18 @@ package dingtalkbot import ( "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" "errors" "fmt" "log/slog" + "net/http" "net/url" + "time" - "github.com/blinkbean/dingtalk" + "github.com/go-resty/resty/v2" "github.com/certimate-go/certimate/pkg/core" ) @@ -20,8 +26,9 @@ type NotifierProviderConfig struct { } type NotifierProvider struct { - config *NotifierProviderConfig - logger *slog.Logger + config *NotifierProviderConfig + logger *slog.Logger + httpClient *resty.Client } var _ core.Notifier = (*NotifierProvider)(nil) @@ -31,9 +38,30 @@ func NewNotifierProvider(config *NotifierProviderConfig) (*NotifierProvider, err return nil, errors.New("the configuration of the notifier provider is nil") } + client := resty.New(). + SetHeader("Content-Type", "application/json"). + SetHeader("User-Agent", "certimate"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if config.Secret != "" { + timestamp := fmt.Sprintf("%d", time.Now().UnixMilli()) + + h := hmac.New(sha256.New, []byte(config.Secret)) + h.Write([]byte(fmt.Sprintf("%s\n%s", timestamp, config.Secret))) + sign := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + qs := req.URL.Query() + qs.Set("timestamp", timestamp) + qs.Set("sign", sign) + req.URL.RawQuery = qs.Encode() + } + + return nil + }) + return &NotifierProvider{ - config: config, - logger: slog.Default(), + config: config, + logger: slog.Default(), + httpClient: client, }, nil } @@ -49,17 +77,35 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s webhookUrl, err := url.Parse(n.config.WebhookUrl) if err != nil { return nil, fmt.Errorf("dingtalk api error: invalid webhook url: %w", err) - } - - var bot *dingtalk.DingTalk - if n.config.Secret == "" { - bot = dingtalk.InitDingTalk([]string{webhookUrl.Query().Get("access_token")}, "") } else { - bot = dingtalk.InitDingTalkWithSecret(webhookUrl.Query().Get("access_token"), n.config.Secret) + const hostname = "oapi.dingtalk.com" + if webhookUrl.Hostname() != hostname { + n.logger.Warn(fmt.Sprintf("the webhook url hostname is not '%s', please make sure it is correct", hostname)) + } } - if err := bot.SendTextMessage(subject + "\n" + message); err != nil { - return nil, fmt.Errorf("dingtalk api error: %w", err) + // REF: https://open.dingtalk.com/document/development/custom-robots-send-group-messages + var result struct { + ErrorCode int `json:"errcode"` + ErrorMessage string `json:"errmsg"` + } + req := n.httpClient.R(). + SetContext(ctx). + SetBody(map[string]any{ + "msgtype": "text", + "text": map[string]string{ + "content": subject + "\n\n" + message, + }, + }) + resp, err := req.Post(webhookUrl.String()) + if err != nil { + return nil, fmt.Errorf("dingtalk api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("dingtalk api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } else if err := json.Unmarshal(resp.Body(), &result); err != nil { + return nil, fmt.Errorf("dingtalk api error: failed to unmarshal response: %w", err) + } else if result.ErrorCode != 0 { + return nil, fmt.Errorf("dingtalk api error: errcode='%d', errmsg='%s'", result.ErrorCode, result.ErrorMessage) } return &core.NotifyResult{}, nil