Skip to content

事件系统概述 ​

插件通过 @SubscribeEvent 注解即可监听机器人的各类事件。

事件按触发时机分为以下几类:

  • 接收事件(event/receive/)— 网关 DISPATCH 推送(群消息、私聊、互动、成员变动、入群申请等)与两个生命周期就绪事件(ReadyEvent 仅 WS 模式、WebhookReadyEvent 仅 Webhook 模式),共 18 种
  • 发送事件(event/send/)— 机器人主动操作(发消息、上传、撤回、禁言、踢人、审批、菜单、面板、素材等)前触发,共 28 种,全部可取消
  • 后置事件(event/send/,XxxNotifyEvent)— 上述操作成功后触发,共 28 种,不可取消,与发送事件一一配对
  • 框架事件(event/)— HeartbeatEvent(连接心跳)、PluginEnableEvent / PluginDisableEvent(插件启用 / 禁用)

「机器人上线」的推荐做法

插件做上线初始化(如启动定时任务)时,推荐实现 Plugin.onActive() 而非监听 ReadyEvent:onActive() 在两种接入模式统一触发一次(WS 全部分片就绪 / Webhook 回调服务就绪),无需判断接入模式与分片;ReadyEvent 是 WebSocket 网关专属事件(每分片各触发一次),Webhook 模式不会触发,对应的是 WebhookReadyEvent。

@SubscribeEvent ​

java
import cn.org.bukkit.bngeuit.Bngeuit;
import cn.org.bukkit.bngeuit.dto.message.User;
import cn.org.bukkit.bngeuit.dto.message.interaction.InteractionResponseCode;
import cn.org.bukkit.bngeuit.event.SubscribeEvent;
import cn.org.bukkit.bngeuit.event.receive.GroupAtMessageEvent;
import cn.org.bukkit.bngeuit.event.receive.C2CMessageEvent;
import cn.org.bukkit.bngeuit.event.receive.ReadyEvent;
import cn.org.bukkit.bngeuit.event.receive.InteractionEvent;

public class MyListener {

    @SubscribeEvent
    public void onGroupAt(GroupAtMessageEvent e) {
        Bngeuit.replyGroupText(
            e.getGroupOpenid(), e.getId(), 1,
            "收到: " + e.getContent()
        );
    }

    @SubscribeEvent
    public void onC2CMessage(C2CMessageEvent e) {
        User author = e.getAuthor();
        if (author != null && author.userOpenid() != null) {
            Bngeuit.replyC2CText(
                author.userOpenid(), e.getId(), 1,
                "收到私聊"
            );
        }
    }

    @SubscribeEvent
    public void onReady(ReadyEvent e) {
        // 机器人启动完成(仅 WebSocket 模式;多分片时每片触发一次,可用 e.getShardId() 区分)
        if (e.getShardId() == 0) {
            // 只对 shard 0 做一次全局初始化
        }
    }

    @SubscribeEvent
    public void onInteraction(InteractionEvent e) {
        // 必须响应,否则客户端一直 loading
        Bngeuit.respondInteraction(e.getId(),
            InteractionResponseCode.SUCCESS);
    }
}

包位置

SubscribeEvent、Event、Cancellable、EventPriority 在 cn.org.bukkit.bngeuit.event; 具体事件类分属 event.receive 与 event.send 两个子包。

规则 ​

  • 标注在 public 方法上
  • 有且仅有 一个参数,类型继承自 Event
  • 方法名、参数名无限制
  • 插件 JAR 内的类会被自动扫描注册

发送事件与取消 ​

发送事件(event/send/)在机器人调用 HTTP API 之前触发,实现 Cancellable 接口。取消后对应操作被跳过,API 返回 null:

java
import cn.org.bukkit.bngeuit.event.SubscribeEvent;
import cn.org.bukkit.bngeuit.event.EventPriority;
import cn.org.bukkit.bngeuit.event.send.GroupMessageSendEvent;

@SubscribeEvent(priority = EventPriority.P1)   // 最先执行
public void onGroupSend(GroupMessageSendEvent e) {
    if (e.getContent() != null && e.getContent().contains("敏感词")) {
        e.setCancelled(true);   // 阻止这条群消息发送
    }
}

后置事件(NotifyEvent) ​

每个发送事件都有一个配对的后置事件,命名规则固定为前置事件名 + Notify:

GroupMessageSendEvent        → GroupMessageSendNotifyEvent
MenuUpdateEvent              → MenuUpdateNotifyEvent
MediaImagePrepareEvent       → MediaImagePrepareNotifyEvent

两者语义严格区分:

前置(SendEvent)后置(NotifyEvent)
触发时机HTTP 调用前HTTP 调用成功后
可否取消可取消(Cancellable)不可取消,只读通知
失败时—前置被取消或 HTTP 报错时不触发
携带内容本次操作的入参getOriginal()(前置事件)+ getResponse()(响应对象)

后置事件的典型用途是拿到只存在于响应里的数据:机器人消息的消息 ID(后续撤回要用)与自引用索引 refIdx(后续引用自己的消息要用)。

java
import cn.org.bukkit.bngeuit.event.send.GroupMessageSendNotifyEvent;
import cn.org.bukkit.bngeuit.dto.message.MessageResponse;

@SubscribeEvent
public void onSent(GroupMessageSendNotifyEvent e) {
    MessageResponse resp = e.getResponse();
    if (resp != null) {
        // 记录 bot 自己发出的消息 ID,供后续撤回/引用
        saveSentMessageId(e.getOriginal().getGroupOpenid(), resp.id());
    }
}

每个后置事件是独立类型,需要哪种就写哪个监听方法即可。

响应可能为 null

getResponse() 在 HTTP 成功但响应体为空时返回 null;返回 {} 的端点(撤回、禁言、删除等)对应的后置事件已在构造中归一为 EmptyResponse 实例,不会是 null。判断成功与否请看「前置有没有触发后置」,不要用 getResponse() == null 反推失败。

框架事件 ​

HeartbeatEvent ​

WebSocket 模式发出心跳包(op=1)或收到心跳确认(op=11)时触发。无载荷、不可取消,每个心跳周期触发两次;Webhook 模式永不触发。

适合做连接活跃度指示、看门狗喂狗这类轻量信号:

java
@SubscribeEvent
public void onHeartbeat(HeartbeatEvent e) {
    lastActiveAt = System.currentTimeMillis();
}

不属于收发事件体系

HeartbeatEvent 不继承 ReceiveEvent / SendEvent / NotifyEvent,按它自己的类型监听即可。

PluginEnableEvent / PluginDisableEvent ​

插件启用完成(onEnable 执行完并登记到 PluginManager)后、插件禁用完成(onDisable 与资源清理执行完)后触发,覆盖启动期批量加载、plugin disable/reload、停服 disableAll 等路径。不可取消,仅作通知;可通过 getPlugin() 反查插件实例。

事件优先级 ​

@SubscribeEvent(priority = EventPriority.XXX),数字越小越先执行:

优先级说明
P1最高,最先执行(适合过滤、权限检查)
P2较高
P3普通(默认)
P4较低
P5最低
P6监看,无论事件是否取消都会执行

@SubscribeEvent(ignoreCancelled = true) 可在事件已被其他处理器取消时仍执行本方法。

触发自定义事件 ​

Bngeuit.callEvent(event) 可同步触发任意 Event 子类:

java
public class MyEvent extends Event { ... }

@SubscribeEvent
public void onMyEvent(MyEvent e) { ... }

// 触发(按优先级分发;若事件实现 Cancellable 且被取消,返回 false)
boolean ok = Bngeuit.callEvent(new MyEvent());

在 JavaPlugin 中使用 ​

java
public class MyPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        getLogger().info("插件已启用");
    }

    @SubscribeEvent
    public void onGroupAt(GroupAtMessageEvent e) {
        Bngeuit.replyGroupText(
            e.getGroupOpenid(), e.getId(), 1,
            "你好," + e.getAuthor().username()
        );
    }
}

自动扫描

插件 JAR 内的所有 @SubscribeEvent 方法会被自动注册,无需手动操作。

事件 Metadata 系统 ​

所有事件都支持 Metadata 系统,允许在不同优先级的处理器之间传递数据。

基本用法 ​

java
import cn.org.bukkit.bngeuit.event.SubscribeEvent;
import cn.org.bukkit.bngeuit.event.EventPriority;
import cn.org.bukkit.bngeuit.metadata.MetadataValue;
import cn.org.bukkit.bngeuit.event.receive.GroupAtMessageEvent;

public class MetadataExample {

    // P1 处理器:设置标记
    @SubscribeEvent(priority = EventPriority.P1)
    public void onCheck(GroupAtMessageEvent e) {
        if (isSpam(e.getContent())) {
            e.setMetadata("spam", new MetadataValue(true, this));
        }
    }

    // P3 处理器:读取标记
    @SubscribeEvent(priority = EventPriority.P3)
    public void onHandle(GroupAtMessageEvent e) {
        MetadataValue spam = e.getMetadata("spam");
        if (spam != null && Boolean.TRUE.equals(spam.value())) {
            // 是垃圾消息,跳过处理
            return;
        }
        // 正常处理...
    }
}

MetadataValue ​

方法返回类型说明
value()Object原始值(可为 null)
source()Object来源对象(通常是设置 metadata 的插件实例)
as(type)T类型转换(类型不匹配抛 ClassCastException)
asSafe(type)T安全类型转换(类型不匹配返回 null)

Event Metadata 方法 ​

方法说明
setMetadata(key, value)设置 metadata
getMetadata(key)获取 metadata(不存在返回 null)
hasMetadata(key)是否存在指定 key
removeMetadata(key)移除指定 key
clearMetadata()移除所有 metadata

生命周期

Metadata 生命周期与事件对象绑定,事件分发完成后自动回收。