1. 程式人生 > >基於C#的釘釘SDK開發(1)--對官方SDK的重構優化

基於C#的釘釘SDK開發(1)--對官方SDK的重構優化

在前段時間,接觸一個很喜歡釘釘並且已在內部場景廣泛使用釘釘進行工廠內部管理的客戶,如釘釘考勤、日常審批、釘釘投影、釘釘門禁等等方面,才體會到原來釘釘已經已經在企業上可以用的很廣泛的,因此回過頭來學習研究下釘釘的一些業務範圍和其SDK的開發工作。釘釘官方的SDK提供了很多方面的封裝,不過相對於Java,.NET版本的一直在變化當中,之前研究釘釘C#版本SDK的時候發現一些問題反映給釘釘開發人員,基本上得不到好的解決和迴應,而在使用官方的SDK的時候,有些資料竟然無法正常獲取(如角色的資訊等),而且官方的SDK使用的時候覺得程式碼較為臃腫,因此萌生了對釘釘官方SDK進行全面重構的想法。本系列隨筆將對整個釘釘SDK涉及的範圍進行分析重構,並分享使用過程中的效果和樂趣。

1、釘釘的介紹

 釘釘(DingTalk)是阿里巴巴集團專為中國企業打造的免費溝通和協同的多端平臺,提供PC版,Web版和手機版,支援手機和電腦間檔案互傳。 釘釘是阿里集團專為中國企業打造的通訊、協同的免費移動辦公平臺,幫助企業內部溝通和商務溝通更加高效安全。

manageme_background

2、使用釘釘官方SDK存在的一些問題或不足

一般我們在開發的時候,傾向於使用現有的輪子,而不是重複發明輪子。不過如果輪子確實不適合或者有更好的想法,那就花點功夫也無妨。

在使用原有的釘釘SDK的時候,發現存在以下一些問題。

1)部分SDK由於引數或者其他問題,導致獲取到的JSON資料無法序列化為正常的屬性,如前段時間的角色列表資訊部分(後來修復了這個問題)。

2)使用SDK物件的程式碼過於臃腫,一些固定化的引數在使用過程中還需要傳入,不太必要而且增加了很多呼叫程式碼。

3)對JSON序列化的部分,沒有采用JSON.NET(Newtonsoft.Json.dll)的標準化方案,而是利用了自定義的JSON解析類,導致整個釘釘SDK的解析過程繁雜很多。

4)對整個釘釘SDK的設計顯得過於複雜而不容易修改。

5)其他一些看不慣的原因

為了避免大範圍的變化導致整個使用介面也變化,我在重構過程中,儘量還是保留釘釘的使用介面,希望使用者能夠無縫對接我重構過的釘釘SDK介面,因此我在極力簡化釘釘SDK的設計過程的時候,儘量相容使用的介面。

而且由於我引入了Json.NET的物件標準序列化和反序列化的處理後,發現程式碼確實簡化了不少,對於重構工作提供了非常的方便。

 我們來對比一下原有釘釘SDK介面的使用程式碼和重構釘釘SDK的使用程式碼。

            IDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
            OapiGettokenRequest request = new OapiGettokenRequest();
            request.Corpid = corpid;
            request.Corpsecret = corpSecret;
            request.SetHttpMethod("GET");
            OapiGettokenResponse response = client.Execute(request);
            return response;

上面的程式碼就是釘釘標準官方SDK的使用程式碼,用來獲取token資訊的一個介面。

其實這個初始化DefaultDingTalkClient,並準備使用 OapiGettokenRequest來獲取應答物件的時候,我們可以把這個URL(https://oapi.dingtalk.com/gettoken)封裝在請求裡面的,不需要使用的時候再去找這個URL,而且對應OapiGettokenRequest 請求的時候,資料提交方式POST或者GET方式也應該確定下來了,不需要使用者再去設定較好。

使用者引數比較少的情況下,可以使用建構函式傳遞,減少程式碼的行數。

然後利用擴充套件函式的方式,我們還可以進一步減少呼叫的程式碼行數的。

我們來看看,我重構程式碼後的呼叫過程,簡化為兩行程式碼即可:

            var request = new OapiGettokenRequest(corpid, corpSecret);
            var response = new DefaultDingTalkClient().Execute(request);

使用擴充套件函式的輔助,我們還可以簡化為一行程式碼,如下所示

var token = new OapiGettokenRequest(corpid, corpSecret).Execute();

對於前面N行程式碼,變為目前的一行程式碼,效果是一樣的,這個就是我希望的效果:簡單是美

如果對於多個Request的呼叫,我們也可以重用DingTalkClient物件的,如下程式碼所示。

            var client = new DefaultDingTalkClient();

            var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
            var token = client.Execute(tokenRequest);

            if (token != null && !token.IsError)
            {
                string id = "1";
                var request = new OapiDepartmentListRequest(id);
                var dept = client.Execute(request, token.AccessToken);
                 ...................

當然,由於請求物件和應答物件,我依舊保留了原來物件的名稱,只是採用了基於JSON.NET的方式來重新處理了一下物件的定義。

例如對於Token的請求和應答物件,原來的Token應答物件定義如下所示

    /// <summary>
    /// OapiGettokenResponse.
    /// </summary>
    public class OapiGettokenResponse : DingTalkResponse
    {
        /// <summary>
        /// access_token
        /// </summary>
        [XmlElement("access_token")]
        public string AccessToken { get; set; }

        /// <summary>
        /// errcode
        /// </summary>
        [XmlElement("errcode")]
        public long Errcode { get; set; }

        /// <summary>
        /// errmsg
        /// </summary>
        [XmlElement("errmsg")]
        public string Errmsg { get; set; }

        /// <summary>
        /// expires_in
        /// </summary>
        [XmlElement("expires_in")]
        public long ExpiresIn { get; set; }

    }

我則使用了基於JSON.NET的標註來替代XmlElement的標註,並簡化了部分基類屬性。這樣Json的屬性名稱雖然是小寫,但是我們轉換為對應實體類後,它的屬性則可以轉換為.NET標準的Pascal方式的屬性名稱。

    /// <summary>
    /// 企業內部開發獲取access_token的應答.
    /// </summary>
    public class OapiGettokenResponse : DingTalkResponse
    {
        /// <summary>
        /// 開放應用的token
        /// </summary>
        [JsonProperty(PropertyName ="access_token")]
        public string AccessToken { get; set; }

        /// <summary>
        /// 失效時間
        /// </summary>
        [JsonProperty(PropertyName ="expires_in")]
        public long ExpiresIn { get; set; }
    }

這樣我在重構這些應答類的時候,所需要的只需要進行一定的替換工作即可。

而對於資料請求類,我則在基類裡面增加一個IsPost屬性來標識是否為POST方式,否則為GET方式的HTTP資料請求方式。

然後根據引數和IsPost的屬性,來構建提交的PostData資料。

如我修改原有的BaseDingTalkRequest基類物件程式碼為下面的程式碼。

    /// <summary>
    /// 基礎TOP請求類,存放一些通用的請求引數。
    /// </summary>
    public abstract class BaseDingTalkRequest<T> : IDingTalkRequest<T> where T : DingTalkResponse
    {   
        /// <summary>
        /// 建構函式
        /// </summary>
        public BaseDingTalkRequest()
        {
            this.IsPost = true;
        }

        /// <summary>
        /// 引數化建構函式
        /// </summary>
        /// <param name="serverurl">請求URL</param>
        /// <param name="isPost">是否為POST方式</param>
        public BaseDingTalkRequest(string serverurl, bool isPost) 
        {
            this.ServerUrl = serverurl;
            this.IsPost = isPost;
        }


        /// <summary>
        /// 提交的資料或者增加的字串
        /// </summary>
        public string PostData 
        {
            get
            {
                string result = "";
                var dict = GetParameters();
                if(dict != null)
                {
                    if (IsPost)
                    {
                        result = dict.ToJson();
                    }
                    else
                    {
                        //return string.Format("corpid={0}&corpsecret={1}", corpid, corpsecret);
                        foreach (KeyValuePair<string, object> pair in dict)
                        {
                            if (pair.Value != null)
                            {
                                result += pair.Key + "=" + pair.Value + "&";
                            }
                        }
                        result = result.Trim('&');
                    }
                }
                return result;
            }
        }

        /// <summary>
        /// 是否POST方式(否則為GET方式)
        /// </summary>
        public virtual bool IsPost { get; set; }

        /// <summary>
        /// 連線URL,替代DefaultDingTalkClient的serverUrl
        /// </summary>
        public virtual string ServerUrl { get; set; }


        /// <summary>
        /// POST獲取GET的引數列表
        /// </summary>
        /// <returns></returns>
        public virtual SortedDictionary<string, object> GetParameters() { return null; }

    }

而對於請求Token的Request等請求物件,我們繼承這個基類即可,如下程式碼所示。

    /// <summary>
    /// 企業內部開發獲取Token的請求
    /// </summary>
    public class OapiGettokenRequest : BaseDingTalkRequest<OapiGettokenResponse>
    {
        public OapiGettokenRequest()
        {
            this.ServerUrl = "https://oapi.dingtalk.com/gettoken";
            this.IsPost = false;
        }

        public OapiGettokenRequest(string corpid, string corpsecret) : this()
        {
            this.Corpid = corpid;
            this.Corpsecret = corpsecret;
        }

        /// <summary>
        /// 企業Id
        /// </summary>
        public string Corpid { get; set; }

        /// <summary>
        /// 企業應用的憑證金鑰
        /// </summary>
        public string Corpsecret { get; set; }

        public override SortedDictionary<string, object> GetParameters()
        {
            SortedDictionary<string, object> parameters = new SortedDictionary<string, object>();
            parameters.Add("corpid", this.Corpid);
            parameters.Add("corpsecret", this.Corpsecret);

            return parameters;
        }
    }

這個請求類,也就確定了請求的URL和資料請求方式(GET、POST),這樣在呼叫的時候,就不用再次指定這些引數了,特別在反覆呼叫的時候,簡化了很多。

通過這幾個類的定義,我們應該對我重構整個釘釘SDK的思路有所瞭解了,基本上就是以細節儘量封裝、簡化使用程式碼的原則進行全面重構的。

而整體的思路還是基於釘釘官方的SDK基礎上進行的。

而對於釘釘SDK的核心類 DefaultDingTalkClient,我們則進行大量的修改重構處理,簡化原來的程式碼(從原來的430行程式碼簡化到90行),而實現功能一樣的。

主要的邏輯就是我們使用了JSON.NET的標準化序列化的方式,減少了釘釘SDK的繁雜的序列化處理,而前面使用了PostData、IsPost屬性也是簡化了請求的處理方式。

        /// <summary>
        /// 執行TOP隱私API請求。
        /// </summary>
        /// <typeparam name="T">領域物件</typeparam>
        /// <param name="request">具體的TOP API請求</param>
        /// <param name="accessToken">使用者會話碼</param>
        /// <param name="timestamp">請求時間戳</param>
        /// <returns>領域物件</returns>
        public T Execute<T>(IDingTalkRequest<T> request, string accessToken, DateTime timestamp) where T : DingTalkResponse
        {
            string url = this.serverUrl;
            //如果已經設定了,則以Request的為主
            if(!string.IsNullOrEmpty(request.ServerUrl))
            {
                url = request.ServerUrl;
            }

            if (!string.IsNullOrEmpty(accessToken))
            {
                url += string.Format("?access_token={0}", accessToken);
            }

            string content = "";
            HttpHelper helper = new HttpHelper();
            helper.ContentType = "application/json";
            content = helper.GetHtml(url, request.PostData, request.IsPost);

            T json = JsonConvert.DeserializeObject<T>(content);
            return json;
        }

3、使用重構的釘釘SDK

1)重構程式碼封裝的呼叫

 為了便於介紹對重構的釘釘SDK的使用情況,我編寫了幾個功能進行測試介面。

獲取Token的操作程式碼如下所示。

        private void btnGetToken_Click(object sender, EventArgs e)
        {
            //獲取訪問Token
            var request = new OapiGettokenRequest(corpid, corpSecret);
            var response = new DefaultDingTalkClient().Execute(request);
            Console.WriteLine(response.ToJson());
        }

對部門資訊及詳細資訊的處理程式碼如下所示。

        private void btnDept_Click(object sender, EventArgs e)
        {
            var client = new DefaultDingTalkClient();

            var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
            var token = client.Execute(tokenRequest);

            if (token != null && !token.IsError)
            {
                Console.WriteLine("獲取部門資訊");

                string id = "1";
                var request = new OapiDepartmentListRequest(id);
                var dept = client.Execute(request, token.AccessToken);
                if (dept != null && dept.Department != null)
                {
                    Console.WriteLine(dept.Department.ToJson());

                    Console.WriteLine("獲取部門詳細資訊");
                    foreach (var item in dept.Department)
                    {
                        var getrequest = new OapiDepartmentGetRequest(item.Id.ToString());
                        var info = client.Execute(getrequest, token.AccessToken);
                        if (info != null)
                        {
                            Console.WriteLine("部門詳細資訊:{0}", info.ToJson());

                            Console.WriteLine("獲取部門使用者資訊");
                            var userrequest = new OapiUserListRequest(info.Id);
                            var list = client.Execute(userrequest, token.AccessToken);
                            if (list != null)
                            {
                                Console.WriteLine(list.ToJson());

                                Console.WriteLine("獲取詳細使用者資訊");
                                foreach (var userjson in list.Userlist)
                                { 
                                    var get = new OapiUserGetRequest(userjson.Userid);
                                    var userInfo = client.Execute(get, token.AccessToken);

                                    if (userInfo != null)
                                    {
                                        Console.WriteLine(userInfo.ToJson());
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("處理出現錯誤:{0}", token.ErrMsg);
            }
        }

從上面的程式碼我們可以看到,對Request請求的處理簡化了很多,不用再輸入煩人的URL資訊,以及是否GET還是POST方式。

獲取角色的處理操作如下所示。

        private void btnRole_Click(object sender, EventArgs e)
        {
            var client = new DefaultDingTalkClient();

            var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
            var token = client.Execute(tokenRequest);

            if (token != null && !token.IsError)
            {
                Console.WriteLine("獲取角色資訊");

                var request = new OapiRoleListRequest();
                var result = client.Execute(request, token.AccessToken);
                if (result != null && result.Result != null && result.Result.List != null)
                {
                    Console.WriteLine("角色資訊:{0}", result.Result.List.ToJson());

                    foreach (var info in result.Result.List)
                    {
                        Console.WriteLine("角色組資訊:{0}", info.ToJson());

                        Console.WriteLine("獲取角色詳細資訊");
                        foreach (var roleInfo in info.Roles)
                        {
                            var roleReq = new OapiRoleGetroleRequest(roleInfo.Id);
                            var detail = client.Execute(roleReq, token.AccessToken);
                            if (detail != null && detail.Role != null)
                            {
                                Console.WriteLine("角色詳細資訊:{0}", detail.Role.ToJson());
                            }
                        }
                    }
                }
            }
        }

獲取的資訊輸出在VS的輸出窗體裡面。

2)使用擴充套件函式簡化程式碼

從上面的程式碼來看,我們看到 DefaultDingTalkClient 還是有點臃腫,我們還可以通過擴充套件函式來對請求進行優化處理。如下程式碼

                var client = new DefaultDingTalkClient();
                var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
                var token = client.Execute(tokenRequest);

我們通過擴充套件函式實現的話,那麼程式碼還可以進一步簡化,如下所示。

var token = new OapiGettokenRequest(corpid, corpSecret).Execute();

對於擴充套件函式的封裝,我們就是把對應的介面IDingTalkRequest增加擴充套件函式即可,如下程式碼所示。

以上就是我對釘釘SDK進行整體化重構的過程,由於我需要把所有的Request和Response兩種型別的類轉換為我需要的內容,因此需要全部的類進行統一處理,每個Request類我需要參考官方提供的URL、POST/GET方式,同時需要進行JSON.NET的標誌替換,以及修改相應的內容,工作量還是不小的,不過為了後期釘釘的整體開發方面,這點付出我覺得應該是值得的。

我對不同業務範圍的定Request和Response進行歸類,把不同的業務範圍放在不同的目錄裡面,同時保留原來的Request和Response物件的類名稱,整個解決方案如下所示。

相關推薦

基於C#的SDK開發1--官方SDK重構優化

在前段時間,接觸一個很喜歡釘釘並且已在內部場景廣泛使用釘釘進行工廠內部管理的客戶,如釘釘考勤、日常審批、釘釘投影、釘釘門禁等等方面,才體會到原來釘釘已經已經在企業上可以用的很廣泛的,因此回過頭來學習研究下釘釘的一些業務範圍和其SDK的開發工作。釘釘官方的SDK提供了很多方面的封裝,不過相對於Java,.NET

基於Python的微信開發1:Hello World

需要安裝一個外掛,itchat。 pip install itchat 然後可以去“圖靈機器人”上註冊一個號,它具有自動回覆功能…… 圖靈機器人 新建一個機器人。 拿到APIkey以後,就可以

EOS Dapp開發1-基於Docker的開發環境搭建

rbo err 通過命令 plugin cat cti nec docker 反饋 隨著EOS主網的上線,相信基於EOS的Dapp開發會越來越多,查閱了很多資料相關的開發資料都不是很多,只能自己摸索,按照網上僅有的幾篇教程,先git clonehttps://github.

基於MT7688的OpenWrt學習筆記1——開發環境搭建

                                          &

C++ Linux伺服器開發1——極速入門必備命令

1.shell簡介 shell是運維和系統管理員操作Linux系統的首選,是一個命令直譯器 命令列---------------->解釋執行 命令列相關:        行首“$”或"#“---------------

以太坊ETH DAPP開發1:實戰開發基於truffle

一、開發環境配置 1、硬體配置 2、依賴工具版本 ~/eth_workspace$geth version Geth Version: 1.8.18-stable Architecture: amd64 Protocol Versions: [63 62] Network Id:

C# 7.0 新特性1基於Tuple的“多”返回值方法

本文基於Roslyn專案中的Issue:#347 展開討論. 回顧 首先,提出一個問題,C#中,如何使一個方法可返回”多個”返回值? 我們先來回顧一下C#6.0 及更早版本的做法。 在C#中,通常我們有以下4種方式使一個方法返回多條資料。 使用 KeyVal

跨平臺C++伺服器程式開發 1瞭解跨平臺開發

跨平臺伺服器程式的作用 所謂跨平臺,主要指Windows和Linux兩個主要平臺。如今絕大多數伺服器後端程式執行在Linux平臺,這是因為Linux具有免費、開源、遠端操控方便、易於大規模運維管理等優點,相比之下,Windows平臺更適用於個人使用者的辦公和娛

基於MFC的USB上位機開發1概述

延伸閱讀: 基於MFC的USB上位機開發(1)概述 基於MFC的USB上位機開發(2)速度測試模組 基於MFC的USB上位機開發(3)資料傳輸模組 基於MFC的USB上位機開發(4)環路模組 基於MFC的USB上位機開發(5)下環路模組 目錄 1 工程準備

最新基於高德地圖的android進階開發1獲取 Map API Key

1.本應用是基於高德地圖的開發為了是能呼叫MAP服務,後面的開發中會公開github原始碼地址。 2.為了應用程式中呼叫第三方Map服務,必須獲取第三方的Map服務的API Key,所以首先在高德官網上註冊賬號,並建立應用如下圖中所示 3.在建立

Visual Studio 2010基於SNMP++開發1

Visual Studio 2010基於SNMP++開發(1) 轉自 http://blog.sina.com.cn/s/blog_8ce3de3b0100v8h3.html 最近一直在研究利用SNMP++的包,在Visual Studio 2010上進行開發。

使用bottle進行web開發1:hello world

matches 動態 bsp allow 模塊 開發 code spec converter 為什麽使用bottle?因為簡單,就一個py文件,和其他模塊沒有依賴,3000多行代碼。 http://www.bottlepy.org/docs/dev/ 既然開始學習

Python web 開發1——新建項目

mage ati 成功 logs web make == 技術分享 blog 1、新建 一個virtulenv mkvirtulenv mxonlie 2、在mxonlie 下安裝Django pip install django==1.9 ps: 為

2018-1-15性能測試之虛擬用戶開發1

base window 體系 工作 網絡 平臺 嵌入 class 神馬 1.1Vuser開發前的準備 1)深入了解系統功能:深入了解系統是進行性能需求分析的前提。2)深入了解系統架構:分析系統的架構弄清楚開展測試需要做哪些準備工作,系統潛在的壓力點在哪,確定重點模擬用戶的哪

56. Python saltstack 二次開發1

方案 roo 刪除 res salt-run file 並不會 font 第一次 Saltstack簡介Salt 是:一個配置管理系統,能夠維護預定義狀態的遠程節點(比如,確保指定的報被安裝,指定的服務在運行);一個分布式遠程執行系統,用來在遠程節點(可以是單個節點,也可以

OpenCV開發1——OpenCV3.4+Python3.5+Windows10安裝問題解決

opencv3.4 Python3.5 opencv-python ImportError DLL load failed OpenCV近幾年功能不斷增強,目標檢測、跟蹤等方面出現了不少新算法。自3.3版開始,火熱的深度神經網絡的功能也加入其中。早期的OpenCV僅支持簡單的視頻播放功能,

基於Tomcat的JSP 詳解1—— 概述

normal pad san borde orm ace text pin style 們使用。 一.為什麽使用JSP 下面基於Tomcat的JSP 詳解(1)—— 概述

Linux驅動開發1——最簡Linux驅動

#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("TOPEET"); static int hello_init(v

熟練使用Lua面向物件:基於table的面向物件實現1

轉:https://www.cnblogs.com/yao2yaoblog/p/6433553.html c++和java語言機制中本身帶有面向物件的內容,而lua設計的思想是超程式設計,沒有面向物件的實現。 但是利用lua的元表(matetable)機制,可以實現面向物件。要講清楚怎樣

C++11 右值引用1

先參考上一節  C++11 左值 右值 ,本節是右值引用的基礎及判斷方法。 一 右值引用 C++11新增的右值引用概念,用&&表示。 二 引用型別 引用型別 可以引用的值類別 備註