@@ -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