- 發(fā)送工作通知-開放平臺(tái):https://open.dingtalk.com/document/isvapp/asynchronous-sending-of-enterprise-session-messages
- 消息通知類型-開放平臺(tái):https://open.dingtalk.com/document/orgapp/message-types-and-data-format#title-x16-76n-jpg
- 調(diào)用釘釘服務(wù)端API發(fā)送工作通知消息-csdn:https://blog.csdn.net/langzitianya/article/details/104200032
- 在線調(diào)試工具: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"
)
//提供發(fā)送釘釘消息相關(guān)接口
const corpMessageTypeKey = "msgtype"
const (
corpMessageTypeText = "text"
corpMessageTypeLink = "link"
corpMessageTypeActionCard = "action_card"
corpMessageTypeMarkdown = "markdown"
)
// 消息通知類型和數(shù)據(jù)格式
type corpMessageTemplate interface {
msg() map[string]interface{}
}
// CorpMessageText 文本消息(text)
type CorpMessageText struct {
Content string `json:"content"` // 消息內(nèi)容,建議500字符以內(nèi)
}
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 卡片消息
// 整體跳轉(zhuǎn)ActionCard樣式撩幽,支持一個(gè)點(diǎn)擊Action库继,必須傳入?yún)?shù) 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"` // 首屏?xí)捦赋龅恼故緝?nèi)容箩艺。
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 釘釘發(fā)送工作通知
func SendCorpMessage(ctx context.Context, userList []string, msg corpMessageTemplate) error {
// getAppAtk 獲取企業(yè)內(nèi)部應(yīng)用的access_token
atk, err := getAppAtk(ctx)
if err != nil {
return gerrors.Wrap(err, "SendCorpMessage getAppAtk err")
}
// sendCorpMessage 釘釘發(fā)送工作通知
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"` // 消息內(nèi)容宪萄,最長(zhǎng)不超過2048個(gè)字節(jié)艺谆,支持以下工作通知類型:文本、圖片拜英、語(yǔ)音静汤、文件、鏈接居凶、OA虫给、Markdown、卡片侠碧。文檔:https://open.dingtalk.com/document/orgapp/message-types-and-data-format
ToAllUser bool `json:"to_all_user"` // 是否發(fā)送給企業(yè)全部用戶
AgentId string `json:"agent_id"` // 發(fā)送消息時(shí)使用的微應(yīng)用的AgentID
DeptIdList string `json:"dept_id_list,omitempty"` // 接收者的部門id列表抹估,最大列表長(zhǎng)度20。接收者是部門ID時(shí)舆床,包括子部門下的所有用戶棋蚌。
UseridList string `json:"userid_list"` // 接收者的userid列表,最大用戶列表長(zhǎng)度100
}
type sendCorpMessageResp struct {
Errcode int `json:"errcode"` // 返回碼
Errmsg string `json:"errmsg"` // 如果接口發(fā)送成功挨队,接收人沒有收到信息谷暮,可調(diào)用獲取工作通知消息的發(fā)送結(jié)果查詢結(jié)果,并對(duì)比文檔中的返回錯(cuò)誤碼盛垦。文檔:https://open.dingtalk.com/document/orgapp/gets-the-result-of-sending-messages-asynchronously-to-the-enterprise
TaskID int `json:"task_id"` // 創(chuàng)建的異步發(fā)送任務(wù)ID
RequestId string `json:"request_id"` // 請(qǐng)求ID
}
// 請(qǐng)求地址
const sendCorpMessageUrl = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"
// sendCorpMessage 釘釘發(fā)送工作通知
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
獲取企業(yè)內(nèi)部應(yīng)用的access_token
// Package dingtalk
// 維護(hù)釘釘企業(yè)內(nèi)應(yīng)用的 atk 以及一些全局配置湿弦,提供釘釘自身相關(guān)業(yè)務(wù)接口
package dingtalk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
//獲取企業(yè)內(nèi)應(yīng)用 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 完善緩存機(jī)制
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
}
//獲取內(nèi)部應(yīng)用 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
}
附:https://github.com/mao888/golang-guide/blob/main/project/%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%A1%88%E5%8F%8A%E8%B0%83%E7%A0%94/%E9%92%89%E9%92%89%E5%8F%91%E9%80%81%E5%B7%A5%E4%BD%9C%E6%B6%88%E6%81%AF%E9%80%9A%E7%9F%A5.md