python版钉钉回调

说明:

因为公司项目有两个系统这两个系统都需要同步审批回调事件所以些了这个钉钉回调分发的中转服务。

话不多说开始上代码
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)

钉钉回调分发到此结束,第二次写写的不好之处还望指正。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值