2020-12-16

本文介绍了作者在C#中对钉钉SDK进行重构的过程,主要目标是简化代码和提高效率。作者发现了官方SDK存在的问题,如无法正常获取某些数据、代码过于臃肿等,于是决定采用JSON.NET进行对象序列化,并通过扩展函数减少调用代码行数。重构后的SDK使得请求接口的调用更简洁,同时保留了原有的功能。通过具体的代码示例展示了重构后的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();
        </span><span style="color: rgba(0, 0, 255, 1)">var</span> tokenRequest = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiGettokenRequest(corpid, corpSecret);
        </span><span style="color: rgba(0, 0, 255, 1)">var</span> token =<span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> client</span>.Execute(tokenRequest);

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (token != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">token.IsError)
        {
            </span><span style="color: rgba(0, 0, 255, 1)">string</span> id = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> request = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiDepartmentListRequest(id);
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> dept =<span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> client</span>.Execute(request, token.AccessToken);
             ...................</span></pre>
复制代码

当然,由于请求对象和应答对象,我依旧保留了原来对象的名称,只是采用了基于JSON.NET的方式来重新处理了一下对象的定义。

例如对于Token的请求和应答对象,原来的Token应答对象定义如下所示

复制代码
    /// <summary>
    /// OapiGettokenResponse.
    /// </summary>
    public class OapiGettokenResponse : DingTalkResponse
    {
        /// <summary>
        /// access_token
        /// </summary>
        [XmlElement("access_token")]
        public string AccessToken { get; set; }
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> errcode
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    [XmlElement(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">errcode</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> Errcode { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> errmsg
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    [XmlElement(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">errmsg</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Errmsg { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> expires_in
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    [XmlElement(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">expires_in</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> ExpiresIn { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

}</span></pre>
复制代码

我则使用了基于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; }
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 失效时间
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    [JsonProperty(PropertyName =<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">expires_in</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> ExpiresIn { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
复制代码

这样我在重构这些应答类的时候,所需要的只需要进行一定的替换工作即可。

而对于数据请求类,我则在基类里面增加一个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;
        }
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 参数化构造函数
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="serverurl"&gt;</span><span style="color: rgba(0, 128, 0, 1)">请求URL</span><span style="color: rgba(128, 128, 128, 1)">&lt;/param&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;param name="isPost"&gt;</span><span style="color: rgba(0, 128, 0, 1)">是否为POST方式</span><span style="color: rgba(128, 128, 128, 1)">&lt;/param&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> BaseDingTalkRequest(<span style="color: rgba(0, 0, 255, 1)">string</span> serverurl, <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> isPost) 
    {
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.ServerUrl =<span style="color: rgba(0, 0, 0, 1)"> serverurl;
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.IsPost =<span style="color: rgba(0, 0, 0, 1)"> isPost;
    }


    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 提交的数据或者增加的字符串
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> PostData 
    {
        </span><span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">
        {
            </span><span style="color: rgba(0, 0, 255, 1)">string</span> result = <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> dict =<span style="color: rgba(0, 0, 0, 1)"> GetParameters();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span>(dict != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (IsPost)
                {
                    result </span>=<span style="color: rgba(0, 0, 0, 1)"> dict.ToJson();
                }
                </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
                {
                    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">return string.Format("corpid={0}&amp;corpsecret={1}", corpid, corpsecret);</span>
                    <span style="color: rgba(0, 0, 255, 1)">foreach</span> (KeyValuePair&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">object</span>&gt; pair <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> dict)
                    {
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pair.Value != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                        {
                            result </span>+= pair.Key + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">=</span><span style="color: rgba(128, 0, 0, 1)">"</span> + pair.Value + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&amp;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
                        }
                    }
                    result </span>= result.Trim(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&amp;</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
        }
    }

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 是否POST方式(否则为GET方式)
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsPost { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 连接URL,替代DefaultDingTalkClient的serverUrl
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">string</span> ServerUrl { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }


    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> POST获取GET的参数列表
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> SortedDictionary&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">object</span>&gt; GetParameters() { <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; }

}</span></pre>
复制代码

而对于请求Token的Request等请求对象,我们继承这个基类即可,如下代码所示。

复制代码
    /// <summary>
    /// 企业内部开发获取Token的请求
    /// </summary>
    public class OapiGettokenRequest : BaseDingTalkRequest<OapiGettokenResponse>
    {
        public OapiGettokenRequest()
        {
            this.ServerUrl = "https://oapi.dingtalk.com/gettoken";
            this.IsPost = false;
        }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> OapiGettokenRequest(<span style="color: rgba(0, 0, 255, 1)">string</span> corpid, <span style="color: rgba(0, 0, 255, 1)">string</span> corpsecret) : <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">()
    {
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.Corpid =<span style="color: rgba(0, 0, 0, 1)"> corpid;
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.Corpsecret =<span style="color: rgba(0, 0, 0, 1)"> corpsecret;
    }

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 企业Id
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Corpid { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 企业应用的凭证密钥
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Corpsecret { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> SortedDictionary&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">object</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> GetParameters()
    {
        SortedDictionary</span>&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">object</span>&gt; parameters = <span style="color: rgba(0, 0, 255, 1)">new</span> SortedDictionary&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">object</span>&gt;<span style="color: rgba(0, 0, 0, 1)">();
        parameters.Add(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">corpid</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.Corpid);
        parameters.Add(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">corpsecret</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.Corpsecret);

        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> parameters;
    }
}</span></pre>
复制代码

这个请求类,也就确定了请求的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;
            }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.IsNullOrEmpty(accessToken))
        {
            url </span>+= <span style="color: rgba(0, 0, 255, 1)">string</span>.Format(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">?access_token={0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, accessToken);
        }

        </span><span style="color: rgba(0, 0, 255, 1)">string</span> content = <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">;
        HttpHelper helper </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpHelper();
        helper.ContentType </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">application/json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
        content </span>=<span style="color: rgba(0, 0, 0, 1)"> helper.GetHtml(url, request.PostData, request.IsPost);

        T json </span>= JsonConvert.DeserializeObject&lt;T&gt;<span style="color: rgba(0, 0, 0, 1)">(content);
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> json;
    }</span></pre>
复制代码

 

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();
        </span><span style="color: rgba(0, 0, 255, 1)">var</span> tokenRequest = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiGettokenRequest(corpid, corpSecret);
        </span><span style="color: rgba(0, 0, 255, 1)">var</span> token =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(tokenRequest);

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (token != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">token.IsError)
        {
            Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">获取部门信息</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

            </span><span style="color: rgba(0, 0, 255, 1)">string</span> id = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> request = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiDepartmentListRequest(id);
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> dept =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(request, token.AccessToken);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (dept != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; dept.Department != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            {
                Console.WriteLine(dept.Department.ToJson());

                Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">获取部门详细信息</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> item <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> dept.Department)
                {
                    </span><span style="color: rgba(0, 0, 255, 1)">var</span> getrequest = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiDepartmentGetRequest(item.Id.ToString());
                    </span><span style="color: rgba(0, 0, 255, 1)">var</span> info =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(getrequest, token.AccessToken);
                    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (info != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                    {
                        Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">部门详细信息:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, info.ToJson());

                        Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">获取部门用户信息</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                        </span><span style="color: rgba(0, 0, 255, 1)">var</span> userrequest = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiUserListRequest(info.Id);
                        </span><span style="color: rgba(0, 0, 255, 1)">var</span> list =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(userrequest, token.AccessToken);
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (list != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                        {
                            Console.WriteLine(list.ToJson());

                            Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">获取详细用户信息</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                            </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> userjson <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> list.Userlist)
                            { 
                                </span><span style="color: rgba(0, 0, 255, 1)">var</span> <span style="color: rgba(0, 0, 255, 1)">get</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiUserGetRequest(userjson.Userid);
                                </span><span style="color: rgba(0, 0, 255, 1)">var</span> userInfo = client.Execute(<span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">, token.AccessToken);

                                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (userInfo != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                                {
                                    Console.WriteLine(userInfo.ToJson());
                                }
                            }
                        }
                    }
                }
            }
        }
        </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
        {
            Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">处理出现错误:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, token.ErrMsg);
        }
    }</span></pre>
复制代码

从上面的代码我们可以看到,对Request请求的处理简化了很多,不用再输入烦人的URL信息,以及是否GET还是POST方式。

获取角色的处理操作如下所示。

复制代码
        private void btnRole_Click(object sender, EventArgs e)
        {
            var client = new DefaultDingTalkClient();
        </span><span style="color: rgba(0, 0, 255, 1)">var</span> tokenRequest = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiGettokenRequest(corpid, corpSecret);
        </span><span style="color: rgba(0, 0, 255, 1)">var</span> token =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(tokenRequest);

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (token != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">token.IsError)
        {
            Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">获取角色信息</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

            </span><span style="color: rgba(0, 0, 255, 1)">var</span> request = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiRoleListRequest();
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> result =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(request, token.AccessToken);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (result != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; result.Result != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; result.Result.List != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            {
                Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">角色信息:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, result.Result.List.ToJson());

                </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> info <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> result.Result.List)
                {
                    Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">角色组信息:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, info.ToJson());

                    Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">获取角色详细信息</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                    </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> roleInfo <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> info.Roles)
                    {
                        </span><span style="color: rgba(0, 0, 255, 1)">var</span> roleReq = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OapiRoleGetroleRequest(roleInfo.Id);
                        </span><span style="color: rgba(0, 0, 255, 1)">var</span> detail =<span style="color: rgba(0, 0, 0, 1)"> client.Execute(roleReq, token.AccessToken);
                        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (detail != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; detail.Role != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                        {
                            Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">角色详细信息:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, detail.Role.ToJson());
                        }
                    }
                }
            }
        }
    }</span></pre>
复制代码

获取的信息输出在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对象的类名称,整个解决方案如下所示。

 

主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
  转载请注明出处:
撰写人:伍华聪  http://www.iqidi.com 
    
感谢你提供的列名信息,我们可以看到: - `"...1"`:可能是 Excel 中第一列无标题的自动命名(比如行号或空列),可以忽略。 - `"STATION"`:站点编号 - `"NAME"`:站点名称 - `"LATITUDE"`:纬度 - `"LONGITUDE"`:经度 - `"ELEVATION"`:海拔 - `"statement"`:可能是一个状态或注释字段 - 后续从 `"2020-01-01"` 到 `"2020-01-31"` 是每日风速数据(共31天) --- ### ✅ 目标更新: 你要做的是:**根据给定的 SHP 范围裁剪站点,保留落在该地理范围内的所有站点及其完整的逐日风速数据。** 下面是适配你实际列名的完整 R 语言代码,并处理好坐标、投影和数据结构问题。 ```r # 加载所需包 library(sf) library(readxl) library(dplyr) # ------------------- 参数设置 ------------------- excel_file <- "your_wind_station_data.xlsx" # 替换为你的实际文件路径 sheet_name <- "Sheet1" # 替换为你的工作表名 shp_file <- "your_boundary.shp" # 替换为你的SHP文件路径 # ------------------- 步骤1: 读取Excel数据 ------------------- df <- read_excel(excel_file, sheet = sheet_name) # 查看列名确认 names(df) <- make.names(names(df)) # 确保列名是合法的(防止空格等问题) cat("原始列名:\n"); print(names(df)) # ------------------- 步骤2: 提取空间信息并创建sf对象 ------------------- # 使用 LATITUDE 和 LONGITUDE 创建空间点(注意:顺序是 LON, LAT) stations_sf <- st_as_sf(df, coords = c("LONGITUDE", "LATITUDE"), # 经度在前,纬度在后 crs = 4326, # WGS84 地理坐标系 dim = "XY") # ------------------- 步骤3: 读取SHP边界并确保其CRS ------------------- boundary <- st_read(shp_file) # 如果boundary不是投影坐标系(如EPSG:32649),则需要检查并转换 # 假设你知道目标投影是 UTM Zone 49N (EPSG:32649),我们统一到这个坐标系进行空间操作 if (is.na(st_crs(boundary))) { stop("SHP文件没有坐标系信息,请先定义正确的CRS!") } # 将站点数据重投影到与SHP相同的坐标系下进行空间判断 stations_projected <- st_transform(stations_sf, crs = st_crs(boundary)) # ------------------- 步骤4: 空间裁剪 —— 找出在SHP范围内的站点 ------------------- # 使用 st_intersects 或 st_within 进行空间子集提取 # 这里使用 [s,t] 语法:返回落在任意多边形内的站点 stations_clipped <- stations_projected[boundary, , op = st_intersects] # 若你想更严格地要求“完全包含”,可用 st_within,但通常 st_intersects 更通用 # ------------------- 步骤5: 转回原始经纬度并提取属性表格 ------------------- # 将结果转回WGS84以便保留原始经纬度格式输出 stations_wgs84 <- st_transform(stations_clipped, crs = 4326) # 去掉geometry列,恢复为普通data.frame,同时保留原始所有列(包括每日数据) result_df <- st_drop_geometry(stations_wgs84) # 可选:重新排序列,把时间序列放在后面 date_cols <- grep("^\\d{4}-\\d{2}-\\d{2}", names(result_df), value = TRUE) non_date_cols <- setdiff(names(result_df), date_cols) final_df <- select(result_df, c(non_date_cols, date_cols)) # 按逻辑排序 # ------------------- 步骤6: 导出结果 ------------------- write.csv(final_df, "clipped_stations_202001.csv", row.names = FALSE, na = "") cat("共保留了", nrow(final_df), "个站点在SHP范围内。\n") ``` --- ### ✅ 关键说明: | 功能 | 说明 | |------|------| | `coords = c("LONGITUDE", "LATITUDE")` | 必须是 **经度在前,纬度在后**,否则位置错误! | | `crs = 4326` | 表示输入的经纬度使用 WGS84 坐标系 | | `st_transform(...)` | 将点从地理坐标(度)转为投影坐标(米),确保与 SHP 在同一空间参考下比较 | | `st_intersects` | 判断点是否与多边形相交(即落在内部),适用于大多数情况 | | `st_drop_geometry()` | 移除空间结构,得到纯数据框用于导出 | > 💡 输出的 CSV 文件将包含原始所有列,包括 `STATION`, `NAME`, `LATITUDE`, `LONGITUDE`, `ELEVATION`, `statement` 和所有日期列(如 `2020-01-01` 等),仅保留位于 SHP 范围内的站点。 --- ### ✅ 示例输出片段(final_df 头几行): ``` ...1 STATION NAME LATITUDE LONGITUDE ELEVATION statement 2020-01-01 2020-01-02 ... 1 1 101 Beijing 39.90 116.4 50.0 good 3.2 4.1 2 2 102 Tianjin 39.08 117.2 10.0 good 5.0 3.8 ... ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值