package ding import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "strconv" "time" ) // TalkClient 钉钉消息推送客户端结构体 type TalkClient struct { webhookURL string secret string } // NewDingTalkClient 创建一个新的钉钉消息推送客户端实例 func NewDingTalkClient(webhookURL, secret string) *TalkClient { return &TalkClient{ webhookURL: webhookURL, secret: secret, } } // generateSign 生成加签 func (c *TalkClient) generateSign(timestamp string) (string, error) { stringToSign := timestamp + "\n" + c.secret h := hmac.New(sha256.New, []byte(c.secret)) _, err := h.Write([]byte(stringToSign)) if err != nil { return "", err } signData := h.Sum(nil) return base64.StdEncoding.EncodeToString(signData), nil } // sendRequest 发送 HTTP 请求 func (c *TalkClient) sendRequest(message map[string]interface{}) error { timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) fullURL := fmt.Sprintf("%s×tamp=%s", c.webhookURL, timestamp) if len(c.secret) > 0 { sign, err := c.generateSign(timestamp) if err != nil { return err } fullURL = fmt.Sprintf("%s&sign=%s", fullURL, sign) } messageJSON, err := json.Marshal(message) if err != nil { return err } req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(messageJSON)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to send message, status code: %d, body: %s", resp.StatusCode, string(body)) } return nil } // SendTextMessage 发送文本消息并可 @ 人员 func (c *TalkClient) SendTextMessage(content string, atMobiles []string, isAtAll bool) error { message := map[string]interface{}{ "msgtype": "text", "text": map[string]string{ "content": content, }, "at": map[string]interface{}{ "atMobiles": atMobiles, "isAtAll": isAtAll, }, } return c.sendRequest(message) } // SendLinkMessage 发送链接消息并可 @ 人员 func (c *TalkClient) SendLinkMessage(title, text, messageURL, picURL string, atMobiles []string, isAtAll bool) error { message := map[string]interface{}{ "msgtype": "link", "link": map[string]string{ "title": title, "text": text, "messageUrl": messageURL, "picUrl": picURL, }, "at": map[string]interface{}{ "atMobiles": atMobiles, "isAtAll": isAtAll, }, } return c.sendRequest(message) } // SendMarkdownMessage 发送 Markdown 消息并可 @ 人员 func (c *TalkClient) SendMarkdownMessage(title, text string, atMobiles []string, isAtAll bool) error { if len(atMobiles) > 0 { var atStr string for _, mobile := range atMobiles { atStr += fmt.Sprintf("@%s", mobile) } text += fmt.Sprintf("\n请尽快处理%s", atStr) } message := map[string]interface{}{ "msgtype": "markdown", "markdown": map[string]string{ "title": title, "text": text, }, "at": map[string]interface{}{ "atMobiles": atMobiles, "isAtAll": isAtAll, }, } return c.sendRequest(message) } // SendJSONAsMarkdown 发送JSON数据(转换为Markdown格式) func (c *TalkClient) SendJSONAsMarkdown(data interface{}, title string) error { // 将JSON数据转换为格式化的字符串 jsonBytes, err := json.MarshalIndent(data, "", " ") if err != nil { return fmt.Errorf("序列化JSON失败: %w", err) } // 构建Markdown内容(使用代码块格式) markdownText := fmt.Sprintf("### %s\n\n```json\n%s\n```", title, string(jsonBytes)) // 发送Markdown消息 return c.SendMarkdownMessage(title, markdownText, nil, false) }