Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b6c8bedf6 | |||
| b37da657fa | |||
| 1931697a87 | |||
|
|
b882e85134 | ||
| e17e31edfd | |||
| bb99e48275 |
88
README.md
88
README.md
@@ -1,31 +1,77 @@
|
||||
# fmt
|
||||
|
||||
#### 介绍
|
||||
1. 简易文件传输管理系统
|
||||
2. 文件下载、文件上传、在线日志
|
||||
## 介绍
|
||||
|
||||
#### 软件架构
|
||||
1. Jdk 21
|
||||
2. Maven 3.6.3
|
||||
3. SpringBoot 3.5.3
|
||||
4. SpringBoot Starter Thymeleaf 3.5.3
|
||||
5. SpringBoot Starter WebSocket 3.5.3
|
||||
7. Hutool 5.8.25
|
||||
7. AXUI 2.1.1
|
||||
`fmt` 是一个基于 Spring Boot 的文件管理工具,旨在提供便捷的文件上传、下载、打包、删除等操作。它结合了简洁的前端界面与强大的后端功能,适用于需要进行文件管理的各类应用场景。
|
||||
|
||||
## 软件架构
|
||||
|
||||
#### 安装教程
|
||||
本项目采用前后端一体化架构,后端基于 Spring Boot 框架,使用 Java 语言开发,前端采用 HTML、CSS 和 JavaScript 技术,结合 `axui` 框架进行界面渲染。主要模块包括:
|
||||
|
||||
1. 项目导入IDEA,配置Jdk、Maven,根据自己实际环境修改配置文件,然后启动项目
|
||||
2. 下载发行版本压缩包,解压后,修改bin目录下的启动文件配置,然后执行即可
|
||||
- **文件上传模块**:支持文件上传并保存至服务器。
|
||||
- **文件下载模块**:支持文件下载及 ZIP 打包下载。
|
||||
- **文件管理模块**:支持文件删除、批量删除等操作。
|
||||
- **日志模块**:实时推送日志信息。
|
||||
- **WebSocket 模块**:用于前后端实时通信。
|
||||
- **工具类模块**:包括 MD5 工具、操作系统工具等。
|
||||
|
||||
#### 使用说明
|
||||
## 安装教程
|
||||
|
||||
1. 启动项目,访问http://127.0.0.1:8098/fmt
|
||||
1. **克隆项目**:
|
||||
```bash
|
||||
git clone https://gitee.com/thzxx/fmt.git
|
||||
```
|
||||
|
||||
#### 参与贡献
|
||||
2. **构建项目**:
|
||||
使用 Maven 构建项目:
|
||||
```bash
|
||||
cd fmt
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
3. **运行项目**:
|
||||
启动 Spring Boot 应用:
|
||||
```bash
|
||||
java -jar target/fmt.jar
|
||||
```
|
||||
|
||||
4. **访问项目**:
|
||||
打开浏览器访问:
|
||||
```
|
||||
http://localhost:8080
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
- **上传文件**:
|
||||
访问 `/upload/index` 页面,选择文件进行上传。
|
||||
|
||||
- **下载文件**:
|
||||
访问 `/download/index` 页面,点击文件进行下载,或使用 `/download/file` 接口下载指定路径文件。
|
||||
|
||||
- **打包下载**:
|
||||
使用 `/download/packZip` 接口,传入文件名列表进行 ZIP 打包下载。
|
||||
|
||||
- **删除文件**:
|
||||
使用 `/download/delete` 或 `/download/batchDel` 接口进行单个或批量删除。
|
||||
|
||||
- **查看日志**:
|
||||
访问 `/logging/index` 页面,实时查看日志信息。
|
||||
|
||||
- **WebSocket 实时通信**:
|
||||
前端通过 WebSocket 连接 `/ws`,接收服务器推送的消息。
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎贡献代码和改进项目。请遵循以下步骤:
|
||||
|
||||
1. Fork 项目。
|
||||
2. 创建新分支。
|
||||
3. 提交代码。
|
||||
4. 创建 Pull Request。
|
||||
|
||||
如发现 Bug 或有改进建议,请提交 Issue 或直接联系项目维护者。
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 MIT 许可证。详情请查看项目根目录下的 `LICENSE` 文件。
|
||||
8
pom.xml
8
pom.xml
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>cn.somkit</groupId>
|
||||
<artifactId>fmt</artifactId>
|
||||
<version>2.0.1</version>
|
||||
<version>2.1.0</version>
|
||||
<name>fmt</name>
|
||||
<description>File Manage System for by SpringBoot</description>
|
||||
<properties>
|
||||
@@ -51,6 +51,12 @@
|
||||
<artifactId>metona-cache-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.metona</groupId>
|
||||
<artifactId>metona-mq-mini-pro</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
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.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class FmtApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FmtApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FmtApplication.class, args);
|
||||
|
||||
if(!MetonaMQUtil.isInitialized()){
|
||||
logger.info("Metona MQ Mini Pro 初始化...");
|
||||
MetonaMQUtil.init();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package cn.somkit.fmt.action;
|
||||
|
||||
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.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -12,8 +16,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@RequestMapping("/logging")
|
||||
public class LoggingAction {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private Cache<String, Object> cache;
|
||||
private Cache<String, String> cache;
|
||||
|
||||
@GetMapping("/index")
|
||||
public String index() throws Exception{
|
||||
@@ -23,6 +29,10 @@ public class LoggingAction {
|
||||
@ResponseBody
|
||||
@PostMapping("/close")
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package cn.somkit.fmt.action;
|
||||
|
||||
import cn.somkit.fmt.utils.OsInfoUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -18,6 +20,8 @@ import java.util.*;
|
||||
@RequestMapping("/upload")
|
||||
public class UploadAction {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Value("${somkit.upload.path.windows}")
|
||||
private String windows_path;
|
||||
|
||||
@@ -40,7 +44,7 @@ public class UploadAction {
|
||||
try {
|
||||
this.saveFile(file); // 保存上传信息
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("文件保存失败:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +60,7 @@ public class UploadAction {
|
||||
* @throws Exception 上传异常
|
||||
*/
|
||||
public void saveFile(MultipartFile file) throws Exception {
|
||||
try {
|
||||
String path = OsInfoUtil.isWindows() ? windows_path :
|
||||
OsInfoUtil.isLinux() ? linux_path : null;
|
||||
assert path != null;
|
||||
@@ -64,6 +69,10 @@ public class UploadAction {
|
||||
filePath = path + File.separator + file.getOriginalFilename();
|
||||
File saveFile = new File(filePath) ;
|
||||
file.transferTo(saveFile); // 文件保存
|
||||
logger.info("文件保存成功:{}", filePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.filter.Filter;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
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.utils.LoggerQueue;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
@@ -27,7 +29,13 @@ public class LogStashFilter extends Filter<ILoggingEvent> {
|
||||
DateUtil.format(Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime(),
|
||||
"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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package cn.somkit.fmt.socket;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.metona.cache.Cache;
|
||||
import cn.somkit.fmt.entity.LoggerMessage;
|
||||
import cn.somkit.fmt.utils.LoggerQueue;
|
||||
import cn.metona.mq.consumer.MessageListener;
|
||||
import cn.metona.mq.core.Message;
|
||||
import cn.metona.mq.util.MetonaMQUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -19,7 +19,7 @@ public class WebSocketServerHandler implements WebSocketHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private Cache<String, Object> cache;
|
||||
private Cache<String, String> cache;
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
@@ -27,10 +27,27 @@ public class WebSocketServerHandler implements WebSocketHandler {
|
||||
}
|
||||
|
||||
private void push(WebSocketSession session) throws IOException {
|
||||
while (StrUtil.isBlankIfStr(cache.get("closed")) || !Boolean.parseBoolean(String.valueOf(cache.get("closed")))) {
|
||||
LoggerMessage log = LoggerQueue.getInstance().poll();
|
||||
if(log != null){
|
||||
session.sendMessage(new TextMessage(JSONUtil.toJsonStr(log)));
|
||||
boolean closed = StrUtil.isNotBlank(cache.get("closed")) && Boolean.parseBoolean(cache.get("closed"));
|
||||
if(!closed){
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ class LogMonitorAdaptive {
|
||||
debug: '#9c27b0', info: '#2196f3', warn: '#ff9800',
|
||||
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.fontWeight = 'bold';
|
||||
line.appendChild(lvlSpan);
|
||||
|
||||
@@ -14,6 +14,18 @@
|
||||
|
||||
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', {
|
||||
theme: 'dark',
|
||||
maxLines: 10000,
|
||||
@@ -32,13 +44,7 @@
|
||||
wordWrap: true, // 日志内容是否自动换行(true=换行,false=横向滚动)
|
||||
//暂停/继续 回调函数
|
||||
onTogglePause: async (isPaused) => {
|
||||
const options = {
|
||||
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('发送日志');
|
||||
await logMonitor(isPaused);
|
||||
},
|
||||
onCreated: () => {
|
||||
console.log('日志容器已创建');
|
||||
@@ -53,6 +59,10 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
ws.onopen = function () {
|
||||
|
||||
}
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
if(event.data){
|
||||
let data = JSON.parse(event.data);
|
||||
|
||||
@@ -29,3 +29,7 @@
|
||||
```
|
||||
> v2.0.1
|
||||
常规BUG修复,参数名称修改
|
||||
> v2.1.0
|
||||
```
|
||||
引入metona-mq-mini-pro消息队列,重构实时日志获取方式
|
||||
```
|
||||
Reference in New Issue
Block a user