Merge remote-tracking branch 'origin/develop'

# Conflicts:
#	README.md
This commit is contained in:
2025-07-25 09:30:36 +08:00
10 changed files with 115 additions and 41 deletions

View File

@@ -10,7 +10,7 @@
</parent> </parent>
<groupId>cn.somkit</groupId> <groupId>cn.somkit</groupId>
<artifactId>fmt</artifactId> <artifactId>fmt</artifactId>
<version>2.0.0</version> <version>2.1.0</version>
<name>fmt</name> <name>fmt</name>
<description>File Manage System for by SpringBoot</description> <description>File Manage System for by SpringBoot</description>
<properties> <properties>
@@ -51,6 +51,12 @@
<artifactId>metona-cache-spring-boot-starter</artifactId> <artifactId>metona-cache-spring-boot-starter</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>cn.metona</groupId>
<artifactId>metona-mq-mini-pro</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,13 +1,23 @@
package cn.somkit.fmt; package cn.somkit.fmt;
import cn.metona.mq.util.MetonaMQUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class FmtApplication { public class FmtApplication {
private static final Logger logger = LoggerFactory.getLogger(FmtApplication.class);
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(FmtApplication.class, args); SpringApplication.run(FmtApplication.class, args);
if(!MetonaMQUtil.isInitialized()){
logger.info("Metona MQ Mini Pro 初始化...");
MetonaMQUtil.init();
}
} }
} }

View File

@@ -1,6 +1,10 @@
package cn.somkit.fmt.action; package cn.somkit.fmt.action;
import cn.metona.cache.Cache; import cn.metona.cache.Cache;
import cn.metona.mq.util.MetonaMQUtil;
import cn.somkit.fmt.socket.WebSocketServerHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -12,8 +16,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/logging") @RequestMapping("/logging")
public class LoggingAction { public class LoggingAction {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired @Autowired
private Cache<String, Object> cache; private Cache<String, String> cache;
@GetMapping("/index") @GetMapping("/index")
public String index() throws Exception{ public String index() throws Exception{
@@ -23,6 +29,10 @@ public class LoggingAction {
@ResponseBody @ResponseBody
@PostMapping("/close") @PostMapping("/close")
public void close(Boolean closed) throws Exception { public void close(Boolean closed) throws Exception {
cache.put("closed", closed); cache.put("closed", String.valueOf(closed));
logger.info("Metona MQ Mini Pro 停止消费者(顺序消息)...");
if(closed){
MetonaMQUtil.stopOrderedConsuming();
}
} }
} }

View File

@@ -2,6 +2,8 @@ package cn.somkit.fmt.action;
import cn.somkit.fmt.utils.OsInfoUtil; import cn.somkit.fmt.utils.OsInfoUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -18,6 +20,8 @@ import java.util.*;
@RequestMapping("/upload") @RequestMapping("/upload")
public class UploadAction { public class UploadAction {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${somkit.upload.path.windows}") @Value("${somkit.upload.path.windows}")
private String windows_path; private String windows_path;
@@ -40,7 +44,7 @@ public class UploadAction {
try { try {
this.saveFile(file); // 保存上传信息 this.saveFile(file); // 保存上传信息
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("文件保存失败:{}", e.getMessage());
} }
} }
} }
@@ -56,6 +60,7 @@ public class UploadAction {
* @throws Exception 上传异常 * @throws Exception 上传异常
*/ */
public void saveFile(MultipartFile file) throws Exception { public void saveFile(MultipartFile file) throws Exception {
try {
String path = OsInfoUtil.isWindows() ? windows_path : String path = OsInfoUtil.isWindows() ? windows_path :
OsInfoUtil.isLinux() ? linux_path : null; OsInfoUtil.isLinux() ? linux_path : null;
assert path != null; assert path != null;
@@ -64,6 +69,10 @@ public class UploadAction {
filePath = path + File.separator + file.getOriginalFilename(); filePath = path + File.separator + file.getOriginalFilename();
File saveFile = new File(filePath) ; File saveFile = new File(filePath) ;
file.transferTo(saveFile); // 文件保存 file.transferTo(saveFile); // 文件保存
logger.info("文件保存成功:{}", filePath);
}
} catch (Exception e) {
throw new RuntimeException(e);
} }
} }
} }

View File

@@ -1,16 +1,14 @@
package cn.somkit.fmt.entity; package cn.somkit.fmt.entity;
import java.time.LocalDateTime;
public class LoggerMessage { public class LoggerMessage {
String level; String level;
String loggerName; String name;
String message; String message;
String timestamp; String timestamp;
public LoggerMessage(String level, String loggerName, String message, String timestamp) { public LoggerMessage(String level, String loggerName, String message, String timestamp) {
this.level = level; this.level = level;
this.loggerName = loggerName; this.name = loggerName;
this.message = message; this.message = message;
this.timestamp = timestamp; this.timestamp = timestamp;
} }
@@ -23,12 +21,12 @@ public class LoggerMessage {
this.level = level; this.level = level;
} }
public String getLoggerName() { public String getName() {
return loggerName; return name;
} }
public void setLoggerName(String loggerName) { public void setName(String name) {
this.loggerName = loggerName; this.name = name;
} }
public String getMessage() { public String getMessage() {

View File

@@ -5,8 +5,10 @@ import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply; import ch.qos.logback.core.spi.FilterReply;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import cn.metona.mq.exception.MessageSendException;
import cn.metona.mq.util.MetonaMQUtil;
import cn.somkit.fmt.entity.LoggerMessage; import cn.somkit.fmt.entity.LoggerMessage;
import cn.somkit.fmt.utils.LoggerQueue;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
@@ -27,7 +29,13 @@ public class LogStashFilter extends Filter<ILoggingEvent> {
DateUtil.format(Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime(), DateUtil.format(Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime(),
"yyyy-MM-dd HH:mm:ss.SSS") "yyyy-MM-dd HH:mm:ss.SSS")
); );
LoggerQueue.getInstance().push(msg); // 单例阻塞队列 try {
if(MetonaMQUtil.isInitialized() && MetonaMQUtil.isOrderedConsumerRunning()){
MetonaMQUtil.send("log-topic", "log-monitor", JSONUtil.toJsonStr(msg));
}
} catch (MessageSendException ex) {
System.out.println("发送消息队列失败");
}
return FilterReply.NEUTRAL; return FilterReply.NEUTRAL;
} }

View File

@@ -1,10 +1,10 @@
package cn.somkit.fmt.socket; package cn.somkit.fmt.socket;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.metona.cache.Cache; import cn.metona.cache.Cache;
import cn.somkit.fmt.entity.LoggerMessage; import cn.metona.mq.consumer.MessageListener;
import cn.somkit.fmt.utils.LoggerQueue; import cn.metona.mq.core.Message;
import cn.metona.mq.util.MetonaMQUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -19,7 +19,7 @@ public class WebSocketServerHandler implements WebSocketHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired @Autowired
private Cache<String, Object> cache; private Cache<String, String> cache;
@Override @Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception { public void afterConnectionEstablished(WebSocketSession session) throws Exception {
@@ -27,10 +27,27 @@ public class WebSocketServerHandler implements WebSocketHandler {
} }
private void push(WebSocketSession session) throws IOException { private void push(WebSocketSession session) throws IOException {
while (StrUtil.isBlankIfStr(cache.get("closed")) || !Boolean.parseBoolean(String.valueOf(cache.get("closed")))) { boolean closed = StrUtil.isNotBlank(cache.get("closed")) && Boolean.parseBoolean(cache.get("closed"));
LoggerMessage log = LoggerQueue.getInstance().poll(); if(!closed){
if(log != null){ try {
session.sendMessage(new TextMessage(JSONUtil.toJsonStr(log))); if(MetonaMQUtil.isInitialized() && !MetonaMQUtil.isOrderedConsumerRunning()){
logger.info("Metona MQ Mini Pro 订阅主题log-topic顺序消息...");
MetonaMQUtil.subscribeOrdered("log-topic", new MessageListener() {
@Override
public void onMessage(Message message) {
try {
session.sendMessage(new TextMessage(message.getBody()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
logger.info("Metona MQ Mini Pro 启动消费者(顺序消息)...");
MetonaMQUtil.startOrderedConsuming();
}
} catch (Exception e) {
logger.error("Metona MQ Mini Pro 异常", e);
} }
} }
} }

View File

@@ -26,7 +26,7 @@ class LogMonitorAdaptive {
timeFormat: 'HH:mm:ss.SSS', // 时间戳格式,可自定义 timeFormat: 'HH:mm:ss.SSS', // 时间戳格式,可自定义
/* --- 日志级别 & 过滤 --- */ /* --- 日志级别 & 过滤 --- */
levels: ['debug', 'info', 'warn', 'error', 'success', 'system'], // 可用日志级别 levels: ['DEBUG', 'INFO', 'WARN', 'ERROR', 'SUCCESS', 'SYSTEM'], // 可用日志级别
// 过滤功能基于 levels如只想显示 info/warn可在这里删减 // 过滤功能基于 levels如只想显示 info/warn可在这里删减
/* --- 功能开关 --- */ /* --- 功能开关 --- */
@@ -51,7 +51,7 @@ class LogMonitorAdaptive {
/* 强制确保 levels 是数组,防止 forEach 报错 */ /* 强制确保 levels 是数组,防止 forEach 报错 */
if (!Array.isArray(this.cfg.levels)) { if (!Array.isArray(this.cfg.levels)) {
this.cfg.levels = ['info', 'warn', 'error']; this.cfg.levels = ['INFO', 'WARN', 'ERROR'];
} }
this.isPaused = false; this.isPaused = false;
@@ -309,7 +309,7 @@ class LogMonitorAdaptive {
debug: '#9c27b0', info: '#2196f3', warn: '#ff9800', debug: '#9c27b0', info: '#2196f3', warn: '#ff9800',
error: '#f44336', success: '#4caf50', system: '#00bcd4' error: '#f44336', success: '#4caf50', system: '#00bcd4'
}; };
lvlSpan.style.color = colors[entry.level] || colors.info; lvlSpan.style.color = colors[entry.level.toLowerCase()] || colors.info;
lvlSpan.style.minWidth = '70px'; lvlSpan.style.minWidth = '70px';
lvlSpan.style.fontWeight = 'bold'; lvlSpan.style.fontWeight = 'bold';
line.appendChild(lvlSpan); line.appendChild(lvlSpan);

View File

@@ -14,6 +14,18 @@
let ws = null; let ws = null;
let logMonitor = async (closed = false) => {
const options = {
url: Fmt.ctx() + '/logging/close',
data: {closed: closed},
method: 'post'
};
await Fmt.axios(options).then(() => {}).catch((err) => console.error(err));
if(ws){
ws.send('发送日志');
}
}
const logger = new LogMonitorAdaptive('#logContainer', { const logger = new LogMonitorAdaptive('#logContainer', {
theme: 'dark', theme: 'dark',
maxLines: 10000, maxLines: 10000,
@@ -32,13 +44,7 @@
wordWrap: true, // 日志内容是否自动换行true=换行false=横向滚动) wordWrap: true, // 日志内容是否自动换行true=换行false=横向滚动)
//暂停/继续 回调函数 //暂停/继续 回调函数
onTogglePause: async (isPaused) => { onTogglePause: async (isPaused) => {
const options = { await logMonitor(isPaused);
url: Fmt.ctx() + '/logging/close',
data: {closed: isPaused},
method: 'post'
};
await Fmt.axios(options).then((result) => console.log(result)).catch((err) => console.error(err));
ws.send('发送日志');
}, },
onCreated: () => { onCreated: () => {
console.log('日志容器已创建'); console.log('日志容器已创建');
@@ -53,10 +59,14 @@
return false; return false;
} }
ws.onopen = function () {
}
ws.onmessage = function (event) { ws.onmessage = function (event) {
if(event.data){ if(event.data){
let data = JSON.parse(event.data); let data = JSON.parse(event.data);
logger.log(data.loggerName + ' : ' + data.message, data.level, data.timestamp); logger.log(data.name + ' : ' + data.message, data.level, data.timestamp);
} }
} }

View File

@@ -27,3 +27,9 @@
重构在线日志页面及实现方式,不再使用读取日志文件方式,自定义日志拦截器实时获取日志 重构在线日志页面及实现方式,不再使用读取日志文件方式,自定义日志拦截器实时获取日志
不再生成自定义日志文件日志打印从INFO改为DEBUG打印更详细的内容 不再生成自定义日志文件日志打印从INFO改为DEBUG打印更详细的内容
``` ```
> v2.0.1
常规BUG修复参数名称修改
> v2.1.0
```
引入metona-mq-mini-pro消息队列重构实时日志获取方式
```