'init'
This commit is contained in:
189
sc-lktx-backend/internal/pkg/dingtalk/dingtalk.go
Normal file
189
sc-lktx-backend/internal/pkg/dingtalk/dingtalk.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// 钉钉开放平台 API 封装
|
||||
package dingtalk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
myconfig "com.sclktx/m/v2/internal/pkg/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Client 钉钉客户端
|
||||
type Client struct {
|
||||
cfg *myconfig.DingTalkConfig
|
||||
httpCl *http.Client
|
||||
token string
|
||||
tokenExp time.Time
|
||||
tokenLock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewClient 创建钉钉客户端
|
||||
func NewClient(cfg *myconfig.DingTalkConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
httpCl: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// tokenResponse 获取 access_token 的响应
|
||||
type tokenResponse struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ExpireIn int64 `json:"expireIn"` // 秒
|
||||
}
|
||||
|
||||
// getAccessToken 获取钉钉 access_token(带缓存)
|
||||
func (c *Client) getAccessToken() (string, error) {
|
||||
c.tokenLock.RLock()
|
||||
if c.token != "" && time.Now().Before(c.tokenExp) {
|
||||
c.tokenLock.RUnlock()
|
||||
return c.token, nil
|
||||
}
|
||||
c.tokenLock.RUnlock()
|
||||
|
||||
c.tokenLock.Lock()
|
||||
defer c.tokenLock.Unlock()
|
||||
|
||||
// 双重检查
|
||||
if c.token != "" && time.Now().Before(c.tokenExp) {
|
||||
return c.token, nil
|
||||
}
|
||||
|
||||
body := map[string]string{
|
||||
"appKey": c.cfg.AppKey,
|
||||
"appSecret": c.cfg.AppSecret,
|
||||
}
|
||||
data, _ := json.Marshal(body)
|
||||
|
||||
logrus.Debug("钉钉获取 access_token 开始")
|
||||
resp, err := c.httpCl.Post("https://api.dingtalk.com/v1.0/oauth2/accessToken", "application/json", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("钉钉获取 access_token HTTP 请求失败")
|
||||
return "", fmt.Errorf("dingtalk getAccessToken request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"status_code": resp.StatusCode,
|
||||
"response": string(respBody),
|
||||
}).Debug("钉钉获取 access_token 响应")
|
||||
|
||||
var tr tokenResponse
|
||||
if err := json.Unmarshal(respBody, &tr); err != nil {
|
||||
logrus.WithError(err).WithField("response", string(respBody)).Error("钉钉获取 access_token 解析失败")
|
||||
return "", fmt.Errorf("dingtalk getAccessToken parse failed: %s", string(respBody))
|
||||
}
|
||||
if tr.AccessToken == "" {
|
||||
logrus.WithField("response", string(respBody)).Error("钉钉获取 access_token 返回空")
|
||||
return "", fmt.Errorf("dingtalk getAccessToken empty token: %s", string(respBody))
|
||||
}
|
||||
|
||||
c.token = tr.AccessToken
|
||||
c.tokenExp = time.Now().Add(time.Duration(tr.ExpireIn-60) * time.Second) // 提前 60 秒刷新
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"expire_in": tr.ExpireIn,
|
||||
}).Info("钉钉 access_token 获取成功")
|
||||
return c.token, nil
|
||||
}
|
||||
|
||||
// RobotBatchSendReq 批量发送机器人消息请求
|
||||
type RobotBatchSendReq struct {
|
||||
RobotCode string `json:"robotCode"`
|
||||
UserIDs []string `json:"userIds"`
|
||||
MsgKey string `json:"msgKey"`
|
||||
MsgParam string `json:"msgParam"`
|
||||
}
|
||||
|
||||
// BatchSendRobotMessage 批量发送人与机器人会话中机器人消息
|
||||
// userIDs: 钉钉用户 unionId 列表
|
||||
// title: 消息标题
|
||||
// markdown: 消息内容(Markdown 格式)
|
||||
func (c *Client) BatchSendRobotMessage(userIDs []string, title, markdown string) error {
|
||||
if len(userIDs) == 0 {
|
||||
logrus.Warn("钉钉 BatchSendRobotMessage 被调用但 userIDs 为空")
|
||||
return nil
|
||||
}
|
||||
if c.cfg.RobotCode == "" {
|
||||
logrus.Warn("钉钉 BatchSendRobotMessage 被调用但 RobotCode 为空")
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"user_ids": userIDs,
|
||||
"title": title,
|
||||
"robot_code": c.cfg.RobotCode,
|
||||
}).Info("钉钉 BatchSendRobotMessage 开始")
|
||||
|
||||
token, err := c.getAccessToken()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("钉钉 BatchSendRobotMessage 获取 token 失败")
|
||||
return err
|
||||
}
|
||||
|
||||
msgParam := map[string]string{
|
||||
"title": title,
|
||||
"text": markdown,
|
||||
}
|
||||
msgParamJSON, _ := json.Marshal(msgParam)
|
||||
|
||||
req := RobotBatchSendReq{
|
||||
RobotCode: c.cfg.RobotCode,
|
||||
UserIDs: userIDs,
|
||||
MsgKey: "sampleMarkdown",
|
||||
MsgParam: string(msgParamJSON),
|
||||
}
|
||||
reqBody, _ := json.Marshal(req)
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"request_body": string(reqBody),
|
||||
}).Debug("钉钉 BatchSendRobotMessage 请求体")
|
||||
|
||||
httpReq, err := http.NewRequest("POST",
|
||||
"https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
|
||||
bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("钉钉 BatchSendRobotMessage 创建请求失败")
|
||||
return fmt.Errorf("dingtalk BatchSendRobotMessage create request failed: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("x-acs-dingtalk-access-token", token)
|
||||
|
||||
resp, err := c.httpCl.Do(httpReq)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("钉钉 BatchSendRobotMessage HTTP 请求失败")
|
||||
return fmt.Errorf("dingtalk BatchSendRobotMessage request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"status_code": resp.StatusCode,
|
||||
"response": string(respBody),
|
||||
}).Debug("钉钉 BatchSendRobotMessage 响应")
|
||||
|
||||
// 成功时钉钉返回 200 空 body 或 processQueryKey
|
||||
if resp.StatusCode >= 400 {
|
||||
errMsg := fmt.Errorf("dingtalk BatchSendRobotMessage failed: status=%d body=%s", resp.StatusCode, string(respBody))
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"status_code": resp.StatusCode,
|
||||
"response": string(respBody),
|
||||
}).Error("钉钉 BatchSendRobotMessage 请求返回错误状态码")
|
||||
return errMsg
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"user_ids": userIDs,
|
||||
"title": title,
|
||||
"response": string(respBody),
|
||||
}).Info("钉钉机器人消息发送成功")
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user