golang實(shí)現(xiàn)釘釘發(fā)送工作消息通知

  1. 發(fā)送工作通知-開放平臺(tái):https://open.dingtalk.com/document/isvapp/asynchronous-sending-of-enterprise-session-messages
  2. 消息通知類型-開放平臺(tái):https://open.dingtalk.com/document/orgapp/message-types-and-data-format#title-x16-76n-jpg
  3. 調(diào)用釘釘服務(wù)端API發(fā)送工作通知消息-csdn:https://blog.csdn.net/langzitianya/article/details/104200032
  4. 在線調(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腾夯,隨后出現(xiàn)的幾起案子颊埃,更是在濱河造成了極大的恐慌,老刑警劉巖蝶俱,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件班利,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡榨呆,警方通過查閱死者的電腦和手機(jī)罗标,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)积蜻,“玉大人闯割,你說我怎么就攤上這事「筒穑” “怎么了宙拉?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)丙笋。 經(jīng)常有香客問我谢澈,道長(zhǎng)煌贴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任澳化,我火速辦了婚禮崔步,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缎谷。我一直安慰自己井濒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布列林。 她就那樣靜靜地躺著瑞你,像睡著了一般。 火紅的嫁衣襯著肌膚如雪希痴。 梳的紋絲不亂的頭發(fā)上者甲,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音砌创,去河邊找鬼虏缸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嫩实,可吹牛的內(nèi)容都是我干的刽辙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼甲献,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宰缤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晃洒,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慨灭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后球及,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氧骤,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年吃引,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筹陵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡际歼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姑蓝,到底是詐尸還是另有隱情鹅心,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布纺荧,位于F島的核電站旭愧,受9級(jí)特大地震影響颅筋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜输枯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一议泵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桃熄,春花似錦先口、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至螟深,卻和暖如春谐宙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背界弧。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工凡蜻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垢箕。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓划栓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親舰讹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茅姜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容