Skip to content

Commit 7c395f7

Browse files
committed
增加webhook相关文档
1 parent f5856a8 commit 7c395f7

File tree

1 file changed

+166
-0
lines changed

1 file changed

+166
-0
lines changed

Writerside/topics/component-qq-guild-Webhook.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,87 @@ class CallbackHandler(
129129
}
130130
```
131131

132+
</tab>
133+
<tab title="Java" group-key="Java">
134+
135+
```Java
136+
/**
137+
* 处理所有qq机器人的回调请求的处理器。
138+
*/
139+
@RestController("/callback")
140+
public class CallbackHandler {
141+
private static final String SIGNATURE_HEAD = "X-Signature-Ed25519";
142+
private static final String TIMESTAMP_HEAD = "X-Signature-Timestamp";
143+
144+
private final Application application;
145+
146+
public CallbackHandler(Application application) {
147+
this.application = application;
148+
}
149+
150+
/**
151+
* 处理 `/callback/qq/{appId}` 的事件回调请求,
152+
* 找到对应的 bot 并向其推送事件。
153+
*/
154+
@PostMapping("/qq/{appId}")
155+
public CompletableFuture<ResponseEntity<?>> handleEvent(
156+
@PathVariable("appId") String appId,
157+
@RequestHeader(SIGNATURE_HEAD) String signature,
158+
@RequestHeader(TIMESTAMP_HEAD) String timestamp,
159+
@RequestBody String payload
160+
) {
161+
// 寻找指定 `appId` 的 QGBot
162+
final var targetBot = application.getBotManagers().stream()
163+
// 1. 寻找类型是 QQGuildBotManager 的 BotManager
164+
.filter(manager -> manager instanceof QQGuildBotManager)
165+
.map(QQGuildBotManager.class::cast)
166+
// 2. 寻找 appId 匹配的 bot
167+
.flatMap(manager ->
168+
// 使用 manager.all() 可以直接访问 QGBot 类型,
169+
// 而使用 manager.allStreamable() 得到的是 Bot 类型,需要再转化一次
170+
// 二者都可以,这里选择第一个方案
171+
Streamable.of(manager.all()).asStream())
172+
.filter(bot -> {
173+
// 寻找 bot.appId 为函数入参 appId 的 bot
174+
// bot.id 本质上也是使用的 appId, 因此直接使用 bot.getId().toString() 也是可以的。
175+
var botAppId = bot.getSource().getTicket().getAppId();
176+
return appId.equals(botAppId);
177+
})
178+
// 得到第一个符合条件的bot
179+
.findFirst()
180+
// 如果没找到,自行处理。这里选择抛出异常并响应404。
181+
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "app " + appId + " not found"));
182+
183+
// 在 servlet web 中,在异步中处理.
184+
// 作用域、是否要用异步等根据你的项目情况调整。
185+
186+
// 如果要进行接口校验,配置 ed25519 校验所需要的内容
187+
final var options = new EmitEventOptions();
188+
options.setEd25519SignatureVerification(
189+
new Ed25519SignatureVerification(
190+
signature,
191+
timestamp
192+
)
193+
);
194+
195+
// 推送事件。如有需要,你也可以选择使用阻塞API (emitEventAsyncBlocking)
196+
var future = targetBot.emitEventAsync(payload, options);
197+
198+
// 得到处理结果(的future),并返回body
199+
// 如果没有需要返回的body,也可以是null
200+
return future.thenApply(result -> {
201+
var body = switch (result) {
202+
case EmitResult.Verified verified -> verified.getVerified();
203+
default -> null;
204+
};
205+
206+
// 将 Body 放到响应体中,返回。
207+
return ResponseEntity.ok(body);
208+
});
209+
}
210+
}
211+
```
212+
132213
</tab>
133214
</tabs>
134215
</tab>
@@ -197,6 +278,91 @@ class CallbackHandler(
197278
}
198279
```
199280

281+
</tab>
282+
<tab title="Java" group-key="Java">
283+
284+
```Java
285+
/**
286+
* 处理所有qq机器人的回调请求的处理器。
287+
*/
288+
@RestController("/callback")
289+
public class CallbackHandler {
290+
private static final String SIGNATURE_HEAD = "X-Signature-Ed25519";
291+
private static final String TIMESTAMP_HEAD = "X-Signature-Timestamp";
292+
293+
private final Application application;
294+
295+
public CallbackHandler(Application application) {
296+
this.application = application;
297+
}
298+
299+
/**
300+
* 处理 `/callback/qq/{appId}` 的事件回调请求,
301+
* 找到对应的 bot 并向其推送事件。
302+
*/
303+
@PostMapping("/qq/{appId}")
304+
public Mono<ResponseEntity<?>> handleEvent(
305+
@PathVariable("appId") String appId,
306+
@RequestHeader(SIGNATURE_HEAD) String signature,
307+
@RequestHeader(TIMESTAMP_HEAD) String timestamp,
308+
@RequestBody String payload
309+
) {
310+
// 寻找指定 `appId` 的 QGBot
311+
final var targetBot = application.getBotManagers().stream()
312+
// 1. 寻找类型是 QQGuildBotManager 的 BotManager
313+
.filter(manager -> manager instanceof QQGuildBotManager)
314+
.map(QQGuildBotManager.class::cast)
315+
// 2. 寻找 appId 匹配的 bot
316+
.flatMap(manager ->
317+
// 使用 manager.all() 可以直接访问 QGBot 类型,
318+
// 而使用 manager.allStreamable() 得到的是 Bot 类型,需要再转化一次
319+
// 二者都可以,这里选择第一个方案
320+
Streamable.of(manager.all()).asStream())
321+
.filter(bot -> {
322+
// 寻找 bot.appId 为函数入参 appId 的 bot
323+
// bot.id 本质上也是使用的 appId, 因此直接使用 bot.getId().toString() 也是可以的。
324+
var botAppId = bot.getSource().getTicket().getAppId();
325+
return appId.equals(botAppId);
326+
})
327+
// 得到第一个符合条件的bot
328+
.findFirst()
329+
// 如果没找到,自行处理。这里选择抛出异常并响应404。
330+
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "app " + appId + " not found"));
331+
332+
// 在 servlet web 中,在异步中处理.
333+
// 作用域、是否要用异步等根据你的项目情况调整。
334+
335+
// 如果要进行接口校验,配置 ed25519 校验所需要的内容
336+
final var options = new EmitEventOptions();
337+
options.setEd25519SignatureVerification(
338+
new Ed25519SignatureVerification(
339+
signature,
340+
timestamp
341+
)
342+
);
343+
344+
targetBot.joinReserve().transform(SuspendReserves.mono())
345+
346+
// 以响应式的方式推送事件
347+
final var mono = targetBot
348+
.emitEventReserve(payload, options)
349+
.transform(SuspendReserves.mono());
350+
351+
// 得到处理结果,并返回body
352+
// 如果没有需要返回的body,也可以是null
353+
return mono.map(result -> {
354+
var body = switch (result) {
355+
case EmitResult.Verified verified -> verified.getVerified();
356+
default -> null;
357+
};
358+
359+
// 将 Body 放到响应体中,返回。
360+
return ResponseEntity.ok(body);
361+
});
362+
}
363+
}
364+
```
365+
200366
</tab>
201367
</tabs>
202368

0 commit comments

Comments
 (0)