QQ机器人进阶版
前言
上一期三角洲的图其实并不是定时任务,而是进阶版的对话式回答,如图

这一期就将定时发送的机器人改为真正意义上的对话式机器人,这一期默认你会SpringBoot,正则等知识喔
开始
创建一个SpringBoot项目,pom如下,注意自己的项目名以及入口类路径
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demoBot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoBotBot</name>
<description>Demo project for Spring Boot</description>
<url/>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mikuac</groupId>
<artifactId>shiro</artifactId>
<version>2.3.5</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.8</version>
<configuration>
<mainClass>com.akaxedx.newqqbot.NewQqBotApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
然后是配置文件
# 该配置为反向连接示例
server:
port: 8089
shiro:
ws:
server:
enable: true
url: "/ws/demo"
plugin-list:
- com.akaxedx.newqqbot.plugins.AtPlugin
#mysql配置 如果你需要的话
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: password
username: qq_bot
url: jdbc:mysql://localhost:3306/qq_bot?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
配置文件中有一行com.akaxedx.newqqbot.plugins.AtPlugin 去创建这个类
这个类最原始的样子是这样的
@Component
public class AtPlugin extends BotPlugin {
private final static Logger logger = LoggerFactory.getLogger(AtPlugin.class);
@Override
public int onGroupMessage(Bot bot, GroupMessageEvent event) {
return MESSAGE_IGNORE;
}
}这里event接收的就是群聊消息,bot可以让机器人进行发送消息等操作
由于上一期我们使用了扣子工作流,我们并不会写很多的处理逻辑,而且,使用Java对网页进行爬取比较麻烦,如果是静态页面还好,一旦需要js渲染,这就需要配置浏览器驱动等等,这太复杂了。所以扣子中提供的大量插件可以成为我们外置的一个库,实现各种各样的功能
基于上一期的选择器,添加一行type,接收密码,构建如下的工作流

其中使用的插件是下面这两个


全球网页爬虫插件支持js渲染,在参数中填入合适的等待时间即可拿到渲染后的html代码,通过html2md对html代码简化,最后通过正则表达式拿到我们想要的内容
p.s.这里我爬取的网页是https://www.kkrb725.com/?viewpage=view%2Foverview
http发送内容和上期基本相同,自行调整text的写法即可
测试没有问题后发布

点击这里的调试,就能看到调用的方法

这里的Authorization:Bearer的内容要去授权页面搞一个访问令牌

然后直接把这个curl丢进deepseek中生成调用的java代码,手写代码是一件很不健康的事
package com.akaxedx.newqqbot.tools;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class CozeWorkflowExecutor {
// 从环境变量获取 Token(推荐)
private static final String AUTH_TOKEN = System.getenv().getOrDefault(
"COZE_API_TOKEN",
"你的密钥"
);
// 基础 API 地址
private static final String API_URL = "https://api.coze.cn/v1/workflow/run";
/**
* 执行 Coze 工作流
*
* @param workflowId 工作流 ID
* @param parameters 参数键值对
* @return API 响应内容
* @throws IOException 网络或 API 错误时抛出
*/
public static String executeWorkflow(String workflowId, Map<String, Object> parameters) throws IOException {
// 构建 JSON 请求体
String jsonBody = buildRequestBody(workflowId, parameters);
// 发送请求
return sendPostRequest(API_URL, AUTH_TOKEN, jsonBody);
}
// 构建 JSON 请求体
private static String buildRequestBody(String workflowId, Map<String, Object> parameters) {
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append("{")
.append("\"workflow_id\":\"").append(workflowId).append("\",")
.append("\"parameters\":{");
// 添加参数
int count = 0;
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
if (count++ > 0) jsonBuilder.append(",");
jsonBuilder.append("\"").append(entry.getKey()).append("\":");
Object value = entry.getValue();
if (value instanceof String) {
jsonBuilder.append("\"").append(escapeJson((String) value)).append("\"");
} else if (value instanceof Number || value instanceof Boolean) {
jsonBuilder.append(value);
} else {
// 其他类型转为字符串
jsonBuilder.append("\"").append(escapeJson(value.toString())).append("\"");
}
}
jsonBuilder.append("}}");
return jsonBuilder.toString();
}
// 转义 JSON 特殊字符
private static String escapeJson(String input) {
return input.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
// 发送 POST 请求
private static String sendPostRequest(String url, String token, String jsonBody) throws IOException {
HttpURLConnection connection = null;
try {
URL apiEndpoint = new URL(url);
connection = (HttpURLConnection) apiEndpoint.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization", "Bearer " + token);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setConnectTimeout(10000); // 10秒连接超时
connection.setReadTimeout(30000); // 30秒读取超时
// 发送请求体
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 获取响应
int responseCode = connection.getResponseCode();
if (responseCode >= 200 && responseCode < 300) {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine);
}
return response.toString();
}
} else {
// 读取错误信息
String errorResponse = readErrorResponse(connection);
throw new IOException("API 错误: " + responseCode + " - " +
connection.getResponseMessage() +
(errorResponse != null ? "\n详情: " + errorResponse : ""));
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
// 读取错误响应内容
private static String readErrorResponse(HttpURLConnection conn) {
try (InputStream es = conn.getErrorStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(es, StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} catch (Exception e) {
return null;
}
}
}这样就能调用这个api了,回到插件类,重写群组方法
package com.akaxedx.newqqbot.plugins;
import com.akaxedx.newqqbot.tools.CozeWorkflowExecutor;
import com.mikuac.shiro.core.Bot;
import com.mikuac.shiro.core.BotPlugin;
import com.mikuac.shiro.dto.event.message.GroupMessageEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Component
public class AtPlugin extends BotPlugin {
private final static Logger logger = LoggerFactory.getLogger(AtPlugin.class);
@Override
public int onGroupMessage(Bot bot, GroupMessageEvent event) {
if(!event.getMessage().equals("密码")) {
return MESSAGE_IGNORE;
}
try {
bot.sendGroupMsg(event.getGroupId(),"正在尝试获取密码",false);
Map<String, Object> sjzParams = new HashMap<>();
sjzParams.put("send_id", event.getGroupId());
sjzParams.put("host", "http://ip:3000/send_group_msg");
sjzParams.put("type", "密码");
String sjzResponse = CozeWorkflowExecutor.executeWorkflow(
"7532313560106942507",
sjzParams
);
} catch (IOException e) {
System.err.println("工作流执行失败: " + e.getMessage());
bot.sendGroupMsg(event.getGroupId(),"获取密码失败,请重试 ",false);
}
return MESSAGE_IGNORE;
}
}
使用maven打包,送上服务器,java -jar 运行
打开上期搭建好的napcat面板的网络配置,新建一个ws客户端,配置如下

搭建完成,实现了发送密码,返回三角洲的地图密码的功能,不想使用扣子的,也可以自行编写crud,实现所有你想实现的功能!