Java开发聊天机器人
Java开发聊天机器人
Shiro是使用JAVA开发的基于 OneBo-V11 协议的 QQ机器人 快速开发框架
示例Demo:https://gitee.com/hidewnd/shiro-demo.git
- SpringBoot3.0.0+
- jdk17+
机器人框架基于springboot开发,可嵌入现有springboot框架项目中或作为独立子服务运行,目前框架支持go-cphttp协议的绝大部分oneBot事件或请求,支持自行扩展事件及请求
其他语言框架:
客户端配置反向连接:ws://127.0.0.1:5555/ws/shiro
非机器人端连接需配置header:x-self-id={机器人账号}
实现效果:

相关依赖
Maven依赖
maven依赖已包含springboot
<dependency> <groupId>com.mikuac</groupId> <artifactId>shiro</artifactId> <version>latest</version></dependency>Gradle Kotlin DSL
implementation("com.mikuac:shiro:latest")Gradle Groovy DSL
implementation 'com.mikuac:shiro:latest'配置文件
server: port: 5555shiro: interceptor: com.hidewnd.shiro.plugins.InterceptorExample # 注解方式编写的插件无需在插件列表(plugin-list)定义 # 插件列表为顺序执行,如果前一个插件返回了 MESSAGE_BLOCK 将不会执行后续插件 plugin-list: - com.hidewnd.shiro.plugins.OverridePlugin ws: # 访问密钥,强烈推荐在公网的服务器设置 access-token: "" # 超时回收,默认10秒 timeout: 10 # 最大文本消息缓冲区 max-text-message-buffer-size: 512000 # 二进制消息的最大长度 max-binary-message-buffer-size: 512000 # 反向 Websocket 连接地址 server: enable: true # 默认值 "/ws/shiro" url: "/ws/shiro" # 最大空闲时间,超过这个时间将关闭会话 max-session-idle-timeout: 900000 # 限速器(令牌桶算法) limiter: # 是否启用限速器 enable: false # 补充速率(每秒补充的令牌数量) rate: 1 # 令牌桶容量 capacity: 1 # 如果该值为 false 时,当令牌获取失败则会直接丢次本次请求 # 如果该值为 true 时,当令牌获取失败则会阻塞当前线程,后续任务将被添加到等待队列 awaitTask: true # 等待超时 timeout: 10 # 线程池配置 task-pool: # 核心线程数(默认线程数) core-pool-size: 10 # 缓冲队列大小 queue-capacity: 200 # 允许线程空闲时间(单位:默认为秒) keep-alive-time: 10 # 最大线程数 max-pool-size: 30 # 线程池名前缀 thread-name-prefix: "ShiroTaskPool-"具体实现
在机器人项目框架中,通常以插件的概念作为机器人的功能体现,一个机器人可拥有多个插件,进而获得多种功能;
插件概念类似于MVC架构中的Controller层,作为对外提供的接口,插件中声明的不同命令即代表可用的接口;
通常一个插件下的命令代表同一主题功能的不同命令,可通过项目构造香一个大型功能拆分为多个子插件开发,便于项目维护;
编写插件-重写父类方式
- 继承父类
BotPlugin - 重写方法:
onGroupMessage、onPrivateMessage、onAnyMessage… - 配置文件
shiro.plugin-list中声明重写类
需注意的点:
- 重写方法实现的插件根据配置文件上下声明顺序依次执行
- 重写方法,如果前一个插件返回了
MESSAGE_BLOCK将不会执行后续插件
代码示例:
package com.hidewnd.shiro.plugins;
import ch.qos.logback.core.util.StringUtil;import com.mikuac.shiro.annotation.common.Order;import com.mikuac.shiro.common.utils.MsgUtils;import com.mikuac.shiro.core.Bot;import com.mikuac.shiro.core.BotPlugin;import com.mikuac.shiro.dto.action.common.ActionData;import com.mikuac.shiro.dto.action.common.MsgId;import com.mikuac.shiro.dto.event.message.GroupMessageEvent;import com.mikuac.shiro.dto.event.message.PrivateMessageEvent;import org.springframework.stereotype.Component;
/** * 目前发现晚于注解方式编写的插件 */@Componentpublic class OverridePlugin extends BotPlugin {
@Order(1) @Override public int onPrivateMessage(Bot bot, PrivateMessageEvent event) { String message = StringUtil.nullStringToEmpty(event.getMessage()); bot.sendPrivateMsg(event.getUserId(), message, false); // 返回 MESSAGE_IGNORE 执行 plugin-list 下一个插件,返回 MESSAGE_BLOCK 则不执行下一个插件 return MESSAGE_IGNORE; }
@Order(2) @Override public int onGroupMessage(Bot bot, GroupMessageEvent event) { String message = StringUtil.nullStringToEmpty(event.getMessage()); bot.sendGroupMsg(event.getGroupId(), message, false); // 返回 MESSAGE_IGNORE 执行 plugin-list 下一个插件,返回 MESSAGE_BLOCK 则不执行下一个插件 return MESSAGE_IGNORE; }}编写插件-注解声明方式
- 一个类代表一个插件,在类上添加注解
@Shiro、@Component的方式由spring接管实例,并识别成一个插件类 - 插件类中的方法通过方法注解
@PrivateMessageHandler、@GroupMessageHandler、AnyMessageHandler声明对不同的消息事件进行处理,常用入参包括:Bot、PrivateMessageEvent、GroupMessageEvent、Matcher…,对于不同类型的事件,可用的入参有所不同。 - 命令支持全匹配、前后匹配、正则匹配,正则匹配可通过
Matcher获取匹配后的参数信息
代码示例:
package com.hidewnd.shiro.plugins;
import cn.hutool.core.util.StrUtil;import com.mikuac.shiro.annotation.AnyMessageHandler;import com.mikuac.shiro.annotation.GroupMessageHandler;import com.mikuac.shiro.annotation.MessageHandlerFilter;import com.mikuac.shiro.annotation.PrivateMessageHandler;import com.mikuac.shiro.annotation.common.Shiro;import com.mikuac.shiro.common.utils.MsgUtils;import com.mikuac.shiro.core.Bot;import com.mikuac.shiro.dto.event.message.AnyMessageEvent;import com.mikuac.shiro.dto.event.message.GroupMessageEvent;import com.mikuac.shiro.dto.event.message.PrivateMessageEvent;import com.mikuac.shiro.enums.AtEnum;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;
import java.util.regex.Matcher;
@Slf4j@Shiro@Componentpublic class ExamplePlugin {
// 当机器人收到的私聊消息消息符合 cmd 值 "hi" 时,这个方法会被调用。 @PrivateMessageHandler @MessageHandlerFilter(cmd = "hi") public void fun1(Bot bot, PrivateMessageEvent event, Matcher matcher) { // 构建消息 String sendMsg = MsgUtils.builder().face(66).text("this is privateMessage callBack").build(); // 发送私聊消息 bot.sendPrivateMsg(event.getUserId(), sendMsg, false); }
// 如果 at 参数设定为 AtEnum.NEED 则只有 at 了机器人的消息会被响应 @GroupMessageHandler @MessageHandlerFilter(at = AtEnum.NEED) public void fun2(GroupMessageEvent event) { // 以注解方式调用可以根据自己的需要来为方法设定参数 // 例如群组消息可以传递 GroupMessageEvent, Bot, Matcher 多余的参数会被设定为 null log.info("群组消息: {}", event.getMessage()); }
@GroupMessageHandler @MessageHandlerFilter(startWith = "测试") public void fun3(Bot bot, GroupMessageEvent event, Matcher matcher) { String message = event.getMessage(); String group = matcher.group(); String text = StrUtil.format("收到群组消息: {},匹配到的内容: {}", message, group); bot.sendGroupMsg(event.getGroupId(), text, false); }
// 同时监听群组及私聊消息 并根据消息类型(私聊,群聊)回复 @AnyMessageHandler @MessageHandlerFilter(cmd = "hi") public void fun3(Bot bot, AnyMessageEvent event) { bot.sendMsg(event, "this is GroupMessage callBack", false); }
@GroupMessageHandler @MessageHandlerFilter(cmd = "^匹配 (.*)?$") public void fun4(Bot bot, GroupMessageEvent event, Matcher matcher) { int index = 1; String message = matcher.group(0); String group = matcher.group(index); while (StrUtil.isNotEmpty(group)) { String text = StrUtil.format("收到群组消息: {},匹配到的内容: {}", message , group); bot.sendGroupMsg(event.getGroupId(), text, false); group = matcher.groupCount() < index ? matcher.group(++index) : null; }
}}机器人上下线事件处理
继承父类CoreEvent,并重写相关方法,可用于对ws上下线事件的处理及客户端黑名单处理
代码示例:
import ch.qos.logback.core.util.StringUtil;import com.mikuac.shiro.core.Bot;import com.mikuac.shiro.core.CoreEvent;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Primary;import org.springframework.stereotype.Component;import org.springframework.web.socket.WebSocketSession;
@Slf4j@Primary@Componentpublic class BotRuntimeEvent extends CoreEvent {
@Override public void online(Bot bot) { // 客户端上线事件 // 例如上线后发送消息给指定的群或好友 // 如需获取上线的机器人账号可以调用 bot.getSelfId() log.info("机器人({}) 上线了", bot.getSelfId()); }
@Override public void offline(long account) { // 客户端离线事件 log.info("机器人({}) 下线了", account); }
@Override public boolean session(WebSocketSession session) { // 可以通过 session.getHandshakeHeaders().getFirst("x-self-id") 获取上线的机器人账号 // 例如当服务端为开放服务时,并且只有白名单内的账号才允许连接,此时可以检查账号是否存在于白名内 // 不存在的话返回 false 即可禁止连接 String botId = session.getHandshakeHeaders().getFirst("x-self-id"); if (StringUtil.isNullOrEmpty(botId)) { log.warn("未知的连接请求"); } log.info("机器人({})请求连接", botId); return true; }}WS拦截器处理
在事件开始响应前后进行预处理操作,可用于对敏感、违规参数预处理、功能命令权限限制等操作
具体步骤:
- 实现接口
BotMessageEventInterceptor,并实现方法:preHandle()、afterCompletion() - 配置文件声明类:
shiro.interceptor= com.hidewnd.shiro.plugins.InterceptorExample
代码示例:
import com.mikuac.shiro.common.utils.MsgUtils;import com.mikuac.shiro.core.Bot;import com.mikuac.shiro.core.BotMessageEventInterceptor;import com.mikuac.shiro.dto.event.message.GroupMessageEvent;import com.mikuac.shiro.dto.event.message.MessageEvent;import com.mikuac.shiro.exception.ShiroException;import org.springframework.stereotype.Component;
/** * 拦截器 */@Componentpublic class InterceptorExample implements BotMessageEventInterceptor { @Override public boolean preHandle(Bot bot, MessageEvent event) throws ShiroException { // 预处理方法,可以在触发事件前做一些处理,返回值为 false 时本次事件将会被拦截 // 使用场景可以是黑名单检查或检查权限等等,具体的使用场景可以发挥自己的想象力 // MessageEvent 可能为右边这三种类型 PrivateMessageEvent、GroupMessageEvent、GuildMessageEvent String message = event.getMessage(); String messageType = event.getMessageType(); if (message.contains("敏感")) { String sendMsg = MsgUtils.builder() .face(37) .text("检测敏感数据,中止执行") .build(); if ("group".equals(messageType)) { bot.sendGroupMsg(((GroupMessageEvent) event).getGroupId(), sendMsg, false); } return false; } return true; }
@Override public void afterCompletion(Bot bot, MessageEvent event) throws ShiroException { // 后置处理方法 // 当 preHandle 返回值为 true 时则会执行 plugin-list,执行完毕后进入到 afterCompletion }}