From e0330b604deae3549d90f0551e59c86bbf36403e Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Wed, 7 Jan 2026 13:30:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=BF=87=E7=A8=8B=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/ThreadManageController.java | 214 +++++++++++ ruoyi-ui/src/api/schedule/thread.js | 73 ++++ ruoyi-ui/src/views/schedule/thread/index.vue | 331 ++++++++++++++++++ 3 files changed, 618 insertions(+) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/schedule/ThreadManageController.java create mode 100644 ruoyi-ui/src/api/schedule/thread.js create mode 100644 ruoyi-ui/src/views/schedule/thread/index.vue diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/schedule/ThreadManageController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/schedule/ThreadManageController.java new file mode 100644 index 0000000..8a26515 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/schedule/ThreadManageController.java @@ -0,0 +1,214 @@ +package com.ruoyi.web.controller.schedule; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.schedule.ScheduleThread; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * 线程管理控制器 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/schedule/thread") +public class ThreadManageController { + + @Autowired + private ScheduleThread scheduleThread; + + /** + * 获取线程列表 + */ + @GetMapping("/list") + public AjaxResult list() { + if (scheduleThread.threadTypeMap.isEmpty()) { + scheduleThread.refreshThreadMap(); + } + return AjaxResult.success(scheduleThread.getAllThreadStatus()); + } + + /** + * 启动线程 + */ + @PostMapping("/start/{key}") + public AjaxResult start(@PathVariable String key) { + try { + scheduleThread.startThread(key); + return AjaxResult.success("启动成功"); + } catch (Exception e) { + return AjaxResult.error("启动失败: " + e.getMessage()); + } + } + + /** + * 停止线程 + */ + @PostMapping("/stop/{key}") + public AjaxResult stop(@PathVariable String key) { + try { + scheduleThread.stopThread(key); + return AjaxResult.success("停止指令已下发"); + } catch (Exception e) { + return AjaxResult.error("停止失败: " + e.getMessage()); + } + } + + /** + * 启动全部线程 + */ + @PostMapping("/startAll") + public AjaxResult startAll() { + try { + for (String key : scheduleThread.threadMap.keySet()) { + try { + scheduleThread.startThread(key); + } catch (Exception e) { + // 忽略单个启动失败,继续启动下一个 + } + } + return AjaxResult.success("全部启动指令已下发"); + } catch (Exception e) { + return AjaxResult.error("操作失败: " + e.getMessage()); + } + } + + /** + * 停止全部线程 + */ + @PostMapping("/stopAll") + public AjaxResult stopAll() { + try { + for (String key : scheduleThread.threadMap.keySet()) { + scheduleThread.stopThread(key); + } + return AjaxResult.success("全部停止指令已下发"); + } catch (Exception e) { + return AjaxResult.error("操作失败: " + e.getMessage()); + } + } + + /** + * 启动分组线程 + */ + @PostMapping("/startGroup/{type}") + public AjaxResult startGroup(@PathVariable String type) { + try { + for (Map.Entry entry : scheduleThread.threadTypeMap.entrySet()) { + if (type.equals(entry.getValue())) { + try { + scheduleThread.startThread(entry.getKey()); + } catch (Exception e) { + } + } + } + return AjaxResult.success("分组启动指令已下发"); + } catch (Exception e) { + return AjaxResult.error("操作失败: " + e.getMessage()); + } + } + + /** + * 停止分组线程 + */ + @PostMapping("/stopGroup/{type}") + public AjaxResult stopGroup(@PathVariable String type) { + try { + for (Map.Entry entry : scheduleThread.threadTypeMap.entrySet()) { + if (type.equals(entry.getValue())) { + scheduleThread.stopThread(entry.getKey()); + } + } + return AjaxResult.success("分组停止指令已下发"); + } catch (Exception e) { + return AjaxResult.error("操作失败: " + e.getMessage()); + } + } + + /** + * 获取线程日志 + */ + @GetMapping("/log/{key}") + public AjaxResult log(@PathVariable String key) { + // 如果key是"ALL",则不进行关键词过滤,显示所有日志(除了堆栈合并逻辑外) + String target = "ALL".equals(key) ? null : key; + + // 日志路径,根据logback配置及实际环境确认 + File file = new File("D:/home/ruoyi/logs/sys-info.log"); + if (!file.exists()) { + return AjaxResult.error("日志文件不存在"); + } + + LinkedList lines = new LinkedList<>(); + // 简单的日志行首匹配模式 (yyyy-MM-dd HH:mm:ss) + Pattern logStartPattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}"); + boolean isMatchingStack = false; + // 如果是查看所有日志,默认所有行都匹配,除非遇到新的日志行且我们想做更复杂的过滤(目前全显) + // 但为了支持堆栈合并,我们还是需要判断行首 + + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"))) { + String line; + while ((line = br.readLine()) != null) { + boolean isLogStart = logStartPattern.matcher(line).find(); + + if (target == null) { + // 查看所有日志:直接添加,或者如果是异常日志查看模式可以在这里过滤 + // 暂时逻辑:全部添加 + lines.add(line); + } else { + // 查看特定线程日志 + if (line.contains(target)) { + isMatchingStack = true; + lines.add(line); + } else if (isLogStart) { + isMatchingStack = false; + } else if (isMatchingStack) { + lines.add(line); + } + } + + // 限制行数,避免内存溢出,保留最后1000行 + if (lines.size() > 1000) { + lines.removeFirst(); + } + } + } catch (Exception e) { + return AjaxResult.error("读取日志失败: " + e.getMessage()); + } + + return AjaxResult.success(lines); + } + + /** + * 获取异常日志 + */ + @GetMapping("/errorLog") + public AjaxResult errorLog() { + File file = new File("D:/home/ruoyi/logs/sys-error.log"); + if (!file.exists()) { + // 如果没有单独的error log,尝试从info log中过滤ERROR级别? + // 假设有 sys-error.log + return AjaxResult.error("异常日志文件不存在"); + } + + LinkedList lines = new LinkedList<>(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"))) { + String line; + while ((line = br.readLine()) != null) { + lines.add(line); + if (lines.size() > 1000) { + lines.removeFirst(); + } + } + } catch (Exception e) { + return AjaxResult.error("读取日志失败: " + e.getMessage()); + } + return AjaxResult.success(lines); + } +} diff --git a/ruoyi-ui/src/api/schedule/thread.js b/ruoyi-ui/src/api/schedule/thread.js new file mode 100644 index 0000000..3b051c3 --- /dev/null +++ b/ruoyi-ui/src/api/schedule/thread.js @@ -0,0 +1,73 @@ +import request from '@/utils/request' + +// 查询线程列表 +export function listThread() { + return request({ + url: '/schedule/thread/list', + method: 'get' + }) +} + +// 启动线程 +export function startThread(key) { + return request({ + url: '/schedule/thread/start/' + key, + method: 'post' + }) +} + +// 停止线程 +export function stopThread(key) { + return request({ + url: '/schedule/thread/stop/' + key, + method: 'post' + }) +} + +// 获取日志 +export function getThreadLog(key) { + return request({ + url: '/schedule/thread/log/' + key, + method: 'get' + }) +} + +// 启动全部线程 +export function startAllThread() { + return request({ + url: '/schedule/thread/startAll', + method: 'post' + }) +} + +// 停止全部线程 +export function stopAllThread() { + return request({ + url: '/schedule/thread/stopAll', + method: 'post' + }) +} + +// 启动分组线程 +export function startGroupThread(type) { + return request({ + url: '/schedule/thread/startGroup/' + type, + method: 'post' + }) +} + +// 停止分组线程 +export function stopGroupThread(type) { + return request({ + url: '/schedule/thread/stopGroup/' + type, + method: 'post' + }) +} + +// 获取异常日志 +export function getErrorLog() { + return request({ + url: '/schedule/thread/errorLog', + method: 'get' + }) +} diff --git a/ruoyi-ui/src/views/schedule/thread/index.vue b/ruoyi-ui/src/views/schedule/thread/index.vue new file mode 100644 index 0000000..2d5deed --- /dev/null +++ b/ruoyi-ui/src/views/schedule/thread/index.vue @@ -0,0 +1,331 @@ + + + +