webapi+Task并行请求不同接口实例

  标题的名称定义不知道是否准确,不过我想表达的意思就是使用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

 

posted @ 2016-04-20 19:34  神牛003  阅读(3596)  评论(1编辑  收藏  举报