1 using xxx.Core.Entity;
2 using Newtonsoft.Json;
3 using Newtonsoft.Json.Linq;
4 using System;
5 using System.Collections.Generic;
6 using System.Configuration;
7 using System.Data;
8 using System.IO;
9 using System.Net;
10 using System.Net.Http;
11 using System.Text;
12 using TianYuan.Data.Dapper.Repositories;
13
14 namespace xxx.Common
15 {
16 /// <summary>
17 /// 企微接口调用通用类
18 /// </summary>
19 public class WXWorkHelper
20 {
21 #region private
22
23 //获取token方法名
24 const string accessTokenMethod = "/cgi-bin/gettoken";
25 //发送消息方法名
26 const string sendMessageMethod = "/cgi-bin/message/send";
27 //上传方法,支持类型包括图片(image)、语音(oice)、视频(video)、普通文件(file)
28 const string uploadMethod = "/cgi-bin/media/upload";
29
30 //接口地址
31 private string apiUrl = "";
32 //企业应用ID
33 private string agentId = "";
34 private string corpId = "";
35 private string corpSecret = "";
36
37 /// <summary>
38 /// 兼容多应用
39 /// </summary>
40 public WXWorkHelper(AppNameEnum appName)
41 {
42 try
43 {
44 //配置信息从数据库中获取
45 string sql = "select a.ApiUrl,a.AgentId,a.CorpId,a.CorpSecret from xxx a,yyy b where a.AppId=b.Id and b.AppCode='"+ (int)appName +"'";
46 DataTable dt = MySqlHelper.ExecuteDataTable(sql);
47 if(dt == null | dt.Rows.Count == 0)
48 {
49 apiUrl = ConfigurationManager.AppSettings["WXWorkAPIUrl"].ToString();
50 agentId = ConfigurationManager.AppSettings["WXWorkAgentId"].ToString();
51 corpId = ConfigurationManager.AppSettings["WXWorkCorpId"].ToString();
52 corpSecret = ConfigurationManager.AppSettings["WXWorkCorpSecret"].ToString();
53 }
54 else
55 {
56 apiUrl = dt.Rows[0]["ApiUrl"].ToString();
57 agentId = dt.Rows[0]["AgentId"].ToString();
58 corpId = dt.Rows[0]["CorpId"].ToString();
59 corpSecret = dt.Rows[0]["CorpSecret"].ToString();
60 }
61 }
62 catch(Exception ex)
63 {
64
65 }
66 }
67
68 #endregion
69
70 #region Send Notification
71
72 /// <summary>
73 /// 发送Text类消息
74 /// </summary>
75 /// <param name="userIdList"></param>
76 /// <param name="messageContent"></param>
77 public bool SendTextNotification(string userIdList, string messageContent)
78 {
79 return SendNotification(GetBody(MsgTypeEnum.Text, userIdList, messageContent));
80 }
81
82 /// <summary>
83 /// 发送Markdown类消息
84 /// </summary>
85 /// <param name="userIdList"></param>
86 /// <param name="messageContent"></param>
87 public bool SendMarkdownNotification(string userIdList, string messageContent)
88 {
89 return SendNotification(GetBody(MsgTypeEnum.Markdown, userIdList, messageContent));
90 }
91
92 /// <summary>
93 /// 发送Image类消息
94 /// </summary>
95 /// <param name="userIdList"></param>
96 /// <param name="messageContent"></param>
97 /// <param name="messageContent"></param>
98 public bool SendImageNotification(string userIdList, string fileName, byte[] bytes)
99 {
100 bool isSuccess = false;
101
102 try
103 {
104 //获取Token
105 string accessToken = GetAccessToken();
106 //上传临时素材
107 string mediaId = Upload(MediaTypeEnum.image, fileName, bytes, accessToken);
108
109 if (mediaId != "")
110 {
111 return SendNotification(GetBody(MsgTypeEnum.Image, userIdList, mediaId));
112 }
113 }
114 catch (Exception ex)
115 {
116
117 }
118
119 return isSuccess;
120 }
121
122 /// <summary>
123 /// 发送News类消息
124 /// </summary>
125 /// <param name="userIdList"></param>
126 /// <param name="list"></param>
127 public bool SendNewsNotification(string userIdList, List<ArticleModel> list)
128 {
129 return SendNotification(GetBody(MsgTypeEnum.News, userIdList, list));
130 }
131
132 #region 执行发送
133
134 /// <summary>
135 /// 执行发送消息
136 /// </summary>
137 /// <param name="messageBody">企微要求固定格式body</param>
138 private bool SendNotification(string messageBody)
139 {
140 bool isSuccess = false;
141
142 try
143 {
144 using (HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(30) })
145 {
146 HttpContent content = new StringContent(messageBody);
147 content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
148
149 string apiMethodUrl = apiUrl + sendMessageMethod + "?access_token=" + GetAccessToken();
150
151 HttpResponseMessage response = httpClient.PostAsync(apiMethodUrl, content).Result;
152 string result = response.Content.ReadAsStringAsync().Result;
153 if (!string.IsNullOrWhiteSpace(result))
154 {
155 var obj = JObject.Parse(result.ToString());
156 if (obj != null)
157 {
158 string code = obj.GetValue("errcode").ToString();
159 if (code.ToLower() == "0")
160 {
161 isSuccess = true;
162 }
163 }
164 }
165 }
166 }
167 catch (Exception ex)
168 {
169
170 }
171
172 return isSuccess;
173 }
174
175 #endregion
176
177 #region 获取消息内容结构
178
179 /// <summary>
180 /// 获取消息内容结构()
181 /// </summary>
182 /// <param name="msgType"></param>
183 /// <param name="userList"></param>
184 /// <param name="messageContent"></param>
185 /// <returns></returns>
186 private string GetBody(MsgTypeEnum msgType, string userList, object messageContent)
187 {
188 string messageBody = "";
189
190 switch (msgType)
191 {
192 case MsgTypeEnum.Text:
193 messageBody = GetTextBody(userList, (string)messageContent);
194 break;
195 case MsgTypeEnum.Image:
196 messageBody = GetImageBody(userList, (string)messageContent);
197 break;
198 case MsgTypeEnum.Markdown:
199 messageBody = GetMarkdownBody(userList, (string)messageContent);
200 break;
201 case MsgTypeEnum.News:
202 messageBody = GetNewsBody(userList, (List<ArticleModel>)messageContent);
203 break;
204 }
205
206 return messageBody;
207 }
208
209 private string GetTextBody(string userList, string messageContent)
210 {
211 string body = "";
212
213 TextBody textBody = new TextBody();
214 textBody.touser = userList;
215 textBody.agentid = agentId;
216 textBody.msgtype = "text";
217 textBody.text = new ContentModel(messageContent);
218
219 body = JsonConvert.SerializeObject(textBody);
220
221 return body;
222 }
223
224 private string GetImageBody(string userList, string mediaId)
225 {
226 string body = "";
227
228 ImageBody imageBody = new ImageBody();
229 imageBody.touser = userList;
230 imageBody.agentid = agentId;
231 imageBody.msgtype = "image";
232 imageBody.image = new ImageModel(mediaId);
233
234 body = JsonConvert.SerializeObject(imageBody);
235
236 return body;
237 }
238
239 private string GetMarkdownBody(string userList, string messageContent)
240 {
241 string body = "";
242
243 MarkdownBody markdownBody = new MarkdownBody();
244 markdownBody.touser = userList;
245 markdownBody.agentid = agentId;
246 markdownBody.msgtype = "markdown";
247 markdownBody.markdown = new ContentModel(messageContent);
248
249 body = JsonConvert.SerializeObject(markdownBody);
250
251 return body;
252 }
253
254 private string GetNewsBody(string userList, List<ArticleModel> list)
255 {
256 string body = "";
257
258 NewsBody newsBody = new NewsBody();
259 newsBody.touser = userList;
260 newsBody.agentid = agentId;
261 newsBody.msgtype = "news";
262
263 NewsModel newsModel = new NewsModel();
264 newsModel.articles = list;
265 newsBody.news = newsModel;
266
267 body = JsonConvert.SerializeObject(newsBody);
268
269 return body;
270 }
271
272 #endregion
273
274 #endregion
275
276 #region Upload
277
278 /// <summary>
279 /// 上传
280 /// </summary>
281 /// <param name="mediaTypeEnum">文件类型</param>
282 /// <param name="fileName">文件名</param>
283 /// <param name="bytes">字节流</param>
284 /// <param name="accessToken"></param>
285 /// <returns></returns>
286 public string Upload(MediaTypeEnum mediaTypeEnum, string fileName, byte[] bytes, string accessToken)
287 {
288 string mediaId = "";
289
290 try
291 {
292 using (HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(30) })
293 {
294 //方法路径
295 string apiMethodUrl = apiUrl + uploadMethod + "?access_token=" + accessToken + "&type=" + mediaTypeEnum.ToString();
296
297 CookieContainer cookieContainer = new CookieContainer();
298 HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiMethodUrl);
299 request.CookieContainer = cookieContainer;
300 request.AllowAutoRedirect = true;
301 request.Method = "POST";
302 string boundary = DateTime.Now.Ticks.ToString("X"); // 随机分隔线
303 request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary;
304
305 byte[] itemBoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
306 byte[] endBoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
307
308 //Header
309 if(fileName == "")
310 {
311 //fileName必填参数
312 fileName = mediaTypeEnum.ToString();
313 }
314 StringBuilder sbHeader = new StringBuilder(string.Format("Content-Disposition:form-data;name=\"media\";filename=\"{0}\";filelength=" + bytes.Length + "\r\nContent-Type:application/octet-stream\r\n\r\n", fileName));
315 byte[] postHeaderBytes = Encoding.UTF8.GetBytes(sbHeader.ToString());
316
317 //请求
318 using (Stream postStream = request.GetRequestStream())
319 {
320 postStream.Write(itemBoundaryBytes, 0, itemBoundaryBytes.Length);
321 postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
322 postStream.Write(bytes, 0, bytes.Length);
323 postStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
324 }
325
326 //响应
327 string responseData = String.Empty;
328 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
329 {
330 using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
331 {
332 responseData = reader.ReadToEnd();
333
334 if (!string.IsNullOrWhiteSpace(responseData))
335 {
336 var obj = JObject.Parse(responseData.ToString());
337 if (obj != null)
338 {
339 var data = obj["media_id"].ToString();
340 if (!string.IsNullOrWhiteSpace(data))
341 {
342 mediaId = data.ToString();
343 }
344 }
345 }
346 }
347 }
348 }
349 }
350 catch(Exception ex)
351 {
352
353 }
354
355 return mediaId;
356 }
357
358 #endregion
359
360 #region Get Access Token
361
362 /// <summary>
363 /// 获取Access Token(企微官方接口,仅CorpId和CorpSecret两个参数)
364 /// </summary>
365 /// <returns></returns>
366 private string GetAccessToken()
367 {
368 string token = "";
369
370 try
371 {
372 try
373 {
374 using (HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(30) })
375 {
376 string apiMethodUrl = apiUrl + accessTokenMethod + "?corpId=" + corpId + "&corpSecret=" + corpSecret;
377
378 HttpResponseMessage response = null;
379 response = httpClient.GetAsync(apiMethodUrl).Result;
380
381 string results = response.Content.ReadAsStringAsync().Result;
382 if (!string.IsNullOrWhiteSpace(results))
383 {
384 var obj = JObject.Parse(results.ToString());
385 if (obj != null)
386 {
387 var data = obj["access_token"].ToString();
388 if (!string.IsNullOrWhiteSpace(data))
389 {
390 token = data.ToString();
391 }
392 }
393 }
394 }
395 }
396 catch (Exception ex)
397 {
398
399 }
400 }
401 catch (Exception ex)
402 {
403
404 }
405
406 return token;
407 }
408
409 #endregion
410 }
411
412 #region 枚举
413
414 /// <summary>
415 /// 应用系统名称
416 /// </summary>
417 public enum AppNameEnum
418 {
419 /// <summary>
420 /// 应用系统1
421 /// </summary>
422 App1 = 1010,
423
424 /// <summary>
425 /// 应用系统2
426 /// </summary>
427 App2 = 1020,
428
429 /// <summary>
430 /// 应用系统3
431 /// </summary>
432 App3 = 1030,453 }
454
455 /// <summary>
456 /// 消息类型,目前仅Text、Markdown和News已实现
457 /// </summary>
458 public enum MsgTypeEnum
459 {
460 /// <summary>
461 /// 文本
462 /// </summary>
463 Text = 1,
464
465 /// <summary>
466 /// 图片
467 /// </summary>
468 Image = 2,
469
470 /// <summary>
471 /// 语音
472 /// </summary>
473 Voice = 3,
474
475 /// <summary>
476 /// 视频
477 /// </summary>
478 Video = 4,
479
480 /// <summary>
481 /// 文件
482 /// </summary>
483 File = 5,
484
485 /// <summary>
486 /// 文本卡片
487 /// </summary>
488 TextCard = 6,
489
490 /// <summary>
491 /// 图文
492 /// </summary>
493 News = 7,
494
495 /// <summary>
496 /// MP图文
497 /// </summary>
498 MPNews = 8,
499
500 /// <summary>
501 /// Markdown格式
502 /// </summary>
503 Markdown = 9,
504
505 /// <summary>
506 /// 小程序
507 /// </summary>
508 Miniprogram = 10,
509
510 /// <summary>
511 /// 卡片模板
512 /// </summary>
513 TemplateCard = 11,
514 }
515
516 /// <summary>
517 /// 上传文件类型
518 /// </summary>
519 public enum MediaTypeEnum
520 {
521 /// <summary>
522 /// 图片
523 /// </summary>
524 image = 10,
525
526 /// <summary>
527 /// 语音
528 /// </summary>
529 voice = 20,
530
531 /// <summary>
532 /// 视频
533 /// </summary>
534 video = 30,
535
536 /// <summary>
537 /// 普通文件
538 /// </summary>
539 file = 40,
540 }
541
542 #endregion
543
544 #region 实体
545
546 /// <summary>
547 /// 消息实体基类
548 /// </summary>
549 public class MessageBaseBody
550 {
551 /// <summary>
552 ///
553 /// </summary>
554 public string agentid { set; get; }
555
556 /// <summary>
557 ///
558 /// </summary>
559 public string touser { set; get; }
560
561 /// <summary>
562 ///
563 /// </summary>
564 public string msgtype { set; get; }
565 }
566
567 /// <summary>
568 /// 文本类消息实体
569 /// </summary>
570 public class TextBody : MessageBaseBody
571 {
572 /// <summary>
573 ///
574 /// </summary>
575 public ContentModel text { set; get; }
576 }
577
578 /// <summary>
579 /// Markdown类消息实体
580 /// </summary>
581 public class MarkdownBody : MessageBaseBody
582 {
583 /// <summary>
584 ///
585 /// </summary>
586 public ContentModel markdown { set; get; }
587 }
588
589 /// <summary>
590 /// 图片类消息实体
591 /// </summary>
592 public class ImageBody : MessageBaseBody
593 {
594 /// <summary>
595 ///
596 /// </summary>
597 public ImageModel image { set; get; }
598 }
599
600 /// <summary>
601 /// News类消息实体
602 /// </summary>
603 public class NewsBody : MessageBaseBody
604 {
605 /// <summary>
606 ///
607 /// </summary>
608 public NewsModel news { set; get; }
609 }
610
611 /// <summary>
612 /// Content实体
613 /// </summary>
614 public class ContentModel
615 {
616 public ContentModel(string content)
617 {
618 this.content = content;
619 }
620
621 /// <summary>
622 ///
623 /// </summary>
624 public string content { set; get; }
625 }
626
627 /// <summary>
628 /// Image内容实体
629 /// </summary>
630 public class ImageModel
631 {
632 public ImageModel(string mediaId)
633 {
634 this.media_id = mediaId;
635 }
636
637 /// <summary>
638 ///
639 /// </summary>
640 public string media_id { set; get; }
641 }
642
643 /// <summary>
644 /// News内容实体
645 /// </summary>
646 public class NewsModel
647 {
648 /// <summary>
649 ///
650 /// </summary>
651 public List<ArticleModel> articles { set; get; }
652 }
653
654 /// <summary>
655 /// News内容结构实体
656 /// </summary>
657 public class ArticleModel
658 {
659 public ArticleModel(string title, string description, string picUrl, string url)
660 {
661 this.title = title;
662 this.description = description;
663 this.picurl = picUrl;
664 this.url = url;
665 }
666
667 /// <summary>
668 ///
669 /// </summary>
670 public string title { set; get; }
671
672 /// <summary>
673 ///
674 /// </summary>
675 public string description { set; get; }
676
677 /// <summary>
678 ///
679 /// </summary>
680 public string picurl { set; get; }
681
682 /// <summary>
683 ///
684 /// </summary>
685 public string url { set; get; }
686 }
687
688 /// <summary>
689 /// Media结构实体
690 /// </summary>
691 public class MediaModel
692 {
693 public MediaModel(string mediaid)
694 {
695 this.media_id = mediaid;
696 }
697
698 /// <summary>
699 ///
700 /// </summary>
701 public string media_id { set; get; }
702 }
703
704 #endregion
705 }