Skip to content

Commit

Permalink
feat: 事件链路升级。增加webhook接入文档
Browse files Browse the repository at this point in the history
  • Loading branch information
rianli committed Aug 27, 2024
1 parent 7d9972e commit 7644048
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 5 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 69 additions & 5 deletions docs/develop/api-v2/dev-prepare/interface-framework/event-emit.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,80 @@

# 事件订阅与异步通知
# 事件订阅与通知

<!-- > 当用户在QQ平台内的一些行为操作或某些接口的有异步返回通知确认机制的场景的时候,QQ 会通过"事件"的方式,通知到开发者服务器,开发者可自行根据具体事件通知来进行下一步响应。譬如用户跟机器人发消息,用户添加机器人好友,机器人被拉入群聊等等事件。 -->

::: tip 说明
当用户在QQ平台内的一些行为操作或某些接口的有异步返回通知确认机制的场景的时候,QQ 会通过"事件"的方式,通知到开发者服务器,开发者可自行根据具体事件通知来进行下一步响应。譬如用户跟机器人发消息,用户添加机器人好友,机器人被拉入群聊等等事件。
:::

## Webhook方式
QQ机器人开放平台支持通过使用HTTP接口接收事件。开发者可通过[管理端](https://q.qq.com)设定回调地址,监听事件等。

## WebSocket 方式
### 数据结构

通过 `WebSocket` 建立与QQ后台的长链接通信管道,当需要事件通知的时候QQ后台通过 `WebSocket` 连接下发事件到开发者服务器上。
#### Payload

网关的上下行消息采用的都是同一个结构,如下:

```json
{
"op": 0,
"d": {},
"t": "GATEWAY_EVENT_NAME"
}
```

##### OpCode

`opcode` 含义如下:

| **CODE** | **名称** | **客户端行为** | **描述** |
| --- | --- | --- | --- |
| 0 | Dispatch | Receive | 服务端进行消息推送 |
| 12 | HTTP Callback ACK | Reply | 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据 |
| 13 | 回调地址验证 | Receive | 开放平台对机器人服务端进行验证 |
| 14 | 回调地址验证 ACK | Reply | 机器人服务端响应开放平台的验证请求 |


### 签名校验
机器人服务端需要对回调请求进行签名验证以保证数据没有被篡改过。
[签名算法](opcode.md)

### 回调地址及事件监听配置

开发者需要提供一个HTTPS回调地址。并选定监听的事件类型。开放平台会将事件通过回调的方式推送给机器人。
<img :src="$withBotBase('/images/api-231017/event_subscription.png')" alt="event_subscription">

开发者配置回调地址时,开放平台会对回调地址进行验证。机器人服务端需要按格式返回签名信息。签名算法同上。 机器人服务端需要在 3 秒内响应200或204,表示接受到事件。
* 请求结构

| **字段** | **描述** |
| --- |------|
| plain_token | 要计算hash的字符串 |
| event_ts | 时间戳 |

* 返回结果

| **字段** | **描述** |
| --- |-------------|
| plain_token | 要计算hash的字符串 |
| signature | 签名 |

例如

回调验证请求:
```json
{"d": {"plain_token": "qgg8vlvZRS6UYooatFL8Aw","event_ts": 1654503849680},"op": 13}
```
返回结果:
```json
{"plain_token": "qgg8vlvZRS6UYooatFL8Aw","signature": "23a89b634c017e5364a1c8d9c8ea909b60dd5599e2bb04bb1558d9c3a121faa5"}
```


## WebSocket 方式(deprecated)

通过 `WebSocket` 建立与QQ机器人开放平台的长链接通信管道,当需要事件通知的时候QQ后台通过 `WebSocket` 连接下发事件到开发者服务器上。

开发者需要维护 `WebSocket` 长链接的状态,包括连接状态维护、登录鉴权、心跳维护、断线恢复重连等。

Expand Down Expand Up @@ -134,7 +198,7 @@ wss://api.sgroup.qq.com/websocket/
```json
{
"op": 1,
"d": 251 // null
"d": 251 //null
}
```

Expand Down Expand Up @@ -182,7 +246,7 @@ wss://api.sgroup.qq.com/websocket/

事件和位移的关系如下:

```yaml
```
GUILDS (1 << 0)
- GUILD_CREATE // 当机器人加入新guild时
- GUILD_UPDATE // 当guild资料发生变更时
Expand Down
75 changes: 75 additions & 0 deletions docs/develop/api-v2/dev-prepare/interface-framework/sign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# 安全和授权

开发者需要对每一次回调请求,根据回调中的签名等信息验证请求者身份,避免安全隐患。目前签名算法使用Ed25519。

## 安全凭证

开发者平台的 Bot Secret 用于加密签名字符串和服务器端验证签名字符串的密钥。用户必须严格保管安全凭证,避免泄露。

## 验证签名

### 1. 签名验证参数

| 字段名 | 说明 | 参考值 |
|-----------------------|---------------------------|-----------------|
| X-Signature-Ed25519 | HTTP Header 中透传 Signature | 3ecd***(64字节) |
| X-Signature-Timestamp | HTTP Header 透传的签名时间戳 | 1636373772 |
| HTTP Body | HTTP 请求中 Body 值 | {"msg":"hello"} |

### 2. 验证签名过程

以下代码以Go语言为例,引用 `crypto/ed25519` 包实现 `Ed25519` 算法

- 根据开发者平台的 Bot Secret 值进行repeat操作得到签名32字节的 seed ,根据 seed 调用 Ed25519 算法生成32字节公钥

```go
// 根据botSecret进行repeat操作后得到seed值计算出公钥
seed := botSecret
for len(seed) < ed25519.SeedSize {
seed = strings.Repeat(seed, 2)
}
rand := strings.NewReader(seed[:ed25519.SeedSize])
publicKey, _, err := ed25519.GenerateKey(rand)
```

- 获取 HTTP Header 中 X-Signature-Ed25519 的值进行 hec (十六进制解码)操作后的得到 Signature 并进行校验

```go
// 取HTTP header中X-Signature-Ed25519(进行hex解码)并校验
signature := req.Header.Get("X-Signature-Ed25519")
if signature == "" {
return false
}
sig, err := hex.DecodeString(signature)
if err != nil {
return false
}
if len(sig) != ed25519.SignatureSize || sig[63]&224 != 0 {
return false
}
```

- 获取 HTTP Header 中 X-Signature-Timestamp 的和 HTTP Body 的值按照 timestamp+body 顺序进行组合成签名体msg

```go
// 取HTTP header中 X-Signature-Timestamp 并校验
timestamp := req.Header.Get("X-Signature-Timestamp")
if timestamp == "" {
return false
}
// 按照timstamp+Body顺序组成签名体
var msg bytes.Buffer
msg.WriteString(timestamp)
var body bytes.Buffer
// copy body into buffers
_, err = io.Copy(&msg, io.TeeReader(r.Body, &body))
if err != nil {
return false
}
```

- 根据公钥、Signature、签名体调用 Ed25519 算法进行验证

```go
ed25519.Verify(publicKey, msg.Bytes(), sig)
```

0 comments on commit 7644048

Please sign in to comment.