当前位置: 代码迷 >> .NET相关 >> 自小弟我总结:对博客园的服务接口进行封装
  详细解决方案

自小弟我总结:对博客园的服务接口进行封装

热度:199   发布时间:2016-04-24 03:00:27.0
自我总结:对博客园的服务接口进行封装

之前在开发博客园新闻客户端的时候,需要获取博客园的新闻数据,最早开发出来的版本使用的是手机版的 html 解释的方式。效果算是做出来了,但是感觉获取到的数据太冗余,于是便查了一下有没有相应的接口。皇天不负有心人,博客园果然开放了些接口供我们使用。

博客服务接口:http://wcf.open.cnblogs.com/blog/help

新闻服务接口:http://wcf.open.cnblogs.com/news/help

博客服务:

UriMethodDescription
48HoursTopViewPosts/{itemCount}GET48小时阅读排行
bloggers/recommend/{pageIndex}/{pageSize}GET分页获取推荐博客列表
bloggers/recommend/countGET获取推荐博客总数
bloggers/searchGET根据作者名搜索博主
post/{postId}/comments/{pageIndex}/{pageSize}GET获取文章评论
post/body/{postId}GET获取文章内容
sitehome/paged/{pageIndex}/{pageSize}GET分页获取首页文章列表
sitehome/recent/{itemcount}GET获取首页文章列表
TenDaysTopDiggPosts/{itemCount}GET10天内推荐排行
u/{blogapp}/posts/{pageIndex}/{pageSize}GET分页获取个人博客文章列表

新闻服务:

UriMethodDescription
GetDataGET获取新闻列表
hot/{itemcount}GET获取热门新闻列表
item/{contentId}GET获取新闻内容
item/{contentId}/comments/{pageIndex}/{pageSize}GET获取新闻评论
recent/{itemcount}GET获取最新新闻列表
recent/paged/{pageIndex}/{pageSize}GET分页获取最新新闻列表
recommend/paged/{pageIndex}/{pageSize}GET分页获取推荐新闻列表

可以看见,博客园官方团队还算是挺厚道的,基本的接口都开放出来了。(除了博客文章按分类获取-_-|||博主我的碎碎念)

 

由于需要尽可能使我们封装好的类库尽可能在多个平台使用,于是乎就想到了使用可移植类库(PCL)来开发。

建起项目

当然是通通选上,哪知道以后哪天会不会心血来潮再搞个什么平台的客户端。

 

因为就分两大类,于是果断码上 BlogService 和 NewsService 两个静态类。

作为相应服务的入口。

 

接下来,看见新闻的接口比较少,先写这部分。

测试 GetData 接口。

啥玩意?!2012年的数据!那这个就不理他了。。。

 

接下来就是 hot(获取热门新闻列表)这个接口。

测试下:http://wcf.open.cnblogs.com/news/hot/10

工作良好,于是开始写这个接口的封装。

在 NewsService 中写上

public static async Task<IEnumerable<News>> HotAsync(int itemCount){    // TODO}

会发觉编译不通过(废话)。

于是新建一个News类先。修改方法。

编译!

不通过!!!

看错误列表:

不是4.5了么?怎么不能用 async?

解决方法:http://www.cnblogs.com/h82258652/p/4119118.html(注:本文为总结性文章,所以时间线的跳跃你们要跟上)

 

装好巨硬给我们的异步补丁包后就可以编译通过了。

 

接下来观察接口返回的xml文档,可以发现每一个entry节点就相当于一条新闻。

根据entry分析,完善News类:

  1 using System;  2   3 namespace SoftwareKobo.CnblogsAPI.Model  4 {  5     /// <summary>  6     /// 新闻。  7     /// </summary>  8     public class News  9     { 10         /// <summary> 11         /// Id。 12         /// </summary> 13         public int Id 14         { 15             get; 16             internal set; 17         } 18  19         /// <summary> 20         /// 标题。 21         /// </summary> 22         public string Title 23         { 24             get; 25             internal set; 26         } 27  28         /// <summary> 29         /// 摘要。 30         /// </summary> 31         public string Summary 32         { 33             get; 34             internal set; 35         } 36  37         /// <summary> 38         /// 发表时间。 39         /// </summary> 40         public DateTime Published 41         { 42             get; 43             internal set; 44         } 45  46         /// <summary> 47         /// 更新时间。 48         /// </summary> 49         public DateTime Updated 50         { 51             get; 52             internal set; 53         } 54  55         /// <summary> 56         /// 新闻链接。 57         /// </summary> 58         public Uri Link 59         { 60             get; 61             internal set; 62         } 63  64         /// <summary> 65         /// 推荐数。 66         /// </summary> 67         public int Diggs 68         { 69             get; 70             internal set; 71         } 72  73         /// <summary> 74         /// 查看数。 75         /// </summary> 76         public int Views 77         { 78             get; 79             internal set; 80         } 81  82         /// <summary> 83         /// 评论数。 84         /// </summary> 85         public int Comments 86         { 87             get; 88             internal set; 89         } 90  91         /// <summary> 92         /// 主题。 93         /// </summary> 94         public string Topic 95         { 96             get; 97             internal set; 98         } 99 100         /// <summary>101         /// 主题图标。102         /// </summary>103         public Uri TopicIcon104         {105             get;106             internal set;107         }108 109         /// <summary>110         /// 转载自。111         /// </summary>112         public string SourceName113         {114             get;115             internal set;116         }117     }118 }
View Code

PS:Q:为什么set方法都写为internal?A:为什么外部要修改?Q:好吧,你赢了。

接下来就开始写我们的 HotAsync 方法了。

HotAsync 方法有一个参数——itemCount,那么当然要进行验证。(不发送这些无谓的请求一方面可以不让用户等待、一方面减轻博客园的压力)

if (itemCount < 1){    throw new ArgumentOutOfRangeException(nameof(itemCount));}

nameof,还没跟上时代节奏的小伙伴就赶紧跟上了,这里不解释。

 

接下来当然是拼接 url。

var url = string.Format(CultureInfo.InvariantCulture, HotUrlTemplate, itemCount);var uri = new Uri(url, UriKind.Absolute);

HotUrlTemplate 的定义:

private const string HotUrlTemplate = "http://wcf.open.cnblogs.com/news/hot/{0}";

常量最好不要出现在方法中这是好习惯哦。

 

准备WebRequest,并且调用GetResponse。

var request = WebRequest.Create(uri);using (var response = await request.GetResponseAsync()){    // TODO}

由于返回的是一个xml,所以直接使用 XDocument 加载(PS:我特讨厌XmlDocument那套API)

var document = XDocument.Load(response.GetResponseStream());

接下来就是将XDocument转换为News实体类的列表,这里我们写到别的方法去,因为下面几个服务接口可能也会用到。

新建NewsHelper类。

根据entry节点的结果,写出以下代码:

 1     internal static class NewsHelper 2     { 3         internal static IEnumerable<News> Deserialize(XDocument document) 4         { 5             var root = document?.Root; 6             if (root == null) 7             { 8                 return null; 9             }10 11             var ns = root.GetDefaultNamespace();12             var news = from entry in root.Elements(ns + "entry")13                        where entry.HasElements14                        let temp = Deserialize(entry)15                        where temp != null16                        select temp;17             return news;18         }19 20         internal static News Deserialize(XElement element)21         {22             if (element == null)23             {24                 return null;25             }26 27             var ns = element.GetDefaultNamespace();28             var id = element.Element(ns + "id");29             var title = element.Element(ns + "title");30             var summary = element.Element(ns + "summary");31             var published = element.Element(ns + "published");32             var updated = element.Element(ns + "updated");33             var href = element.Element(ns + "link")?.Attribute("href");34             var diggs = element.Element(ns + "diggs");35             var views = element.Element(ns + "views");36             var comments = element.Element(ns + "comments");37             var topic = element.Element(ns + "topic");38             var topicIcon = element.Element(ns + "topicIcon");39             var sourceName = element.Element(ns + "sourceName");40 41             if (id == null42                 || title == null43                 || summary == null44                 || published == null45                 || updated == null46                 || href == null47                 || diggs == null48                 || views == null49                 || comments == null50                 || topic == null51                 || topicIcon == null52                 || sourceName == null)53             {54                 return null;55             }56 57             return new News58             {59                 Id = int.Parse(id.Value, CultureInfo.InvariantCulture),60                 Title = WebUtility.HtmlDecode(title.Value),61                 Summary = WebUtility.HtmlDecode(summary.Value),62                 Published = DateTime.Parse(published.Value, CultureInfo.InvariantCulture),63                 Updated = DateTime.Parse(updated.Value, CultureInfo.InvariantCulture),64                 Link = new Uri(href.Value, UriKind.Absolute),65                 Diggs = int.Parse(diggs.Value, CultureInfo.InvariantCulture),66                 Views = int.Parse(views.Value, CultureInfo.InvariantCulture),67                 Comments = int.Parse(comments.Value, CultureInfo.InvariantCulture),68                 Topic = topic.Value,69                 TopicIcon = topicIcon.IsEmpty ? null : new Uri(topicIcon.Value, UriKind.Absolute),70                 SourceName = sourceName.Value71             };72         }73     }
View Code

由于我们需要的是IEnumerable<News>,所以直接返回Linq的结果就行了,不用ToList或者啥的。

 

注意PCL中是没有HtmlDecode、HtmlEncode的,nuget上有一个PCL用的,可惜人家作者没更新了,版本过旧,引用不了,没办法,自己动手丰衣足食。

项目地址:https://github.com/h82258652/SoftwareKobo.Net.WebUtility

Nuget地址:https://www.nuget.org/packages/SoftwareKobo.Net.WebUtility/

也是自己随便写的,反正能处理一下常见的字符就算了。(=_=)(巨硬的源码我实在是看不懂。。还打算照抄的……)

 

接下来回到 HotAsync 补充最后一句:

return NewsHelper.Deserialize(document);

完事。

其他什么 Recent(最新的)、Recommend(推荐)如法炮制。(Recent有两个接口,选择分页那个好了,反正感觉博客园内部实现也是重载)

 

接下来看新闻内容这个接口:

写上 DetailAsync 方法:

public static async Task<NewsDetail> DetailAsync(int newsId)

当然也有建上NewsDetail类。

 1     /// <summary> 2     /// 新闻内容。 3     /// </summary> 4     public class NewsDetail 5     { 6         /// <summary> 7         /// Id。 8         /// </summary> 9         public int Id10         {11             get;12             internal set;13         }14 15         /// <summary>16         /// 标题。17         /// </summary>18         public string Title19         {20             get;21             internal set;22         }23 24         /// <summary>25         /// 转载自。26         /// </summary>27         public string SourceName28         {29             get;30             internal set;31         }32 33         /// <summary>34         /// 发表时间。35         /// </summary>36         public DateTime SubmitDate37         {38             get;39             internal set;40         }41 42         /// <summary>43         /// 内容。44         /// </summary>45         public string Content46         {47             get;48             internal set;49         }50 51         /// <summary>52         /// 新闻中用到的图片的路径。53         /// </summary>54         public ReadOnlyCollection<Uri> ImageUrl55         {56             get;57             internal set;58         }59 60         /// <summary>61         /// 上一条新闻的 Id。62         /// </summary>63         public int? PrevNews64         {65             get;66             internal set;67         }68 69         /// <summary>70         /// 下一条新闻的 Id。71         /// </summary>72         public int? NextNews73         {74             get;75             internal set;76         }77 78         /// <summary>79         /// 评论数。80         /// </summary>81         public int CommentCount82         {83             get;84             internal set;85         }86     }
View Code

注意:

1、ImageUrl 用了 ReadOnlyCollection,原因同上,外部没必要修改。

2、PreNews 和 NextNews 使用可空类型,因为不一定有上一条或下一条。(都最新了,还能有下一条么)

 

接下来也是写个Helper,将Xml的内容转换为对象列表。完事。

剩下来获取评论也是差不多。

博客方面的接口封装基本上也是这么搞,注意的是新闻的评论和博客文章的评论可以用同一个模型来表达。

 

都写完后,感觉获取新闻获取评论不方便啊,得先访问Id,再调NewsService。天生懒,没办法,写个扩展方法。

 1     /// <summary> 2     /// 新闻扩展。 3     /// </summary> 4     public static class NewsExtension 5     { 6         /// <summary> 7         /// 获取新闻评论。 8         /// </summary> 9         /// <param name="news">新闻。</param>10         /// <param name="pageIndex">第几页,从 1 开始。</param>11         /// <param name="pageSize">每页条数。</param>12         /// <returns>新闻评论。</returns>13         /// <exception cref="ArgumentNullException">新闻为 null。</exception>14         public static async Task<IEnumerable<Comment>> CommentAsync(this News news, int pageIndex, int pageSize)15         {16             if (news == null)17             {18                 throw new ArgumentNullException(nameof(news));19             }20             return await NewsService.CommentAsync(news.Id, pageIndex, pageSize);21         }22 23         /// <summary>24         /// 获取新闻内容。25         /// </summary>26         /// <param name="news">新闻。</param>27         /// <returns>新闻内容。</returns>28         /// <exception cref="ArgumentNullException">新闻为 null。</exception>29         public static async Task<NewsDetail> DetailAsync(this News news)30         {31             if (news == null)32             {33                 throw new ArgumentNullException(nameof(news));34             }35             return await NewsService.DetailAsync(news.Id);36         }37     }
View Code

Q:为什么不在News类里写?A:没什么,保持模型纯正而已。-_-|||也好管理。

 

完事了,Release编译,弄到nuget包里,发布。测试添加引用,没注释!!!

回到项目,修改项目属性,生成XML,勾上!!

把XML一同弄到nuget包里,再发布,测试,好了。

 

At The End:

项目地址:https://github.com/h82258652/SoftwareKobo.CnblogsAPI

 

Nuget地址:https://www.nuget.org/packages/SoftwareKobo.CnblogsAPI/

博主我(h82258652)的话:有什么建议欢迎在下面评论提,毕竟博主我也是菜鸟。

↑重点当然要大只字!

  相关解决方案