企业微信消息推送通用类(仅Text、Markdown、Image、News类型实现,功能完善中)
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 }