花了几天时间,消耗了九牛六虎之力,新浪微博大部分API已经封装,但有部分API实在太难封装。

说起这封装,我必须严重地、从人品和技术层面鄙视一下新浪的程序员,实在太菜了。估计菜鸟都被大企业吸收了,菜到连面向对象都不懂。建议新浪的菜菜们向淘宝学习下,人家淘宝还同时有XML和JSON两种数据格式。

同样的内容,返回的JSON对象居然会出现不同结构,更可恶的,像公共API中获取城市列表,国家区域代码列表的返回结果,实在让人不得不发笑。那些JSON用JS读起来都困难,更何况要进行封装呢。根本没法封装,因此在论坛上抱怨的人不少,可是新浪官方呢,置之不管,就当没看见一样,看来,大企业就是这点水平。

题外话不多说,回归主题。除了权限不够和无法封装的API外,其余的API都封装了。先上一张代码地图。

这图有点像蜘蛛网,非常有美感,可惜是静止的,如果会动的,一定很好看。

我不可能把代码一行一行地向大家讲述,因为这样做会相当恶心。现在菜菜们都整天拿开源来装逼,所以,为了迎合广大菜菜所说的所谓“趋势”,我也决定把我这份乱七八糟的代码向全人类公开。

下载地址:http://vdisk.weibo.com/s/z7iFc2gCCwC1b

接下来,就给大家说说原理和思路,这才是编程的关键。

1、不管是微博API还是其他的开放平台的API,都有N多共同点。首先,用户要用自己的帐号登录,然后由用户决定是否授权给我们的应用程序,如果用户已同意授权,我们会得到一个授权码(Code)。

2、拿到Code后,我们要用这个Code来换取一个Access Token,就相当于用户授予我们一把钥匙,我们再用这把钥匙去开启库房的门,然后取出一个临时令牌,就好比在汉代,大臣代表天子巡视诸侯国时,手里要持着天子的符节一样。有了Token,我们的应用才能进行一系列操作。

微博的API的每一次调用,其实就是一轮HTTP请求-响应的往返过程,说白了,就是一问一答,我们调用API是问,服务器完成相关处理后返回结果给我们,为答。我们也知道,HTTP发送数据,用得最多的两种方法是GET和POST,至于是GET还是POST,我们按照API文档的说明去干就行了。

而对于服务器返回的数据(JSON),我是通过数据协定的形式来进行封装的。比如:

一个表示用户信息的回复JSON如下:

{
"id": 1404376560,
"screen_name": "zaku",
"name": "zaku",
"province": "11",
"city": "5",
"location": "北京 朝阳区",
"description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
"url": "http://blog.sina.com.cn/zaku",
"profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
"domain": "zaku",
"gender": "m",
"followers_count": 1204,
"friends_count": 447,
"statuses_count": 2908,
"favourites_count": 0,
"created_at": "Fri Aug 28 00:00:00 +0800 2009",
"following": false,
"allow_all_act_msg": false,
"geo_enabled": true,
"verified": false,
"status": {
"created_at": "Tue May 24 18:04:53 +0800 2011",
"id": 11142488790,
"text": "我的相机到了。",
"source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>",
"favorited": false,
"truncated": false,
"in_reply_to_status_id": "",
"in_reply_to_user_id": "",
"in_reply_to_screen_name": "",
"geo": null,
"mid": "5610221544300749636",
"annotations": [],
"reposts_count": 5,
"comments_count": 8
},
"allow_all_comment": true,
"avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
"verified_reason": "",
"follow_me": false,
"online_status": 0,
"bi_followers_count": 215
}

我们可以根据JSON中每个成员的名字,用代码封装成一个类。

    /// <summary>
/// 用户实体
/// </summary>
[DataContract]
public class UserInfo
{
/// <summary>
/// 用户UID
/// </summary>
[DataMember(Name = "id")]
public long ID { get; set; } /// <summary>
/// 字符串型的用户UID
/// </summary>
[DataMember(Name = "idstr")]
public string IDStr { get; set; } /// <summary>
/// 用户昵称
/// </summary>
[DataMember(Name = "screen_name")]
public string ScreenName { get; set; } /// <summary>
/// 友好显示名称
/// </summary>
[DataMember(Name = "name")]
public string Name { get; set; } /// <summary>
/// 用户所在省级ID
/// </summary>
[DataMember(Name = "province")]
public int Province { get; set; } /// <summary>
/// 用户所在城市ID
/// </summary>
[DataMember(Name = "city")]
public int City { get; set; } /// <summary>
/// 用户所在地
/// </summary>
[DataMember(Name = "location")]
public string Location { get; set; } /// <summary>
/// 用户个人描述
/// </summary>
[DataMember(Name = "description")]
public string Description { get; set; } /// <summary>
/// 用户博客地址
/// </summary>
[DataMember(Name = "url")]
public string Url { get; set; } /// <summary>
/// 用户头像地址(中图),50×50像素
/// </summary>
[DataMember(Name = "profile_image_url")]
public string ProfileImageUrl { get; set; } /// <summary>
/// 用户的微博统一URL地址
/// </summary>
[DataMember(Name = "profile_url")]
public string ProfileUrl { get; set; } /// <summary>
/// 用户的个性化域名
/// </summary>
[DataMember(Name = "domain")]
public string Domain { get; set; } /// <summary>
/// 用户的微号
/// </summary>
[DataMember(Name = "weihao")]
public string WeiHao { get; set; } /// <summary>
/// 性别,m:男、f:女、n:未知
/// </summary>
[DataMember(Name = "gender")]
public string Gender { get; set; } /// <summary>
/// 粉丝数
/// </summary>
[DataMember(Name = "followers_count")]
public int FollowersCount { get; set; } /// <summary>
/// 关注数
/// </summary>
[DataMember(Name = "friends_count")]
public int FriendsCount { get; set; } /// <summary>
/// 微博数
/// </summary>
[DataMember(Name = "statuses_count")]
public int StatusesCount { get; set; } /// <summary>
/// 收藏数
/// </summary>
[DataMember(Name = "favourites_count")]
public int FavouritesCount { get; set; } /// <summary>
/// 用户创建(注册)时间
/// </summary>
[DataMember(Name = "created_at")]
public string CreatedAt { get; set; } /// <summary>
/// 暂未支持
/// </summary>
[DataMember(Name = "following")]
public bool Following { get; set; } /// <summary>
/// 是否允许所有人给我发私信,true:是,false:否
/// </summary>
[DataMember(Name = "allow_all_act_msg")]
public bool AllowAllActMsg { get; set; } /// <summary>
/// 是否允许标识用户的地理位置,true:是,false:否
/// </summary>
[DataMember(Name = "geo_enabled")]
public bool GeoEnabled { get; set; } /// <summary>
/// 是否是微博认证用户,即加V用户,true:是,false:否
/// </summary>
[DataMember(Name = "verified")]
public bool Verified { get; set; } /// <summary>
/// 暂未支持
/// </summary>
[DataMember(Name = "verified_type")]
public int VerifiedType { get; set; } /// <summary>
/// 用户备注信息
/// </summary>
[DataMember(Name = "remark")]
public string Remark { get; set; } /// <summary>
/// 用户的最近一条微博信息
/// </summary>
[DataMember(Name = "status")]
public Status Status { get; set; } /// <summary>
/// 当对象数据未返回完整微博信息时,将填充该字段
/// </summary>
[DataMember(Name = "status_id")]
public long StatusID { get; set; } /// <summary>
/// 是否允许所有人对我的微博进行评论,true:是,false:否
/// </summary>
[DataMember(Name = "allow_all_comment")]
public bool AllowAllComment { get; set; } /// <summary>
/// 用户头像地址(大图),180×180像素
/// </summary>
[DataMember(Name = "avatar_large")]
public string AvatarLarge { get; set; } /// <summary>
/// 用户头像地址(高清),高清头像原图
/// </summary>
[DataMember(Name = "avatar_hd")]
public string AvatarHd { get; set; } /// <summary>
/// 认证原因
/// </summary>
[DataMember(Name = "verified_reason")]
public string VerifiedReason { get; set; } /// <summary>
/// 该用户是否关注当前登录用户,true:是,false:否
/// </summary>
[DataMember(Name = "follow_me")]
public bool FollowMe { get; set; } /// <summary>
/// 用户的在线状态,0:不在线、1:在线
/// </summary>
[DataMember(Name = "online_status")]
public int OnlineStatus { get; set; } /// <summary>
/// 用户的互粉数
/// </summary>
[DataMember(Name = "bi_followers_count")]
public int BiFollowersCount { get; set; } /// <summary>
/// 用户当前的语言版本,zh-cn:简体中文,zh-tw:繁体中文,en:英语
/// </summary>
[DataMember(Name = "lang")]
public string Lang { get; set; }
}

其中,Status属性表示用户发表的最新一条微博,它由一个表示微博的对象构成。

{
"created_at": "Tue May 24 18:04:53 +0800 2011",
"id": 11142488790,
"text": "我的相机到了。",
"source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>",
"favorited": false,
"truncated": false,
"in_reply_to_status_id": "",
"in_reply_to_user_id": "",
"in_reply_to_screen_name": "",
"geo": null,
"mid": "5610221544300749636",
"annotations": [],
"reposts_count": 5,
"comments_count": 8
}

同样,我们也用一个数据协定类来封装它,表示一条微博的信息。

    /// <summary>
/// 微博实体
/// </summary>
[DataContract]
public class Status
{
/// <summary>
/// 微博创建时间
/// </summary>
[DataMember(Name = "created_at")]
public string CreateAt { get; set; } /// <summary>
/// 微博ID
/// </summary>
[DataMember(Name = "id")]
public Int64 ID { get; set; } /// <summary>
/// 微博MID
/// </summary>
[DataMember(Name = "mid")]
public Int64 MID { set; get; } /// <summary>
/// 字符串型的微博ID
/// </summary>
[DataMember(Name = "idstr")]
public string IDStr { get; set; } /// <summary>
/// 微博信息内容
/// </summary>
[DataMember(Name = "text")]
public string Text { get; set; } /// <summary>
/// 微博来源
/// </summary>
[DataMember(Name = "source")]
public string Source { get; set; } /// <summary>
/// 是否已收藏,true:是,false:否
/// </summary>
[DataMember(Name = "favorited")]
public bool Favorited { get; set; } /// <summary>
/// 是否被截断,true:是,false:否
/// </summary>
[DataMember(Name = "truncated")]
public bool Truncated { get; set; } /// <summary>
/// (暂未支持)回复ID
/// </summary>
[DataMember(Name = "in_reply_to_status_id")]
public string InReplyToStatusID { get; set; } /// <summary>
/// (暂未支持)回复人UID
/// </summary>
[DataMember(Name = "in_reply_to_user_id")]
public string InReplyToUserID { get; set; } /// <summary>
/// (暂未支持)回复人昵称
/// </summary>
[DataMember(Name = "in_reply_to_screen_name")]
public string InReplyToScreenName { get; set; } /// <summary>
/// 缩略图片地址
/// </summary>
[DataMember(Name = "thumbnail_pic")]
public string ThumbnailPic { get; set; } /// <summary>
/// 中等尺寸图片地址
/// </summary>
[DataMember(Name = "bmiddle_pic")]
public string BmiddlePic { get; set; } /// <summary>
/// 原始图片地址
/// </summary>
[DataMember(Name = "original_pic")]
public string OriginalPic { get; set; } /// <summary>
/// 地理信息
/// </summary>
[DataMember(Name = "geo")]
public GeoInfo Geo { get; set; } /// <summary>
/// 微博作者的用户信息
/// </summary>
[DataMember(Name = "user")]
public UserInfo User { get; set; } /// <summary>
/// 被转发的原微博信息字段,当该微博为转发微博时返回
/// </summary>
[DataMember(Name = "retweeted_status")]
public Status RetweetedStatus { get; set; } /// <summary>
/// 转发数
/// </summary>
[DataMember(Name = "reposts_count")]
public int RepostsCount { get; set; } /// <summary>
/// 评论数
/// </summary>
[DataMember(Name = "comments_count")]
public int CommentsCount { get; set; } /// <summary>
/// 表态数
/// </summary>
[DataMember(Name = "attitudes_count")]
public int AttitudesCount { get; set; } /// <summary>
/// 暂未支持
/// </summary>
[DataMember(Name = "mlevel")]
public int Mlevel { get; set; } /// <summary>
/// 微博的可见性及指定可见分组信息。该object中type取值,0:普通微博,1:私密微博,3:指定分组微博,4:密友微博;list_id为分组的组号
/// </summary>
[DataMember(Name = "visible")]
public dynamic Visible { get; set; } /// <summary>
/// 微博配图地址。多图时返回多图链接。无配图返回“[]”
/// </summary>
[DataMember(Name = "pic_urls")]
public dynamic PicUrls { get; set; } /// <summary>
/// 微博流内的推广微博ID
/// </summary>
[DataMember(Name = "ad")]
public dynamic Ad { get; set; } /// <summary>
/// 当不返回user字段时,此属性可填充UID
/// </summary>
[DataMember(Name = "uid")]
public long Uid { get; set; }
}

我们在接收到服务器的回应后,直接对数据进行反序列化,就可以得到这些数据的封装实例,面向对象,操作起来更方便。这些被封装的基本类型,我都放到了Models目录下。为了实现反序列化,我定义了一个类,公开一个静态方法,以便从JSON数据生成对象实例。

    public class JsonSerializeHelper
{
public static T ReadDataFromJson<T>(Stream inStream)
{
DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(T));
return (T)s.ReadObject(inStream);
}
}

因为我们要反序列化的类型是多种多样的,无法定论,因此,这里使用泛型参数较合适。具体反序列化为哪种类型的实例,在运行时决定。

由于每个API的调用都是一轮HTTP请求/回应,无论你调用哪个API都一样,这个行为是通过的,所以,我就统一进行提取,并使用.NET 4.5新增的HttpClient类来处理,这个类很强大,把一些不太好处理的HTTP请求都为我们封装好了,尤其是像form-data这种POST的数据,如MultiPart form data这些,尤其是向服务器上传文件时较容易处理,省去许多不必要的机械性工作,将人类从无必要的代码中解放出来,极大地提高生产率,这是.NET最牛X的地方。

        internal static async Task<TResult> SendRequestWithMultipartFormDataAsync<TResult>(string relateUrl, IDictionary<string, object> parms, string filename)
{
Uri reqUri = new Uri(API_BASE_RUI);
reqUri = new Uri(reqUri, relateUrl);
TResult result = default(TResult);
using (HttpClient client = new HttpClient())
{
string b = "---------------------" + DateTime.Now.Ticks.ToString("x");
MultipartFormDataContent formData = new MultipartFormDataContent(b);
foreach (var pair in parms)
{
string str = pair.Value as string;
if (str != null)
{
StringContent stringContent = new StringContent(pair.Value as string);
formData.Add(stringContent, pair.Key);
} Stream stream = pair.Value as Stream;
if (stream != null)
{
StreamContent streamContent = new StreamContent(stream);
formData.Add(streamContent, pair.Key, filename);
}
}
var response = await client.PostAsync(reqUri, formData);
if (response.IsSuccessStatusCode)
{
using (Stream backstream = await response.Content.ReadAsStreamAsync())
{
result = JsonSerializeHelper.ReadDataFromJson<TResult>(backstream);
}
}
else
{
ErrorData err = null;
using (Stream errstream = await response.Content.ReadAsStreamAsync())
{
err = JsonSerializeHelper.ReadDataFromJson<ErrorData>(errstream);
}
throw new WeiboException(err);
}
} return result;
} internal static async Task<string> HttpGetStringAsync(string relateUrl)
{
Uri base_uri = new Uri(API_BASE_RUI);
Uri request_uri = new Uri(base_uri, relateUrl);
string result = "";
using (HttpClient client = new HttpClient())
{
result = await client.GetStringAsync(request_uri);
}
return result;
} internal static async Task<Stream> HttpGetStreamAsync(string relateUrl)
{
Uri base_uri = new Uri(API_BASE_RUI);
base_uri = new Uri(base_uri, relateUrl);
Stream streamres = null;
using (HttpClient client = new HttpClient())
{
var streamtmp = await client.GetStreamAsync(base_uri);
streamres = new MemoryStream();
streamtmp.CopyTo(streamres);
streamtmp.Dispose();
}
return streamres;
}

上面仅仅列举了一两个方法,详细的代码大家可以下载源代码看。

在我发布的解决方案中,有一个项目名为WeiboTest,它是一个单元测试项目,是我在封装API过程用来测试调用而写的。

这个SDK可用在桌面程序和Web应用程序中,对于WP和Store App,我一开始是考虑建一个可移植的Portable项目的,但由于我仅在试水阶段,后来就没考虑移植了,其实绝大部分代码是可以通用的,但有一小部分不行,比如文件操作就不能通用,因为WP上的文件是通过独立存储来处理的,而不像桌面环境中那样使用物理文件来处理。等代码完善后,我会考虑将它改为一个通用类库。

下面就介绍一下我封装了哪些API。

一、登录授权部分我合为一体了,

不使用新浪弹出的授权页面,而是直接模拟用户登录时POST的数据,直接获取Code,然后调用OAuth2的API换取Token。同时还公开用于取消授权的RevokeOAuth2Async。

二、微博模块。

  1、获取最近的公共微博列表,

  2、获取好友/关注人/自己的最新微博列表。

  3、获取与当前登录用户相互关注的用户的最新微博列表。

  4、获取某条微博的转发列表。

  5、获取@当前用户 的微博列表。

  6、根据微博ID获取单条微博的详细信息。

  7、批量获取微博的转发数和评论数。

  8、微博ID和MID的相互获取。

  9、获取微博官方表情列表。

  10、转发微博。

  11、发表微博 / 发表带图片的微博。

  12、删除微博。

三、评论模块。

  1、获取某条微博的评论列表。

  2、获取当前登录用户所发表的评论列表。

  3、获取当前用户接收到的评论列表。

  4、获取@当前用户的评论列表。

  5、批量获取评论列表。

  6、发表评论。

  7、删除指定评论。

  8、批量删除评论。

  9、回复某条评论。

四、帐号模块。

  1、获取当前用户的隐私设置信息。是设置项,不是获取隐私,别想歪了。

  2、获取学校列表,这个好像没什么用。

五、关系模块。

  1、获取当前登录用户的关注列表。

  2、获取两个用户之间的共同关注人列表。比如我关注了A,而C也关注了A,因而我和C的共同关注人就是A。

  3、获取用户的相互关注人列表。也就是有哪些人跟我互粉。

  4、获取当前用户的粉丝列表。即哪些人关注了我。

  5、获取用户的活跃粉丝列表。这个不知道干什么的,反正我测试的时候返回空。

  6、获取当前登录用户关注列表中同时关注了某用户的列表。比如,我关注了A,获取我关注的用户中也关注了A的用户列表。

  7、关注一位用户。

  8、取消关注某位用户。

六、用户模块。

  1、根据用户ID获取用户信息。

  2、批量获取用户的关注数、粉丝数、微博数。

  3、通过个性域名获取用户信息以及最新发表的一条微博。

七、短链接模块。

  1、长链接转为短链接。

  2、短链接转为长链接。

八、地理位置模块。

  1、根据IP地址返回地理信息。

  2、根据具体地址返回地理坐标信息。

  3、根据地理坐标(经度,纬度)返回地址信息。

这个SDK不算很完美,由于新浪的程序员比较菜,有些JSON数据结构相当不合理,暂时无法封装,等到哪天我想到解决方法后再补充。

随机推荐

  1. weight属性你用的真的6嘛?

    相信大家在日常开发中一定使用过weight这个属性,它的作用一个是权重,另一个就是渲染优先级,但是你真的能很6的使用它嘛?如果不是,那么请继续往下看!!! 我们知道,当weight起到不同作用的时候, ...

  2. css遮罩代码(已验证)

    #mask { background-color: rgb(0, 0, 0); display:none; opacity: 0.0; /* Safari, Opera */ -moz-opacity ...

  3. BZOJ-1861 Book 书架 Splay

    1861: [Zjoi2006]Book 书架 Time Limit: 4 Sec Memory Limit: 64 MB Submit: 1010 Solved: 588 [Submit][Stat ...

  4. 使用Spring + Jedis集成Redis

    转自:http://my.oschina.net/u/866380/blog/521658 摘要 使用Spring和Jedis完成分片Redis的集成 一.集成环境 Tomcat7 JDK1.7 Je ...

  5. mybatis0203 一对一查询 resultMap实现

    resultType实现的时候先要确定po类(数据库类)满不满足要求,如果不满足就要自定义一个pojo类(工具类). resultMap提供一对一关联查询的映射和一对多关联查询映射,一对一映射思路:将 ...

  6. MySQL性能优化神器—explain

    一.explain是什么? 简单来讲就是官方给的一个优化工具,直接在你的SQL语句前加上explain,执行整条语句,之后你就可以根据执行结果优化你的SQL啦,废话不多说,直接刚实例 测试实例 1.创 ...

  7. Windows Server 2016-域站点链接及复制时间调整

    本章简单为大家介绍如何新建域链接并调整复制计划.生产环境中很多情况下需要我们手工去创建站点复制链接,并根据实际带宽情况调整复制计划以减轻网络压力等.站点内部署多台域控制器,域控制器间的 复制链接建议通 ...

  8. openstack Q版部署-----虚拟机密码修改问题

    一.修改镜像密码 1.打开一个要修改的镜像 随便找一台centos服务器 [root@linux-node1 ~]#wget http://cloud.centos.org/centos/7/imag ...

  9. python 字符串与16进制 转化

    def str_to_hex(s): return r"/x"+r'/x'.join([hex(ord(c)).replace('0x', '') for c in s]) def ...

  10. 鼠标监听事件MouseListener

    public class Demo extends JFrame { private JTextArea textArea; public Demo() { setBounds(100, 100, 4 ...