• 钉钉企业微应用开发C#-HTTP回调接口


    官方的STREAM回调推送的方式,试了几次都认证不过,就放弃了还是用HTTP的模式吧。

    1. ///
    2. /// 应用回调
    3. ///
    4. ///
    5. ///
    6. public static Dictionary<string, string> CallBack(DingCallBackModel model)
    7. {
    8. WriteLogHelper.WriteLogsAsync(JsonHelper.SerializeObject(model), "实例回调日志");
    9. var timeStamp = GetTimestamp(DateTime.Now).ToString();
    10. // 2. 使用加解密类型
    11. DingTalkEncryptor callbackCrypto = new DingTalkEncryptor(AppsettingsConfig.DingDingConfig.Token, AppsettingsConfig.DingDingConfig.AesKey, AppsettingsConfig.DingDingConfig.AppKey);
    12. string decryptMsg = callbackCrypto.getDecryptMsg(model.Msg_signature, model.TimeStamp, model.Nonce, model.Encrypt);
    13. string msg = "success";
    14. // 3. 反序列化回调事件json数据
    15. var approveModel = JsonHelper.DeserializeObject(decryptMsg);
    16. WriteLogHelper.WriteLogsAsync(JsonHelper.SerializeObject(approveModel), "实例回调日志-解密");
    17. var type = approveModel.EventType;
    18. switch (type)
    19. {
    20. case "check_url":
    21. WriteLogHelper.WriteLogsAsync("审批回调:" + JsonHelper.SerializeObject(model), "实例回调日志");
    22. // 测试回调
    23. break;
    24. case "bpms_instance_change":
    25. // 实例改变
    26. break;
    27. }
    28. var rspMsg = callbackCrypto.getEncryptedMap(msg);
    29. return rspMsg;
    30. }

    check_url就是钉应用后台设置回调URL时传入数据的类型。拿到数据

         public string Msg_signature { set; get; } = "";
            public string TimeStamp { set; get; }
            public string Nonce { set; get; } = "123456";
            ///


            /// 加密后的结果
            ///

            public string Encrypt { set; get; }

    分为几部分,前面几项用来解密Encrypt密文的参数。

    通过提供的DingTalkEncryptor 的  getDecryptMsg方法来进行解密

    得到解密后的内容序列化成对象。

    1. /**
    2. * 钉钉开放平台加解密方法
    3. */
    4. public class DingTalkEncryptor
    5. {
    6. //private static readonly Charset CHARSET = Charset.forName("utf-8");
    7. //private static readonly Base64 base64 = new Base64();
    8. private byte[] aesKey;
    9. private String token;
    10. private String corpId;
    11. /**ask getPaddingBytes key固定长度**/
    12. private static readonly int AES_ENCODE_KEY_LENGTH = 43;
    13. /**加密随机字符串字节长度**/
    14. private static readonly int RANDOM_LENGTH = 16;
    15. /**
    16. * 构造函数
    17. * @param token 钉钉开放平台上,开发者设置的token
    18. * @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey
    19. * @param corpId 企业自建应用-事件订阅, 使用appKey
    20. * 企业自建应用-注册回调地址, 使用corpId
    21. * 第三方企业应用, 使用suiteKey
    22. *
    23. * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息
    24. */
    25. public DingTalkEncryptor(String token, String encodingAesKey, String corpId)
    26. {
    27. if (null == encodingAesKey || encodingAesKey.Length != AES_ENCODE_KEY_LENGTH)
    28. {
    29. throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);
    30. }
    31. this.token = token;
    32. this.corpId = corpId;
    33. aesKey = Convert.FromBase64String(encodingAesKey + "=");
    34. }
    35. /**
    36. * 将和钉钉开放平台同步的消息体加密,返回加密Map
    37. */
    38. public Dictionary getEncryptedMap(String plaintext)
    39. {
    40. var time = DateTime.Now.Millisecond;
    41. return getEncryptedMap(plaintext, time);
    42. }
    43. /**
    44. * 将和钉钉开放平台同步的消息体加密,返回加密Map
    45. * @param plaintext 传递的消息体明文
    46. * @param timeStamp 时间戳
    47. * @param nonce 随机字符串
    48. * @return
    49. * @throws DingTalkEncryptException
    50. */
    51. public Dictionary getEncryptedMap(String plaintext, long timeStamp)
    52. {
    53. if (null == plaintext)
    54. {
    55. throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);
    56. }
    57. var nonce = Utils.getRandomStr(RANDOM_LENGTH);
    58. if (null == nonce)
    59. {
    60. throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL);
    61. }
    62. String encrypt = this.encrypt(nonce, plaintext);
    63. String signature = getSignature(token, timeStamp.ToString(), nonce, encrypt);
    64. Dictionary resultMap = new Dictionary();
    65. resultMap["msg_signature"] = signature;
    66. resultMap["encrypt"] = encrypt;
    67. resultMap["timeStamp"] = timeStamp.ToString();
    68. resultMap["nonce"] = nonce;
    69. return resultMap;
    70. }
    71. /**
    72. * 密文解密
    73. * @param msgSignature 签名串
    74. * @param timeStamp 时间戳
    75. * @param nonce 随机串
    76. * @param encryptMsg 密文
    77. * @return 解密后的原文
    78. * @throws DingTalkEncryptException
    79. */
    80. public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)
    81. {
    82. //校验签名
    83. String signature = getSignature(token, timeStamp, nonce, encryptMsg);
    84. if (!signature.Equals(msgSignature))
    85. {
    86. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
    87. }
    88. // 解密
    89. String result = decrypt(encryptMsg);
    90. return result;
    91. }
    92. /*
    93. * 对明文加密.
    94. * @param text 需要加密的明文
    95. * @return 加密后base64编码的字符串
    96. */
    97. private String encrypt(String random, String plaintext)
    98. {
    99. try
    100. {
    101. byte[] randomBytes = System.Text.Encoding.UTF8.GetBytes(random);// random.getBytes(CHARSET);
    102. byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plaintext);// plaintext.getBytes(CHARSET);
    103. byte[] lengthByte = Utils.int2Bytes(plainTextBytes.Length);
    104. byte[] corpidBytes = System.Text.Encoding.UTF8.GetBytes(corpId);// corpId.getBytes(CHARSET);
    105. //MemoryStream byteStream = new MemoryStream();
    106. var bytestmp = new List<byte>();
    107. bytestmp.AddRange(randomBytes);
    108. bytestmp.AddRange(lengthByte);
    109. bytestmp.AddRange(plainTextBytes);
    110. bytestmp.AddRange(corpidBytes);
    111. byte[] padBytes = PKCS7Padding.getPaddingBytes(bytestmp.Count);
    112. bytestmp.AddRange(padBytes);
    113. byte[] unencrypted = bytestmp.ToArray();
    114. RijndaelManaged rDel = new RijndaelManaged();
    115. rDel.Mode = CipherMode.CBC;
    116. rDel.Padding = PaddingMode.Zeros;
    117. rDel.Key = aesKey;
    118. rDel.IV = aesKey.ToList().Take(16).ToArray();
    119. ICryptoTransform cTransform = rDel.CreateEncryptor();
    120. byte[] resultArray = cTransform.TransformFinalBlock(unencrypted, 0, unencrypted.Length);
    121. return Convert.ToBase64String(resultArray, 0, resultArray.Length);
    122. //Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
    123. //SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
    124. //IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
    125. //cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
    126. //byte[] encrypted = cipher.doFinal(unencrypted);
    127. //String result = base64.encodeToString(encrypted);
    128. //return result;
    129. }
    130. catch (Exception e)
    131. {
    132. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);
    133. }
    134. }
    135. /*
    136. * 对密文进行解密.
    137. * @param text 需要解密的密文
    138. * @return 解密得到的明文
    139. */
    140. private String decrypt(String text)
    141. {
    142. byte[] originalArr;
    143. try
    144. {
    145. byte[] toEncryptArray = Convert.FromBase64String(text);
    146. RijndaelManaged rDel = new RijndaelManaged();
    147. rDel.Mode = CipherMode.CBC;
    148. rDel.Padding = PaddingMode.Zeros;
    149. rDel.Key = aesKey;
    150. rDel.IV = aesKey.ToList().Take(16).ToArray();
    151. ICryptoTransform cTransform = rDel.CreateDecryptor();
    152. originalArr = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    153. //return System.Text.UTF8Encoding.UTF8.GetString(resultArray);
    154. 设置解密模式为AES的CBC模式
    155. //Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
    156. //SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
    157. //IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
    158. //cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
    159. 使用BASE64对密文进行解码
    160. //byte[] encrypted = Base64.decodeBase64(text);
    161. 解密
    162. //originalArr = cipher.doFinal(encrypted);
    163. }
    164. catch (Exception e)
    165. {
    166. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);
    167. }
    168. String plainText;
    169. String fromCorpid;
    170. try
    171. {
    172. // 去除补位字符
    173. byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
    174. Console.Out.WriteLine("bytes size:" + bytes.Length);
    175. // 分离16位随机字符串,网络字节序和corpId
    176. byte[] networkOrder = bytes.Skip(16).Take(4).ToArray();// Arrays.copyOfRange(bytes, 16, 20);
    177. for (int i = 0; i < 4; i++)
    178. {
    179. Console.Out.WriteLine("networkOrder size:" + (int)networkOrder[i]);
    180. }
    181. Console.Out.WriteLine("bytes plainText:" + networkOrder.Length + " " + JsonSerializer.Serialize(networkOrder));
    182. int plainTextLegth = Utils.bytes2int(networkOrder);
    183. Console.Out.WriteLine("bytes size:" + plainTextLegth);
    184. plainText = System.Text.UTF8Encoding.UTF8.GetString(bytes.Skip(20).Take(plainTextLegth).ToArray()); // new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);
    185. fromCorpid = System.Text.UTF8Encoding.UTF8.GetString(bytes.Skip(20 + plainTextLegth).ToArray()); //new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
    186. Console.Out.WriteLine("bytes plainText:" + plainText);
    187. }
    188. catch (Exception e)
    189. {
    190. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);
    191. }
    192. Console.Out.WriteLine(fromCorpid + "=====" + corpId);
    193. // corpid不相同的情况
    194. if (!fromCorpid.Equals(corpId))
    195. {
    196. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);
    197. }
    198. return plainText;
    199. }
    200. /**
    201. * 数字签名
    202. * @param token isv token
    203. * @param timestamp 时间戳
    204. * @param nonce 随机串
    205. * @param encrypt 加密文本
    206. * @return
    207. * @throws DingTalkEncryptException
    208. */
    209. public String getSignature(String token, String timestamp, String nonce, String encrypt)
    210. {
    211. try
    212. {
    213. Console.Out.WriteLine(encrypt);
    214. String[] array = new String[] { token, timestamp, nonce, encrypt };
    215. Array.Sort(array, StringComparer.Ordinal);
    216. //var tmparray = array.ToList();
    217. //tmparray.Sort(new JavaStringComper());
    218. //array = tmparray.ToArray();
    219. Console.Out.WriteLine("array:" + JsonSerializer.Serialize(array));
    220. StringBuilder sb = new StringBuilder();
    221. for (int i = 0; i < 4; i++)
    222. {
    223. sb.Append(array[i]);
    224. }
    225. String str = sb.ToString();
    226. Console.Out.WriteLine(str);
    227. //MessageDigest md = MessageDigest.getInstance("SHA-1");
    228. //md.update(str.getBytes());
    229. //byte[] digest = md.digest();
    230. System.Security.Cryptography.SHA1 hash = System.Security.Cryptography.SHA1.Create();
    231. System.Text.Encoding encoder = System.Text.Encoding.ASCII;
    232. byte[] combined = encoder.GetBytes(str);
    233. byte 转换
    234. //sbyte[] myByte = new sbyte[]
    235. //byte[] mySByte = new byte[myByte.Length];
    236. //for (int i = 0; i < myByte.Length; i++)
    237. //{
    238. // if (myByte[i] > 127)
    239. // mySByte[i] = (sbyte)(myByte[i] - 256);
    240. // else
    241. // mySByte[i] = (sbyte)myByte[i];
    242. //}
    243. byte[] digest = hash.ComputeHash(combined);
    244. StringBuilder hexstr = new StringBuilder();
    245. String shaHex = "";
    246. for (int i = 0; i < digest.Length; i++)
    247. {
    248. shaHex = ((int)digest[i]).ToString("x");// Integer.toHexString(digest[i] & 0xFF);
    249. if (shaHex.Length < 2)
    250. {
    251. hexstr.Append(0);
    252. }
    253. hexstr.Append(shaHex);
    254. }
    255. return hexstr.ToString();
    256. }
    257. catch (Exception e)
    258. {
    259. throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
    260. }
    261. }
    262. }
    263. /**
    264. * 钉钉开放平台加解密异常类
    265. */
    266. public class DingTalkEncryptException : Exception
    267. {
    268. /**成功**/
    269. public static readonly int SUCCESS = 0;
    270. /**加密明文文本非法**/
    271. public readonly static int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;
    272. /**加密时间戳参数非法**/
    273. public readonly static int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;
    274. /**加密随机字符串参数非法**/
    275. public readonly static int ENCRYPTION_NONCE_ILLEGAL = 900003;
    276. /**不合法的aeskey**/
    277. public readonly static int AES_KEY_ILLEGAL = 900004;
    278. /**签名不匹配**/
    279. public readonly static int SIGNATURE_NOT_MATCH = 900005;
    280. /**计算签名错误**/
    281. public readonly static int COMPUTE_SIGNATURE_ERROR = 900006;
    282. /**计算加密文字错误**/
    283. public readonly static int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;
    284. /**计算解密文字错误**/
    285. public readonly static int COMPUTE_DECRYPT_TEXT_ERROR = 900008;
    286. /**计算解密文字长度不匹配**/
    287. public readonly static int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;
    288. /**计算解密文字corpid不匹配**/
    289. public readonly static int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;
    290. private static Dictionary<int, String> msgMap = new Dictionary<int, String>();
    291. static DingTalkEncryptException()
    292. {
    293. msgMap[SUCCESS] = "成功";
    294. msgMap[ENCRYPTION_PLAINTEXT_ILLEGAL] = "加密明文文本非法";
    295. msgMap[ENCRYPTION_TIMESTAMP_ILLEGAL] = "加密时间戳参数非法";
    296. msgMap[ENCRYPTION_NONCE_ILLEGAL] = "加密随机字符串参数非法";
    297. msgMap[SIGNATURE_NOT_MATCH] = "签名不匹配";
    298. msgMap[COMPUTE_SIGNATURE_ERROR] = "签名计算失败";
    299. msgMap[AES_KEY_ILLEGAL] = "不合法的aes key";
    300. msgMap[COMPUTE_ENCRYPT_TEXT_ERROR] = "计算加密文字错误";
    301. msgMap[COMPUTE_DECRYPT_TEXT_ERROR] = "计算解密文字错误";
    302. msgMap[COMPUTE_DECRYPT_TEXT_LENGTH_ERROR] = "计算解密文字长度不匹配";
    303. msgMap[COMPUTE_DECRYPT_TEXT_CORPID_ERROR] = "计算解密文字corpid不匹配";
    304. }
    305. private int code;
    306. public DingTalkEncryptException(int exceptionCode) : base(msgMap[exceptionCode])
    307. {
    308. this.code = exceptionCode;
    309. }
    310. }
    311. /*
    312. * PKCS7算法的加密填充
    313. */
    314. public class PKCS7Padding
    315. {
    316. //private readonly static Charset CHARSET = Charset.forName("utf-8");
    317. private readonly static int BLOCK_SIZE = 32;
    318. /**
    319. * 填充mode字节
    320. * @param count
    321. * @return
    322. */
    323. public static byte[] getPaddingBytes(int count)
    324. {
    325. int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
    326. if (amountToPad == 0)
    327. {
    328. amountToPad = BLOCK_SIZE;
    329. }
    330. char padChr = chr(amountToPad);
    331. String tmp = string.Empty; ;
    332. for (int index = 0; index < amountToPad; index++)
    333. {
    334. tmp += padChr;
    335. }
    336. return System.Text.Encoding.UTF8.GetBytes(tmp);
    337. }
    338. /**
    339. * 移除mode填充字节
    340. * @param decrypted
    341. * @return
    342. */
    343. public static byte[] removePaddingBytes(byte[] decrypted)
    344. {
    345. int pad = (int)decrypted[decrypted.Length - 1];
    346. if (pad < 1 || pad > BLOCK_SIZE)
    347. {
    348. pad = 0;
    349. }
    350. //Array.Copy()
    351. var output = new byte[decrypted.Length - pad];
    352. Array.Copy(decrypted, output, decrypted.Length - pad);
    353. return output;
    354. }
    355. private static char chr(int a)
    356. {
    357. byte target = (byte)(a & 0xFF);
    358. return (char)target;
    359. }
    360. }
    361. /**
    362. * 加解密工具类
    363. */
    364. public class Utils
    365. {
    366. /**
    367. *
    368. * @return
    369. */
    370. public static String getRandomStr(int count)
    371. {
    372. String baset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    373. Random random = new Random();
    374. StringBuilder sb = new StringBuilder();
    375. for (int i = 0; i < count; i++)
    376. {
    377. int number = random.Next(baset.Length);
    378. sb.Append(baset[number]);
    379. }
    380. return sb.ToString();
    381. }
    382. /*
    383. * int转byte数组,高位在前
    384. */
    385. public static byte[] int2Bytes(int count)
    386. {
    387. byte[] byteArr = new byte[4];
    388. byteArr[3] = (byte)(count & 0xFF);
    389. byteArr[2] = (byte)(count >> 8 & 0xFF);
    390. byteArr[1] = (byte)(count >> 16 & 0xFF);
    391. byteArr[0] = (byte)(count >> 24 & 0xFF);
    392. return byteArr;
    393. }
    394. /**
    395. * 高位在前bytes数组转int
    396. * @param byteArr
    397. * @return
    398. */
    399. public static int bytes2int(byte[] byteArr)
    400. {
    401. int count = 0;
    402. for (int i = 0; i < 4; ++i)
    403. {
    404. count <<= 8;
    405. count |= byteArr[i] & 255;
    406. }
    407. return count;
    408. }
    409. }
    410. public class JavaStringComper : IComparer<string>
    411. {
    412. public int Compare(string x, string y)
    413. {
    414. return String.Compare(x, y);
    415. }
    416. }
    417. // 测试加解密的正确性
    418. public class Program
    419. {
    420. public static void Main(string[] args)
    421. {
    422. String[] a = new String[] { "1", "W", "t" };
    423. var ding = new DingTalkEncryptor("tokenxxxx", "o1w0aum42yaptlz8alnhwikjd3jenzt9cb9wmzptgus", "dingxxxxxx");
    424. var msg = ding.getEncryptedMap("success");
    425. Console.Out.WriteLine(msg);
    426. //msg_signature, $data->timeStamp, $data->nonce, $data->encrypt
    427. var text = ding.getDecryptMsg(msg["msg_signature"], msg["timeStamp"], msg["nonce"], msg["encrypt"]);
    428. Console.Out.WriteLine(text);
    429. // "msg_signature":"c01beb7b06384cf416e04930aed794684aae98c1","encrypt":"","timeStamp":,"nonce":""
    430. //{"timeStamp":"1605695694141","msg_signature":"702c953056613f5c7568b79ed134a27bd2dcd8d0",
    431. //"encrypt":"","nonce":"WelUQl6bCqcBa2fMc6eI"}
    432. text = ding.getDecryptMsg("f36f4ba5337d426c7d4bca0dbcb06b3ddc1388fc", "1605695694141", "WelUQl6bCqcBa2fM", "X1VSe9cTJUMZu60d3kyLYTrBq5578ZRJtteU94wG0Q4Uk6E/wQYeJRIC0/UFW5Wkya1Ihz9oXAdLlyC9TRaqsQ==");
    433. Console.Out.WriteLine(text);
    434. }
    435. }

     public class ApprovalInstanceModel
        {

            //public long Id { set; get; } = 0;
            public string EventType { set; get; }
            public string ProcessInstanceId { set; get; }
            ///


            /// 企业ID
            ///

            public string CorpId { set; get; }

            public string Title { set; get; }
            ///


            /// 审批状态类型
            ///

            public string Type { set; get; }

            ///


            /// 审批详情URL
            ///

            public string Url { set; get; }
            ///
            /// 审批结果 agree refuse 或为空
            ///

            public string Result { set; get; }
            ///
            /// 员工ID
            ///

            public string StaffId { set; get; }

            ///


            /// 审批模板code
            ///

            public string ProcessCode { set; get; }
            public long FinishTime { set; get; } = 0;
            public long CreateTime { set; get; } = 0;

            public DateTime CreateTimeNormal { set; get; }
            public DateTime FinishTimeNormal { set; get; }

      
        }

    bpms_instance_change 类型是审批发起与结束时会触发的用来更新我们的微应用单据数据的状态,以及更新时有审批进度的URL,可以关联起来。

    好了以上就是审批回调URL的接口内容了。

  • 相关阅读:
    Redis群集
    想要精通算法和SQL的成长之路 - 受限条件下可到达节点的数目
    为什么golang不支持可重入锁
    PyTorch入门学习(十五):现有网络模型的使用及修改
    【数据结构】图综合练习--构建邻接表
    开放式运动耳机排行榜,排行靠前的五款高性能耳机分享
    SSM房屋租赁系统
    Qt中INI 配置文件的读写操作
    需要在html中加CSS,怎么加
    milvus数据库-连接
  • 原文地址:https://blog.csdn.net/u011540323/article/details/134249316