標題的名稱定義不知道是否準確,不過我想表達的意思就是使用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 }
主要是在例項的時候讀取各個業務模組的配置檔案,初始化一些常用並且是公共的屬性資訊;其次建立了一個虛方法_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 }
.注意這裡使用 : 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 }
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 }
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