美文网首页石臼墙话板河南科技学院三月听我说
golang实现钉钉发送工作消息通知

golang实现钉钉发送工作消息通知

作者: 胡小毛 | 来源:发表于2023-03-08 16:42 被阅读0次
    1. 发送工作通知-开放平台:https://open.dingtalk.com/document/isvapp/asynchronous-sending-of-enterprise-session-messages
    2. 消息通知类型-开放平台:https://open.dingtalk.com/document/orgapp/message-types-and-data-format#title-x16-76n-jpg
    3. 调用钉钉服务端API发送工作通知消息-csdn:https://blog.csdn.net/langzitianya/article/details/104200032
    4. 在线调试工具: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
    }
    

    附: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

    相关文章

      网友评论

        本文标题:golang实现钉钉发送工作消息通知

        本文链接:https://www.haomeiwen.com/subject/htgrldtx.html