基于C#的钉钉SDK开发(1)--对官方SDK的重构优化
在前段时间,接触一个很喜欢钉钉并且已在内部场景广泛使用钉钉进行工厂内部管理的客户,如钉钉考勤、日常审批、钉钉投影、钉钉门禁等等方面,才体会到原来钉钉已经已经在企业上可以用的很广泛的,因此回过头来学习研究下钉钉的一些业务范围和其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的重构优化的更多相关文章
- ESP8266 RTOS SDK开发
ESP8266 RTOS SDK开发 目录 ESP8266 RTOS SDK开发 一.源码RTOS SDK包的下载和编译 二.固件烧录 1.管脚定义 三.程序例程 ## 1.PWM设置 连接MQTT ...
- 基于Live555实现RtspServer及高清高分辨率和高码率视频传输优化
基于Live555实现RtspServer及高清高码率视频传输优化 最近做了一些pc和嵌入式平台的RTSP服务器项目,大多数的要求是简单但是功能全面,并且性能还要强劲.综合考虑后,基本都是在基于liv ...
- 钉钉服务器端SDK PHP版
项目地址: https://github.com/web3d/DingtalkSDK.git 钉钉官方有些简单的demo,但封装得有些粗糙. 开发的过程中,做了一个有点小意思的工具:json数据转换为 ...
- 钉钉企业应用C#开发笔记之一(免登)
关于钉钉 钉钉是阿里推出的企业移动OA平台,本身提供了丰富的通用应用,同时其强大的后台API接入能力让企业接入自主开发的应用成为可能,可以让开发者实现几乎任何需要的功能. 近期因为工作需要研究了一下钉 ...
- 基于华为物联网IOT的应用开发 --- 基于.net 的SDK封装
最近,物联网的概念比较热门,一大批厂商抢着占领物联网的高低,包括有华为物联网.阿里云物联网.腾讯物联网.AWS物联网等等,无法一一列举,一般物联网包含设备侧开发.平台侧开发.应用侧开发,三个部分构成了 ...
- C# 钉钉第三方开发接入
钉钉开放平台 本文是针对钉钉开放平台的基于dotNetCore服务端开发和配置的描述 钉钉可开发的程序包括 企业内部应用,第三方企业应用,第三方个人应用 一.环境搭建 1.钉钉开发需要企业钉钉账号,如 ...
- CabloyJS一站式助力微信、企业微信、钉钉开发 - 钉钉篇
前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...
- CabloyJS一站式助力微信、企业微信、钉钉开发 - 企业微信篇
前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...
- CabloyJS一站式助力微信、企业微信、钉钉开发 - 微信篇
前言 现在软件开发不仅要面对前端碎片化,还要面对后端碎片化.针对前端碎片化,CabloyJS提供了pc=mobile+pad的跨端自适应方案,参见:自适应布局:pc = mobile + pad 在这 ...
随机推荐
- #5 Python面向对象(四)
前言 本节将是Python面向对象的最后一篇博文了,这节将记录类的特殊方法.特殊成员方法.旧类和新类的不同,以及一些其他知识.Go! 一.类的特殊方法 Python有三种特殊方法:实例方法.静态方法. ...
- 如何将html特殊字符编码转换成特殊字符_html十进制编码字符转回来
备注:有时候我们会莫名其妙遇到一些特殊字符: 这些字符在网页上能正常显示,但是在APP特殊情景并不识别这些字符: 如:' 这个其实是单引号: ' 百度后发现,它其实是HTML特殊 ...
- C# 操作Word 中的OLE——插入、编辑、读取 OLE
概述 OLE,Object Linking and Embedding,即对象连接与嵌入.我们在设计程序时,OLE可以用来创建复合文档,把文字.声音.图像.表格.应用程序等类型的信息组合在一起,在Wo ...
- Dynamics 365中的非交互式账号(Non-interactive User)介绍
摘要: 本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复272或者20180616可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyon ...
- 阿里巴巴AI Lab成立两年,都做了些什么?
https://mp.weixin.qq.com/s/trkCGvpW6aCgnFwLxrGmvQ 撰稿 & 整理|Debra 编辑|Debra 导读:在 2018 云栖人工智能峰会上,阿里巴 ...
- Registrator中文文档
目录 快速入门 概述 准备 运行Registrator 运行Redis 下一步 运行参考 运行Registrator Docker选项 Registrator选项 Consul ACL令牌 注册URI ...
- 2018年IOS/Android UI设计规范
更多参考: 2017最新设计尺寸及规范 UI : 2018年IOS/Android UI设计规范 转载:https://www.jianshu.com/p/03e5cdd4ffd6
- C# MessageBox自动关闭
本文以一个简单的小例子,介绍如何让MessageBox弹出的对话框,在几秒钟内自动关闭.特别是一些第三方插件(如:dll)弹出的对话框,最为适用.本文仅供学习分享使用,如有不足之处,还请指正. 概述 ...
- Docker创建JIRA 7.2.4中文破解版
目录 目录 1.介绍 1.1.什么是JIRA? 2.JIRA的官网在哪里? 3.如何下载安装? 4.对JIRA进行配置 4.1.打开浏览器:http://localhost:20012 4.2.JIR ...
- python 通过元类控制类的创建
一.python中如何创建类? 1. 直接定义类 class A: a = 'a' 2. 通过type对象创建 在python中一切都是对象 在上面这张图中,A是我们平常在python中写的类,它可以 ...