Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 事件链路升级:增加webhook接入文档。 #233

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-open-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: wagoid/commitlint-github-action@v4
- uses: actions/setup-node@v2
with:
node-version: '16'
node-version: '18'
check-latest: true
- name: Get yarn cache directory path
id: yarn-cache-dir-path
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 77 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,88 @@

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

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

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

## Webhook方式

## WebSocket 方式
**当前方式灰度中,仅灰度用户可使用** 其它用户请使用 [websocket方式](#websocket方式)

通过 `WebSocket` 建立与QQ后台的长链接通信管道,当需要事件通知的时候QQ后台通过 `WebSocket` 连接下发事件到开发者服务器上。
灰度用户如遇问题可通过QQ机器人反馈助手反馈 <img :src="$withBotBase('/images/api-231017/qqrobot-feedback.jpg')" alt="feedback">

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

### 数据结构

#### 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 | 签名 |

例如机器人账号
```
appid: 11111111
secret: DG5g3B4j9X2KOErG
```
回调验证请求:
```
headers: User-Agent:[QQBot-Callback] X-Bot-Appid:[11111111]
body: {"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13},
```
机器人应返回:
```
body: {"plain_token": "Arq0D5A61EgUu4OxUvOp","signature": "87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfcadc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706"}
```

## WebSocket方式

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

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

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

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

事件和位移的关系如下:

```yaml
```
GUILDS (1 << 0)
- GUILD_CREATE // 当机器人加入新guild时
- GUILD_UPDATE // 当guild资料发生变更时
Expand Down
97 changes: 97 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,97 @@
# 安全和授权

开发者需要对每一次回调请求,根据回调中的签名等信息验证请求者身份,避免安全隐患。目前签名算法使用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, privateKey, err := ed25519.GenerateKey(rand)
```

DEMO
```
【输入】
secret: naOC0ocQE3shWLAfffVLB1rhYPG7
seed: naOC0ocQE3shWLAfffVLB1rhYPG7naOC
【输出】
publicKey: [215 195 98 254 120 174 248 31 242 50 135 180 147 98 139 93 176 42 60 79 227 11 33 94 77 25 96 155 93 118 103 58]
privateKey: [110 97 79 67 48 111 99 81 69 51 115 104 87 76 65 102 102 102 86 76 66 49 114 104 89 80 71 55 110 97 79 67 215 195 98 254 120 174 248 31 242 50 135 180 147 98 139 93 176 42 60 79 227 11 33 94 77 25 96 155 93 118 103 58]
```

- 获取 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)
```

DEMO
```
【输入】
secret: naOC0ocQE3shWLAfffVLB1rhYPG7
body: { "op": 0,"d": {}, "t": "GATEWAY_EVENT_NAME"}
timestamp: 1725442341

【输出】
sig: 865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002

```
Loading