新浪微博SDK開發(1):總述


花了幾天時間,消耗了九牛六虎之力,新浪微博大部分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數據結構相當不合理,暫時無法封裝,等到哪天我想到解決方法后再補充。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM