之前在開發(fā)博客園新聞客戶端的時(shí)候,需要獲取博客園的新聞數(shù)據(jù),最早開發(fā)出來的版本使用的是手機(jī)版的 html 解釋的方式。效果算是做出來了,但是感覺獲取到的數(shù)據(jù)太冗余,于是便查了一下有沒有相應(yīng)的接口。皇天不負(fù)有心人,博客園果然開放了些接口供我們使用。
博客服務(wù)接口:http://wcf.open.VEVb.com/blog/help
新聞服務(wù)接口:http://wcf.open.VEVb.com/news/help
博客服務(wù):
Uri | Method | Description |
48HoursTopViewPosts/{itemCount} | GET | 48小時(shí)閱讀排行 |
bloggers/recommend/{pageIndex}/{pageSize} | GET | 分頁獲取推薦博客列表 |
bloggers/recommend/count | GET | 獲取推薦博客總數(shù) |
bloggers/search | GET | 根據(jù)作者名搜索博主 |
post/{postId}/comments/{pageIndex}/{pageSize} | GET | 獲取文章評論 |
post/body/{postId} | GET | 獲取文章內(nèi)容 |
sitehome/paged/{pageIndex}/{pageSize} | GET | 分頁獲取首頁文章列表 |
sitehome/recent/{itemcount} | GET | 獲取首頁文章列表 |
TenDaysTopDiggPosts/{itemCount} | GET | 10天內(nèi)推薦排行 |
u/{blogapp}/posts/{pageIndex}/{pageSize} | GET | 分頁獲取個(gè)人博客文章列表 |
新聞服務(wù):
Uri | Method | Description |
GetData | GET | 獲取新聞列表 |
hot/{itemcount} | GET | 獲取熱門新聞列表 |
item/{contentId} | GET | 獲取新聞內(nèi)容 |
item/{contentId}/comments/{pageIndex}/{pageSize} | GET | 獲取新聞評論 |
recent/{itemcount} | GET | 獲取最新新聞列表 |
recent/paged/{pageIndex}/{pageSize} | GET | 分頁獲取最新新聞列表 |
recommend/paged/{pageIndex}/{pageSize} | GET | 分頁獲取推薦新聞列表 |
可以看見,博客園官方團(tuán)隊(duì)還算是挺厚道的,基本的接口都開放出來了。(除了博客文章按分類獲取-_-|||博主我的碎碎念)
由于需要盡可能使我們封裝好的類庫盡可能在多個(gè)平臺使用,于是乎就想到了使用可移植類庫(PCL)來開發(fā)。
建起項(xiàng)目
當(dāng)然是通通選上,哪知道以后哪天會不會心血來潮再搞個(gè)什么平臺的客戶端。
因?yàn)榫头謨纱箢悾谑枪麛啻a上 BlogService 和 NewsService 兩個(gè)靜態(tài)類。
作為相應(yīng)服務(wù)的入口。
接下來,看見新聞的接口比較少,先寫這部分。
測試 GetData 接口。
啥玩意?!2012年的數(shù)據(jù)!那這個(gè)就不理他了。。。
接下來就是 hot(獲取熱門新聞列表)這個(gè)接口。
測試下:http://wcf.open.VEVb.com/news/hot/10
工作良好,于是開始寫這個(gè)接口的封裝。
在 NewsService 中寫上
public static async Task<IEnumerable<News>> HotAsync(int itemCount){ // TODO}
會發(fā)覺編譯不通過(廢話)。
于是新建一個(gè)News類先。修改方法。
編譯!
不通過!!!
看錯(cuò)誤列表:
不是4.5了么?怎么不能用 async?
解決方法:http://www.companysz.com/h82258652/p/4119118.html(注:本文為總結(jié)性文章,所以時(shí)間線的跳躍你們要跟上)
裝好巨硬給我們的異步補(bǔ)丁包后就可以編譯通過了。
接下來觀察接口返回的xml文檔,可以發(fā)現(xiàn)每一個(gè)entry節(jié)點(diǎn)就相當(dāng)于一條新聞。
根據(jù)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 /// 標(biāo)題。 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 /// 發(fā)表時(shí)間。 39 /// </summary> 40 public DateTime Published 41 { 42 get; 43 internal set; 44 } 45 46 /// <summary> 47 /// 更新時(shí)間。 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 /// 推薦數(shù)。 66 /// </summary> 67 public int Diggs 68 { 69 get; 70 internal set; 71 } 72 73 /// <summary> 74 /// 查看數(shù)。 75 /// </summary> 76 public int Views 77 { 78 get; 79 internal set; 80 } 81 82 /// <summary> 83 /// 評論數(shù)。 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 /// 主題圖標(biāo)。102 /// </summary>103 public Uri TopicIcon104 {105 get;106 internal set;107 }108 109 /// <summary>110 /// 轉(zhuǎn)載自。111 /// </summary>112 public string SourceName113 {114 get;115 internal set;116 }117 }118 }
PS:Q:為什么set方法都寫為internal?A:為什么外部要修改?Q:好吧,你贏了。
接下來就開始寫我們的 HotAsync 方法了。
HotAsync 方法有一個(gè)參數(shù)——itemCount,那么當(dāng)然要進(jìn)行驗(yàn)證。(不發(fā)送這些無謂的請求一方面可以不讓用戶等待、一方面減輕博客園的壓力)
if (itemCount < 1){ throw new ArgumentOutOfRangeException(nameof(itemCount));}
nameof,還沒跟上時(shí)代節(jié)奏的小伙伴就趕緊跟上了,這里不解釋。
接下來當(dāng)然是拼接 url。
var url = string.Format(CultureInfo.InvariantCulture, HotUrlTemplate, itemCount);var uri = new Uri(url, UriKind.Absolute);
HotUrlTemplate 的定義:
PRivate const string HotUrlTemplate = "http://wcf.open.VEVb.com/news/hot/{0}";
常量最好不要出現(xiàn)在方法中這是好習(xí)慣哦。
準(zhǔn)備WebRequest,并且調(diào)用GetResponse。
var request = WebRequest.Create(uri);using (var response = await request.GetResponseAsync()){ // TODO}
由于返回的是一個(gè)xml,所以直接使用 XDocument 加載(PS:我特討厭XmlDocument那套API)
var document = XDocument.Load(response.GetResponseStream());
接下來就是將XDocument轉(zhuǎn)換為News實(shí)體類的列表,這里我們寫到別的方法去,因?yàn)橄旅鎺讉€(gè)服務(wù)接口可能也會用到。
新建NewsHelper類。
根據(jù)entry節(jié)點(diǎn)的結(jié)果,寫出以下代碼:
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 }
由于我們需要的是IEnumerable<News>,所以直接返回Linq的結(jié)果就行了,不用ToList或者啥的。
注意PCL中是沒有HtmlDecode、HtmlEncode的,nuget上有一個(gè)PCL用的,可惜人家作者沒更新了,版本過舊,引用不了,沒辦法,自己動手豐衣足食。
項(xiàng)目地址:https://github.com/h82258652/SoftwareKobo.Net.WebUtility
Nuget地址:https://www.nuget.org/packages/SoftwareKobo.Net.WebUtility/
也是自己隨便寫的,反正能處理一下常見的字符就算了。(=_=)(巨硬的源碼我實(shí)在是看不懂。。還打算照抄的……)
接下來回到 HotAsync 補(bǔ)充最后一句:
return NewsHelper.Deserialize(document);
完事。
其他什么 Recent(最新的)、Recommend(推薦)如法炮制。(Recent有兩個(gè)接口,選擇分頁那個(gè)好了,反正感覺博客園內(nèi)部實(shí)現(xiàn)也是重載)
接下來看新聞內(nèi)容這個(gè)接口:
寫上 DetailAsync 方法:
public static async Task<NewsDetail> DetailAsync(int newsId)
當(dāng)然也有建上NewsDetail類。
1 /// <summary> 2 /// 新聞內(nèi)容。 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 /// 標(biāo)題。17 /// </summary>18 public string Title19 {20 get;21 internal set;22 }23 24 /// <summary>25 /// 轉(zhuǎn)載自。26 /// </summary>27 public string SourceName28 {29 get;30 internal set;31 }32 33 /// <summary>34 /// 發(fā)表時(shí)間。35 /// </summary>36 public DateTime SubmitDate37 {38 get;39 internal set;40 }41 42 /// <summary>43 /// 內(nèi)容。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 /// 評論數(shù)。80 /// </summary>81 public int CommentCount82 {83 get;84 internal set;85 }86 }
注意:
1、ImageUrl 用了 ReadOnlyCollection,原因同上,外部沒必要修改。
2、PreNews 和 NextNews 使用可空類型,因?yàn)椴灰欢ㄓ猩弦粭l或下一條。(都最新了,還能有下一條么)
接下來也是寫個(gè)Helper,將Xml的內(nèi)容轉(zhuǎn)換為對象列表。完事。
剩下來獲取評論也是差不多。
博客方面的接口封裝基本上也是這么搞,注意的是新聞的評論和博客文章的評論可以用同一個(gè)模型來表達(dá)。
都寫完后,感覺獲取新聞獲取評論不方便啊,得先訪問Id,再調(diào)NewsService。天生懶,沒辦法,寫個(gè)擴(kuò)展方法。
1 /// <summary> 2 /// 新聞擴(kuò)展。 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">每頁條數(shù)。</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 /// 獲取新聞內(nèi)容。25 /// </summary>26 /// <param name="news">新聞。</param>27 /// <returns>新聞內(nèi)容。</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包里,發(fā)布。測試添加引用,沒注釋!!!
回到項(xiàng)目,修改項(xiàng)目屬性,生成XML,勾上!!
把XML一同弄到nuget包里,再發(fā)布,測試,好了。
At The End:
項(xiàng)目地址:https://github.com/h82258652/SoftwareKobo.CnblogsAPI
Nuget地址:https://www.nuget.org/packages/SoftwareKobo.CnblogsAPI/
博主我(h82258652)的話:有什么建議歡迎在下面評論提,畢竟博主我也是菜鳥。
↑重點(diǎn)當(dāng)然要大只字!
新聞熱點(diǎn)
疑難解答
圖片精選