webapi+Task並行請求不同介面例項

神牛003發表於2016-04-20

  標題的名稱定義不知道是否準確,不過我想表達的意思就是使用Task特性來同時請求多個不同的介面,然後合併資料;我想這種場景的開發對於對接過其他公司介面的人不會陌生,本人也是列屬於之內,更多的是使用最原始的非同步委託的方法去處理,今天抽空寫了一個使用4.5新特性Task來處理這種場景;各位看客有什麼疑問或者好的建議及分享請部落格通知,謝謝。

  A.專案結構圖

  

  B.namespace Pm.V.PM_BLL下面的BaseClass定義如下:

 1  public abstract class BaseClass
 2     {
 3 
 4         #region 初始化xml配置資訊 BaseClass
 5 
 6         /// <summary>
 7         /// 初始化xml配置資訊
 8         /// </summary>
 9         /// <param name="xmlConf"></param>
10         public BaseClass(string xmlConfigPath)
11         {
12 
13             try
14             {
15 
16                 if (string.IsNullOrEmpty(xmlConfigPath))
17                 {
18 
19                     //預設各個Xml配置
20                     var defaultConfigFolder = "PluginXml";
21                     var baseAddr = AppDomain.CurrentDomain.BaseDirectory;
22                     xmlConfigPath = Path.Combine(baseAddr, defaultConfigFolder, this.GetType().Name + ".xml");
23                 }
24 
25                 XmlDocument doc = new XmlDocument();
26                 doc.Load(xmlConfigPath);
27 
28                 Config = new BaseConfig();
29                 Config.Url = doc.SelectSingleNode("//Pm/Url") == null ? "" : doc.SelectSingleNode("//Pm/Url").InnerXml;
30                 Config.UserName = doc.SelectSingleNode("//Pm/UserName") == null ? "" : doc.SelectSingleNode("//Pm/UserName").InnerXml;
31                 Config.UserKey = doc.SelectSingleNode("//Pm/UserKey") == null ? "" : doc.SelectSingleNode("//Pm/UserKey").InnerXml;
32 
33                 Config.Doc = doc;
34             }
35             catch (Exception ex)
36             {
37 
38                 throw new Exception(ex.Message);
39             }
40 
41         }
42 
43         /// <summary>
44         /// xml配置資訊
45         /// </summary>
46         public BaseConfig Config;
47         #endregion
48 
49         #region  獲取文章資訊 _GetArticles  +BaseResponse
50 
51         /// <summary>
52         /// 獲取文章資訊
53         /// </summary>
54         /// <param name="request"></param>
55         /// <returns></returns>
56         public virtual MoArticlesResponse _GetArticles(object request)
57         {
58             return null;
59         }
60         #endregion
61 
62     }
63 
64     /// <summary>
65     /// xml配置檔案資訊
66     /// </summary>
67     public class BaseConfig
68     {
69 
70         /// <summary>
71         /// 介面地址
72         /// </summary>
73         public string Url { get; set; }
74 
75         /// <summary>
76         /// 賬號
77         /// </summary>
78         public string UserName { get; set; }
79 
80         /// <summary>
81         /// 密碼|祕鑰
82         /// </summary>
83         public string UserKey { get; set; }
84 
85         /// <summary>
86         /// xml檔案全部資訊
87         /// </summary>
88         public XmlDocument Doc { get; set; }
89 
90     }
View Code

  主要是在例項的時候讀取各個業務模組的配置檔案,初始化一些常用並且是公共的屬性資訊;其次建立了一個虛方法_GetArticles,注意裡面的引數是object這個將再後面的時候重提;

  C.下面就是請求第三方業務實現類的程式碼,這裡測試的時候分別使用

    1.CnblogsClass類抓取部落格園首頁的部落格列表資訊

  1    /// <summary>
  2     /// 部落格園資訊(如果涉及到資訊來源問題,請及時聯絡開源作者,謝謝)
  3     /// </summary>
  4     public class CnblogsClass : BaseClass
  5     {
  6         /// <summary>
  7         /// 初始化xml配置
  8         /// </summary>
  9         public CnblogsClass() : base("") { }
 10 
 11         #region  獲取文章資訊 _GetArticles  +BaseResponse
 12 
 13         /// <summary>
 14         /// 獲取文章資訊
 15         /// </summary>
 16         /// <param name="request"></param>
 17         /// <returns></returns>
 18         public override MoArticlesResponse _GetArticles(object obj)
 19         {
 20             #region 初始化資訊
 21 
 22             var request = new MoArticlesRequest();
 23             var response = new MoArticlesResponse();
 24             var sbLog = new StringBuilder(string.Empty);
 25             var url = this.Config.Url;   //這裡獲取配置檔案的url
 26             #endregion
 27 
 28             try
 29             {
 30 
 31                 #region 介面驗證資料
 32 
 33                 request = obj as MoArticlesRequest;
 34                 if (request == null)
 35                 {
 36 
 37                     response.ErrorCode = (int)EHelper.PmException.獲取資料為空;
 38                     response.ErrorMsg = EHelper.PmException.獲取資料為空.ToString();
 39                     return response;
 40                 }
 41 
 42                 #endregion
 43 
 44                 #region 請求資料
 45 
 46 
 47                 sbLog.AppendFormat("請求地址:{0}\n", url);
 48                 var result = PublicClass._HttpGet(url);   //這裡一般都是post資料到第三方介面,測試沒有第三方可以使用,所以使用抓取部落格園首頁資料
 49                 sbLog.AppendFormat("返回資訊:{0}\n", result);
 50                 #endregion
 51 
 52                 #region 解析
 53 
 54                 //使用正則解析資料
 55                 var rgs = Regex.Matches(result, "class=\"titlelnk\"\\s+href=\"(?<link>http://www(\\w|\\.|\\/)+\\.html)\"[^>]+>(?<title>[^<]+)<\\/a>[^D]+a>(?<des>[^<]+)[^D]+lightblue\">(?<author>\\w+)<\\/a>[^D]+釋出於(?<publishtime>\\s+(\\d|-|\\s|:)+)[^<]+");
 56 
 57                 if (rgs.Count <= 0)
 58                 {
 59 
 60                     response.ErrorCode = (int)EHelper.PmException.獲取資料為空;
 61                     response.ErrorMsg = EHelper.PmException.獲取資料為空.ToString();
 62                     return response;
 63                 }
 64 
 65                 foreach (Match item in rgs)
 66                 {
 67 
 68                     var article = new MoArticle();
 69 
 70                     article.Author = item.Groups["author"].Value;
 71                     article.LinkUrl = item.Groups["link"].Value;
 72                     article.Title = item.Groups["title"].Value;
 73                     article.PublishTime = item.Groups["publishtime"].Value;
 74                   //  article.Des = item.Groups["des"].Value;
 75                     article.DataType = (int)EHelper.DataType.部落格園;
 76 
 77                     if (response.MoArticles.Count > 5) { continue; }
 78                     response.MoArticles.Add(article);
 79                 }
 80 
 81                 response.IsSuccess = true;
 82                 #endregion
 83             }
 84             catch (Exception ex)
 85             {
 86 
 87                 sbLog.AppendFormat("異常資訊:{0}\n", ex.Message);
 88                 response.ErrorCode = (int)EHelper.PmException.獲取資料異常;
 89                 response.ErrorMsg = EHelper.PmException.獲取資料異常.ToString();
 90             }
 91             finally
 92             {
 93 
 94                 #region 第三方原始資訊-日誌
 95 
 96                 //PublicClass._WriteLog(sbLog.ToString(), "Cnblogs");
 97 
 98                 #endregion
 99             }
100             return response;
101         }
102         #endregion
103     }
View Code

    .注意這裡使用 : base("")直接繼承了上面說的父類的方法,來初始化配置資訊(當然這個可能不算知識點)

    .接下來就是實現的_GetArticles方法裡面PublicClass._HttpGet方法封裝的HttpClient獲取部落格園的資料

    .解析了返回的資料資訊(這裡使用的正則,可能有些同學覺得正則可能還不太熟悉,可以自行百度參考分析下)

    .記錄日誌,我這裡是記錄的文字日誌暫時註釋了,因為怕抓取的資訊多,忘記刪除佔用空間

    以上幾點就是實際情況中經常遇到的步奏,這個處理步奏在從來沒有對接過第三方介面的人還是值得學習的

    2.HuJiangClass類是抓取了部落格園中.Net第一頁的資料,步奏和方法和上面相同,請關注程式碼部分

  1     public class HuJiangClass : BaseClass
  2     {
  3         /// <summary>
  4         /// 初始化xml配置
  5         /// </summary>
  6         public HuJiangClass() : base("") { }
  7 
  8 
  9         #region  獲取文章資訊 _GetArticles  +BaseResponse
 10 
 11         /// <summary>
 12         /// 獲取文章資訊
 13         /// </summary>
 14         /// <param name="request"></param>
 15         /// <returns></returns>
 16         public override MoArticlesResponse _GetArticles(object obj)
 17         {
 18             #region 初始化資訊
 19 
 20             var request = new MoArticlesRequest();
 21             var response = new MoArticlesResponse();
 22             var sbLog = new StringBuilder(string.Empty);
 23             var url = this.Config.Url;   //這裡獲取配置檔案的url
 24             #endregion
 25 
 26             try
 27             {
 28 
 29                 #region 介面驗證資料
 30 
 31                 request = obj as MoArticlesRequest;
 32                 if (request == null)
 33                 {
 34 
 35                     response.ErrorCode = (int)EHelper.PmException.獲取資料為空;
 36                     response.ErrorMsg = EHelper.PmException.獲取資料為空.ToString();
 37                     return response;
 38                 }
 39 
 40                 #endregion
 41 
 42                 #region 請求資料
 43 
 44                 sbLog.AppendFormat("請求地址:{0}\n", url);
 45                 var result = PublicClass._HttpGet(url);   //這裡一般都是post資料到第三方介面,測試沒有第三方可以使用,所以使用抓取部落格園首頁資料
 46                 sbLog.AppendFormat("返回資訊:{0}\n", result);
 47                 #endregion
 48 
 49                 #region 解析
 50 
 51                 //使用正則解析資料
 52                 var rgs = Regex.Matches(result, "class=\"titlelnk\"\\s+href=\"(?<link>http://www(\\w|\\.|\\/)+\\.html)\"[^>]+>(?<title>[^<]+)<\\/a>[^D]+post_item_summary\">(?<des>[^<]+)[^D]+lightblue\">(?<author>\\w+)<\\/a>[^D]+釋出於(?<publishtime>\\s+(\\d|-|\\s|:)+)[^<]+");
 53 
 54                 if (rgs.Count <= 0)
 55                 {
 56 
 57                     response.ErrorCode = (int)EHelper.PmException.獲取資料為空;
 58                     response.ErrorMsg = EHelper.PmException.獲取資料為空.ToString();
 59                     return response;
 60                 }
 61 
 62                 foreach (Match item in rgs)
 63                 {
 64 
 65                     var article = new MoArticle();
 66 
 67                     article.Author = item.Groups["author"].Value;
 68                     article.LinkUrl = item.Groups["link"].Value;
 69                     article.Title = item.Groups["title"].Value;
 70                     article.PublishTime = item.Groups["publishtime"].Value;
 71                    // article.Des = item.Groups["des"].Value;
 72                     article.DataType = (int)EHelper.DataType.部落格園NET技術;
 73                     if (response.MoArticles.Count > 5) { continue; }
 74                     response.MoArticles.Add(article);
 75                 }
 76 
 77                 response.IsSuccess = true;
 78                 #endregion
 79             }
 80             catch (Exception ex)
 81             {
 82 
 83                 sbLog.AppendFormat("異常資訊:{0}\n", ex.Message);
 84                 response.ErrorCode = (int)EHelper.PmException.獲取資料異常;
 85                 response.ErrorMsg = EHelper.PmException.獲取資料異常.ToString();
 86             }
 87             finally
 88             {
 89 
 90                 #region 第三方原始資訊-日誌
 91 
 92                 //PublicClass._WriteLog(sbLog.ToString(), "Cnblogs");
 93 
 94                 #endregion
 95             }
 96             return response;
 97         }
 98         #endregion
 99 
100     }
View Code

  D.今天要講的主要內容來了,以上算是過度,讓人瞭解對接第三方介面的一些處理步奏和簡單的封裝吧;這裡將看到的是Pm.Api.Controllers空間下BlogsController裡面的Post方法,程式碼如:

  1  // GET api/<controller>
  2         public IEnumerable<string> Get()
  3         {
  4             return new string[] { "歡迎使用-神牛步行3開源框架" };
  5         }
  6 
  7         // POST api/<controller>
  8         public async Task<HttpResponseMessage> Post()
  9         {
 10             HttpResponseMessage response = new HttpResponseMessage();
 11             var baseResponse = new BaseResponse();
 12             try
 13             {
 14                 #region 驗證
 15 
 16                 //獲取post資料
 17                 HttpContent content = Request.Content;
 18                 var param = await content.ReadAsStringAsync();
 19                 var baseRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<BaseRequest>(param);
 20                 if (string.IsNullOrEmpty(baseRequest.FunName))
 21                 {
 22                     baseResponse.ErrorMsg = EHelper.PmException.請求引數為空.ToString();
 23                     baseResponse.ErrorCode = (int)EHelper.PmException.請求引數為空;
 24                     response.Content = new StringContent(await JsonConvert.SerializeObjectAsync(baseResponse));
 25                     return response;
 26                 }
 27                 else if (string.IsNullOrEmpty(baseRequest.UserName))
 28                 {
 29 
 30                     baseResponse.ErrorMsg = EHelper.PmException.賬號不正確.ToString();
 31                     baseResponse.ErrorCode = (int)EHelper.PmException.賬號不正確;
 32                     response.Content = new StringContent(await JsonConvert.SerializeObjectAsync(baseResponse));
 33                     return response;
 34                 }
 35                 else if (!Enum.IsDefined(typeof(EHelper.DataType), baseRequest.DataType))
 36                 {
 37                     baseResponse.ErrorMsg = EHelper.PmException.引數不合法.ToString();
 38                     baseResponse.ErrorCode = (int)EHelper.PmException.引數不合法;
 39                     response.Content = new StringContent(await JsonConvert.SerializeObjectAsync(baseResponse));
 40                     return response;
 41                 }
 42                 //驗證賬號及token
 43 
 44                 #endregion
 45 
 46                 #region 業務
 47 
 48                 var dataTypes = Enum.GetValues(typeof(EHelper.DataType));
 49                 switch (baseRequest.FunName)
 50                 {
 51                     //獲取文章集合資訊
 52                     case "_GetArticles":
 53 
 54                         //json反序列獲取資料
 55                         var r_GetArticles = Newtonsoft.Json.JsonConvert.DeserializeObject<MoArticlesRequest>(param);
 56 
 57                         //初始化任務量
 58                         var tasks = new Task<MoArticlesResponse>[baseRequest.DataType == 0 ? dataTypes.Length : 1];
 59                         var proxy = new Pm_Proxy();
 60                         var j = 0;  //真實任務座標
 61                         for (int i = 0; i < dataTypes.Length; i++)
 62                         {
 63                             var item = dataTypes.GetValue(i);
 64                             var nType = Convert.ToInt32(item);
 65                             if (nType != baseRequest.DataType && 0 != baseRequest.DataType) { continue; }
 66 
 67                             //使用任務做並行
 68                             var dataType = proxy._DataType(nType);
 69                             var task = Task.Factory.StartNew<MoArticlesResponse>(dataType._GetArticles, r_GetArticles);
 70                             tasks[j] = task;
 71                             j++;
 72                         }
 73                         //30s等待
 74                         Task.WaitAll(tasks, 1000 * 1 * 30);
 75 
 76                         //獲取任務執行的結果(整合資料)
 77                         var articles = new MoArticlesResponse();
 78                         foreach (var task in tasks)
 79                         {
 80                             if (!task.IsCompleted) { continue; }
 81                             articles.MoArticles.AddRange(task.Result.MoArticles);
 82                         }
 83 
 84                         articles.IsSuccess = articles.MoArticles.Count > 0;
 85                         baseResponse = articles;
 86                         break;
 87 
 88                     default:
 89                         break;
 90                 }
 91                 response.Content = new StringContent(await JsonConvert.SerializeObjectAsync(baseResponse));
 92                 #endregion
 93             }
 94             catch (Exception ex)
 95             {
 96                 baseResponse.ErrorMsg = EHelper.PmException.獲取資料異常.ToString();
 97                 baseResponse.ErrorCode = (int)EHelper.PmException.獲取資料異常;
 98             }
 99             return response;
100         }
View Code

    1.首先使用了var baseRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<BaseRequest>(param); 來第一次返序列化,得到驗證的基本資訊如賬號,token等

    2.常用的介面形式使用引數節點名稱來確定將要執行的方法,或者直接在節點值中標註方法的名稱,因此有了這麼一句switch (baseRequest.FunName)來判斷程式的走向

    3.在此反序列得到真實呼叫者post給介面的資料(上面第一次反序列使用的是基類,基類裡面就包含了驗證需要的屬性,為什麼這裡不直接使用第一次反序列的物件呢,因為這裡將要傳遞給後面方法引數的值是子類裡面封裝的)

    4.開始定義Task任務的數量,一般根據有幾個第三方介面第一幾個吧,Task<MoArticlesResponse>[]保證後面產生的任務量

    5.Task.Factory.StartNew<MoArticlesResponse>(dataType._GetArticles, r_GetArticles) 方法來建立任務,這裡要說的是dataType._GetArticles是之前上面說的請求第三方介面定義的方法,r_GetArticles這個是需要傳遞的值是object的,這也是StartNew固定的引數型別;再通過Task<MoArticlesResponse>[]儲存建立的任務;

    6.Task.WaitAll(tasks, 1000 * 1 * 30);這個WaitAll是自帶的,意思是等待任務執行多少毫秒,也算是知識點吧,第一個引數是任務陣列,是陣列的形式,第二個是毫秒單位的等待時間

    7.最後通過foreach (var task in tasks) 來迴圈整合task.Result返回的結果

 之後是效果截圖DataType表示不同的資料來源:

 

 

  大致就是這些吧,不知道說的東西是否說明白了,這就是處理對接第三方不同介面的業務邏輯,也是使用task來並行處理的方法;如果有朋友覺得可能說的不太好或者有什麼錯誤地方,還請直接發部落格資訊,謝謝;

  原始碼地址:https://github.com/shenniubuxing3/Niu.Pm

 

相關文章