1. 程式人生 > 其它 >使用asp.net開發釘釘群機器人全過程

使用asp.net開發釘釘群機器人全過程

集團是使用釘釘進行工作交流的, 發現群裡有很多問題其實是重複的,就在想是不是可以使用釘釘的群機器人,雖然說的確是可以部分實現,但是感覺還是差點什麼,而且公司內部很多東西也不方便放上去,所以就想開發一個群機器人,然後就看釘釘開發文件,發現是有這個功能的,就開始研究,官方文件使用的語言主要是Java,並沒有c#或者asp.net的相關文件,這就意味著要從頭開始開發, 所幸的是他是有c#的SDK開發包,開發包裡是有DLL的,這樣能省下不少事,廢話不多說,上鍊接

https://open.dingtalk.com/document/resourcedownload/download-server-sdk

開啟頁面後往下拉,知道如圖所示處

我下載的是.net版本,下載下來後,匯入到專案中即可
然後是配置機器人,這些在往上教程很多就不多贅訴了,直接上圖

一開始我是在頁面上面寫的,看到官方文件上面說到了header,考慮到可能要使用到request 獲取,就直接在頁面寫了,

 

 

後來在頁面上通過以後改到了WebService中,畢竟感覺上webservice 會好一些,

 

 把訊息接收地址改成了這樣,其實兩者程式碼類似,只是我可能更喜歡在接口裡寫

 1  protected string secret = 改成你自己的機器人的appSecret;
 2     #region 機器人操作類
 3     [WebMethod]
4 public void Reboot() 5 { 6 string result = ""; 7 using (StreamReader reader = new StreamReader(HttpContext.Current.Request.InputStream, Encoding.UTF8)) 8 { 9 result = reader.ReadToEnd(); 10 } 11 try 12 { 13 string
sign = HttpContext.Current.Request.Headers.GetValues("sign")[0].ToString(); 14 string timestamp = HttpContext.Current.Request.Headers.GetValues("timestamp")[0].ToString(); 15 string json = result; 16 CommonJsonModel model = SymmetricMethod.DeSerialize(json); 17 string text = model.GetModel("text").GetValue("content"); 18 string sessionWebhook = model.GetValue("sessionWebhook"); 19 string senderStaffId = model.GetValue("senderStaffId"); 20 DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "呼叫機器人", text + "--" + sessionWebhook + "--" + senderStaffId, sign + "----------" + timestamp, result, HttpContext.Current.Request.Headers, "呼叫機器人"); 21 } 22 catch (Exception ex) 23 { 24 DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "呼叫機器人", result, ex.Message, "介面呼叫來源不正確","", "呼叫機器人"); 25 } 26 } 27 #endregion

這是webservice 介面的

 1   string result = "";
 2         using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
 3         {
 4             result = reader.ReadToEnd();
 5         }
 6         try
 7         {
 8             string sign = Request.Headers.GetValues("sign")[0].ToString();
 9             string timestamp = Request.Headers.GetValues("timestamp")[0].ToString();
10             string json = result;
11             CommonJsonModel model = SymmetricMethod.DeSerialize(json);
12             string text = model.GetModel("text").GetValue("content");
13             string sessionWebhook = model.GetValue("sessionWebhook");
14             string senderStaffId = model.GetValue("senderStaffId");
15             DBHelper.InsertRebootLog(Request, Request.Url.ToString(), "呼叫機器人", text + "--" + sessionWebhook + "--" + senderStaffId, sign + "----------" + timestamp, result, Request.Headers, "呼叫機器人");
16         }
17         catch (Exception ex)
18         {
19             DBHelper.InsertRebootLog(Request, Request.Url.ToString(), "呼叫機器人", result, ex.Message, "", "", "呼叫機器人");
20         }

這是寫在頁面Page_Load方法裡面的,因為只要執行到這個頁面,就是直接執行,沒有任何其他操作,所以一定要寫在Page_Load方法裡

那麼json 解析的原始碼我也放後面,也就是 CommonJsonModel 這個方法的程式碼

直接建兩個類,名字分別是CommonJsonModelAnalyzer 和 CommonJsonModel 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Web;
  4 using System.Text;
  5 
  6 /// <summary>
  7 ///CommonJsonModelAnalyzer 的摘要說明
  8 /// </summary>
  9 public class CommonJsonModelAnalyzer
 10 {
 11     public CommonJsonModelAnalyzer()
 12     {
 13         //
 14         //TODO: 在此處新增建構函式邏輯
 15         //
 16 
 17     }
 18     protected string _GetKey(string rawjson)
 19     {
 20         if (string.IsNullOrEmpty(rawjson))
 21             return rawjson;
 22 
 23         rawjson = rawjson.Trim();
 24 
 25         string[] jsons = rawjson.Split(new char[] { ':' });
 26 
 27         if (jsons.Length < 2)
 28             return rawjson;
 29 
 30         return jsons[0].Replace("\"", "").Trim();
 31     }
 32 
 33     protected string _GetValue(string rawjson)
 34     {
 35         if (string.IsNullOrEmpty(rawjson))
 36             return rawjson;
 37 
 38         rawjson = rawjson.Trim();
 39 
 40         string[] jsons = rawjson.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
 41 
 42         if (jsons.Length < 2)
 43             return rawjson;
 44 
 45         StringBuilder builder = new StringBuilder();
 46 
 47         for (int i = 1; i < jsons.Length; i++)
 48         {
 49             builder.Append(jsons[i]);
 50 
 51             builder.Append(":");
 52         }
 53 
 54         if (builder.Length > 0)
 55             builder.Remove(builder.Length - 1, 1);
 56 
 57         string value = builder.ToString();
 58 
 59         if (value.StartsWith("\""))
 60             value = value.Substring(1);
 61 
 62         if (value.EndsWith("\""))
 63             value = value.Substring(0, value.Length - 1);
 64 
 65         return value;
 66     }
 67 
 68     protected List<string> _GetCollection(string rawjson)
 69     {
 70         //[{},{}]
 71 
 72         List<string> list = new List<string>();
 73 
 74         if (string.IsNullOrEmpty(rawjson))
 75             return list;
 76 
 77         rawjson = rawjson.Trim();
 78 
 79         StringBuilder builder = new StringBuilder();
 80 
 81         int nestlevel = -1;
 82 
 83         int mnestlevel = -1;
 84 
 85         for (int i = 0; i < rawjson.Length; i++)
 86         {
 87             if (i == 0)
 88                 continue;
 89             else if (i == rawjson.Length - 1)
 90                 continue;
 91 
 92             char jsonchar = rawjson[i];
 93 
 94             if (jsonchar == '{')
 95             {
 96                 nestlevel++;
 97             }
 98 
 99             if (jsonchar == '}')
100             {
101                 nestlevel--;
102             }
103 
104             if (jsonchar == '[')
105             {
106                 mnestlevel++;
107             }
108 
109             if (jsonchar == ']')
110             {
111                 mnestlevel--;
112             }
113 
114             if (jsonchar == ',' && nestlevel == -1 && mnestlevel == -1)
115             {
116                 list.Add(builder.ToString());
117 
118                 builder = new StringBuilder();
119             }
120             else
121             {
122                 builder.Append(jsonchar);
123             }
124         }
125 
126         if (builder.Length > 0)
127             list.Add(builder.ToString());
128 
129         return list;
130     }
131 }
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Web;
  4 
  5 /// <summary>
  6 ///CommonJsonModel 的摘要說明
  7 /// </summary>
  8 public class CommonJsonModel : CommonJsonModelAnalyzer
  9 {
 10     private string rawjson;
 11 
 12     private bool isValue = false;
 13 
 14     private bool isModel = false;
 15 
 16     private bool isCollection = false;
 17     private string json;
 18 
 19    internal CommonJsonModel(string rawjson)
 20     {
 21         this.rawjson = rawjson;
 22 
 23         if (string.IsNullOrEmpty(rawjson))
 24             throw new Exception("missing rawjson");
 25 
 26         rawjson = rawjson.Trim();
 27 
 28         if (rawjson.StartsWith("{"))
 29         {
 30             isModel = true;
 31         }
 32         else if (rawjson.StartsWith("["))
 33         {
 34             isCollection = true;
 35         }
 36         else
 37         {
 38             isValue = true;
 39         }
 40     }
 41 
 42     public string Rawjson
 43     {
 44         get { return rawjson; }
 45     }
 46 
 47     public bool IsValue()
 48     {
 49         return isValue;
 50     }
 51     public bool IsValue(string key)
 52     {
 53         if (!isModel)
 54             return false;
 55 
 56         if (string.IsNullOrEmpty(key))
 57             return false;
 58 
 59         foreach (string subjson in base._GetCollection(this.rawjson))
 60         {
 61             CommonJsonModel model = new CommonJsonModel(subjson);
 62 
 63             if (!model.IsValue())
 64                 continue;
 65 
 66             if (model.Key == key)
 67             {
 68                 CommonJsonModel submodel = new CommonJsonModel(model.Value);
 69 
 70                 return submodel.IsValue();
 71             }
 72         }
 73 
 74         return false;
 75     }
 76     public bool IsModel()
 77     {
 78         return isModel;
 79     }
 80     public bool IsModel(string key)
 81     {
 82         if (!isModel)
 83             return false;
 84 
 85         if (string.IsNullOrEmpty(key))
 86             return false;
 87 
 88         foreach (string subjson in base._GetCollection(this.rawjson))
 89         {
 90             CommonJsonModel model = new CommonJsonModel(subjson);
 91 
 92             if (!model.IsValue())
 93                 continue;
 94 
 95             if (model.Key == key)
 96             {
 97                 CommonJsonModel submodel = new CommonJsonModel(model.Value);
 98 
 99                 return submodel.IsModel();
100             }
101         }
102 
103         return false;
104     }
105     public bool IsCollection()
106     {
107         return isCollection;
108     }
109     public bool IsCollection(string key)
110     {
111         if (!isModel)
112             return false;
113 
114         if (string.IsNullOrEmpty(key))
115             return false;
116 
117         foreach (string subjson in base._GetCollection(this.rawjson))
118         {
119             CommonJsonModel model = new CommonJsonModel(subjson);
120 
121             if (!model.IsValue())
122                 continue;
123 
124             if (model.Key == key)
125             {
126                 CommonJsonModel submodel = new CommonJsonModel(model.Value);
127 
128                 return submodel.IsCollection();
129             }
130         }
131 
132         return false;
133     }
134 
135 
136     /// <summary>
137     /// 當模型是物件,返回擁有的key
138     /// </summary>
139     /// <returns></returns>
140     public List<string> GetKeys()
141     {
142         if (!isModel)
143             return null;
144 
145         List<string> list = new List<string>();
146 
147         foreach (string subjson in base._GetCollection(this.rawjson))
148         {
149             string key = new CommonJsonModel(subjson).Key;
150 
151             if (!string.IsNullOrEmpty(key))
152                 list.Add(key);
153         }
154 
155         return list;
156     }
157 
158     /// <summary>
159     /// 當模型是物件,key對應是值,則返回key對應的值
160     /// </summary>
161     /// <param name="key"></param>
162     /// <returns></returns>
163     public string GetValue(string key)
164     {
165         if (!isModel)
166             return null;
167 
168         if (string.IsNullOrEmpty(key))
169             return null;
170 
171         foreach (string subjson in base._GetCollection(this.rawjson))
172         {
173             CommonJsonModel model = new CommonJsonModel(subjson);
174 
175             if (!model.IsValue())
176                 continue;
177 
178             if (model.Key == key)
179                 return model.Value;
180         }
181 
182         return null;
183     }
184 
185     /// <summary>
186     /// 模型是物件,key對應是物件,返回key對應的物件
187     /// </summary>
188     /// <param name="key"></param>
189     /// <returns></returns>
190     public CommonJsonModel GetModel(string key)
191     {
192         if (!isModel)
193             return null;
194 
195         if (string.IsNullOrEmpty(key))
196             return null;
197 
198         foreach (string subjson in base._GetCollection(this.rawjson))
199         {
200             CommonJsonModel model = new CommonJsonModel(subjson);
201 
202             if (!model.IsValue())
203                 continue;
204 
205             if (model.Key == key)
206             {
207                 CommonJsonModel submodel = new CommonJsonModel(model.Value);
208 
209                 if (!submodel.IsModel())
210                     return null;
211                 else
212                     return submodel;
213             }
214         }
215 
216         return null;
217     }
218 
219     /// <summary>
220     /// 模型是物件,key對應是集合,返回集合
221     /// </summary>
222     /// <param name="key"></param>
223     /// <returns></returns>
224     public CommonJsonModel GetCollection(string key)
225     {
226         if (!isModel)
227             return null;
228 
229         if (string.IsNullOrEmpty(key))
230             return null;
231 
232         foreach (string subjson in base._GetCollection(this.rawjson))
233         {
234             CommonJsonModel model = new CommonJsonModel(subjson);
235 
236             if (!model.IsValue())
237                 continue;
238 
239             if (model.Key == key)
240             {
241                 CommonJsonModel submodel = new CommonJsonModel(model.Value);
242 
243                 if (!submodel.IsCollection())
244                     return null;
245                 else
246                     return submodel;
247             }
248         }
249 
250         return null;
251     }
252 
253     /// <summary>
254     /// 模型是集合,返回自身
255     /// </summary>
256     /// <returns></returns>
257     public List<CommonJsonModel> GetCollection()
258     {
259         List<CommonJsonModel> list = new List<CommonJsonModel>();
260 
261         if (IsValue())
262             return list;
263 
264         foreach (string subjson in base._GetCollection(rawjson))
265         {
266             list.Add(new CommonJsonModel(subjson));
267         }
268 
269         return list;
270     }
271 
272 
273 
274 
275     /// <summary>
276     /// 當模型是值物件,返回key
277     /// </summary>
278     private string Key
279     {
280         get
281         {
282             if (IsValue())
283                 return base._GetKey(rawjson);
284 
285             return null;
286         }
287     }
288     /// <summary>
289     /// 當模型是值物件,返回value
290     /// </summary>
291     private string Value
292     {
293         get
294         {
295             if (!IsValue())
296                 return null;
297 
298             return base._GetValue(rawjson);
299         }
300     }
301 }

另外還要再建一個呼叫json解析方法的類 我的名稱叫做SymmetricMethod,你們就隨意起

在這個類裡面寫一個方法

1  public static CommonJsonModel DeSerialize(string json)
2     {
3         return new CommonJsonModel(json);
4     }

一定要靜態類,方便呼叫

其實到這一步一些關鍵內容的核心已經全部寫完了,接下來就是如何使用

按照官方文件的說法,是需要對資訊進行驗證的

開發者需對header中的timestamp和sign進行驗證,以判斷是否是來自釘釘的合法請求,避免其他仿冒釘釘呼叫開發者的HTTPS服務傳送資料,具體驗證邏輯如下:

timestamp 與系統當前時間戳如果相差1小時以上,則認為是非法的請求。

sign 與開發者自己計算的結果不一致,則認為是非法的請求。

必須當timestamp和sign同時驗證通過,才能認為是來自釘釘的合法請求。

其中會有sign 計算方法,那麼我們就按照文件說的做,

sign的計算方法

header中的timestamp + "\n" + 機器人的appSecret當做簽名字串,使用HmacSHA256演算法計算簽名,然後進行Base64 encode,得到最終的簽名值。 
 1   //獲得時間戳
 2     public static long ToUTC(DateTime time)
 3     {
 4         var zts = TimeZoneInfo.Local.BaseUtcOffset;
 5         var yc = new DateTime(1970, 1, 1).Add(zts);
 6         return (long)(DateTime.Now - yc).TotalMilliseconds;
 7     }
 8     //計算簽名值
 9     public static string GetHmac(string message, string secret)
10     {
11         byte[] keyByte = Encoding.UTF8.GetBytes(secret);
12         byte[] messageBytes = Encoding.UTF8.GetBytes(message);
13         using (var hmacsha256 = new HMACSHA256(keyByte))
14         {
15             byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
16             string hash = Convert.ToBase64String(hashmessage).Replace("+"," ");  
17             return hash;
18         }
19     }

以上兩段程式碼網上就能搜到,其中計算簽名值網上寫的並不完全,因為我們計算出來的簽名值與釘釘的實際簽名值就差一個“+”和“ ”,所以在最後直接替換就可以了 

 1 private bool GetSign(string timestamp, string secret, string sign)
 2     {
 3         try
 4         {
 5             //獲取當前時間的時間戳
 6             long currentTime = SymmetricMethod.ToUTC(DateTime.Now);
 7             long dingTimestamp = long.Parse(timestamp);
 8             long time = currentTime - dingTimestamp;
 9             string stringToSign = SymmetricMethod.GetHmac(dingTimestamp + "\n" + secret, secret).ToString();
10             if (time < 3600000 && sign.Equals(stringToSign))
11             {
12                 return true;
13             }
14             return false;
15         }
16         catch (Exception ex)
17         {
18             return false;
19         }
20     }

這樣我們就獲得了釘釘返回的sign 和timestamp 和我們自己計算出來的sign ,然後根據規則進行判斷即可

那麼最終合在一起形成這樣一段程式碼

 1   #region 機器人操作類
 2     [WebMethod]
 3     public void Reboot()
 4     {
 5         string result = "";
 6         using (StreamReader reader = new StreamReader(HttpContext.Current.Request.InputStream, Encoding.UTF8))
 7         {
 8             result = reader.ReadToEnd();
 9         }
10         try
11         {
12             string sign = HttpContext.Current.Request.Headers.GetValues("sign")[0].ToString();
13             string timestamp = HttpContext.Current.Request.Headers.GetValues("timestamp")[0].ToString();
14             string json = result;
15             CommonJsonModel model = SymmetricMethod.DeSerialize(json);
16             string text = model.GetModel("text").GetValue("content");
17             string sessionWebhook = model.GetValue("sessionWebhook");
18             string senderStaffId = model.GetValue("senderStaffId");
19             DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "呼叫機器人", text + "--" + sessionWebhook + "--" + senderStaffId, sign + "----------" + timestamp, result, HttpContext.Current.Request.Headers, "呼叫機器人");
20 
21             if (GetSign(timestamp, secret, sign))//驗證,如果不通過另行操作或者不返回都可以
22             {              
23                 DefaultDingTalkClient client = new DefaultDingTalkClient(sessionWebhook);
24                 text(client, userid, "返回文字測試效果");
25                 markdown(client, userid, "測試markdown", "返回markdown測試效果");
26                 actionCard(client, userid, "測試actionCard", "返回actionCard測試效果", "點選詳情", "https://www.taiwei6.com");
27             }
28         }
29         catch (Exception ex)
30         {
31             DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "呼叫機器人", result, ex.Message, "介面呼叫來源不正確","", "呼叫機器人");
32         }        
33     }

釘釘機器人總共是能夠範圍三種類型的分別是text ,markdown,actioncard ,

上原始碼

  1     /**
  2     * 實現@人員
  3     * @param client
  4     * @param userId
  5      * 返回文字
  6     */
  7     private void text(DefaultDingTalkClient client, String userId, string textcontent)
  8     {
  9         try
 10         {
 11             OapiRobotSendRequest request = new OapiRobotSendRequest();
 12             request.Msgtype = "text";
 13             OapiRobotSendRequest.TextDomain text = new OapiRobotSendRequest.TextDomain();
 14             text.Content = " @" + userId + " \n  " + textcontent;
 15             request.Text_ = text;
 16             OapiRobotSendRequest.AtDomain at = new OapiRobotSendRequest.AtDomain();
 17 
 18             List<string> userids = new List<string>();
 19             userids.Add(userId);
 20             at.AtUserIds = userids;
 21             //           isAtAll型別如果不為Boolean,請升級至最新SDK
 22             at.IsAtAll = false;
 23             request.At_ = at;
 24             OapiRobotSendResponse response = client.Execute(request);
 25             int code = Convert.ToInt32(response.Errcode);
 26             string msg = response.Errmsg;
 27         }
 28         catch (Exception e)
 29         {
 30 
 31         }
 32     }
 33 
 34     /**
 35     * markdown@人員效果
 36     *
 37     * @param client
 38     * @param userId
 39      * 
 40      * 返回markdown
 41      * 
 42     */
 43     private void markdown(DefaultDingTalkClient client, String userId, string title, string textcontent)
 44     {
 45         try
 46         {
 47             OapiRobotSendRequest request = new OapiRobotSendRequest();
 48             request.Msgtype = "markdown";
 49             OapiRobotSendRequest.MarkdownDomain markdown = new OapiRobotSendRequest.MarkdownDomain();
 50             markdown.Title = title;
 51             markdown.Text = " @" + userId + "  \n  " + textcontent;
 52             request.Markdown_ = markdown;
 53             OapiRobotSendRequest.AtDomain at = new OapiRobotSendRequest.AtDomain();
 54             List<string> userids = new List<string>();
 55             userids.Add(userId);
 56             at.AtUserIds = userids;
 57             //          isAtAll型別如果不為Boolean,請升級至最新SDK
 58             at.IsAtAll = false;
 59             request.At_ = at;
 60             OapiRobotSendResponse response = client.Execute(request);
 61             int code = Convert.ToInt32(response.Errcode);
 62             string msg = response.Errmsg;
 63         }
 64         catch (Exception e)
 65         {
 66 
 67         }
 68     }
 69     /**
 70        * actionCard@人員效果
 71        * @param client
 72        * @param userId
 73        */
 74     private void actionCard(DefaultDingTalkClient client, String userId, string title, string textcontent, string SingleTitle, string url)
 75     {
 76         try
 77         {
 78             OapiRobotSendRequest request = new OapiRobotSendRequest();
 79             request.Msgtype = "actionCard";
 80             OapiRobotSendRequest.ActioncardDomain actionCard = new OapiRobotSendRequest.ActioncardDomain();
 81             actionCard.Title = title;
 82             actionCard.Text = " @" + userId + "  \n  " + textcontent;
 83             ;
 84             actionCard.SingleTitle = SingleTitle;
 85             actionCard.SingleURL = url;
 86             request.ActionCard_ = actionCard;
 87             OapiRobotSendRequest.AtDomain at = new OapiRobotSendRequest.AtDomain();
 88             List<string> userids = new List<string>();
 89             userids.Add(userId);
 90             at.AtUserIds = userids;
 91             //          isAtAll型別如果不為Boolean,請升級至最新SDK
 92             at.IsAtAll = false;
 93             request.At_ = at;
 94             OapiRobotSendResponse response = client.Execute(request);
 95             int code = Convert.ToInt32(response.Errcode);
 96             string msg = response.Errmsg;
 97         }
 98         catch (Exception e)
 99         {
100 
101         }
102     }

文件中還提到有幾種markdown 的用法,分別是標題,引用,字型,連結,圖片,有序列表,無序列表的使用,從他的案例中可以看出,只是傳入的text加上特殊符號即可

標題
# 一級標題
## 二級標題
### 三級標題
#### 四級標題
##### 五級標題
###### 六級標題
 
引用
> A man who stands for nothing will fall for anything.
 
文字加粗、斜體
**bold**
*italic*
 
連結
[this is a link](https://www.dingtalk.com/)
 
圖片
![](http://name.com/pic.jpg)
 
無序列表
- item1
- item2
 
有序列表
1. item1
2. item2

換行(建議\n前後各新增兩個空格)
  \n  

至此,開發釘釘群機器人的所有開發過程寫完了。