@@ -129,6 +129,87 @@ class CallbackHandler(
129
129
}
130
130
```
131
131
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
+
132
213
</tab >
133
214
</tabs >
134
215
</tab >
@@ -197,6 +278,91 @@ class CallbackHandler(
197
278
}
198
279
```
199
280
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
+
200
366
</tab >
201
367
</tabs >
202
368
0 commit comments