在前段时间,接触一个很喜欢钉钉并且已在内部场景广泛使用钉钉进行工厂内部管理的客户,如钉钉考勤、日常审批、钉钉投影、钉钉门禁等等方面,才体会到原来钉钉已经已经在企业上可以用的很广泛的,因此回过头来学习研究下钉钉的一些业务范围和其SDK的开发工作。钉钉官方的SDK提供了很多方面的封装,不过相对于Java,.NET版本的一直在变化当中,之前研究钉钉C#版本SDK的时候发现一些问题反映给钉钉开发人员,基本上得不到好的解决和回应,而在使用官方的SDK的时候,有些数据竟然无法正常获取(如角色的信息等),而且官方的SDK使用的时候觉得代码较为臃肿,因此萌生了对钉钉官方SDK进行全面重构的想法。本系列随笔将对整个钉钉SDK涉及的范围进行分析重构,并分享使用过程中的效果和乐趣。

1、钉钉的介绍

钉钉(DingTalk)是阿里巴巴集团专为中国企业打造的免费沟通和协同的多端平台,提供PC版,Web版和手机版,支持手机和电脑间文件互传。 钉钉是阿里集团专为中国企业打造的通讯、协同的免费移动办公平台,帮助企业内部沟通和商务沟通更加高效安全。

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 = "";
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 = "";
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的重构优化的更多相关文章

  1. ESP8266 RTOS SDK开发

    ESP8266 RTOS SDK开发 目录 ESP8266 RTOS SDK开发 一.源码RTOS SDK包的下载和编译 二.固件烧录 1.管脚定义 三.程序例程 ## 1.PWM设置 连接MQTT ...

  2. 基于Live555实现RtspServer及高清高分辨率和高码率视频传输优化

    基于Live555实现RtspServer及高清高码率视频传输优化 最近做了一些pc和嵌入式平台的RTSP服务器项目,大多数的要求是简单但是功能全面,并且性能还要强劲.综合考虑后,基本都是在基于liv ...

  3. 钉钉服务器端SDK PHP版

    项目地址: https://github.com/web3d/DingtalkSDK.git 钉钉官方有些简单的demo,但封装得有些粗糙. 开发的过程中,做了一个有点小意思的工具:json数据转换为 ...

  4. 钉钉企业应用C#开发笔记之一(免登)

    关于钉钉 钉钉是阿里推出的企业移动OA平台,本身提供了丰富的通用应用,同时其强大的后台API接入能力让企业接入自主开发的应用成为可能,可以让开发者实现几乎任何需要的功能. 近期因为工作需要研究了一下钉 ...

  5. 基于华为物联网IOT的应用开发 --- 基于.net 的SDK封装

    最近,物联网的概念比较热门,一大批厂商抢着占领物联网的高低,包括有华为物联网.阿里云物联网.腾讯物联网.AWS物联网等等,无法一一列举,一般物联网包含设备侧开发.平台侧开发.应用侧开发,三个部分构成了 ...

  6. C# 钉钉第三方开发接入

    钉钉开放平台 本文是针对钉钉开放平台的基于dotNetCore服务端开发和配置的描述 钉钉可开发的程序包括 企业内部应用,第三方企业应用,第三方个人应用 一.环境搭建 1.钉钉开发需要企业钉钉账号,如 ...

  7. CabloyJS一站式助力微信、企业微信、钉钉开发 - 钉钉篇

    前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...

  8. CabloyJS一站式助力微信、企业微信、钉钉开发 - 企业微信篇

    前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...

  9. CabloyJS一站式助力微信、企业微信、钉钉开发 - 微信篇

    前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...

随机推荐

  1. linux 防火墙详细介绍

    1.其实匹配扩展中,还有需要加-m引用模块的显示扩展,默认是隐含扩展,不要使用 -m状态检测的包过滤-m state       --state {NEW,ESTATBLISHED,INVALID,R ...

  2. Mybatis sql映射文件浅析 Mybatis简介(三)

    简介 除了配置相关之外,另一个核心就是SQL映射,MyBatis 的真正强大也在于它的映射语句. Mybatis创建了一套规则以XML为载体映射SQL 之前提到过,各项配置信息将Mybatis应用的整 ...

  3. eclipse svn插件卸载 重新安装 Subclipse卸载安装 The project was not built since its build path is incomplete This client is too old to work with the working copy at

    安装插件的原则就是,要按照规则,插件与本地的svn版本要一致, 这样子本地和eclipse上面就可以无缝使用,不会出现问题 1.卸载eclipse  svn插件 2,安装新版的svn插件 2.1,下载 ...

  4. 【Node.js】二、基于Express框架 + 连接MongoDB + 写后端接口

    在上节,我们讲了如何搭建express环境,现在我们说说如何通过node.js写服务接口给前端调用 1. 首先通过MongoDB建好数据库与表格 例如,我的数据库名字为db_demo,数据库表格为go ...

  5. Restful API设计规范及实战

    Restful API的概念在此就不费口舌了,博友们网上查哈定义文章很多,直入正题吧: 首先抛出一个问题:判断id为 用户下,名称为 使命召唤14(COD14) 的产品是否存在(话说我还是很喜欢玩类似 ...

  6. Java 浅拷贝和深拷贝

    一看就懂的,java深拷贝浅拷贝 将一个对象的引用复制给另外一个对象,一共有三种方式.第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝.所以大家知道了哈,这三种概念实际上都是为了拷贝对象啊. ...

  7. js 高级程序设计(笔记)

    第二章 1.为了避免浏览器在呈现页面时出现明显的延迟,现代Web 应用程序一般都把全部JavaScript 引用放在<body>元素中页面内容的后面. 第三章 1.ECMAScript 中 ...

  8. 少侠学代码系列(二)->JS实现

    少侠:小子,休息好了没,赶紧的 帅气的我:好了好了,嚷什么 少侠:(拔刀)嗯? 帅气的我:少侠,淡定淡定,我们来看秘籍吧,刚刚我们说了JS实现是由三个部分组成的 核心(ECMAScript),文档对象 ...

  9. 操作系统:修改VirtualBox for Mac的虚拟硬盘大小

    我安装的是Mac版的VirtualBox,不能从GUI上修改硬盘大小,但是实在是大小不够用了. 百度后得知,可以用命令行修改. 1.打开终端,输入sudo su,取得管理员权限 $ sudo su P ...

  10. cmd实现批量文件的base64加密并双击加密文件后正常运行

    之前,一个朋友让我用cmd做个简单的文档加密(base64),对于不太懂电脑的人来说看不懂就行.但是当那个人点击加密后的文件可以正常运行,问咋写? 其实,像这种要求不高的加密来说,随便下载个加密软件就 ...