WinForm开发钉钉(1) 调用机器人发送消息到钉钉群

时间:2022-08-16 08:48:21

此文章借鉴钉钉系列教程http://blog.****.net/wxbluethink/article/details/77435242,增加自己的理解,记录钉钉通过调用机器人发送消息到钉钉群。

环境:VS2015    WinForm  控制台程序

1、首先在钉钉后端添加机器人,具体步骤参考钉钉开发文档:

https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.ABt1ET&treeId=257&articleId=105735&docType=1

2、设置好之后,在后台写代码(源码从调用到最基础的工具类的顺序呈现):

1>、先看调用方法:

namespace MyDDTest
{
    class Program
    {
        static void Main ( string [ ] args )
        {
            ApiTool . SendTextMsg ("其他测试" ,Microapplication .Other. ToString ( ) );
        }
    }
}

2>、发送消息的方法体如下:

        /// <summary>
        /// 通过机器人发送公告到钉钉群
        /// </summary>
        /// <param name="Content">发送的消息内容</param>
        /// <param name="AgentID">微应用</param>
        /// <returns>发送JSON格式</returns>
        public static SendMessageResult SendTextMsg (string Content,string AgentID )
        {
            var txtmsg = new
            {
                //开发者ID
                touser = ConfigHelper.FetchDeveloperID(),
                //发送消息类型
                msgtype = MsgType . text . ToString ( ),
                //微应用ID
                agentid = ConfigHelper.FetchAgentID(AgentID),
                //消息内容
                text = new
                {
                    content = Content
                }
            };
            //发送消息的URL
            string apiurl = FormatApiUrlWithToken ( Urls . message_send );
            //把上面的内容转成JSON格式
            string json = JsonConvert . SerializeObject ( txtmsg );
            //以POST请求的方式发送消息
            var result = Analyze . POST<SendMessageResult> ( apiurl ,json );

            //发送消息
            return result;
        }
3>、<1>中调用方法的参数来源( Microapplication .Other. ToString ())
namespace MyDDTest
{
    /// <summary>
    /// 钉钉微应用列举,代表钉钉后台所有的微应用
    /// </summary>
    public enum Microapplication
    {
        /// <summary>
        /// 签到
        /// </summary>
        Sign,
        /// <summary>
        /// 考勤打卡
        /// </summary>
        CkeckWork,
        /// <summary>
        /// 日志
        /// </summary>
        Log,
        /// <summary>
        /// 公告
        /// </summary>
        Notice,
        /// <summary>
        /// 审批
        /// </summary>
        Approval,
        /// <summary>
        /// 钉邮
        /// </summary>
        Mail,
        /// <summary>
        /// 钉盘
        /// </summary>
        Disc,
        /// <summary>
        /// 智能报表
        /// </summary>
        Report,
        /// <summary>
        /// 电话会议
        /// </summary>
        Teleconferencing,
        /// <summary>
        /// 视频会议
        /// </summary>
        Videoconferencing,
        /// <summary>
        /// 客户管理
        /// </summary>
        CustomerManager,
        /// <summary>
        /// 办公电话
        /// </summary>
        OfficePhone,
        /// <summary>
        /// 钉钉运动
        /// </summary>
        Motion,
        /// <summary>
        /// 企业主页
        /// </summary>
        Homepage,
        /// <summary>
        /// 其他测试
        /// </summary>
        Other
    }
}

4>、获取开发者ID类,类名ConfigHelper.cs,内容从app.config中读取:

using System;
using System . Configuration;

namespace MyDDTest
{
    public class ConfigHelper
    {
        /// <summary>
        /// 得到CorpId
        /// </summary>
        /// <returns></returns>
        public static string FetchCorpID ( )
        {
            return FetchValue ( Keys . corpid );
        }

        /// <summary>
        /// 得到corpSecret
        /// </summary>
        /// <returns></returns>
        public static string FetchCorpSecret ( )
        {
            return FetchValue ( Keys . corpsecret );
        }

        /// <summary>
        /// 得到AgentID
        /// </summary>
        /// <param name="names"></param>
        /// <returns></returns>
        public static string FetchAgentID ( string names )
        {
            return FetchValue ( names );
        }

        /// <summary>
        /// 得到开发者ID,必须是钉钉管理员
        /// </summary>
        /// <returns></returns>
        public static string FetchDeveloperID ( )
        {
            return FetchValue ( "DevelopmentID" );
        }

        /// <summary>
        /// 根据key获取config中的value
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string FetchValue ( string key )
        {
            string value = ConfigurationManager . AppSettings [ key ];
            if ( value == null )
            {
                throw new Exception ( $"{key} is null.请确认配置文件中是否已配置." );
            }

            return value;
        }

    }
}

5>、app.config中参数,key对应<3>中的列举:

      <appSettings>
    <add key="corpid" value="ding%¥@#¥%%……&&**()()*&……&………………" />
    <add key="corpsecret" value="Oa@#@¥%%¥……&&*&……%¥¥#@##¥¥%%" />
    <add key="Sign" value="1111111" />
    <add key="CkeckWork" value="111111" />
    <add key="Log" value="111111" />
    <add key="Notice" value="111111" />
    <add key="Approval" value="1111111" />
    <add key="Mail" value="111111" />
    <add key="Disc" value="111111" />
    <add key="Report" value="111111" />
    <add key="Teleconferencing" value="111111" />
    <add key="Videoconferencing" value="111111" />
    <add key="CustomerManager" value="111111" />
    <add key="OfficePhone" value="11111111" />
    <add key="Motion" value="1111111" />
    <add key="Homepage" value="11111111" />
    <add key="DevelopmentID" value="11111111111111111111" />
    <add key="Other" value="111111111111" />
  </appSettings>

6>、消息类型,可以参考钉钉开发文档

https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.o5zZ8p&treeId=374&articleId=104972&docType=1

以下通过枚举呈现:

namespace MyDDTest
{
    public enum MsgType
    {
        text,
        actionCard,
        image,
        voice,
        file,
        link,
        OA,
        markdown
    }
}

7>、获取发送消息的url请求地址

        /// <summary>
        /// 获取发送消息的url
        /// </summary>
        /// <param name="Url">url</param>
        /// <param name="forceUpdate"></param>
        /// <returns></returns>
        public static string FormatApiUrlWithToken ( string Url ,bool forceUpdate = false )
        {
            //获取token
            UpdateAccessToken ( forceUpdate );
            string apiurl = $"{Url}?{Keys . access_token}={AccessToken . Value}";

            return apiurl;
        }

8>、获取Token

        /// <summary>
        /// 接受Token
        /// </summary>
        /// <param name="forced">true:强制更新.false:按缓存是否到期来更新</param>
        public static void UpdateAccessToken ( bool forced = false )
        {
            //ConstVars.CACHE_TIME是缓存时间(常量,也可放到配置文件中),这样在有效期内则直接从缓存中获取票据,不需要再向服务器中获取。
            if ( !forced && AccessToken . Begin . AddSeconds ( ConstVars . CACHE_TIME ) >= DateTime . Now )
            {
                return;
            }

            string CorpID = ConfigHelper . FetchCorpID ( );
            string CorpSecret = ConfigHelper . FetchCorpSecret ( );
            string TokenUrl = Urls . gettoken;
            string apiurl = $"{TokenUrl}?{Keys . corpid}={CorpID}&{Keys . corpsecret}={CorpSecret}";
            TokenResult tokenResult = Analyze . Get<TokenResult> ( apiurl );           

            if ( tokenResult . ErrCode == ErrCodeEnum . OK )
            {
                AccessToken . Value = tokenResult . Access_token;
                AccessToken . Begin = DateTime . Now;
            }
        }

9>、发送消息的URL来源

namespace MyDDTest
{
    /// <summary>
    /// SDK使用的URL  
    /// </summary>
    public sealed class Urls
    {
        /// <summary>
        /// 创建会话
        /// </summary>
        public const string chat_create="https://oapi.dingtalk.com/chat/create";

        /// <summary>
        /// 获取会话信息
        /// </summary>
        public const string chat_get="https://oapi.dingtalk.com/chat/get";

        /// <summary>
        /// 发送会话消息
        /// </summary>
        public const string chat_send="https://oapi.dingtalk.com/chat/send";

        /// <summary>
        /// 更新会话消息
        /// </summary>
        public const string chat_update="https://oapi.dingtalk.com/chat/update";

        /// <summary>
        /// 获取部门列表
        /// </summary>
        public const string department_list="https://oapi.dingtalk.com/department/list";

        /// <summary>
        /// 获取访问票记
        /// </summary>
        public const string gettoken="https://oapi.dingtalk.com/gettoken";

        /// <summary>
        /// 发送消息
        /// </summary>
        public const string message_send="https://oapi.dingtalk.com/message/send";

        /// <summary>
        /// 用户列表
        /// </summary>
        public const string user_list="https://oapi.dingtalk.com/user/list";

        /// <summary>
        /// 用户详情
        /// </summary>
        public const string user_get="https://oapi.dingtalk.com/user/get";

        /// <summary>
        /// 获取JSAPI的票据
        /// </summary>
        public const string get_jsapi_ticket="https://oapi.dingtalk.com/get_jsapi_ticket";

        /// <summary>
        /// 发起审批
        /// </summary>
        public const string get_Examination_and_approval="https://eco.taobao.com/router/rest";

    }
}
10>、<2>中的
var result = Analyze . POST<SendMessageResult> ( apiurl ,json );

请求方式来源:

Analyze:请求类

using Newtonsoft . Json;
using System . Collections . Generic;

namespace MyDDTest
{
    public class Analyze
    {
        /// <summary>
        /// GET请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="requestUrl"></param>
        /// <returns></returns>
        public static T Get<T> ( string requestUrl ) where T : ResultPackage, new()
        {
            string resultJson = RequestHelper . Get ( requestUrl );
            return AnalyzeResult<T> ( resultJson );
        }

        /// <summary>
        /// POST请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="requestUrl"></param>
        /// <param name="requestParamOfJsonStr"></param>
        /// <returns></returns>
        public static T POST<T> ( string requestUrl ,string requestParamOfJsonStr ) where T : ResultPackage, new()
        {
            string resultJson = RequestHelper . Post ( requestUrl ,requestParamOfJsonStr );
            return AnalyzeResult<T> ( resultJson );
        }

        /// <summary>
        /// 分析结果
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="resultJson"></param>
        /// <returns></returns>
        public static T AnalyzeResult<T> ( string resultJson ) where T : ResultPackage, new()
        {
            ResultPackage tempResult = null;
            if ( !string . IsNullOrEmpty ( resultJson ) )
            {
                tempResult = JsonConvert . DeserializeObject<ResultPackage> ( resultJson );
            }
            T result = null;
            if ( tempResult != null && tempResult . IsOK ( ) )
            {
                result = JsonConvert . DeserializeObject<T> ( resultJson );
            }
            else if ( tempResult != null )
            {
                result = tempResult as T;
            }
            else if ( tempResult == null )
            {
                result = new T ( );
            }

            result . Json = resultJson;
            return result;
        }
    }
}
RequestHelper:请求帮助类
using System . Collections . Generic;
using System . IO;
using System . Net;
using System . Text;

namespace MyDDTest
{
    public class RequestHelper
    {
        /// <summary>
        /// 执行基本的命令方法,以GET方法
        /// </summary>
        /// <param name="apiurl"></param>
        /// <returns></returns>
        public static string Get ( string apiurl )
        {
            WebRequest request = WebRequest . Create ( @apiurl );
            request . Method = "GET";
            WebResponse response = request . GetResponse ( );
            Stream stream = response . GetResponseStream ( );
            Encoding encode = Encoding . UTF8;
            StreamReader reader = new StreamReader ( stream ,encode );
            string resultJson = reader . ReadToEnd ( );
            return resultJson;
        }
        
        /// <summary>
        /// 以post方式提交命令
        /// </summary>
        /// <param name="apiurl"></param>
        /// <param name="jsonString"></param>
        /// <returns></returns>
        public static string Post ( string apiurl ,string jsonString )
        {
            WebRequest requset = WebRequest . Create ( @apiurl );
            requset . Method = "POST";
            requset . ContentType = "application/json";

            byte [ ] bs = Encoding . UTF8 . GetBytes ( jsonString );
            requset . ContentLength = bs . Length;
            Stream newStream = requset . GetRequestStream ( );
            newStream . Write ( bs ,0 ,bs . Length );
            newStream . Close ( );

            WebResponse response = requset . GetResponse ( );
            Stream stream = response . GetResponseStream ( );
            Encoding encode = Encoding . UTF8;
            StreamReader reader = new StreamReader ( stream ,encode );
            string resultJson = reader . ReadToEnd ( );
            return resultJson;
        }
    }
}
SendMessageResult:请求结果类
namespace MyDDTest
{
    public class SendMessageResult:ResultPackage
    {
        public string receiver
        {
            get; set;
        }
    }
}
ResultPackage:请求结果类
namespace MyDDTest
{
    public class ResultPackage
    {
        public ErrCodeEnum ErrCode
        {
            get; set;
        } = ErrCodeEnum . Unknown;

        /// <summary>
        /// 错误消息
        /// </summary>
        public string ErrMsg
        {
            get;set;
        }

        /// <summary>
        /// 结果的Json形式
        /// </summary>
        public string Json
        {
            get;
            set;
        }

        public bool IsOK ( )
        {
            return ErrCode == ErrCodeEnum . OK;
        }

        public override string ToString ( )
        {
            string info = $"{nameof ( ErrCode )}:{ErrCode},{nameof ( ErrMsg )}:{ErrMsg}";

            return info;
        }

    }
}

11>、<8>中的TokenResult

namespace MyDDTest
{
    public class TokenResult:ResultPackage
    {
        public string Access_token
        {
            get; set;
        }
    }
}

12>、<8>中的AccessToken

        /// <summary>
        /// 创建静态字段,保证全局一致
        /// </summary>
        public static AccessToken AccessToken=new AccessToken();
13>、<12>中的
AccessToken 
using System;

namespace MyDDTest
{
    /// <summary>
    /// 访问票据
    /// </summary>
    public class AccessToken
    {
        /// <summary>
        /// 票据的值
        /// </summary>
        public string Value
        {
            get;set;
        }

        /// <summary>
        /// 票据的开始时间
        /// </summary>
        public DateTime Begin
        {
            get;
            set;
        } = DateTime . Parse ( "1970-01-01" );
    }
}

14>、<8>中的缓存

namespace MyDDTest
{
    public class ConstVars
    {
        /// <summary>
        ///  缓存的JS票据的KEY  
        /// </summary>
        public const string CACHE_JS_TICKET_KEY = "CACHE_JS_TICKET_KEY";
        
        /// <summary>
        /// 缓存时间
        /// </summary>
        public const int  CACHE_TIME = 5000;

    }
}

15>、<8>中的请求错误码ErrCodeEnum . OK

namespace MyDDTest
{
    /// <summary>
    /// 请求返回的错误码
    /// </summary>
    public enum ErrCodeEnum
    {
       OK=0,
       
       VolidAccessToken=40014,

       /// <summary>
       /// 未知
       /// </summary>
       Unknown=int.MaxValue

    }
}
到此为止,所有的内容都已经发完,如果有不明白的地方可以留言,如果有错误的地方也请指出,谢谢。