天天看點

自我總結:對部落格園的服務接口進行封裝

之前在開發部落格園新聞用戶端的時候,需要擷取部落格園的新聞資料,最早開發出來的版本使用的是手機版的 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} 分頁擷取推薦部落格清單
bloggers/recommend/count 擷取推薦部落格總數
bloggers/search 根據作者名搜尋部落客
post/{postId}/comments/{pageIndex}/{pageSize} 擷取文章評論
post/body/{postId} 擷取文章内容
sitehome/paged/{pageIndex}/{pageSize} 分頁擷取首頁文章清單
sitehome/recent/{itemcount} 擷取首頁文章清單
TenDaysTopDiggPosts/{itemCount} 10天内推薦排行
u/{blogapp}/posts/{pageIndex}/{pageSize} 分頁擷取個人部落格文章清單

新聞服務:

GetData 擷取新聞清單
hot/{itemcount} 擷取熱門新聞清單
item/{contentId} 擷取新聞内容
item/{contentId}/comments/{pageIndex}/{pageSize} 擷取新聞評論
recent/{itemcount} 擷取最新新聞清單
recent/paged/{pageIndex}/{pageSize} 分頁擷取最新新聞清單
recommend/paged/{pageIndex}/{pageSize} 分頁擷取推薦新聞清單

可以看見,部落格園官方團隊還算是挺厚道的,基本的接口都開放出來了。(除了部落格文章按分類擷取-_-|||部落客我的碎碎念)

由于需要盡可能使我們封裝好的類庫盡可能在多個平台使用,于是乎就想到了使用可移植類庫(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     }      

由于我們需要的是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     }      

注意:

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     }      

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)的話:有什麼建議歡迎在下面評論提,畢竟部落客我也是菜鳥。

↑重點當然要大隻字!