基於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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...