Hidewnd Docs Hidewnd Docs
首页
  • 常用开发环境
  • 搭建Typecho博客
  • Qsign部署
  • Linux常用命令
  • Java开发聊天机器人
  • Lua
  • JVM
  • 设计模式
  • 技艺成本
  • 剑三网站合集
  • 合并大区角色数据处理
  • 25PT冷龙峰教学笔记
  • 小本本
GitHub (opens new window)
首页
  • 常用开发环境
  • 搭建Typecho博客
  • Qsign部署
  • Linux常用命令
  • Java开发聊天机器人
  • Lua
  • JVM
  • 设计模式
  • 技艺成本
  • 剑三网站合集
  • 合并大区角色数据处理
  • 25PT冷龙峰教学笔记
  • 小本本
GitHub (opens new window)
  • Qsign 签名服务部署
  • 常用开发环境
  • 搭建Typecho博客
  • Linux常用命令
  • WebSocket开发
  • Java开发聊天机器人
    • 相关依赖
    • 配置文件
    • 具体实现
      • 编写插件-重写父类方式
      • 编写插件-注解声明方式
      • 机器人上下线事件处理
      • WS拦截器处理
  • 文档
hidewnd
2025-01-22
目录

Java开发聊天机器人

# Java开发聊天机器人

Shiro是使用JAVA开发的基于 OneBo-V11 (opens new window) 协议的 QQ机器人 快速开发框架

文档:快速开始 | Shiro SDK Document (opens new window)

示例Demo:https://gitee.com/hidewnd/shiro-demo.git (opens new window)

  • SpringBoot3.0.0+
  • jdk17+

机器人框架基于springboot开发,可嵌入现有springboot框架项目中或作为独立子服务运行,目前框架支持go-cphttp协议的绝大部分oneBot事件或请求,支持自行扩展事件及请求

其他语言框架:

  • Python:NoneBot (opens new window)
  • TypeScript:Koishi (opens new window)

客户端配置反向连接:ws://127.0.0.1:5555/ws/shiro

非机器人端连接需配置header:x-self-id={机器人账号}

实现效果:

image-20250122173714710

# 相关依赖

Maven依赖

maven依赖已包含springboot

<dependency>
  <groupId>com.mikuac</groupId>
  <artifactId>shiro</artifactId>
  <version>latest</version>
</dependency>
1
2
3
4
5

Gradle Kotlin DSL

implementation("com.mikuac:shiro:latest")
1

Gradle Groovy DSL

implementation 'com.mikuac:shiro:latest'
1

# 配置文件

server:  
  port: 5555  
shiro:  
  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-"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 具体实现

在机器人项目框架中,通常以插件的概念作为机器人的功能体现,一个机器人可拥有多个插件,进而获得多种功能;

插件概念类似于MVC架构中的Controller层,作为对外提供的接口,插件中声明的不同命令即代表可用的接口;

通常一个插件下的命令代表同一主题功能的不同命令,可通过项目构造香一个大型功能拆分为多个子插件开发,便于项目维护;

# 编写插件-重写父类方式

  1. 继承父类BotPlugin
  2. 重写方法:onGroupMessage、onPrivateMessage、onAnyMessage......
  3. 配置文件shiro.plugin-list中声明重写类

需注意的点:

  1. 重写方法实现的插件根据配置文件上下声明顺序依次执行
  2. 重写方法,如果前一个插件返回了 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;

/**
 * 目前发现晚于注解方式编写的插件
 */
@Component
public 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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 编写插件-注解声明方式

  1. 一个类代表一个插件,在类上添加注解@Shiro、@Component的方式由spring接管实例,并识别成一个插件类
  2. 插件类中的方法通过方法注解@PrivateMessageHandler、@GroupMessageHandler、AnyMessageHandler声明对不同的消息事件进行处理,常用入参包括:Bot、PrivateMessageEvent、GroupMessageEvent、Matcher......,对于不同类型的事件,可用的入参有所不同。
  3. 命令支持全匹配、前后匹配、正则匹配,正则匹配可通过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
@Component
public 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;
        }

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

# 机器人上下线事件处理

继承父类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
@Component
public 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;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# WS拦截器处理

在事件开始响应前后进行预处理操作,可用于对敏感、违规参数预处理、功能命令权限限制等操作

具体步骤:

  1. 实现接口BotMessageEventInterceptor,并实现方法:preHandle()、afterCompletion()
  2. 配置文件声明类: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;

/**
 * 拦截器
 */
@Component
public 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
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
帮助我改善此页面 (opens new window)
上次更新: 2025/01/22, 18:58:53
WebSocket开发

← WebSocket开发

Theme by Vdoing | Copyright © 2024-2025 Hidewnd
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式