代码提交

This commit is contained in:
2025-07-17 23:00:52 +08:00
parent 7400f85c88
commit 3d3fe0cd96
17 changed files with 149 additions and 564 deletions

36
pom.xml
View File

@@ -17,41 +17,33 @@
<java.version>21</java.version> <java.version>21</java.version>
</properties> </properties>
<repositories>
<repository>
<id>metona-maven</id>
<url>https://gitee.com/thzxx/maven/raw/master</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
<version>8.3.2</version>
</dependency>
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId> <artifactId>hutool-json</artifactId>
<version>5.8.21</version> <version>5.8.25</version>
</dependency>
<dependency>
<groupId>cn.metona</groupId>
<artifactId>metona-cache-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -1,11 +1,6 @@
package cn.somkit.fmt.action; package cn.somkit.fmt.action;
import cn.somkit.fmt.annotation.ApiOperate;
import cn.somkit.fmt.config.RocksDBConfig;
import cn.somkit.fmt.utils.MD5Utils;
import cn.somkit.fmt.utils.OsInfoUtil; import cn.somkit.fmt.utils.OsInfoUtil;
import cn.somkit.fmt.utils.RocksDBUtils;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -50,48 +45,15 @@ public class DownloadAction {
File folder = new File(path); File folder = new File(path);
File[] listOfFiles = folder.listFiles(); File[] listOfFiles = folder.listFiles();
List<Map<String, Object>> list = new ArrayList<>(); List<Map<String, Object>> list = new ArrayList<>();
List<String> keys = RocksDBUtils.getAllKey(RocksDBConfig.RocksDB_Column_Family);
if(listOfFiles != null){ if(listOfFiles != null){
for (File file : listOfFiles) { for (File file : listOfFiles) {
if(!file.isFile()){ if(!file.isFile()){
continue; continue;
} }
if(!keys.contains(file.getName())){ Map<String, Object> map = getObjectMap(file);
try (FileInputStream is = new FileInputStream(file)) {
String hash = MD5Utils.md5HashCode(is);
RocksDBUtils.put(RocksDBConfig.RocksDB_Column_Family, file.getName(), hash);
}catch (Exception e){e.printStackTrace();}
}
BigDecimal filesize = BigDecimal.valueOf(file.length())
.divide(BigDecimal.valueOf(1024 * 1024), 2, RoundingMode.HALF_UP);
Long time = file.lastModified();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String filetime = format.format(time);
Map<String, Object> map = new HashMap<>();
map.put("filename", file.getName());
map.put("filepath", file.getAbsolutePath());
map.put("filesize", filesize);
map.put("filetime", filetime);
list.add(map); list.add(map);
} }
List<String> filenames = list.stream().map(map -> String.valueOf(map.get("filename"))).toList();
keys.forEach(key -> {
try {
if(!filenames.contains(key)){
RocksDBUtils.delete(RocksDBConfig.RocksDB_Column_Family, key);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}else{
keys.forEach(key -> {
try {
RocksDBUtils.delete(RocksDBConfig.RocksDB_Column_Family, key);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
} }
if(StringUtils.hasText(keyboard) && !CollectionUtils.isEmpty(list)){ if(StringUtils.hasText(keyboard) && !CollectionUtils.isEmpty(list)){
list = list.stream().filter(map -> String.valueOf(map.get("filename")) list = list.stream().filter(map -> String.valueOf(map.get("filename"))
@@ -104,7 +66,20 @@ public class DownloadAction {
return mv; return mv;
} }
@ApiOperate(description = "StreamingResponseBody方式下载文件") private static Map<String, Object> getObjectMap(File file) {
BigDecimal filesize = BigDecimal.valueOf(file.length())
.divide(BigDecimal.valueOf(1024 * 1024), 2, RoundingMode.HALF_UP);
Long time = file.lastModified();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String filetime = format.format(time);
Map<String, Object> map = new HashMap<>();
map.put("filename", file.getName());
map.put("filepath", file.getAbsolutePath());
map.put("filesize", filesize);
map.put("filetime", filetime);
return map;
}
@GetMapping(value = "/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) @GetMapping(value = "/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public DeferredResult<ResponseEntity<StreamingResponseBody>> downloadFileByPath(String path, Boolean temp) throws Exception { public DeferredResult<ResponseEntity<StreamingResponseBody>> downloadFileByPath(String path, Boolean temp) throws Exception {
final File file = new File(path); final File file = new File(path);
@@ -147,7 +122,6 @@ public class DownloadAction {
@PostMapping("/packZip") @PostMapping("/packZip")
@ResponseBody @ResponseBody
@ApiOperate(description = "批量文件打包下载")
public Map<String, Object> packZip(String filenames) throws Exception{ public Map<String, Object> packZip(String filenames) throws Exception{
try { try {
String path = OsInfoUtil.isWindows() ? windows_path : String path = OsInfoUtil.isWindows() ? windows_path :
@@ -191,17 +165,14 @@ public class DownloadAction {
@PostMapping("/delete") @PostMapping("/delete")
@ResponseBody @ResponseBody
@ApiOperate(description = "单文件删除")
public String delete(String path) throws Exception{ public String delete(String path) throws Exception{
File file = new File(path); File file = new File(path);
RocksDBUtils.delete(RocksDBConfig.RocksDB_Column_Family, file.getName());
FileSystemUtils.deleteRecursively(file); FileSystemUtils.deleteRecursively(file);
return "删除成功"; return "删除成功";
} }
@PostMapping("/batchDel") @PostMapping("/batchDel")
@ResponseBody @ResponseBody
@ApiOperate(description = "批量文件删除")
public String batchDel(String filenames) throws Exception{ public String batchDel(String filenames) throws Exception{
String path = OsInfoUtil.isWindows() ? windows_path : String path = OsInfoUtil.isWindows() ? windows_path :
OsInfoUtil.isLinux() ? linux_path : null; OsInfoUtil.isLinux() ? linux_path : null;
@@ -213,7 +184,6 @@ public class DownloadAction {
if(!Arrays.asList(filenames.split(",")).contains(file.getName())){ if(!Arrays.asList(filenames.split(",")).contains(file.getName())){
continue; continue;
} }
RocksDBUtils.delete(RocksDBConfig.RocksDB_Column_Family, file.getName());
FileSystemUtils.deleteRecursively(file); FileSystemUtils.deleteRecursively(file);
} }
return "删除成功"; return "删除成功";

View File

@@ -1,6 +1,5 @@
package cn.somkit.fmt.action; package cn.somkit.fmt.action;
import cn.somkit.fmt.annotation.ApiOperate;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;

View File

@@ -1,9 +1,12 @@
package cn.somkit.fmt.action; package cn.somkit.fmt.action;
import cn.somkit.fmt.annotation.ApiOperate; import cn.somkit.fmt.entity.LoggerMessage;
import cn.somkit.fmt.utils.LoggerQueue;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller @Controller
@RequestMapping("/logging") @RequestMapping("/logging")
@@ -13,4 +16,10 @@ public class LoggingAction {
public String index() throws Exception{ public String index() throws Exception{
return "logging"; return "logging";
} }
@ResponseBody
@PostMapping("/get")
public LoggerMessage getLoggerMessage() {
return LoggerQueue.getInstance().poll();
}
} }

View File

@@ -1,14 +1,9 @@
package cn.somkit.fmt.action; package cn.somkit.fmt.action;
import cn.somkit.fmt.annotation.ApiOperate;
import cn.somkit.fmt.config.RocksDBConfig;
import cn.somkit.fmt.utils.MD5Utils;
import cn.somkit.fmt.utils.OsInfoUtil; import cn.somkit.fmt.utils.OsInfoUtil;
import cn.somkit.fmt.utils.RocksDBUtils;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
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.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -36,7 +31,6 @@ public class UploadAction {
@PostMapping("/execute") @PostMapping("/execute")
@ResponseBody @ResponseBody
@ApiOperate(description = "文件上传")
public Map<String, Object> execute(HttpServletRequest request) throws Exception{ public Map<String, Object> execute(HttpServletRequest request) throws Exception{
//多个文件上传 就只是简单的多文件上传保存在本地的磁盘 //多个文件上传 就只是简单的多文件上传保存在本地的磁盘
if (request instanceof MultipartHttpServletRequest mrequest) { if (request instanceof MultipartHttpServletRequest mrequest) {
@@ -67,28 +61,9 @@ public class UploadAction {
assert path != null; assert path != null;
String filePath = ""; String filePath = "";
if (file != null && file.getSize() > 0) { // 有文件上传 if (file != null && file.getSize() > 0) { // 有文件上传
String fileName = verify(file, null);// 创建文件名称 filePath = path + File.separator + file.getOriginalFilename();
if(StringUtils.hasText(fileName)){ File saveFile = new File(filePath) ;
String hash = MD5Utils.md5HashCode(file.getInputStream()); file.transferTo(saveFile); // 文件保存
RocksDBUtils.put(RocksDBConfig.RocksDB_Column_Family, fileName, hash);
filePath = path + File.separator + fileName;
File saveFile = new File(filePath) ;
file.transferTo(saveFile); // 文件保存
}
} }
} }
private String verify(MultipartFile file, String filename) throws Exception {
String key = StringUtils.hasText(filename) ? filename : Objects.requireNonNull(file.getOriginalFilename());
String hash = RocksDBUtils.get(RocksDBConfig.RocksDB_Column_Family, key);
if(!StringUtils.hasText(hash)){
return StringUtils.hasText(filename) ? filename : file.getOriginalFilename();
}
String newHash = MD5Utils.md5HashCode(file.getInputStream());
if(!hash.equals(newHash)){
String newFilename = "(1)" + file.getOriginalFilename();
return verify(file, newFilename);
}
return null;
}
} }

View File

@@ -1,14 +0,0 @@
package cn.somkit.fmt.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiOperate {
/**
* 描述
*/
String description() default "";
}

View File

@@ -1,93 +0,0 @@
package cn.somkit.fmt.annotation.aspect;
import cn.hutool.json.JSONUtil;
import cn.somkit.fmt.annotation.ApiOperate;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class ApiOperateAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** 换行符 */
private static final String LINE_SEPARATOR = System.lineSeparator();
/** 以自定义 @ApiOperate 注解为切点 */
@Pointcut("@annotation(apiOperate)")
public void ApiOperate(ApiOperate apiOperate) {}
/**
* 环绕注入
* @param joinPoint
* @param apiOperate
* @return
* @throws Throwable
*/
@Around(value = "ApiOperate(apiOperate)", argNames = "joinPoint,apiOperate")
public Object doAround(ProceedingJoinPoint joinPoint, ApiOperate apiOperate) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
long timeConsuming = System.currentTimeMillis() - startTime;
// 打印请求相关参数
logger.info("========================================== Start ==========================================");
// 打印请求 url
logger.info("URL : {}", request.getRequestURL().toString());
// 打印描述信息
logger.info("Description : {}", apiOperate.description());
// 打印 Http method
logger.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
// 打印请求的 IP
logger.info("IP : {}", request.getRemoteAddr());
// 打印请求Headers
logger.info("Header Args : {}", headers(request));
// 打印请求入参
logger.info("Request Args : {}", Arrays.toString(joinPoint.getArgs()));
// 打印出参
logger.info("Response Args : {}", JSONUtil.toJsonStr(result));
// 执行耗时
logger.info("Time-Consuming : {} ms", timeConsuming);
// 接口结束后换行,方便分割查看
logger.info("=========================================== End ===========================================" + LINE_SEPARATOR);
return result;
}
/**
* 获取所有header参数
* @param request
* @return Map<String, String> headers
*/
private Map<String, String> headers(HttpServletRequest request){
Map<String, String> headerMap = new HashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
headerMap.put(name, value);
}
return headerMap;
}
}

View File

@@ -1,36 +0,0 @@
package cn.somkit.fmt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class RocksDBConfig {
public static String RocksDB_Path_Windows = "D://RocksDB";
public static String RocksDB_Path_Linux = "/usr/local/rocksdb";
public static String RocksDB_Column_Family = "default";
@Value("${somkit.db.rocks.path.windows}")
public void setRocksDB_Path_Windows(String rocksDB_Path_Windows) {
if(StringUtils.hasText(rocksDB_Path_Windows)){
RocksDB_Path_Windows = rocksDB_Path_Windows;
}
}
@Value("${somkit.db.rocks.path.linux}")
public void setRocksDB_Path_Linux(String rocksDB_Path_Linux) {
if(StringUtils.hasText(rocksDB_Path_Linux)){
RocksDB_Path_Linux = rocksDB_Path_Linux;
}
}
@Value("${somkit.db.rocks.column-family}")
public void setRocksDB_Column_Family(String rocksDB_Column_Family){
if(StringUtils.hasText(rocksDB_Column_Family)){
RocksDB_Column_Family = rocksDB_Column_Family;
}
}
}

View File

@@ -1,46 +0,0 @@
package cn.somkit.fmt.config;
import cn.somkit.fmt.entity.LoggerMessage;
import cn.somkit.fmt.utils.ApplicationContextProvider;
import cn.somkit.fmt.utils.LoggerQueue;
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketLogConfig implements WebSocketMessageBrokerConfigurer {
/* 不再通过构造函数注入,而是第一次使用时再拿 */
private volatile SimpMessagingTemplate template;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-logs").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
}
@PostConstruct
public void startPush() {
new Thread(() -> {
while (true) {
LoggerMessage log = LoggerQueue.getInstance().poll();
if (log != null) {
if (template == null) {
// 延迟获取
template = ApplicationContextProvider.getBean(SimpMessagingTemplate.class);
}
template.convertAndSend("/topic/logs", log);
}
}
}, "log-push").start();
}
}

View File

@@ -1,5 +1,6 @@
package cn.somkit.fmt.filter; package cn.somkit.fmt.filter;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent; 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;
@@ -11,15 +12,35 @@ import java.time.ZoneId;
public class LogStashFilter extends Filter<ILoggingEvent> { public class LogStashFilter extends Filter<ILoggingEvent> {
Level level;
public LogStashFilter() {
}
@Override @Override
public FilterReply decide(ILoggingEvent e) { public FilterReply decide(ILoggingEvent e) {
LoggerMessage msg = new LoggerMessage( if (!this.isStarted()) {
e.getLevel().toString(), return FilterReply.NEUTRAL;
e.getLoggerName(), } else {
e.getFormattedMessage(), LoggerMessage msg = new LoggerMessage(
Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime() e.getLevel().toString(),
); e.getLoggerName(),
LoggerQueue.getInstance().push(msg); // 单例阻塞队列 e.getFormattedMessage(),
return FilterReply.ACCEPT; Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime()
);
LoggerQueue.getInstance().push(msg); // 单例阻塞队列
return e.getLevel().isGreaterOrEqual(this.level) ? FilterReply.NEUTRAL : FilterReply.DENY;
}
}
public void setLevel(String level) {
this.level = Level.toLevel(level);
}
public void start() {
if (this.level != null) {
super.start();
}
} }
} }

View File

@@ -1,235 +0,0 @@
package cn.somkit.fmt.utils;
import cn.somkit.fmt.config.RocksDBConfig;
import org.rocksdb.*;
import org.springframework.util.ObjectUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class RocksDBUtils {
private static RocksDB rocksDB;
public static ConcurrentMap<String, ColumnFamilyHandle> columnFamilyHandleMap = new ConcurrentHashMap<>();
public static int GET_KEYS_BATCH_SIZE = 100000;
static {
try {
String rocksDBPath = null; //RocksDB文件目录
if (OsInfoUtil.isWindows()) {
rocksDBPath = RocksDBConfig.RocksDB_Path_Windows; // 指定windows系统下RocksDB文件目录
} else if(OsInfoUtil.isLinux()){
rocksDBPath = RocksDBConfig.RocksDB_Path_Linux; // 指定linux系统下RocksDB文件目录
}
RocksDB.loadLibrary();
Options options = new Options();
options.setCreateIfMissing(true); //如果数据库不存在则创建
List<byte[]> cfArr = RocksDB.listColumnFamilies(options, rocksDBPath); // 初始化所有已存在列族
List<ColumnFamilyDescriptor> columnFamilyDescriptors = new ArrayList<>(); //ColumnFamilyDescriptor集合
if (!ObjectUtils.isEmpty(cfArr)) {
for (byte[] cf : cfArr) {
columnFamilyDescriptors.add(new ColumnFamilyDescriptor(cf, new ColumnFamilyOptions()));
}
} else {
columnFamilyDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, new ColumnFamilyOptions()));
}
DBOptions dbOptions = new DBOptions();
dbOptions.setCreateIfMissing(true);
List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>(); //ColumnFamilyHandle集合
rocksDB = RocksDB.open(dbOptions, rocksDBPath, columnFamilyDescriptors, columnFamilyHandles);
for (int i = 0; i < columnFamilyDescriptors.size(); i++) {
ColumnFamilyHandle columnFamilyHandle = columnFamilyHandles.get(i);
String cfName = new String(columnFamilyDescriptors.get(i).getName(), StandardCharsets.UTF_8);
columnFamilyHandleMap.put(cfName, columnFamilyHandle);
}
System.out.println("RocksDB init success!! path:" + rocksDBPath);
} catch (Exception e) {
System.out.println("RocksDB init failure!! error:" + e.getMessage());
e.printStackTrace();
}
}
private RocksDBUtils(){}
public static ColumnFamilyHandle cfAddIfNotExist(String cfName) throws RocksDBException {
ColumnFamilyHandle columnFamilyHandle;
if (!columnFamilyHandleMap.containsKey(cfName)) {
columnFamilyHandle = rocksDB.createColumnFamily(new ColumnFamilyDescriptor(cfName.getBytes(), new ColumnFamilyOptions()));
columnFamilyHandleMap.put(cfName, columnFamilyHandle);
System.out.println("cfAddIfNotExist success!! cfName:" + cfName);
} else {
columnFamilyHandle = columnFamilyHandleMap.get(cfName);
}
return columnFamilyHandle;
}
/**
* 列族,删除(如果存在)
*/
public static void cfDeleteIfExist(String cfName) throws RocksDBException {
if (columnFamilyHandleMap.containsKey(cfName)) {
rocksDB.dropColumnFamily(columnFamilyHandleMap.get(cfName));
columnFamilyHandleMap.remove(cfName);
System.out.println("cfDeleteIfExist success!! cfName:" + cfName);
} else {
System.out.println("cfDeleteIfExist containsKey!! cfName:" + cfName);
}
}
/**
* 增
*/
public static void put(String cfName, String key, String value) throws RocksDBException {
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
rocksDB.put(columnFamilyHandle, key.getBytes(), value.getBytes());
}
/**
* 增(批量)
*/
public static void batchPut(String cfName, Map<String, String> map) throws RocksDBException {
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
WriteOptions writeOptions = new WriteOptions();
WriteBatch writeBatch = new WriteBatch();
for (Map.Entry<String, String> entry : map.entrySet()) {
writeBatch.put(columnFamilyHandle, entry.getKey().getBytes(), entry.getValue().getBytes());
}
rocksDB.write(writeOptions, writeBatch);
}
/**
* 删
*/
public static void delete(String cfName, String key) throws RocksDBException {
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
rocksDB.delete(columnFamilyHandle, key.getBytes());
}
/**
* 查
*/
public static String get(String cfName, String key) throws RocksDBException {
String value = null;
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
byte[] bytes = rocksDB.get(columnFamilyHandle, key.getBytes());
if (!ObjectUtils.isEmpty(bytes)) {
value = new String(bytes, StandardCharsets.UTF_8);
}
return value;
}
/**
* 查(多个键值对)
*/
public static Map<String, String> multiGetAsMap(String cfName, List<String> keys) throws RocksDBException {
Map<String, String> map = new HashMap<>(keys.size());
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
List<ColumnFamilyHandle> columnFamilyHandles;
List<byte[]> keyBytes = keys.stream().map(String::getBytes).collect(Collectors.toList());
columnFamilyHandles = IntStream.range(0, keys.size()).mapToObj(i -> columnFamilyHandle).collect(Collectors.toList());
List<byte[]> bytes = rocksDB.multiGetAsList(columnFamilyHandles, keyBytes);
for (int i = 0; i < bytes.size(); i++) {
byte[] valueBytes = bytes.get(i);
String value = "";
if (!ObjectUtils.isEmpty(valueBytes)) {
value = new String(valueBytes, StandardCharsets.UTF_8);
}
map.put(keys.get(i), value);
}
return map;
}
/**
* 查(多个值)
*/
public static List<String> multiGetValueAsList(String cfName, List<String> keys) throws RocksDBException {
List<String> values = new ArrayList<>(keys.size());
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
List<byte[]> keyBytes = keys.stream().map(String::getBytes).collect(Collectors.toList());
for (int i = 0; i < keys.size(); i++) {
columnFamilyHandles.add(columnFamilyHandle);
}
List<byte[]> bytes = rocksDB.multiGetAsList(columnFamilyHandles, keyBytes);
for (byte[] valueBytes : bytes) {
String value = "";
if (!ObjectUtils.isEmpty(valueBytes)) {
value = new String(valueBytes, StandardCharsets.UTF_8);
}
values.add(value);
}
return values;
}
/**
* 查(所有键)
*/
public static List<String> getAllKey(String cfName) throws RocksDBException {
List<String> list = new ArrayList<>();
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
try (RocksIterator rocksIterator = rocksDB.newIterator(columnFamilyHandle)) {
for (rocksIterator.seekToFirst(); rocksIterator.isValid(); rocksIterator.next()) {
list.add(new String(rocksIterator.key(), StandardCharsets.UTF_8));
}
}
return list;
}
/**
* 分片查(键)
*/
public static List<String> getKeysFrom(String cfName, String lastKey) throws RocksDBException {
List<String> list = new ArrayList<>(GET_KEYS_BATCH_SIZE);
// 获取列族Handle
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName);
try (RocksIterator rocksIterator = rocksDB.newIterator(columnFamilyHandle)) {
if (lastKey != null) {
rocksIterator.seek(lastKey.getBytes(StandardCharsets.UTF_8));
rocksIterator.next();
} else {
rocksIterator.seekToFirst();
}
// 一批次最多 GET_KEYS_BATCH_SIZE 个 key
while (rocksIterator.isValid() && list.size() < GET_KEYS_BATCH_SIZE) {
list.add(new String(rocksIterator.key(), StandardCharsets.UTF_8));
rocksIterator.next();
}
}
return list;
}
/**
* 查(所有键值)
*/
public static Map<String, String> getAll(String cfName) throws RocksDBException {
Map<String, String> map = new HashMap<>();
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
try (RocksIterator rocksIterator = rocksDB.newIterator(columnFamilyHandle)) {
for (rocksIterator.seekToFirst(); rocksIterator.isValid(); rocksIterator.next()) {
map.put(new String(rocksIterator.key(), StandardCharsets.UTF_8), new String(rocksIterator.value(), StandardCharsets.UTF_8));
}
}
return map;
}
/**
* 查总条数
*/
public static int getCount(String cfName) throws RocksDBException {
int count = 0;
ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle
try (RocksIterator rocksIterator = rocksDB.newIterator(columnFamilyHandle)) {
for (rocksIterator.seekToFirst(); rocksIterator.isValid(); rocksIterator.next()) {
count++;
}
}
return count;
}
}

View File

@@ -15,18 +15,52 @@ spring:
multipart: multipart:
max-file-size: 1024MB max-file-size: 1024MB
max-request-size: 10240MB max-request-size: 10240MB
threads:
virtual:
enabled: true
somkit: somkit:
upload: upload:
path: path:
windows: D://data/install/upload windows: D://data/install/upload
linux: /mnt/files linux: /mnt/files
db:
rocks:
path:
windows: D://RocksDB//fmt
linux: /usr/local/rocksdb/fmt
column-family: default
logging: logging:
config: classpath:logback-spring.xml config: classpath:logback-spring.xml
metona:
cache:
# 缓存类型,支持以下类型:
# - CONCURRENT_HASH_MAP: 基于 ConcurrentHashMap 的线程安全缓存
# - WEAK_HASH_MAP: 基于 WeakHashMap 的弱引用缓存
# - LINKED_HASH_MAP: 基于 LinkedHashMap 的 LRU 缓存
type: LINKED_HASH_MAP
# 缓存的初始容量,默认值为 16
initial-capacity: 128
# 缓存的最大容量,当缓存条目数超过该值时,会根据策略移除旧条目
# 仅对 LINKED_HASH_MAP 类型有效
maximum-size: 2000
# 写入后过期时间(单位由 time-unit 指定)
# 默认值为 -1表示永不过期
expire-after-write: -1
# 访问后过期时间(单位由 time-unit 指定)
# 默认值为 -1表示永不过期
expire-after-access: -1
# 时间单位,支持以下值:
# - NANOSECONDS: 纳秒
# - MICROSECONDS: 微秒
# - MILLISECONDS: 毫秒(默认)
# - SECONDS: 秒
# - MINUTES: 分钟
# - HOURS: 小时
# - DAYS: 天
time-unit: MILLISECONDS
# 是否记录缓存统计信息(如命中率、加载次数等)
# 默认值为 false
record-stats: true

View File

@@ -9,7 +9,7 @@
<level>DEBUG</level> <level>DEBUG</level>
</filter> </filter>
<filter class="cn.somkit.fmt.filter.LogStashFilter"> <filter class="cn.somkit.fmt.filter.LogStashFilter">
<level>DEBUG</level>
</filter> </filter>
<encoder> <encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern> <pattern>${CONSOLE_LOG_PATTERN}</pattern>

View File

@@ -40,6 +40,12 @@ class LogMonitorAdaptive {
enableFontSize: true, // 是否提供“字体大小 +/-”按钮 enableFontSize: true, // 是否提供“字体大小 +/-”按钮
enableWordWrap: true, // 是否提供“换行/不换行”切换按钮 enableWordWrap: true, // 是否提供“换行/不换行”切换按钮
/* --- 暂停/继续 的回调函数 --- */
onTogglePause: () => {},
/* --- 创建完成 的回调函数 --- */
onCreated: () => {},
...opts ...opts
}; };
@@ -59,6 +65,11 @@ class LogMonitorAdaptive {
this.initDOM(); this.initDOM();
this.bindResize(); this.bindResize();
this.bindGlobalEvents(); this.bindGlobalEvents();
//执行回调函数
if(this.cfg.onCreated && typeof this.cfg.onCreated === 'function'){
this.cfg.onCreated();
}
} }
/* ------------------------ 初始化 ------------------------ */ /* ------------------------ 初始化 ------------------------ */
@@ -332,6 +343,10 @@ class LogMonitorAdaptive {
this.pauseBtn.style.backgroundColor = this.isPaused this.pauseBtn.style.backgroundColor = this.isPaused
? (this.cfg.theme === 'dark' ? '#d32f2f' : '#ff5252') ? (this.cfg.theme === 'dark' ? '#d32f2f' : '#ff5252')
: (this.cfg.theme === 'dark' ? '#444' : '#e7e7e7'); : (this.cfg.theme === 'dark' ? '#444' : '#e7e7e7');
//执行回调函数
if(this.cfg.onTogglePause && typeof this.cfg.onTogglePause === 'function'){
this.cfg.onTogglePause(this.isPaused);
}
} }
toggleTheme() { toggleTheme() {

View File

@@ -15,9 +15,8 @@
<body> <body>
<!-- 日志容器 --> <!-- 日志容器 -->
<div id="logContainer"></div> <div id="logContainer"></div>
<script th:src="@{/common/js/basic.js}" type="text/javascript" charset="utf-8"></script>
<script th:src="@{/common/js/LogMonitorAdaptive.js}" type="text/javascript" charset="utf-8"></script> <script th:src="@{/common/js/LogMonitorAdaptive.js}" type="text/javascript" charset="utf-8"></script>
<script th:src="@{/common/js/sockjs.js}" type="text/javascript" charset="utf-8"></script>
<script th:src="@{/common/js/stomp.js}" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" th:inline="javascript" charset="utf-8"> <script type="text/javascript" th:inline="javascript" charset="utf-8">
window.onload = () => { window.onload = () => {
const logger = new LogMonitorAdaptive('#logContainer', { const logger = new LogMonitorAdaptive('#logContainer', {
@@ -35,16 +34,24 @@
enableWordWrap: true, enableWordWrap: true,
showTimestamp: true, // 是否显示时间戳 showTimestamp: true, // 是否显示时间戳
showLevel: true, // 是否显示日志级别标签 showLevel: true, // 是否显示日志级别标签
}); //暂停/继续 回调函数
onTogglePause: (isPaused) => {
const socket = new SockJS('/fmt/ws-logs'); console.log(isPaused ? '暂停' : '继续');
const stomp = Stomp.over(socket); },
stomp.connect({}, () => { onCreated: () => {
stomp.subscribe('/topic/logs', (payload) => { console.log('日志容器已创建');
const log = JSON.parse(payload.body); let index = setInterval(() => {
console.log(log); const options = {
logger.log(log.message, log.level); url: Fmt.ctx() + '/logging/get',
}); data: {},
method: 'post'
};
Fmt.axios(options).then((result) => {
console.log(result);
logger.log(result.message, result.level);
}).catch((err) => clearInterval(index));
}, 500);
}
}); });
} }
</script> </script>

View File

@@ -33,7 +33,7 @@
<span class="ax-line"></span> <span class="ax-line"></span>
</div> </div>
<div class="ax-item"> <div class="ax-item">
<a th:href="@{/logging/index}" class="ax-text">在线日志</a> <a th:href="@{/logging/index}" target="_blank" class="ax-text">在线日志</a>
<span class="ax-line"></span> <span class="ax-line"></span>
</div> </div>
</div> </div>

View File

@@ -1,13 +0,0 @@
package cn.somkit.fmt;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class FmtApplicationTests {
@Test
void contextLoads() {
}
}