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

来源:https://www.cnblogs.com/wuhuacong/archive/2018/09/06/9593483.html
-Advertisement-
Play Games

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


在前段時間,接觸一個很喜歡釘釘並且已在內部場景廣泛使用釘釘進行工廠內部管理的客戶,如釘釘考勤、日常審批、釘釘投影、釘釘門禁等等方面,才體會到原來釘釘已經已經在企業上可以用的很廣泛的,因此回過頭來學習研究下釘釘的一些業務範圍和其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對象的類名稱,整個解決方案如下所示。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 採用nginx和.net core 部署一套api介面到伺服器上,發現獲取到的ip地址為127.0.0.1 經過檢查發現,需要在nginx配置上以下參數 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; pro ...
  • 兩個類之間交互 ...
  • 公司電腦禁止,只有一個老的vs2017的安裝目錄(之前通過 --layout 安裝時生成的離線文件)。找了一圈百度,沒能解決問題,最後,問bing,查微軟的官方網站命令,最後得到的結論相當簡單: ...
  • abstract as base bool break byte case catch char checked decimal default delegate continue double do else enum ecent explicit finally fixed float for ...
  • C# 運算符重載 您可以重定義或重載 C# 中內置的運算符。因此,程式員也可以使用用戶自定義類型的運算符。重載運算符是具有特殊名稱的函數,是通過關鍵字 operator後跟運算符的符號來定義的。與其他函數一樣,重載運算符有返回類型和參數列表。 例如,請看下麵的函數: public static Bo ...
  • 低版本IE6/7/8/9瀏覽器沒有定義console對象,所以代碼會中斷執行。自己測試,ie11也沒有(打開控制台的情況下可以用) 可以用如下代碼完美解決。 ...
  • C# 多態性 多態性意味著有多重形式。在面向對象編程範式中,多態性往往表現為"一個介面,多個功能"。 多態性可以是靜態的或動態的。在靜態多態性中,函數的響應是在編譯時發生的。在動態多態性中,函數的響應是在運行時發生的。 多態性意味著有多重形式。在面向對象編程範式中,多態性往往表現為"一個介面,多個功 ...
  • 一、Quartz.Net介紹 Quartz.NET是一個開源的作業調度框架,非常適合在平時的工作中,定時輪詢資料庫同步,定時郵件通知,定時處理數據等。 Quartz.NET允許開發人員根據時間間隔(或天)來調度作業。它實現了作業和觸發器的多對多關係,還能把多個作業與不同的觸發器關聯。整合了 Quar ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...