说明:
因为公司项目有两个系统这两个系统都需要同步审批回调事件所以些了这个钉钉回调分发的中转服务。
话不多说开始上代码
1.首先创建出需要的模型字段
from django.db import models
class DingTag(models.Model):
"""钉钉事件"""
url = models.ForeignKey('Url', models.DO_NOTHING)
tag = models.CharField(max_length=30, blank=True, null=True)
remark = models.CharField(max_length=255, blank=True, null=True)
create_date = models.DateTimeField(blank=True, null=True)
modify_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'ding_tag'
class Url(models.Model):
"""需要回调的url"""
url = models.CharField(max_length=200, blank=True, null=True)
remark = models.CharField(max_length=50, blank=True, null=True)
create_date = models.DateTimeField(blank=True, null=True)
modify_date = models.DateTimeField(blank=True, null=True)
path = models.CharField(max_length=255)
db_name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'url'
2.封装一个类来加密解密和封装钉钉需要的success返回值
class DingTalkCrypto():
def __init__(self, encodingAesKey, key):
self.encodingAesKey = encodingAesKey
self.key = key
self.aesKey = base64.b64decode(self.encodingAesKey + '=')
self.token = '12345'
def encrypt(self, content):
"""加密"""
msg_len = self.length(content)
content = 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)
return base64.b64encode(aesEncrypt).decode().replace('\n', '')
def length(self, content):
"""将msg_len转为符合要求的四位字节长度"""
l = len(content)
return struct.pack('>l', l)
def pks7encode(self, content):
"""安装PKCS#7标准填充字符串"""
l = len(content)
output = io.StringIO()
val = 32 - (l % 32)
for _ in range(val):
output.write('%02x' % val)
return bytes(content, 'utf-8') + binascii.unhexlify(output.getvalue())
def pks7decode(self, content):
nl = len(content)
val = int(binascii.hexlify(content[-1].encode()), 16)
if val > 32:
raise ValueError('Input is not padded or padding is corrupt')
l = nl - val
return content[:l]
def decrypt(self, content):
"""解密数据"""
# 钉钉返回的消息体
content = base64.b64decode(content)
iv = self.aesKey[:16] # 初始向量
aesDecode = AES.new(self.aesKey, AES.MODE_CBC, iv)
decodeRes = aesDecode.decrypt(content)[20:].decode().replace(self.key, '')
return self.pks7decode(decodeRes)
def generateRandomKey(self, size,
chars=string.ascii_letters + string.ascii_lowercase + string.ascii_uppercase + string.digits):
"""生成加密所需要的随机字符串"""
return ''.join(choice(chars) for i in range(size))
def generateSignature(self, nonce, timestamp, token, msg_encrypt):
"""生成签名"""
signList = ''.join(sorted([nonce, timestamp, token, msg_encrypt])).encode()
return hashlib.sha1(signList).hexdigest()
def result_success(encode_aes_key, token, corp_id):
"""封装success返回值"""
dtc = DingTalkCrypto(encode_aes_key,corp_id)
encrypt = dtc.encrypt('success')
timestamp = str(int(round(time.time())))
nonce = dtc.generateRandomKey(8)
# 生成签名
signature = dtc.generateSignature(nonce, timestamp, token, encrypt)
# 构造返回数据
new_data = {
'msg_signature': signature,
'timeStamp': timestamp,
'nonce': nonce,
'encrypt': encrypt
}
return new_data
def encrypt_result(encode_aes_key, din_corpid, encrypt):
"""解密钉钉回调的返回值"""
dtc = DingTalkCrypto(encode_aes_key, din_corpid)
return dtc.decrypt(encrypt)
def getDecryptMsg(msg_signature, timeStamp, nonce, content, encode_aes_key, din_corpid):
dtc = DingTalkCrypto(encode_aes_key, din_corpid)
sign = dtc.generateSignature(nonce, timeStamp, keys.TOKEN, content)
if msg_signature != sign:
raise ValueError('signature check error')
3.向钉钉服务器注册审批事件
def register_call(request):
access_token = get_token() # 获取微应用后台免登的access_token
url = 'https://oapi.dingtalk.com/call_back/update_call_back?access_token=%s' % access_token
data = {
'call_back_tag': ['user_add_org', 'user_modify_org', 'user_leave_org', 'user_active_org', 'org_admin_add',
'org_admin_remove', 'org_dept_create', 'org_dept_modify', 'org_dept_remove', 'org_remove',
'org_change', 'label_user_change', 'label_conf_add', 'label_conf_del', 'label_conf_modify',
'edu_user_insert', 'edu_user_update', 'edu_user_delete', 'edu_user_relation_insert',
'edu_user_relation_update', 'edu_user_relation_delete', 'edu_dept_insert', 'edu_dept_update',
'edu_dept_delete', 'bpms_task_change', 'bpms_instance_change', 'chat_add_member',
'chat_remove_member', 'chat_quit', 'chat_update_owner', 'chat_update_title', 'chat_disband',
'check_in', 'attendance_check_record', 'attendance_schedule_change', 'attendance_overtime_duration',
'meetingroom_book', 'meetingroom_room_info'],
'token': 自己的token,
'aes_key': 自己的AES_KEY,
'url': '你自己的url',
'access_token': access_token,
}
res = requests.post(url, json=data)
return HttpResponse(res)
3.分发审批事件
def call_back(request):
"""分发审批事件"""
token = keys.TOKEN
result_success(keys.AES_KEY, token, keys.CORP_ID)
data = request.body
json_data = json.loads(data)
request_data = request.GET
getDecryptMsg(request_data['signature'], request_data['timestamp'], request_data['nonce'],
json_data['encrypt'], keys.AES_KEY, keys.CORP_ID)
type_info = encrypt_result(keys.AES_KEY, keys.CORP_ID, json_data['encrypt'])
type_name = json.loads(type_info)
name = type_name.get('EventType')
print(name)
# 查询事件
state = None
ding_tag = DingTag.objects.filter(tag=name)
for url in ding_tag:
urls = url.url.url
session = requests.session()
try:
if url.url.db_name:
session.get(url=urls + 'web?db={}'.format(url.url.db_name))
state = session.post(urls + url.url.path, json=json_data)
else:
url2 = urls + url.url.path + '?timestamp={0}&nonce={1}&signature={2}'.format(request_data['timestamp'],
request_data['nonce'], request_data['signature'])
state = requests.post(url2, json=json_data)
except ConnectionAbortedError:
logger.error(ConnectionAbortedError)
if state is None:
response = result_success(keys.AES_KEY, token, keys.CORP_ID)
return JsonResponse(response)
return HttpResponse(state.content)
4.获取请求数数据
@csrf_exempt
def get_data(request):
"""获取请求数据"""
data = request.POST
token = get_token() # 获取微应用后台免登的access_token
url = 'https://oapi.dingtalk.com/topapi/processinstance/create?access_token=%s' % token
res = requests.post(url, data)
response = json.loads(res.text)
return JsonResponse(response)
钉钉回调分发到此结束,第二次写写的不好之处还望指正。