钉钉事件回调:当钉钉中人员变动,审批流转时,钉钉会向指定的回调接口传送数据,以便对数据进行处理。
官方给出的例子还是很老的,用一般处理程序实现ashx的。其逻辑很简单,将自己写的接口,注册到钉钉中去,接收钉钉的回调事件,处理完成,并返回字符串"success"的加密字符串。
1、注册接口。需要有公网地址,有两种方法:一种是写代码注册,另一种是在钉钉管理后台设置即可,建议在后台设置。
注册代码
OapiCallBackRegisterCallBackRequest req = new OapiCallBackRegisterCallBackRequest();req.Url = $"{GlobalRes.ServerRuntimeConfig.SiteLocaltion}/DDCallBack/Process"; //接口的公网地址req.AesKey = GlobalRes.ServerRuntimeConfig.GetSubcareAeskey(); //消息加密aes_key,后台设置req.Token = GlobalRes.ServerRuntimeConfig.GetSubcareSigToken(); //签名token,后台设置req.CallBackTag = new List<string>() { "bpms_task_change" , "bpms_instance_change" }; //需要订阅的事件DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/call_back/register_call_back");var res= client.Execute(req, GlobalRes.ServerRuntimeConfig.GetToken()); //获取会话token,并执行Logger.Info(res.Body,"Global");
钉钉开发平台相关设置:进入管理后台,并点击相关微应用,在事件订阅页签填写api公网接口,并打开需要订阅的事件,

2、消息加解密:钉钉传递过来的消息是加密消息,需要解密;向钉钉返回数据需要加密。
using System;using System.Collections;using System.Security.Cryptography;using System.Text;namespace CommonLib.DDTalk{/// <summary>/// 加密类/// </summary>public class DingTalkCrypt{private string m_sEncodingAESKey;private string m_sToken;private string m_sSuiteKey;/**ask getPaddingBytes key固定长度**/private static int AES_ENCODE_KEY_LENGTH = 43;/**加密随机字符串字节长度**/private static int RANDOM_LENGTH = 16;enum DingTalkCryptErrorCode{/**成功**/SUCCESS = 0,/**加密明文文本非法**/ENCRYPTION_PLAINTEXT_ILLEGAL = 900001,/**加密时间戳参数非法**/ENCRYPTION_TIMESTAMP_ILLEGAL = 900002,/**加密随机字符串参数非法**/ENCRYPTION_NONCE_ILLEGAL = 900003,/**不合法的aeskey**/AES_KEY_ILLEGAL = 900004,/**签名不匹配**/SIGNATURE_NOT_MATCH = 900005,/**计算签名错误**/COMPUTE_SIGNATURE_ERROR = 900006,/**计算加密文字错误**/COMPUTE_ENCRYPT_TEXT_ERROR = 900007,/**计算解密文字错误**/COMPUTE_DECRYPT_TEXT_ERROR = 900008,/**计算解密文字长度不匹配**/COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009,/**计算解密文字suiteKey不匹配**/COMPUTE_DECRYPT_TEXT_SuiteKey_ERROR = 900010,};/// <summary>/// 构造函数/// </summary>/// <param name="token">钉钉开放平台上,开发者设置的token</param>/// <param name="encodingAesKey">钉钉开放台上,开发者设置的EncodingAESKey</param>/// <param name="suiteKey">钉钉开放平台上,开发者设置的suiteKey</param>public DingTalkCrypt(string token, string encodingAesKey, string suiteKey){m_sToken = token;m_sSuiteKey = suiteKey;m_sEncodingAESKey = encodingAesKey;}/// <summary>/// 将消息加密,返回加密后字符串/// </summary>/// <param name="sReplyMsg">传递的消息体明文</param>/// <param name="sTimeStamp">时间戳</param>/// <param name="sNonce">随机字符串</param>/// <param name="sEncryptMsg">加密后的消息信息</param>/// <returns>成功0,失败返回对应的错误码</returns>public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg, ref string signature){if (string.IsNullOrEmpty(sReplyMsg))return (int)DingTalkCryptErrorCode.ENCRYPTION_PLAINTEXT_ILLEGAL;if (string.IsNullOrEmpty(sTimeStamp))return (int)DingTalkCryptErrorCode.ENCRYPTION_TIMESTAMP_ILLEGAL;if (string.IsNullOrEmpty(sNonce))return (int)DingTalkCryptErrorCode.ENCRYPTION_NONCE_ILLEGAL;if (m_sEncodingAESKey.Length != AES_ENCODE_KEY_LENGTH)return (int)DingTalkCryptErrorCode.AES_KEY_ILLEGAL;string raw = "";try{raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sSuiteKey);}catch (Exception){return (int)DingTalkCryptErrorCode.AES_KEY_ILLEGAL;}string msgSigature = "";int ret = GenerateSignature(m_sToken, sTimeStamp, sNonce, raw, ref msgSigature);sEncryptMsg = raw;signature = msgSigature;return ret;}/// <summary>/// 密文解密/// </summary>/// <param name="sMsgSignature">签名串</param>/// <param name="sTimeStamp">时间戳</param>/// <param name="sNonce">随机串</param>/// <param name="sPostData">密文</param>/// <param name="sMsg">解密后的原文,当return返回0时有效</param>/// <returns>成功0,失败返回对应的错误码</returns>public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg){if (m_sEncodingAESKey.Length != AES_ENCODE_KEY_LENGTH)return (int)DingTalkCryptErrorCode.AES_KEY_ILLEGAL;string sEncryptMsg = sPostData;int ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);string cpid = "";try{sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);}catch (FormatException){sMsg = "";return (int)DingTalkCryptErrorCode.COMPUTE_DECRYPT_TEXT_SuiteKey_ERROR;}catch (Exception){sMsg = "";return (int)DingTalkCryptErrorCode.COMPUTE_DECRYPT_TEXT_SuiteKey_ERROR;}if (cpid != m_sSuiteKey)return (int)DingTalkCryptErrorCode.COMPUTE_DECRYPT_TEXT_SuiteKey_ERROR;return ret;}/// <summary>/// 生成签名/// </summary>/// <param name="sToken"></param>/// <param name="sTimeStamp"></param>/// <param name="sNonce"></param>/// <param name="sMsgEncrypt"></param>/// <param name="sMsgSignature"></param>/// <returns></returns>public static int GenerateSignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature){ArrayList AL = new ArrayList();AL.Add(sToken);AL.Add(sTimeStamp);AL.Add(sNonce);AL.Add(sMsgEncrypt);AL.Sort(new DictionarySort());string raw = "";for (int i = 0; i < AL.Count; ++i){raw += AL[i];}SHA1 sha;ASCIIEncoding enc;string hash = "";try{sha = new SHA1CryptoServiceProvider();enc = new ASCIIEncoding();byte[] dataToHash = enc.GetBytes(raw);byte[] dataHashed = sha.ComputeHash(dataToHash);hash = BitConverter.ToString(dataHashed).Replace("-", "");hash = hash.ToLower();}catch (Exception){return (int)DingTalkCryptErrorCode.COMPUTE_SIGNATURE_ERROR;}sMsgSignature = hash;return 0;}/// <summary>/// 验证签名/// </summary>/// <param name="sToken"></param>/// <param name="sTimeStamp"></param>/// <param name="sNonce"></param>/// <param name="sMsgEncrypt"></param>/// <param name="sSigture"></param>/// <returns></returns>private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture){string hash = "";int ret = 0;ret = GenerateSignature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);if (ret != 0)return ret;if (hash == sSigture)return 0;else{return (int)DingTalkCryptErrorCode.SIGNATURE_NOT_MATCH;}}/// <summary>/// 验证URL/// </summary>/// <param name="sMsgSignature">签名串,对应URL参数的msg_signature</param>/// <param name="sTimeStamp">时间戳,对应URL参数的timestamp</param>/// <param name="sNonce">随机串,对应URL参数的nonce</param>/// <param name="sEchoStr">经过加密的消息体,对应URL参数的encrypt</param>/// <param name="sReplyEchoStr"></param>/// <returns></returns>public int VerifyURL(string sMsgSignature, string sTimeStamp, string sNonce, string sEchoStr, ref string sReplyEchoStr){int ret = 0;if (m_sEncodingAESKey.Length != 43){return (int)DingTalkCryptErrorCode.AES_KEY_ILLEGAL;}ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEchoStr, sMsgSignature);sReplyEchoStr = "";string cpid = "";try{sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid); //m_sCorpID);}catch (Exception){sReplyEchoStr = "";return (int)DingTalkCryptErrorCode.COMPUTE_SIGNATURE_ERROR;}if (cpid != m_sSuiteKey){sReplyEchoStr = "";return (int)DingTalkCryptErrorCode.COMPUTE_DECRYPT_TEXT_SuiteKey_ERROR;}return ret;}/// <summary>/// 字典排序/// </summary>public class DictionarySort : System.Collections.IComparer{public int Compare(object oLeft, object oRight){string sLeft = oLeft as string;string sRight = oRight as string;int iLeftLength = sLeft.Length;int iRightLength = sRight.Length;int index = 0;while (index < iLeftLength && index < iRightLength){if (sLeft[index] < sRight[index])return -1;else if (sLeft[index] > sRight[index])return 1;elseindex++;}return iLeftLength - iRightLength;}}}}
3、接收并处理钉钉回调消息
using BLL.INV;using common.DDTalk;using CommonLib;using CommonLib.DDTalk;using Newtonsoft.Json;using Newtonsoft.Json.Linq;using System;using System.Collections;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Http;using System.Threading.Tasks;using System.Web.Http;namespace YJYOA.Controllers{[RoutePrefix("api/dtalkcallback")]public class DTalkCallbackController : ApiController{[HttpPost,Route("process")]public dynamic Process([FromUri]string signature,[FromUri]string timestamp,[FromUri]string nonce,[FromBody] JObject param){string sbToken = GlobalRes.ServerRuntimeConfig.GetSubcareSigToken(); //钉钉后台设置string sbAeskey = GlobalRes.ServerRuntimeConfig.GetSubcareAeskey(); //钉钉后台设置string encryData = param.GetValue("encrypt").ToString();DingTalkCrypt dCrypt = new DingTalkCrypt(sbToken, sbAeskey, GlobalRes.ServerRuntimeConfig.DDConfig.CorpId);string plainData = "";dCrypt.DecryptMsg(signature, timestamp, nonce, encryData, ref plainData); //解密消息var dataObj = JsonConvert.DeserializeObject<Hashtable>(plainData); //反序列化消息string eventType = dataObj["EventType"].ToString();switch (eventType){case "bpms_instance_change": //审批流处理string instId = dataObj["processInstanceId"].ToString();string procCode = dataObj["processCode"].ToString();if (GlobalRes.GetCallBackProcessList().Contains(procCode)){Task.Run(() =>{try{var response = DDHelper.GetProcInst(GlobalRes.ServerRuntimeConfig.GetToken(), instId);string status = response.Status;if ("COMPLETED,TERMINATED".Contains(status)){Logger.Info(plainData, "钉钉回调");string toStatus = status == "COMPLETED" ? "C" : "T";GlobalRes.BllFactory.CreateBll<ScrapBll>("amc_data").UpdateStatus(procCode, instId, toStatus, "amc_data");}}catch (Exception ex){Logger.Error(ex, "钉钉回调");}});}break;default:break;}string res = "success";string encryStr = "";string sig = "";dCrypt = new DingTalkCrypt(sbToken, sbAeskey, GlobalRes.ServerRuntimeConfig.DDConfig.EApps[0].AppKey); //如果在钉钉后台注册接口这里要填写微应用的appkey,自己注册应该为cropidint ret=dCrypt.EncryptMsg(res, timestamp, nonce, ref encryStr, ref sig); //加密消息return new{msg_signature = sig,encrypt = encryStr,timeStamp = timestamp,nonce = nonce};}}}
文章转载自不想做程序员,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




