zoukankan      html  css  js  c++  java
  • 自我总结:对博客园的服务接口进行封装

    之前在开发博客园新闻客户端的时候,需要获取博客园的新闻数据,最早开发出来的版本使用的是手机版的 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)的话:有什么建议欢迎在下面评论提,毕竟博主我也是菜鸟。

    ↑重点当然要大只字!

  • 相关阅读:

    Qt5开发实战学习——遇到的知识
    VS2017+Qt5.9.3,VS无法打开纯Qt工程(pro文件)
    QT的OpenGL
    QT之pro文件
    虚拟机网卡还原默认设置
    qt编译QOCI驱动连接oracle数据库
    关于数据库Oracle的创建、连接和Qt的小结
    联想G50-70恢复出厂设置
    移植解决方案到中标麒麟(一)——遇到的一些问题
  • 原文地址:https://www.cnblogs.com/h82258652/p/4160853.html
Copyright © 2011-2022 走看看