
·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> ASP.NET网站开发 >> 自我总结:对博客园的服务接口进行封装
之前在开发博客园新闻客户端的时候,需要获取博客园的新闻数据,最早开发出来的版本使用的是手机版的 html 解释的方式。效果算是做出来了,但是感觉获取到的数据太冗余,于是便查了一下有没有相应的接口。皇天不负有心人,博客园果然开放了些接口供我们使用。
博客服务接口:http://wcf.open.cnblogs.com/blog/help
新闻服务接口:http://wcf.open.cnblogs.com/news/help
博客服务:
| Uri | Method | Description |
| 48HoursTopViewPosts/{itemCount} | GET | 48小时阅读排行 |
| bloggers/recommend/{pageIndex}/{pageSize} | GET | 分页获取推荐博客列表 |
| bloggers/recommend/count | GET | 获取推荐博客总数 |
| bloggers/search | GET | 根据作者名搜索博主 |
| post/{postId}/comments/{pageIndex}/{pageSize} | GET | 获取文章评论 |
| post/body/{postId} | GET | 获取文章内容 |
| sitehome/paged/{pageIndex}/{pageSize} | GET | 分页获取首页文章列表 |
| sitehome/recent/{itemcount} | GET | 获取首页文章列表 |
| TenDaysTopDiggPosts/{itemCount} | GET | 10天内推荐排行 |
| u/{blogapp}/posts/{pageIndex}/{pageSize} | GET | 分页获取个人博客文章列表 |
新闻服务:
| Uri | Method | Description |
| GetData | GET | 获取新闻列表 |
| 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 TopicIcon
104 {
105 get;
106 internal set;
107 }
108
109 /// <summary>
110 /// 转载自。
111 /// </summary>
112 public string SourceName
113 {
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.HasElements
14 let temp = Deserialize(entry)
15 where temp != null
16 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 == null
42 || title == null
43 || summary == null
44 || published == null
45 || updated == null
46 || href == null
47 || diggs == null
48 || views == null
49 || comments == null
50 || topic == null
51 || topicIcon == null
52 || sourceName == null)
53 {
54 return null;
55 }
56
57 return new News
58 {
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.Value
71 };
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 Id
10 {
11 get;
12 internal set;
13 }
14
15 /// <summary>
16 /// 标题。
17 /// </summary>
18 public string Title
19 {
20 get;
21 internal set;
22 }
23
24 /// <summary>
25 /// 转载自。
26 /// </summary>
27 public string SourceName
28 {
29 get;
30 internal set;
31 }
32
33 /// <summary>
34 /// 发表时间。
35 /// </summary>
36 public DateTime SubmitDate
37 {
38 get;
39 internal set;
40 }
41
42 /// <summary>
43 /// 内容。
44 /// </summary>
45 public string Content
46 {
47 get;
48 internal set;
49 }
50
51 /// <summary>
52 /// 新闻中用到的图片的路径。
53 /// </summary>
54 public ReadOnlyCollection<Uri> ImageUrl
55 {
56 get;
57 internal set;
58 }
59
60 /// <summary>
61 /// 上一条新闻的 Id。
62 /// </summary>
63 public int? PrevNews
64 {
65 get;
66 internal set;
67 }
68
69 /// <summary>
70 /// 下一条新闻的 Id。
71 /// </summary>
72 public int? NextNews
73 {
74 get;
75 internal set;
76 }
77
78 /// <summary>
79 /// 评论数。
80 /// </summary>
81 public int CommentCount
82 {
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)的话:有什么建议欢迎在下面评论提,毕竟博主我也是菜鸟。
↑重点当然要大只字!