# 事件订阅

# 知音楼会向应用推送订阅的事件,例如isv应用的授权、suite_ticket、会议录制知等。通过订阅这些事件,可以更好地与知音楼集成。你只需告知音楼当某个事件发生时,知音楼需要推送消息到哪个URL,知音楼会以HTTP POST请求的方式将事件内容以JSON格式推送给你。

# 使用场景

  • 在会议中开启了会议录制,会议结束后想要获取到录制文件,此时就可以订阅会议录制事件,订阅后,当会议结束,就会收到来自知音楼的会议录制推送

上面只是其中一个案例,用户可以根据自己的需求选择性订阅对应事件 事件列表

# 事件订阅流程

目前只能通过人工手动配置接口回调地址和encrypt密钥,配置地址和订阅事件后,应用就可以接收到对应的事件推送

# 接收并响应事件

  • 为了保证事件数据的安全性,所有事件推送数据均为加密后的数据,业务方收到数据后需要对数据内容进行解密方可使用,
  • 推送数据以post请求,请求体为json的方式发送到应用设定的回调地址(回调地址后和encrypt密钥同时设置)
  • 回调接口在3000ms内响应http状态码为200且响应报文为如下报文内容认为服务商应用已正常接收回调,否则认为是失败的
{
    "code": 200
}
  • 事件推送的数据结构
    {
      "event_id":"c6b8b25e-e983-4db6-a75a-3c9dd97914ef",//事件ID
      "timestamp":1670335546,//时间戳
      "encrypt":"ae337f99c4dbec3ddc98bfd196af822a",//加密的消息内容
    }

encrypt解密后的数据结构请参考 事件列表

# 关于事件失败重试

  • 在第一次推送失败后依次按照 60s、10m、30m、2h重试,共计四次重试,如果四次还不成功则丢弃该事件,不再重试

# 关于事件来源验证

  • 事件推送时会在请求头增加 X-Request-Timestamp、X-Request-Nonce、X-Signature三个字段,将X-Request-Timestamp、X-Request-Nonce、encrypt密钥 拼接后按照 encode('utf-8') 编码得到 byte[] b1,再拼接上请求的原始 body, 得到一个 byte[] param。将 param 用 sha256 加密,得到字符串 sign, 校验 sign 是否和请求头 X-Signature 一致。

签名计算示例(php)

<?php
    $encrypt_key = "";  // 开放平台后台的 Encrypt Key
    $timestamp = "";
    $nonce = "";
    $body = ""; // 指整个请求体,不要在反序列化后再计算
    $signature = hash("sha256", $timestamp . $nonce . $encrypt_key . $body);

签名计算示例(java)

using System.Security.Cryptography;
public static string calculateSignature(string timestamp, string nonce, string encryptKey, string body) {
        StringBuilder content = new StringBuilder();
        content.Append(timestamp);
        content.Append(nonce);
        content.Append(encryptKey);
        content.Append(body);
        SHA256 sha256 = new SHA256CryptoServiceProvider();  
        byte[] bytes_out = sha256.ComputeHash(Encoding.Default.GetBytes(content.ToString()));  
        string result = BitConverter.ToString(bytes_out);  
        result = result.Replace("-", "");  
        return result;  
}

签名计算示例(golang)

import (
   "crypto/sha256"
   "fmt"
)
func calculateSignature(timestamp, nonce, encryptKey, bodystring string) string {
   var b strings.Builder
   b.WriteString(timestamp)
   b.WriteString(nonce)
   b.WriteString(encryptKey)
   b.WriteString(bodystring) //bodystring 指整个请求体,不要在反序列化后再计算
   bs := []byte(b.String())
   h := sha256.New()
   h.Write(bs)
   bs = h.Sum(nil)
   sig := fmt.Sprintf("%x", bs)
   return sig
}

签名计算示例(python3)

import hashlib
bytes_b1 = (timestamp + nonce + encrypt_key).encode('utf-8')
bytes_b = bytes_b1 + body
h = hashlib.sha256(bytes_b)
signature = h.hexdigest()

签名计算示例(Node.js)

var crypto = require('crypto');
function calculateSignature(timestamp, nonce, encryptKey, body) {
        const content = timestamp + nonce + encryptKey + body
        const sign = crypto.createHash('sha256').update(content).digest('hex');
        return sign
}

# 关于事件内容解密

  • 使用app_secret/suite_secret作为密钥,使用AES-256-ECB算法来解密

消息解密计算示例(golang)

import (
    "bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"errors"
)
func AESDecryptString(input, aesKey string) (string, error) {
	if keyString == "" {
		return "", nil
	}
	originalData, err := base64.StdEncoding.DecodeString(keyString)
	if err != nil {
		return "", err
	}

	block, err := aes.NewCipher([]byte(aesKey)])
	if err != nil {
		logx.Errorf("Decrypt key error: % x", key)
		return nil, err
	}

	decrypter := NewECBDecrypter(block)
	decrypted := make([]byte, len(originalData))
	decrypter.CryptBlocks(decrypted, originalData)

	return pkcs5Unpadding(decrypted, decrypter.BlockSize()),nil
}
上次更新: 2/17/2023, 4:50:02 PM
foo