标题的名称定义不知道是否准确,不过我想表达的意思就是使用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 ///7 /// 初始化xml配置信息 8 /// 9 /// 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 /// 44 /// xml配置信息 45 /// 46 public BaseConfig Config; 47 #endregion 48 49 #region 获取文章信息 _GetArticles +BaseResponse 50 51 /// 52 /// 获取文章信息 53 /// 54 /// 55 /// 56 public virtual MoArticlesResponse _GetArticles(object request) 57 { 58 return null; 59 } 60 #endregion 61 62 } 63 64 /// 65 /// xml配置文件信息 66 /// 67 public class BaseConfig 68 { 69 70 /// 71 /// 接口地址 72 /// 73 public string Url { get; set; } 74 75 /// 76 /// 账号 77 /// 78 public string UserName { get; set; } 79 80 /// 81 /// 密码|秘钥 82 /// 83 public string UserKey { get; set; } 84 85 /// 86 /// xml文件全部信息 87 /// 88 public XmlDocument Doc { get; set; } 89 90 }
主要是在实例的时候读取各个业务模块的配置文件,初始化一些常用并且是公共的属性信息;其次创建了一个虚方法_GetArticles,注意里面的参数是object这个将再后面的时候重提;
C.下面就是请求第三方业务实现类的代码,这里测试的时候分别使用
1.CnblogsClass类抓取博客园首页的博客列表信息
1 ///2 /// 博客园信息(如果涉及到信息来源问题,请及时联系开源作者,谢谢) 3 /// 4 public class CnblogsClass : BaseClass 5 { 6 /// 7 /// 初始化xml配置 8 /// 9 public CnblogsClass() : base("") { } 10 11 #region 获取文章信息 _GetArticles +BaseResponse 12 13 /// 14 /// 获取文章信息 15 /// 16 /// 17 /// 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=\"(?http://www(\\w|\\.|\\/)+\\.html)\"[^>]+>(? [^<]+)<\\/a>[^D]+a>(? "); 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 }[^<]+)[^D]+lightblue\">(? \\w+)<\\/a>[^D]+发布于(? \\s+(\\d|-|\\s|:)+)[^<]+
.注意这里使用 : base("")直接继承了上面说的父类的方法,来初始化配置信息(当然这个可能不算知识点)
.接下来就是实现的_GetArticles方法里面PublicClass._HttpGet方法封装的HttpClient获取博客园的数据
.解析了返回的数据信息(这里使用的正则,可能有些同学觉得正则可能还不太熟悉,可以自行百度参考分析下)
.记录日志,我这里是记录的文本日志暂时注释了,因为怕抓取的信息多,忘记删除占用空间
以上几点就是实际情况中经常遇到的步奏,这个处理步奏在从来没有对接过第三方接口的人还是值得学习的
2.HuJiangClass类是抓取了博客园中.Net第一页的数据,步奏和方法和上面相同,请关注代码部分
1 public class HuJiangClass : BaseClass 2 { 3 ///4 /// 初始化xml配置 5 /// 6 public HuJiangClass() : base("") { } 7 8 9 #region 获取文章信息 _GetArticles +BaseResponse 10 11 /// 12 /// 获取文章信息 13 /// 14 /// 15 /// 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=\"(?http://www(\\w|\\.|\\/)+\\.html)\"[^>]+>(? [^<]+)<\\/a>[^D]+post_item_summary\">(? "); 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]+lightblue\">(? \\w+)<\\/a>[^D]+发布于(? \\s+(\\d|-|\\s|:)+)[^<]+
D.今天要讲的主要内容来了,以上算是过度,让人了解对接第三方接口的一些处理步奏和简单的封装吧;这里将看到的是Pm.Api.Controllers空间下BlogsController里面的Post方法,代码如:
1 // GET api/2 public IEnumerable<string> Get() 3 { 4 return new string[] { "欢迎使用-神牛步行3开源框架" }; 5 } 6 7 // POST api/ 8 public async Task 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 (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 (param); 56 57 //初始化任务量 58 var tasks = new Task [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 (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
2.常用的接口形式使用参数节点名称来确定将要执行的方法,或者直接在节点值中标注方法的名称,因此有了这么一句switch (baseRequest.FunName)来判断程序的走向
3.在此反序列得到真实调用者post给接口的数据(上面第一次反序列使用的是基类,基类里面就包含了验证需要的属性,为什么这里不直接使用第一次反序列的对象呢,因为这里将要传递给后面方法参数的值是子类里面封装的)
4.开始定义Task任务的数量,一般根据有几个第三方接口第一几个吧,Task
5.Task.Factory.StartNew
6.Task.WaitAll(tasks, 1000 * 1 * 30);这个WaitAll是自带的,意思是等待任务执行多少毫秒,也算是知识点吧,第一个参数是任务数组,是数组的形式,第二个是毫秒单位的等待时间
7.最后通过foreach (var task in tasks) 来循环整合task.Result返回的结果
之后是效果截图DataType表示不同的数据来源:
大致就是这些吧,不知道说的东西是否说明白了,这就是处理对接第三方不同接口的业务逻辑,也是使用task来并行处理的方法;如果有朋友觉得可能说的不太好或者有什么错误地方,还请直接发博客信息,谢谢;
源码地址:https://github.com/shenniubuxing3/Niu.Pm