前言

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

这一期就将定时发送的机器人改为真正意义上的对话式机器人,这一期默认你会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,实现所有你想实现的功能!