你的位置:首页 > ASP.net教程

[ASP.net教程]webapi+Task并行请求不同接口实例


  标题的名称定义不知道是否准确,不过我想表达的意思就是使用Task特性来同时请求多个不同的接口,然后合并数据;我想这种场景的开发对于对接过其他公司接口的人不会陌生,本人也是列属于之内,更多的是使用最原始的异步委托的方法去处理,今天抽空写了一个使用4.5新特性Task来处理这种场景;各位看客有什么疑问或者好的建议及分享请博客通知,谢谢。

  A.项目结构图

  

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

 1 public abstract class BaseClass 2   { 3  4     #region 初始化 5  6     /// <summary> 7     /// 初始化 8     /// </summary> 9     /// <param name="10     public BaseClass(string 11     {12 13       try14       {15 16         if (string.IsNullOrEmpty(17         {18 19           //默认各个20           var defaultConfigFolder = "Plugin";21           var baseAddr = AppDomain.CurrentDomain.BaseDirectory;22           this.GetType().Name + ".");23         }24 25         new 26         doc.Load(27 28         Config = new BaseConfig();29         Config.Url = doc.SelectSingleNode("//Pm/Url") == null ? "" : doc.SelectSingleNode("//Pm/Url").Inner30         Config.UserName = doc.SelectSingleNode("//Pm/UserName") == null ? "" : doc.SelectSingleNode("//Pm/UserName").Inner31         Config.UserKey = doc.SelectSingleNode("//Pm/UserKey") == null ? "" : doc.SelectSingleNode("//Pm/UserKey").Inner32 33         Config.Doc = doc;34       }35       catch (Exception ex)36       {37 38         throw new Exception(ex.Message);39       }40 41     }42 43     /// <summary>44     /// 45     /// </summary>46     public BaseConfig Config;47     #endregion48 49     #region 获取文章信息 _GetArticles +BaseResponse50 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     #endregion61 62   }63 64   /// <summary>65   /// 66   /// </summary>67   public class BaseConfig68   {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     /// 87     /// </summary>88     public 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     /// 初始化 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     #endregion103   }

View Code

    .注意这里使用 : base("")直接继承了上面说的父类的方法,来初始化配置信息(当然这个可能不算知识点)

    .接下来就是实现的_GetArticles方法里面PublicClass._HttpGet方法封装的HttpClient获取博客园的数据

    .解析了返回的数据信息(这里使用的正则,可能有些同学觉得正则可能还不太熟悉,可以自行百度参考分析下)

    .记录日志,我这里是记录的文本日志暂时注释了,因为怕抓取的信息多,忘记删除占用空间

    以上几点就是实际情况中经常遇到的步奏,这个处理步奏在从来没有对接过第三方接口的人还是值得学习的

    2.HuJiangClass类是抓取了博客园中.Net第一页的数据,步奏和方法和上面相同,请关注代码部分

 1   public class HuJiangClass : BaseClass 2   { 3     /// <summary> 4     /// 初始化 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