钉钉配置事件订阅(Python)
钉钉配置事件订阅
0.需求分析
需要实现钉钉企业通讯录同步至企业微信通讯录,这就需要用到钉钉的事件与回调
1.配置应用
登陆开放平台
https://open-dev.dingtalk.com/
去企业内部开发里面,先创建个应用,后面都借用这个应用来调接口

创建完成应用后进入应用,找到下面红框内的数据,后面会用到

进入应用中的事件与回调,自动生成aes_key和token,然后保存就好这两个数据

2.服务开发
2.1请求验证
配置上面1.配置应用中的事件订阅下的请求网址如http://yourserver/api/callback时,需要把这个网址的接口开发好。钉钉会发送一个类似下面这样的请求:
一个POST类型的http请求,携带了部分url参数和json的加密参数
请求的路径参数包括signature、msg_signature、timestamp、nonce,请求体只有一个encrypt的json
curl -X 'POST' \'http://yourserver:80/api/callback?signature=369beedea8d1c8d1ad18936e827d29d0c8415baf&msg_signature=369beedea8d1c8d1ad18936e827d29d0c8415baf×tamp=1660634610203&nonce=kBms4hUF' \-H 'accept: application/json' \-H 'Content-Type: application/json' \-d '{"encrypt": "4Q4JHq88OCR3P+8v2mcFHLT6dmaaYAckaUBVk1spJnCx7u9raGZVAxVUIuQg3loL8LjIQj+5YC3+HJcehTsJXu1qMOv5TKdb4+koO55g8WCYZP/vebg2RZQC2gBlN2zv"
}'
钉钉服务器会向配置好的接口服务中发送请求,用来验证双方通信的真实性,接口服务端可通过解密encrypt中的数据来验证是否是来自钉钉服务器,而返回的加密的success字符串能让钉钉服务器验证是否是来自用户,属于双向验证
2.2接口开发
2.2.1技术选型
对于创建接口服务这种需求,钉钉开放官网文档有些示例,比如Java等,都是可以实现的,因为需求很简单,故采用胶水语言Python进行开发
Python中想做接口服务,有两种选择,一是Flask轻量化接口服务,二是Django(Python Web框架)。经过简单比较后,选择Flask轻量化接口服务
主要代码如下:
DingDingCallback
# !/usr/bin/python3
# encoding:utf-8
'''
dingding通讯录同步至企业微信
采用Flask写一个实时监听的接口
收到钉钉的通讯录变更请求后,修改请求中的数据直接请求企业微信通讯录相关的API
'''
import flask
import json
import DingTalkCrypto# 钉钉事件订阅aeskey
aes_key = "xxxxxxxxxxxxxT329ssbVn5Bo"
# 钉钉事件订阅token
token = "xxxxxxxxxxxIgBpKsBE"
# 钉钉appkey
app_key = "xxxxxxxxxxxxxxx"# 实例化api,把当前这个python文件当作一个服务,__name__代表当前这个python文件
api = flask.Flask(__name__)# 'index'是接口路径,methods不写,默认get请求
@api.route('/sync/test', methods=['get'])
# get方式访问
def index():ren = {'msg': '成功访问首页', 'msg_code': 200}print("测试接口成功请求!!!")# json.dumps 序列化时对中文默认使用的ascii编码.想输出中文需要指定ensure_ascii=Falsereturn json.dumps(ren, ensure_ascii=False)# post方式访问(josn格式参数)
@api.route('/sync', methods=['post'])
def loginjosn():# 1.通过flask获取请求中的参数列表args = flask.request.args# 2.获取需要解密的参数signature = args.get('signature') # 实际打印中signature和msg_signature是一样的msg_signature = args.get('msg_signature')timestamp = args.get('timestamp')nonce = args.get('nonce')# 3.获取post请求中的json数据encrypt = flask.request.json.get('encrypt')print(encrypt)# 4.调用加密解密工具类# DingCallbackCrypto3是官方提供的demo: https://github.com/open-dingtalk/dingtalk-callback-Crypto# 参数说明:'''1.token为应用中事件订阅下的签名token的数据2.aes_key为应用中事件订阅下的加密aes_key的数据3.app_key为应用的应用信息中的AppKey的数据注意:(这块需要具体问题具体分析,可以参考官方文档)1.开发者后台配置的订阅事件为应用级事件推送,此时app_key参数为应用的APP_KEY2.当使用HTTP回调注册接口方式接收钉钉推送的订阅事件时,是以企业为维度推送的,app_key为CorpId'''dingCrypto = DingTalkCrypto.DingTalkCrypto3(token, aes_key, app_key)# 5.解密回调事件decrypt_msg = dingCrypto.getDecryptMsg(msg_signature, timestamp, nonce, encrypt)print(decrypt_msg) # 打印结果: {"EventType":"check_url"}# 6.必须返回一个加密的success,让钉钉服务器进行确认success_map = dingCrypto.getEncryptedMap("success")return success_mapif __name__ == '__main__':api.run(port=6666, debug=True, host='0.0.0.0') # 启动服务# debug=True,改了代码后,不用重启,它会自动重启# 'host='0.0.0.0'可以被所有请求访问到
DingTalkCrypto
需要注意的是:
众所周知,Crypto是个老坑包了,如果下载不了Crypto或者说用不了Crypto类库,那么需要下载pycryptodome
pycrypto、pycrytodome和crypto是一个东西,crypto在python上面的名字是pycrypto,它是一个第三方库,但是已经停止更新四年了(截止到2023-02-16),所以不建议安装这个库
下载pycryptodome库之前需要把之前安装的crypto库都卸载干净
pip uninstall pycrypto
pip uninstall cryptography
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome
# -*- coding: utf-8 -*-
# 依赖Crypto类库
# API说明
# getEncryptedMap 生成回调处理成功后success加密后返回给钉钉的json数据
# decrypt 用于从钉钉接收到回调请求后import time
import io, base64, binascii, hashlib, string, struct
from random import choice
from Crypto.Cipher import AES"""
@param token 钉钉开放平台上,开发者设置的token
@param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey
@param corpId 企业自建应用-事件订阅, 使用appKey企业自建应用-注册回调地址, 使用corpId第三方企业应用, 使用suiteKey
"""
class DingTalkCrypto3:def __init__(self, token, encodingAesKey, key):self.encodingAesKey = encodingAesKeyself.key = keyself.token = tokenself.aesKey = base64.b64decode(self.encodingAesKey + '=')# 生成回调处理完成后的success加密数据def getEncryptedMap(self, content):encryptContent = self.encrypt(content)timeStamp = str(int(time.time()))nonce = self.generateRandomKey(16)sign = self.generateSignature(nonce, timeStamp, self.token,encryptContent)return {'msg_signature':sign,'encrypt':encryptContent,'timeStamp':timeStamp,'nonce':nonce}# 解密钉钉发送的数据def getDecryptMsg(self, msg_signature, timeStamp, nonce, content):"""解密:param content::return:"""sign = self.generateSignature(nonce, timeStamp, self.token,content)print(sign, msg_signature)if msg_signature != sign:raise ValueError('signature check error')content = base64.decodebytes(content.encode('UTF-8')) # 钉钉返回的消息体iv = self.aesKey[:16] # 初始向量aesDecode = AES.new(self.aesKey, AES.MODE_CBC, iv)decodeRes = aesDecode.decrypt(content)#pad = int(binascii.hexlify(decodeRes[-1]),16)pad = int(decodeRes[-1])if pad > 32:raise ValueError('Input is not padded or padding is corrupt')decodeRes = decodeRes[:-pad]l = struct.unpack('!i', decodeRes[16:20])[0]# 获取去除初始向量,四位msg长度以及尾部corpidnl = len(decodeRes)if decodeRes[(20+l):].decode() != self.key:raise ValueError('corpId 校验错误')return decodeRes[20:(20+l)].decode()def encrypt(self, content):"""加密:param content::return:"""msg_len = self.length(content)content = ''.join([self.generateRandomKey(16) , msg_len.decode() , content , self.key])contentEncode = self.pks7encode(content)iv = self.aesKey[:16]aesEncode = AES.new(self.aesKey, AES.MODE_CBC, iv)aesEncrypt = aesEncode.encrypt(contentEncode.encode())return base64.encodebytes(aesEncrypt).decode('UTF-8')# 生成回调返回使用的签名值def generateSignature(self, nonce, timestamp, token, msg_encrypt):print(type(nonce), type(timestamp), type(token), type(msg_encrypt))v = msg_encryptsignList = ''.join(sorted([nonce, timestamp, token, v]))return hashlib.sha1(signList.encode()).hexdigest()def length(self, content):"""将msg_len转为符合要求的四位字节长度:param content::return:"""l = len(content)return struct.pack('>l', l)def pks7encode(self, content):"""安装 PKCS#7 标准填充字符串:param text: str:return: str"""l = len(content)output = io.StringIO()val = 32 - (l % 32)for _ in range(val):output.write('%02x' % val)# print "pks7encode",content,"pks7encode", val, "pks7encode", output.getvalue()return content + binascii.unhexlify(output.getvalue()).decode()def pks7decode(self, content):nl = len(content)val = int(binascii.hexlify(content[-1]), 16)if val > 32:raise ValueError('Input is not padded or padding is corrupt')l = nl - valreturn content[:l]def generateRandomKey(self, size,chars=string.ascii_letters + string.ascii_lowercase + string.ascii_uppercase + string.digits):"""生成加密所需要的随机字符串:param size::param chars::return:"""return ''.join(choice(chars) for i in range(size))if __name__ == '__main__':dingCrypto = DingTalkCrypto3("xxxxxxxx", "xxxxxxxxxxxxxxxxxx", "xxxxxxxxxxx")success = dingCrypto.encrypt("success")print(success)
2.3接口验证
写好接口服务后放到有公网IP的地方运行(直接在python或者conda虚拟python环境下运行py文件、打包成可执行文件执行均可)
将服务接口复制到钉钉应用中的事件与回调中的请求网址中,点击保存即可
需要注意的是:
在开发过程中会测试事件订阅是否完成,高频率的刷新网页可能会导致事件订阅的aes_key和token被刷新或者没刷新但是因为缓存的问题,会有偏差,需要注意事件订阅中的两个值和接口服务代码中的两个值要一一对应起来,如果对应有误的话,保存的时候会报错
相关文章:
钉钉配置事件订阅(Python)
钉钉配置事件订阅 0.需求分析 需要实现钉钉企业通讯录同步至企业微信通讯录,这就需要用到钉钉的事件与回调 1.配置应用 登陆开放平台 https://open-dev.dingtalk.com/去企业内部开发里面,先创建个应用,后面都借用这个应用来调接口 创建完…...
Linux-Udev机制
一:Udev概述 udev 是一个用户空间的设备管理器,用于为事件设置处理程序。作为守护进程, udev 接收的事件主要由 linux 内核生成,这些事件是外部设备产生的物理事件。总之, udev 探测外设和热插拔,将设备控制权传递给内核,例如加载内核模块或设备固件。udev 是一个用户空…...
ERP是什么?中小商户有必要用吗?秦丝、金蝶、管家婆哪家强?
ERP系统刚开始传入中国的时候,基本上只有超大型或大型企业有条件实施,不过最近几年随着小微企业、中小商户的信息化需求不断增长,ERP软件已慢慢被普遍使用。但是仍然有不少中小商户,还没搞清楚ERP到底是什么,看到大家都…...
pytorch离线安装
windows下离线安装pytorch,很多内网机,无法连接外网,只能下载whl文件进行离线安装下载pytorch,地址https://download.pytorch.org/whl/torch_stable.html我是windows,Python37,没有gpu,所以选择…...
数据结构-算法的时间复杂度(1.1)
目录 1. 算法效率 2. 时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3 举例说明: 写在最后: 1. 算法效率 我们该如何判断一个算法的好坏? 衡量一个算法的好坏,是从时间和空间两个维度比较的, 而今天…...
Cygwin安装与Mingw
共同点:window下编译环境 区别:cygwin(gnu windows)模拟Linux编译环境, mingw模拟window编译环境,生成.exe可执行文件 目录 Cygwin安装 一、官网下载 二、双击安装 三、选择安装路径后,到连接方式如图 四、添加连…...
教育舆情监测方案有哪些,TOOM讲解教育舆情的应对与处理?
教育舆情方案是针对教育领域的舆情事件或问题而制定的应对方案。其主要目的是通过有效的信息收集、分析、处理和传播,帮助教育机构或相关组织及时掌握和应对公众舆论的发展趋势,维护良好的舆情形象和声誉,教育舆情监测方案有哪些,…...
c语言操作文件
1、文件缓冲区 文件缓冲区的目的:提高访问效率 提高磁盘使用寿命 刷新就是将当前缓冲区数据全部提交。 不刷新时,程序在崩溃时缓冲区内容无法输出(有些情形会带来错误) 文件缓冲区的四种刷新方式 行刷新(遇到换行符…...
【C语言】初识指针
目录 一、指针是什么 二、指针和指针类型 三、野指针 四、指针运算 五、指针和数组 六、二级指针 七、指针数组 一、指针是什么 指针就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度…...
FFMPEG自学一 音视频解封装
一、音视频包含哪些数据对于一个mp4文件我们可以通过音视频分析软件打开查看内部信息。从两图可以看出mp4文件一般包含 音频流 视频流等。对于上面的字段大致分析如下Format编码方式AVC现在大部分视频都是这种编码方式,即H264。CodecId编码器idavc1H264封装有2种格式…...
HoloLens 2 丨打包丨MRTK丨Unity丨新手教学
HoloLens 2打包流程制作前言开发工具介绍Visual Studio 2019MRTK插件或示例程序下载打包流程介绍Unity操作修改Visual Studio修改Hololens 修改Hololens 密码忘记总结前言 提示:今日功能介绍 使用 MRTK制作hololens 2的打包流程制作的新手教学。 开发工具介绍 这…...
AcWing语法基础课笔记 第四章 C++中的数组
第四章 C中的数组 程序 逻辑 数据,数组是存储数据的强而有力的手段。 ——闫学灿 一维数组 数组的定义 数组的定义方式和变量类似。 数组的初始化 在main函数内部,未初始化的数组中的元素是随机的。 访问数组元素 通过下标访问数…...
UTF小结
运行测试 编辑测试 运行模式:程序集Platform平台选择 Any Platforms编辑模式:程序集Platform平台选择 Editor 特性 Test、UnityTest特性:测试方法需要添加Test或UnityTest特性,测试方法是公有的SetUp、TearDown特性:…...
(考研湖科大教书匠计算机网络)第四章网络层-第六节3:开放最短路径优先OSPF的基本工作原理
获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:OSPF概述(1)概述(2)细节阐述A:链路状态和代价B:问候分组和邻居表Cÿ…...
积水在线监测仪——积水点、易涝点水位监测设备
一、设备概述 积水在线监测仪是一款用于城市积水点、易涝点等场景的水位监测设备,设备采用电池供电,无需另外供电,安装方便,使用简单。可以时监测水点、易涝点水位情况,当水位数据超过阈值后触发告警上传,…...
DCMM认证机构
一、什么是DCMM DCMM认证,又称为数据管理能力成熟度评估,依据 都是GB/T -《数据管理能力成熟度评估模型》,这是我国首个数据管理领域的国家标准,由国家质量监督检验检疫总局、国家标准化管理委员会于年3月15日正式发布。DCMM认证…...
Golang基于文件魔数判断文件类型
本文介绍基于魔数判断文件类型,涉及文件查找读取内容、文件魔数、字节比较,最后还介绍函数参数的知识。 查找位置 File.Seek()函数可以设置偏移位置,为下一次读或写确定偏移量,具体起点有whence确定:0标识相对文件开始…...
MySQL——索引视图练习题
学生表:Student (Sno, Sname, Ssex , Sage, Sdept) 学号,姓名,性别,年龄,所在系 Sno为主键 课程表:Course (Cno, Cname,) 课程号,课程名 Cno为主键 学生选课表:SC (Sno, Cno, Score)…...
哈希表题目:矩阵置零
文章目录题目标题和出处难度题目描述要求示例数据范围进阶解法一思路和算法代码复杂度分析解法二思路和算法代码复杂度分析解法三思路和算法代码复杂度分析题目 标题和出处 标题:矩阵置零 出处:73. 矩阵置零 难度 3 级 题目描述 要求 给定一个 m…...
HTTP API自动化测试从手工到平台的演变
不管是 Web 系统,还是移动 APP,前后端逻辑的分离设计已经是常态化,相互之间通过 API 调用进行数据交互。在基于 API 约定的开发模式下,如何加速请求 / 响应的 API 测试,让研发人员及早参与到调试中来呢?既然…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
