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

谷歌浏览器插件

项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

【单片机期末】单片机系统设计

主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

网络编程(UDP编程)

思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...