当前位置: 首页 > news >正文

钉钉配置事件订阅(Python)

钉钉配置事件订阅

0.需求分析

需要实现钉钉企业通讯录同步至企业微信通讯录,这就需要用到钉钉的事件与回调

1.配置应用

登陆开放平台

https://open-dev.dingtalk.com/

去企业内部开发里面,先创建个应用,后面都借用这个应用来调接口

在这里插入图片描述

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

在这里插入图片描述
进入应用中的事件与回调,自动生成aes_keytoken,然后保存就好这两个数据

在这里插入图片描述

2.服务开发

2.1请求验证

配置上面1.配置应用中的事件订阅下的请求网址如http://yourserver/api/callback时,需要把这个网址的接口开发好。钉钉会发送一个类似下面这样的请求:

一个POST类型的http请求,携带了部分url参数和json的加密参数

请求的路径参数包括signaturemsg_signaturetimestampnonce,请求体只有一个encryptjson

curl -X 'POST' \'http://yourserver:80/api/callback?signature=369beedea8d1c8d1ad18936e827d29d0c8415baf&msg_signature=369beedea8d1c8d1ad18936e827d29d0c8415baf&timestamp=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

pycryptopycrytodomecrypto是一个东西,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特性&#xff1a…...

(考研湖科大教书匠计算机网络)第四章网络层-第六节3:开放最短路径优先OSPF的基本工作原理

获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:OSPF概述(1)概述(2)细节阐述A:链路状态和代价B:问候分组和邻居表C&#xff…...

积水在线监测仪——积水点、易涝点水位监测设备

一、设备概述 积水在线监测仪是一款用于城市积水点、易涝点等场景的水位监测设备,设备采用电池供电,无需另外供电,安装方便,使用简单。可以时监测水点、易涝点水位情况,当水位数据超过阈值后触发告警上传,…...

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 测试,让研发人员及早参与到调试中来呢?既然…...

ViGEmBus虚拟控制器驱动完全指南:从技术原理到场景落地的突破方案

ViGEmBus虚拟控制器驱动完全指南:从技术原理到场景落地的突破方案 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus 价值定位:重新定义…...

解决插件管理痛点:Scarab的智能高效管理方案

解决插件管理痛点:Scarab的智能高效管理方案 【免费下载链接】Scarab An installer for Hollow Knight mods written in Avalonia. 项目地址: https://gitcode.com/gh_mirrors/sc/Scarab 你是否曾为部署一个心仪的游戏插件而耗费整个下午?好不容易…...

Cesium 视角控制全攻略:禁用鼠标交互的多种方法

1. 为什么需要禁用Cesium鼠标交互? 在开发基于Cesium的三维地理信息系统时,我们经常会遇到需要限制用户视角操作的场景。比如在展示固定路线的飞行演示时,如果允许用户随意旋转地图,可能会打乱预设的动画效果;在嵌入式…...

如何在普通PC上低成本部署Qwen3?VLLM轻量化配置指南

如何在普通PC上低成本部署Qwen3?VLLM轻量化配置指南 对于大多数个人开发者和小型团队来说,高性能服务器和顶级显卡往往是可望而不可及的奢侈品。但别担心,即使你只有一台普通PC,也能通过合理的配置和优化手段成功部署Qwen3这样的大…...

AI辅助开发:如何优化CiteSpace关键词聚类图谱线条的可视化效果

作为一名经常和文献计量数据打交道的开发者,我深知CiteSpace这类工具生成的关键词共现图谱有多“劝退”。密密麻麻的线条交织在一起,像一团理不清的毛线,关键信息被淹没在视觉噪音里。传统的力导向布局算法在处理大规模、高密度网络时&#x…...

如何通过MCP协议实现AI助手与Figma设计的双向交互

如何通过MCP协议实现AI助手与Figma设计的双向交互 【免费下载链接】cursor-talk-to-figma-mcp Cursor Talk To Figma MCP 项目地址: https://gitcode.com/GitHub_Trending/cu/cursor-talk-to-figma-mcp 在当今的设计开发工作流中,设计工具与AI助手之间的割裂…...

中国有实力的科技公司有哪些

中国有实力的科技公司有哪些3中国有实力的科技公司全景分析:从互联网巨头到硬科技领军者本文基于2025-2026年最新产业数据,梳理中国具备全球竞争力的科技公司矩阵。文章采用结构化数据呈现方式,重点分析华为、腾讯、阿里巴巴、比亚迪及美的集…...

知网vs维普vs万方:用同一款工具降AI率效果差多少?

知网vs维普vs万方:用同一款工具降AI率效果差多少? 很多同学在降AI率的时候有一个疑问:学校用的是知网检测,我在某个平台降完之后,如果学校临时换成维普或万方,效果还能达标吗? 这个问题的本质是…...

C++模板编程:从函数到类的实现原理

1. C模板编程深度解析:从函数模板到类模板的实现原理1.1 模板编程的必要性在C开发中,经常会遇到需要处理不同数据类型但逻辑相同的场景。以二维坐标点类为例,传统实现方式需要为每种数据类型创建独立的类:// 浮点型坐标点类 class…...

效率翻倍:用快马平台将你的效率工具idea一键生成

最近工作总是被各种琐事打断,效率直线下降。作为一个经常需要同时处理多个项目的开发者,我特别需要一个能结合待办事项和番茄工作法的工具。传统的待办清单缺少时间管理,而单独的番茄钟应用又没法直观关联具体任务。于是决定自己动手开发一个…...