暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

webapi钉钉事件回调

不想做程序员 2021-02-09
2242

钉钉事件回调:当钉钉中人员变动,审批流转时,钉钉会向指定的回调接口传送数据,以便对数据进行处理。

官方给出的例子还是很老的,用一般处理程序实现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;
else
index++;
}
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,自己注册应该为cropid
int ret=dCrypt.EncryptMsg(res, timestamp, nonce, ref encryStr, ref sig); //加密消息
return new
{
msg_signature = sig,
encrypt = encryStr,
timeStamp = timestamp,
nonce = nonce
};
}
}
}


文章转载自不想做程序员,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论