Skip to content

Commit f9c2bda

Browse files
committed
feat: 事件链路升级。增加webhook接入文档
1 parent 7d9972e commit f9c2bda

File tree

4 files changed

+145
-6
lines changed

4 files changed

+145
-6
lines changed

.github/workflows/pr-open-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: wagoid/commitlint-github-action@v4
1616
- uses: actions/setup-node@v2
1717
with:
18-
node-version: '16'
18+
node-version: '18'
1919
check-latest: true
2020
- name: Get yarn cache directory path
2121
id: yarn-cache-dir-path
125 KB
Loading

docs/develop/api-v2/dev-prepare/interface-framework/event-emit.md

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,80 @@
11

2-
# 事件订阅与异步通知
2+
# 事件订阅与通知
33

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

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

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

11-
## WebSocket 方式
13+
### 数据结构
1214

13-
通过 `WebSocket` 建立与QQ后台的长链接通信管道,当需要事件通知的时候QQ后台通过 `WebSocket` 连接下发事件到开发者服务器上。
15+
#### Payload
16+
17+
网关的上下行消息采用的都是同一个结构,如下:
18+
19+
```json
20+
{
21+
"op": 0,
22+
"d": {},
23+
"t": "GATEWAY_EVENT_NAME"
24+
}
25+
```
26+
27+
##### OpCode
28+
29+
`opcode` 含义如下:
30+
31+
| **CODE** | **名称** | **客户端行为** | **描述** |
32+
| --- | --- | --- | --- |
33+
| 0 | Dispatch | Receive | 服务端进行消息推送 |
34+
| 12 | HTTP Callback ACK | Reply | 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据 |
35+
| 13 | 回调地址验证 | Receive | 开放平台对机器人服务端进行验证 |
36+
| 14 | 回调地址验证 ACK | Reply | 机器人服务端响应开放平台的验证请求 |
37+
38+
39+
### 签名校验
40+
机器人服务端需要对回调请求进行签名验证以保证数据没有被篡改过。
41+
[签名算法](opcode.md)
42+
43+
### 回调地址及事件监听配置
44+
45+
开发者需要提供一个HTTPS回调地址。并选定监听的事件类型。开放平台会将事件通过回调的方式推送给机器人。
46+
<img :src="$withBotBase('/images/api-231017/event_subscription.png')" alt="event_subscription">
47+
48+
开发者配置回调地址时,开放平台会对回调地址进行验证。机器人服务端需要按格式返回签名信息。签名算法同上。 机器人服务端需要在 3 秒内响应200或204,表示接受到事件。
49+
* 请求结构
50+
51+
| **字段** | **描述** |
52+
| --- |------|
53+
| plain_token | 要计算hash的字符串 |
54+
| event_ts | 时间戳 |
55+
56+
* 返回结果
57+
58+
| **字段** | **描述** |
59+
| --- |-------------|
60+
| plain_token | 要计算hash的字符串 |
61+
| signature | 签名 |
62+
63+
例如
64+
65+
回调验证请求:
66+
```json
67+
{"d": {"plain_token": "qgg8vlvZRS6UYooatFL8Aw","event_ts": 1654503849680},"op": 13}
68+
```
69+
返回结果:
70+
```json
71+
{"plain_token": "qgg8vlvZRS6UYooatFL8Aw","signature": "23a89b634c017e5364a1c8d9c8ea909b60dd5599e2bb04bb1558d9c3a121faa5"}
72+
```
73+
74+
75+
## WebSocket 方式(deprecated)
76+
77+
通过 `WebSocket` 建立与QQ机器人开放平台的长链接通信管道,当需要事件通知的时候QQ后台通过 `WebSocket` 连接下发事件到开发者服务器上。
1478

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

@@ -134,7 +198,7 @@ wss://api.sgroup.qq.com/websocket/
134198
```json
135199
{
136200
"op": 1,
137-
"d": 251 // null
201+
"d": 251 //null
138202
}
139203
```
140204

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

183247
事件和位移的关系如下:
184248

185-
```yaml
249+
```
186250
GUILDS (1 << 0)
187251
- GUILD_CREATE // 当机器人加入新guild时
188252
- GUILD_UPDATE // 当guild资料发生变更时
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# 安全和授权
2+
3+
开发者需要对每一次回调请求,根据回调中的签名等信息验证请求者身份,避免安全隐患。目前签名算法使用Ed25519。
4+
5+
## 安全凭证
6+
7+
开发者平台的 Bot Secret 用于加密签名字符串和服务器端验证签名字符串的密钥。用户必须严格保管安全凭证,避免泄露。
8+
9+
## 验证签名
10+
11+
### 1. 签名验证参数
12+
13+
| 字段名 | 说明 | 参考值 |
14+
|-----------------------|---------------------------|-----------------|
15+
| X-Signature-Ed25519 | HTTP Header 中透传 Signature | 3ecd***(64字节) |
16+
| X-Signature-Timestamp | HTTP Header 透传的签名时间戳 | 1636373772 |
17+
| HTTP Body | HTTP 请求中 Body 值 | {"msg":"hello"} |
18+
19+
### 2. 验证签名过程
20+
21+
以下代码以Go语言为例,引用 `crypto/ed25519` 包实现 `Ed25519` 算法
22+
23+
- 根据开发者平台的 Bot Secret 值进行repeat操作得到签名32字节的 seed ,根据 seed 调用 Ed25519 算法生成32字节公钥
24+
25+
```go
26+
// 根据botSecret进行repeat操作后得到seed值计算出公钥
27+
seed := botSecret
28+
for len(seed) < ed25519.SeedSize {
29+
seed = strings.Repeat(seed, 2)
30+
}
31+
rand := strings.NewReader(seed[:ed25519.SeedSize])
32+
publicKey, _, err := ed25519.GenerateKey(rand)
33+
```
34+
35+
- 获取 HTTP Header 中 X-Signature-Ed25519 的值进行 hec (十六进制解码)操作后的得到 Signature 并进行校验
36+
37+
```go
38+
// 取HTTP header中X-Signature-Ed25519(进行hex解码)并校验
39+
signature := req.Header.Get("X-Signature-Ed25519")
40+
if signature == "" {
41+
return false
42+
}
43+
sig, err := hex.DecodeString(signature)
44+
if err != nil {
45+
return false
46+
}
47+
if len(sig) != ed25519.SignatureSize || sig[63]&224 != 0 {
48+
return false
49+
}
50+
```
51+
52+
- 获取 HTTP Header 中 X-Signature-Timestamp 的和 HTTP Body 的值按照 timestamp+body 顺序进行组合成签名体msg
53+
54+
```go
55+
// 取HTTP header中 X-Signature-Timestamp 并校验
56+
timestamp := req.Header.Get("X-Signature-Timestamp")
57+
if timestamp == "" {
58+
return false
59+
}
60+
// 按照timstamp+Body顺序组成签名体
61+
var msg bytes.Buffer
62+
msg.WriteString(timestamp)
63+
var body bytes.Buffer
64+
// copy body into buffers
65+
_, err = io.Copy(&msg, io.TeeReader(r.Body, &body))
66+
if err != nil {
67+
return false
68+
}
69+
```
70+
71+
- 根据公钥、Signature、签名体调用 Ed25519 算法进行验证
72+
73+
```go
74+
ed25519.Verify(publicKey, msg.Bytes(), sig)
75+
```

0 commit comments

Comments
 (0)