一、项目背景
现在钉钉已经成为中国企业进入智能移动办公时代的一种新型工作方式,同时钉钉也允许企业将自身系统接入钉钉,更多的包容性、同样的快捷性,为钉钉赢来了越来越好的口碑。
前段时间我们公司就将内部系统接入到钉钉。如果说你之前做过微信的接入,那么在钉钉的接入上会很容易上手,因为不得不说钉钉的接入比微信要更简单、开发文档的思路也清晰很多!
二、开始接入
1、开始接入钉钉之前,首先需要了解企业接入钉钉的概述:https://open-doc.dingtalk.com/microapp/bgb96b/klan6s
2、了解完成之后,就需要注册钉钉企业并登陆钉钉企业后台。然后就关键的一步就是获取企业Cor平ID和CorpSecret:
https://open-doc.dingtalk.com/microapp/bgb96b/pw80ts
这里的CorpID和CorpSecret是为了接入钉钉获取Access_Token,在后面的很多接口中都必须使用。
3、如果需要将公司内部系统的组织架构、人员信息、角色管理、考勤管理、签到、审批等接入钉钉,可以通过浏览钉钉的开发者文档了解详细的信息:
4、在企业接入钉钉之后,还可以在企业工作台创建微应用,并可以在人员账户接入钉钉之后,实现企业应用的免登:
https://open-doc.dingtalk.com/microapp/bgb96b/ooekwb
5、另外,钉钉的接入工作还有很多,我这里总结了很多实用的链接,仅供大家参考。如果对大家有帮助,请点一个赞!
1、钉钉第三方免登;
2、钉钉消息推送;
3、服务端API调试;
4、钉钉API接口总览;
5、考勤接口;
6、人员部门接口;
7、免登接口;
8、调试工具;
9、钉钉弹窗业务;
10、钉钉RC版(下载钉钉RC版之后和钉钉一样登录,在点击工作台应用之后,浏览器输入http://localhost:16888/即可对工作台应用进行调试,是调试工作台应用的非常不错的工具)。
三、代码实现
1、获取AccessToken。AccessToken是通过企业CorpID和CorpSecret换取的接入钉钉的凭证,基本所有的钉钉接口都需要AccessToken,所以第一步就是获取AccessToken。钉钉为AccessToken提供的有效时长为7200s,在有效时间内每次请求都将自动延时,而钉钉提的要求是不允许对AccessToken进行高频率请求。所以我们可以采用缓存的方式,将AccessToken缓存起来,缓存时间少于7200s,即可在每次失效前再次延长。
private static String dd_host = ConfigurationManager.AppSettings["DDHost"];
private static String dd_corpid = ConfigurationManager.AppSettings["DD_corpid"];
private static String dd_corpsecret = ConfigurationManager.AppSettings["DD_corpsecret"];
private static String dd_accesstoken = string.Empty;
private static DateTime dd_accesstokentime;
public static String appSecret = string.Empty;
/// <summary>
/// 发起请求
/// </summary>
/// <param name="url">地址</param>
/// <param name="data">数据</param>
/// <param name="reqtype">请求类型</param>
/// <returns></returns>
private static String Request(string url, string data, string reqtype)
{
if (url.IndexOf('?') == -1 && url != "gettoken")
url += ("?access_token=" + dd_accesstoken);
else if (url.IndexOf('?') > -1 && url.IndexOf("gettoken") == -1)
url += ("&access_token=" + dd_accesstoken);
HttpWebRequest web = (HttpWebRequest)HttpWebRequest.Create(dd_host + url);
web.ContentType = "application/json";
web.Method = reqtype;
if (data.Length > 0 && reqtype.Trim().ToUpper() == "POST")
{
var parameters = new Dictionary<string, string>();
parameters.Add("body", data);
string sign = SignTopRequest(parameters, appSecret, Constants.SIGN_METHOD_MD5);
url += ("&sign=" + sign);
byte[] postBytes = Encoding.UTF8.GetBytes(data);
web.ContentLength = postBytes.Length;
using (Stream reqStream = web.GetRequestStream())
{
reqStream.Write(postBytes, 0, postBytes.Length);
}
}
string html = string.Empty;
using (HttpWebResponse response = (HttpWebResponse)web.GetResponse())
{
Stream responseStream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
html = streamReader.ReadToEnd();
}
return html;
}
/// <summary>
/// 更新AccessToken
/// </summary>
public static void GetAccessToken()
{
//从缓存中获取Token,如果缓存中已经过期,再从接口获取;
object DDToken = CacheHelper.GetCache("dd_accesstoken");
if (DDToken == null)
{
//获取Token;
if (dd_accesstokentime == null || (DateTime.Now.Ticks - dd_accesstokentime.Ticks) >= 5000)
{
dd_accesstokentime = DateTime.Now;
dd_accesstoken = JsonConvert.DeserializeObject<AccessTokenModel>(Request("gettoken?corpid=" + dd_corpid + "&corpsecret=" + dd_corpsecret, "", "GET")).access_token;
}
//将Token存入缓存
CacheHelper.AddCache("dd_accesstoken", dd_accesstoken, 115);
}
else
dd_accesstoken = DDToken.ToString();
}
/// <summary>
/// 给TOP请求签名。
/// </summary>
/// <param name="parameters">所有字符型的TOP请求参数</param>
/// <param name="secret">签名密钥</param>
/// <param name="signMethod">签名方法,可选值:md5, hmac</param>
/// <returns>签名</returns>
public static string SignTopRequest(IDictionary<string, string> parameters, string secret, string signMethod)
{
// 第一步:把字典按Key的字母顺序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第二步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
if (Constants.SIGN_METHOD_MD5.Equals(signMethod))
{
query.Append(secret);
}
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
{
query.Append(key).Append(value);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if (Constants.SIGN_METHOD_HMAC.Equals(signMethod))
{
HMACMD5 hmac = new HMACMD5(Encoding.UTF8.GetBytes(secret));
bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
}
else
{
query.Append(secret);
MD5 md5 = MD5.Create();
bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
}
// 第四步:把二进制转化为大写的十六进制
StringBuilder result = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
result.Append(bytes[i].ToString("X2"));
}
return result.ToString();
}
//Token Model
public class AccessTokenModel
{
public string access_token { get; set; }
public int errcode { get; set; }
public string errmsg { get; set; }
}
public sealed class Constants
{
public const string CHARSET_UTF8 = "utf-8";
public const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public const string DATE_TIME_MS_FORMAT = "yyyy-MM-dd HH:mm:ss.fff";
public const string SIGN_METHOD_MD5 = "md5";
public const string SIGN_METHOD_HMAC = "hmac";
public const string SIGN_METHOD_HMAC_SHA256 = "hmac-sha256";
public const string LOG_SPLIT = "^_^";
public const string LOG_FILE_NAME = "topsdk.log";
public const string ACCEPT_ENCODING = "Accept-Encoding";
public const string CONTENT_ENCODING = "Content-Encoding";
public const string CONTENT_ENCODING_GZIP = "gzip";
public const string ERROR_RESPONSE = "error_response";
public const string ERROR_CODE = "code";
public const string ERROR_MSG = "msg";
public const string QIMEN_CLOUD_ERROR_RESPONSE = "response";
public const string QIMEN_CLOUD_ERROR_CODE = "code";
public const string QIMEN_CLOUD_ERROR_MSG = "message";
public const string SDK_VERSION = "top-sdk-net-20180118";
public const string SDK_VERSION_CLUSTER = "top-sdk-net-cluster-20180118";
public const string APP_KEY = "app_key";
public const string FORMAT = "format";
public const string METHOD = "method";
public const string TIMESTAMP = "timestamp";
public const string VERSION = "v";
public const string SIGN = "sign";
public const string SIGN_METHOD = "sign_method";
public const string PARTNER_ID = "partner_id";
public const string SESSION = "session";
public const string FORMAT_XML = "xml";
public const string FORMAT_JSON = "json";
public const string SIMPLIFY = "simplify";
public const string TARGET_APP_KEY = "target_app_key";
public const string QM_ROOT_TAG_REQ = "request";
public const string QM_ROOT_TAG_RSP = "response";
public const string QM_CUSTOMER_ID = "customerId";
public const string QM_CONTENT_TYPE = "text/xml;charset=utf-8";
public const string QM_TARGETAPPKEY = "targetAppkey";
public const string CTYPE_DEFAULT = "application/octet-stream";
public const string CTYPE_FORM_DATA = "application/x-www-form-urlencoded";
public const string CTYPE_FILE_UPLOAD = "multipart/form-data";
public const string CTYPE_TEXT_XML = "text/xml";
public const string CTYPE_TEXT_PLAIN = "text/plain";
public const string CTYPE_APP_JSON = "application/json";
public const int READ_BUFFER_SIZE = 1024 * 4;
}
2、接口示例。
因接入信息接口相对复杂且独特性太强,这里使用获取信息接口作为示例。
/// <summary>
/// 获取钉钉上所有的部门信息列表
/// </summary>
/// <returns></returns>
public static List<DDDepartmentList> GetDepartmentList()
{
GetAccessToken();
string json = Request("department/list?access_token=" + dd_accesstoken, "", "GET");
List<DDDepartmentList> List = new List<DDDepartmentList>();
JsonListDepartment jsonResult = JsonConvert.DeserializeObject<JsonListDepartment>(json);
if (jsonResult.errcode == 0)
{
List = jsonResult.department;
}
return List;
}
public class DDDepartmentList
{
//是否创建部门组
private bool _createDeptGroup;
public bool createDeptGroup
{
get { return _createDeptGroup; }
set { _createDeptGroup = value; }
}
//部门名称
private string _name;
public string name
{
get { return _name; }
set { _name = value; }
}
//部门id
private string _id;
public string id
{
get { return _id; }
set { _id = value; }
}
//自动添加用户
private bool _autoAddUser;
public bool autoAddUser
{
get { return _autoAddUser; }
set { _autoAddUser = value; }
}
//父级ID
private string _parentid;
public string parentid
{
get { return _parentid; }
set { _parentid = value; }
}
//钉钉接口中的order 对应亿童的组织架构ID
private string _order;
public string order
{
get { return _order; }
set { _order = value; }
}
}
public class JsonListDepartment
{
public int errcode { get; set; }
public List<DDDepartmentList> department { get; set; }
}
四、总结
钉钉的接入相对微信接入,个人认为是要简单许多,而且文档清晰、demo参考性强、SDK也比较多。上述是C#接入钉钉的真实实现,公司内部系统接入钉钉之后,近700个大小部门、1500位员工信息都能快速和钉钉进行同步更新,公司十多个微应用也能在钉钉上使用,进一步实现了职能办公。
本文属于个人原创作品,属于个人完成公司项目之后的含泪总结,谢绝转载、抄袭。
如果您有疑问或者希望沟通交流,可以联系QQ:865562060。