golang实现钉钉发送工作消息通知
2023-03-08 本文已影响0人
胡小毛
- 发送工作通知-开放平台:https://open.dingtalk.com/document/isvapp/asynchronous-sending-of-enterprise-session-messages
- 消息通知类型-开放平台:https://open.dingtalk.com/document/orgapp/message-types-and-data-format#title-x16-76n-jpg
- 调用钉钉服务端API发送工作通知消息-csdn:https://blog.csdn.net/langzitianya/article/details/104200032
- 在线调试工具:https://open-dev.dingtalk.com/apiExplorer#/?devType=org&api=dingtalk.oapi.message.corpconversation.asyncsend_v2
dingtalk/message.go
package dingtalk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
//提供发送钉钉消息相关接口
const corpMessageTypeKey = "msgtype"
const (
corpMessageTypeText = "text"
corpMessageTypeLink = "link"
corpMessageTypeActionCard = "action_card"
corpMessageTypeMarkdown = "markdown"
)
// 消息通知类型和数据格式
type corpMessageTemplate interface {
msg() map[string]interface{}
}
// CorpMessageText 文本消息(text)
type CorpMessageText struct {
Content string `json:"content"` // 消息内容,建议500字符以内
}
func (c CorpMessageText) msg() map[string]interface{} {
return map[string]interface{}{
corpMessageTypeKey: corpMessageTypeText,
corpMessageTypeText: c,
}
}
// CorpMessageLink 链接消息
type CorpMessageLink struct {
MessageUrl string `json:"messageUrl"`
PicUrl string `json:"picUrl"`
Title string `json:"title"`
Text string `json:"text"`
}
func (c CorpMessageLink) msg() map[string]interface{} {
return map[string]interface{}{
corpMessageTypeKey: corpMessageTypeLink,
corpMessageTypeLink: c,
}
}
// CorpMessageActionCard 卡片消息
// 整体跳转ActionCard样式,支持一个点击Action,必须传入参数 single_title和 single_url
type CorpMessageActionCard struct {
Title string `json:"title"`
Markdown string `json:"markdown"`
SingleTitle string `json:"single_title"`
SingleUrl string `json:"single_url"`
}
func (c CorpMessageActionCard) msg() map[string]interface{} {
return map[string]interface{}{
corpMessageTypeKey: corpMessageTypeActionCard,
corpMessageTypeActionCard: c,
}
}
// CorpMessageMarkdown markdown消息
type CorpMessageMarkdown struct {
Title string `json:"title"` // 首屏会话透出的展示内容。
Text string `json:"text"` // markdown格式的消息,最大不超过5000字符
}
func (c CorpMessageMarkdown) msg() map[string]interface{} {
return map[string]interface{}{
corpMessageTypeKey: corpMessageTypeMarkdown,
corpMessageTypeMarkdown: c,
}
}
// SendCorpMessage https://open.dingtalk.com/document/isvapp-server/asynchronous-sending-of-enterprise-session-messages
// SendCorpMessage 钉钉发送工作通知
func SendCorpMessage(ctx context.Context, userList []string, msg corpMessageTemplate) error {
// getAppAtk 获取企业内部应用的access_token
atk, err := getAppAtk(ctx)
if err != nil {
return gerrors.Wrap(err, "SendCorpMessage getAppAtk err")
}
// sendCorpMessage 钉钉发送工作通知
err = sendCorpMessage(ctx, atk, userList, false, msg)
if err != nil {
return gerrors.Wrap(err, "SendCorpMessage sendCorpMessage err")
}
return nil
}
type sendCorpMessageReq struct {
Msg map[string]interface{} `json:"msg"` // 消息内容,最长不超过2048个字节,支持以下工作通知类型:文本、图片、语音、文件、链接、OA、Markdown、卡片。文档:https://open.dingtalk.com/document/orgapp/message-types-and-data-format
ToAllUser bool `json:"to_all_user"` // 是否发送给企业全部用户
AgentId string `json:"agent_id"` // 发送消息时使用的微应用的AgentID
DeptIdList string `json:"dept_id_list,omitempty"` // 接收者的部门id列表,最大列表长度20。接收者是部门ID时,包括子部门下的所有用户。
UseridList string `json:"userid_list"` // 接收者的userid列表,最大用户列表长度100
}
type sendCorpMessageResp struct {
Errcode int `json:"errcode"` // 返回码
Errmsg string `json:"errmsg"` // 如果接口发送成功,接收人没有收到信息,可调用获取工作通知消息的发送结果查询结果,并对比文档中的返回错误码。文档:https://open.dingtalk.com/document/orgapp/gets-the-result-of-sending-messages-asynchronously-to-the-enterprise
TaskID int `json:"task_id"` // 创建的异步发送任务ID
RequestId string `json:"request_id"` // 请求ID
}
// 请求地址
const sendCorpMessageUrl = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"
// sendCorpMessage 钉钉发送工作通知
func sendCorpMessage(ctx context.Context, atk string, userList []string, toAll bool, msg corpMessageTemplate) error {
if len(userList) <= 0 {
return nil
}
query := url.Values{}
query.Add("access_token", atk)
queryUrl := fmt.Sprintf("%s?%s", sendCorpMessageUrl, query.Encode())
dataMsgField := msg.msg()
bodyContent := sendCorpMessageReq{
Msg: dataMsgField,
ToAllUser: toAll,
AgentId: config.GlobConfig.DingTalk.AgentID,
DeptIdList: "",
UseridList: strings.Join(userList, constants.Comma),
}
body, err := json.Marshal(bodyContent)
if err != nil {
return gerrors.Wrap(err, "sendCorpMessage Marshal err")
}
code, resp, err := gutil.HttpPostJson(queryUrl, body, nil)
if err != nil {
return gerrors.Wrap(err, "sendCorpMessage http err")
}
if code != http.StatusOK {
return fmt.Errorf("sendCorpMessage HttpGet code: %v, resp: %v", code, resp)
}
res := &sendCorpMessageResp{}
if err = json.Unmarshal(resp, res); err != nil {
return gerrors.Wrap(err, "sendCorpMessage Unmarshal err")
}
if res.Errcode != 0 {
return fmt.Errorf("sendCorpMessage res.Errcode: %d, res.ErrMsg: %s", res.Errcode, res.Errmsg)
}
return nil
}
dingtalk/dingtalk.go
获取企业内部应用的access_token
// Package dingtalk
// 维护钉钉企业内应用的 atk 以及一些全局配置,提供钉钉自身相关业务接口
package dingtalk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
//获取企业内应用 atk
const getAppAtkUrl = "https://oapi.dingtalk.com/gettoken"
type getAppAtkResp struct {
Errcode int64 `json:"errcode"`
AccessToken string `json:"access_token"`
Errmsg string `json:"errmsg"`
ExpiresIn int64 `json:"expires_in"`
}
func getAppAtk(ctx context.Context) (string, error) {
appKey := config.GlobConfig.DingTalk.AppKey
appSecret := config.GlobConfig.DingTalk.AppSecret
appAgentID := config.GlobConfig.DingTalk.AgentID
//先从缓存中获取
//todo 完善缓存机制
atk, err := gredis.Redis(constants.RedisSentinelName).Get(ctx,
fmt.Sprintf("%s%s", constants.RedisUserConsoleAtk, appAgentID))
if err != nil {
if err != gredis.ErrNotFound {
return "", gerrors.Wrap(err, "getAppAtk get redis atk err")
}
}
if atk != constants.EmptyString {
return atk, nil
}
//获取内部应用 atk
query := url.Values{}
query.Add("appkey", appKey)
query.Add("appsecret", appSecret)
queryUrl := fmt.Sprintf("%s?%s", getAppAtkUrl, query.Encode())
code, resp, err := gutil.HttpGet(queryUrl, nil, nil)
if err != nil {
return "", gerrors.Wrap(err, "getAppAtk err")
}
if code != http.StatusOK {
return "", fmt.Errorf("getAppAtk HttpGet code: %v, resp: %v", code, resp)
}
res := &getAppAtkResp{}
if err = json.Unmarshal(resp, res); err != nil {
return "", gerrors.Wrap(err, "getAppAtk Unmarshal err")
}
if res.Errcode != 0 {
return "", fmt.Errorf("getAppAtk res code not 0 ")
}
gredis.Redis(constants.RedisSentinelName).Set(ctx,
fmt.Sprintf("%s%s", constants.RedisUserConsoleAtk, appAgentID),
res.AccessToken,
time.Duration(res.ExpiresIn-60)*time.Second)
return res.AccessToken, nil
}