diff --git a/pom.xml b/pom.xml index d62182c..006bc36 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.1 + 3.5.3 cn.somkit @@ -14,44 +14,42 @@ fmt fmt - 17 + 21 + + + metona-maven + https://gitee.com/thzxx/maven/raw/master + + + org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-test - test - + org.springframework.boot spring-boot-starter-thymeleaf + org.springframework.boot spring-boot-starter-websocket - - org.aspectj - aspectjweaver - 1.9.7 - - - - org.rocksdb - rocksdbjni - 8.3.2 - - cn.hutool hutool-json - 5.8.21 + 5.8.25 + + + + cn.metona + metona-cache-spring-boot-starter + 1.0.0 diff --git a/src/main/java/cn/somkit/fmt/action/DownloadAction.java b/src/main/java/cn/somkit/fmt/action/DownloadAction.java index 8587e50..0c1762a 100644 --- a/src/main/java/cn/somkit/fmt/action/DownloadAction.java +++ b/src/main/java/cn/somkit/fmt/action/DownloadAction.java @@ -1,13 +1,7 @@ package cn.somkit.fmt.action; -import cn.somkit.fmt.annotation.ApiOperate; -import cn.somkit.fmt.config.FmtConfig; -import cn.somkit.fmt.config.RocksDBConfig; -import cn.somkit.fmt.utils.MD5Utils; -import cn.somkit.fmt.utils.ParamUtils; -import cn.somkit.fmt.utils.PathUtils; -import cn.somkit.fmt.utils.RocksDBUtils; -import jakarta.servlet.http.HttpServletResponse; +import cn.somkit.fmt.utils.OsInfoUtil; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -37,54 +31,29 @@ import java.util.zip.ZipOutputStream; @RequestMapping("/download") public class DownloadAction { + @Value("${somkit.upload.path.windows}") + private String windows_path; + + @Value("${somkit.upload.path.linux}") + private String linux_path; + @GetMapping("/index") public ModelAndView index(String keyboard) throws Exception{ - PathUtils.directory(ParamUtils.getUploadPath()); - File folder = new File(ParamUtils.getUploadPath()); + String path = OsInfoUtil.isWindows() ? windows_path : + OsInfoUtil.isLinux() ? linux_path : null; + assert path != null; + File folder = new File(path); File[] listOfFiles = folder.listFiles(); List> list = new ArrayList<>(); - List keys = RocksDBUtils.getAllKey(RocksDBConfig.RocksDB_Column_Family); + if(listOfFiles != null){ for (File file : listOfFiles) { if(!file.isFile()){ continue; } - if(!keys.contains(file.getName())){ - 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 map = new HashMap<>(); - map.put("filename", file.getName()); - map.put("filepath", file.getAbsolutePath()); - map.put("filesize", filesize); - map.put("filetime", filetime); + Map map = getObjectMap(file); list.add(map); } - List 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)){ list = list.stream().filter(map -> String.valueOf(map.get("filename")) @@ -97,7 +66,20 @@ public class DownloadAction { return mv; } - @ApiOperate(description = "StreamingResponseBody方式下载文件") + private static Map 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 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) public DeferredResult> downloadFileByPath(String path, Boolean temp) throws Exception { final File file = new File(path); @@ -140,15 +122,20 @@ public class DownloadAction { @PostMapping("/packZip") @ResponseBody - @ApiOperate(description = "批量文件打包下载") public Map packZip(String filenames) throws Exception{ try { - PathUtils.directory(ParamUtils.getUploadPath()); - PathUtils.directory(ParamUtils.getTempFilePath()); + String path = OsInfoUtil.isWindows() ? windows_path : + OsInfoUtil.isLinux() ? linux_path : null; + assert path != null; + //临时文件目录 + String temp = "temp"; + String zipPath = path + File.separator + temp; + File zipFile = new File(zipPath); + if (!zipFile.exists()) zipFile.mkdirs(); String zipName = String.valueOf(System.currentTimeMillis()); - String zipDir = ParamUtils.getTempFilePath() + File.separator + zipName + ".zip"; + String zipDir = zipPath + File.separator + zipName + ".zip"; ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipDir)); - File folder = new File(ParamUtils.getUploadPath()); + File folder = new File(path); File[] listOfFiles = folder.listFiles(); assert listOfFiles != null; for (File file : listOfFiles) { @@ -178,27 +165,25 @@ public class DownloadAction { @PostMapping("/delete") @ResponseBody - @ApiOperate(description = "单文件删除") public String delete(String path) throws Exception{ File file = new File(path); - RocksDBUtils.delete(RocksDBConfig.RocksDB_Column_Family, file.getName()); FileSystemUtils.deleteRecursively(file); return "删除成功"; } @PostMapping("/batchDel") @ResponseBody - @ApiOperate(description = "批量文件删除") public String batchDel(String filenames) throws Exception{ - PathUtils.directory(ParamUtils.getUploadPath()); - File folder = new File(ParamUtils.getUploadPath()); + String path = OsInfoUtil.isWindows() ? windows_path : + OsInfoUtil.isLinux() ? linux_path : null; + assert path != null; + File folder = new File(path); File[] listOfFiles = folder.listFiles(); assert listOfFiles != null; for (File file : listOfFiles) { if(!Arrays.asList(filenames.split(",")).contains(file.getName())){ continue; } - RocksDBUtils.delete(RocksDBConfig.RocksDB_Column_Family, file.getName()); FileSystemUtils.deleteRecursively(file); } return "删除成功"; diff --git a/src/main/java/cn/somkit/fmt/action/IndexAction.java b/src/main/java/cn/somkit/fmt/action/IndexAction.java index 9d7a52a..d90d530 100644 --- a/src/main/java/cn/somkit/fmt/action/IndexAction.java +++ b/src/main/java/cn/somkit/fmt/action/IndexAction.java @@ -1,6 +1,5 @@ package cn.somkit.fmt.action; -import cn.somkit.fmt.annotation.ApiOperate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; diff --git a/src/main/java/cn/somkit/fmt/action/LoggingAction.java b/src/main/java/cn/somkit/fmt/action/LoggingAction.java index 41f2720..bea8a45 100644 --- a/src/main/java/cn/somkit/fmt/action/LoggingAction.java +++ b/src/main/java/cn/somkit/fmt/action/LoggingAction.java @@ -1,16 +1,37 @@ package cn.somkit.fmt.action; -import cn.somkit.fmt.annotation.ApiOperate; +import cn.metona.cache.Cache; +import cn.somkit.fmt.entity.LoggerMessage; +import cn.somkit.fmt.utils.LoggerQueue; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; 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.ResponseBody; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; @Controller @RequestMapping("/logging") public class LoggingAction { + @Autowired + private Cache cache; + @GetMapping("/index") public String index() throws Exception{ return "logging"; } + + @ResponseBody + @PostMapping("/close") + public void close(Boolean closed) throws Exception { + cache.put("closed", closed); + } } diff --git a/src/main/java/cn/somkit/fmt/action/UploadAction.java b/src/main/java/cn/somkit/fmt/action/UploadAction.java index 82daca4..2014d39 100644 --- a/src/main/java/cn/somkit/fmt/action/UploadAction.java +++ b/src/main/java/cn/somkit/fmt/action/UploadAction.java @@ -1,15 +1,9 @@ package cn.somkit.fmt.action; -import cn.somkit.fmt.annotation.ApiOperate; -import cn.somkit.fmt.config.FmtConfig; -import cn.somkit.fmt.config.RocksDBConfig; -import cn.somkit.fmt.utils.MD5Utils; -import cn.somkit.fmt.utils.ParamUtils; -import cn.somkit.fmt.utils.PathUtils; -import cn.somkit.fmt.utils.RocksDBUtils; +import cn.somkit.fmt.utils.OsInfoUtil; import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,6 +18,12 @@ import java.util.*; @RequestMapping("/upload") public class UploadAction { + @Value("${somkit.upload.path.windows}") + private String windows_path; + + @Value("${somkit.upload.path.linux}") + private String linux_path; + @GetMapping("/index") public String index() throws Exception{ return "upload"; @@ -31,16 +31,14 @@ public class UploadAction { @PostMapping("/execute") @ResponseBody - @ApiOperate(description = "文件上传") public Map execute(HttpServletRequest request) throws Exception{ //多个文件上传 就只是简单的多文件上传保存在本地的磁盘 if (request instanceof MultipartHttpServletRequest mrequest) { - PathUtils.directory(ParamUtils.getUploadPath()); List files = mrequest.getFiles("file"); // 取出每一个上传文件 for (MultipartFile file : files) { try { - this.saveFile(file);// 保存上传信息 + this.saveFile(file); // 保存上传信息 } catch (Exception e) { e.printStackTrace(); } @@ -58,30 +56,14 @@ public class UploadAction { * @throws Exception 上传异常 */ public void saveFile(MultipartFile file) throws Exception { + String path = OsInfoUtil.isWindows() ? windows_path : + OsInfoUtil.isLinux() ? linux_path : null; + assert path != null; String filePath = ""; - if (file != null && file.getSize() > 0) {// 有文件上传 - String fileName = verify(file, null);// 创建文件名称 - if(StringUtils.hasText(fileName)){ - String hash = MD5Utils.md5HashCode(file.getInputStream()); - RocksDBUtils.put(RocksDBConfig.RocksDB_Column_Family, fileName, hash); - filePath = ParamUtils.getUploadPath() + File.separator + fileName; - File saveFile = new File(filePath) ; - file.transferTo(saveFile); // 文件保存 - } + if (file != null && file.getSize() > 0) { // 有文件上传 + filePath = path + File.separator + file.getOriginalFilename(); + 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; - } } diff --git a/src/main/java/cn/somkit/fmt/annotation/ApiOperate.java b/src/main/java/cn/somkit/fmt/annotation/ApiOperate.java deleted file mode 100644 index 957ec89..0000000 --- a/src/main/java/cn/somkit/fmt/annotation/ApiOperate.java +++ /dev/null @@ -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 ""; -} diff --git a/src/main/java/cn/somkit/fmt/annotation/aspect/ApiOperateAspect.java b/src/main/java/cn/somkit/fmt/annotation/aspect/ApiOperateAspect.java deleted file mode 100644 index 1229765..0000000 --- a/src/main/java/cn/somkit/fmt/annotation/aspect/ApiOperateAspect.java +++ /dev/null @@ -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 headers - */ - private Map headers(HttpServletRequest request){ - Map headerMap = new HashMap<>(); - Enumeration enumeration = request.getHeaderNames(); - while (enumeration.hasMoreElements()) { - String name = enumeration.nextElement(); - String value = request.getHeader(name); - headerMap.put(name, value); - } - return headerMap; - } -} diff --git a/src/main/java/cn/somkit/fmt/config/LogSocketConfig.java b/src/main/java/cn/somkit/fmt/config/LogSocketConfig.java deleted file mode 100644 index b5a5d88..0000000 --- a/src/main/java/cn/somkit/fmt/config/LogSocketConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.somkit.fmt.config; - -import cn.somkit.fmt.utils.PathUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -@Component -public class LogSocketConfig { - - public static String Log_File_Path = "./logs/fmt-server.log"; - - public static String Max_Read_Length = "500"; - - public static String Read_Interval = "1000"; - - @Value("${somkit.logging.socket.log-file-path}") - public void setLog_File_Path(String log_File_Path) { - if(StringUtils.hasText(log_File_Path)){ - //如果使用相对路径,转换为绝对路径 - if(log_File_Path.startsWith("./")){ - log_File_Path = PathUtils.resolve(log_File_Path); - } - Log_File_Path = log_File_Path; - } - } - - @Value("${somkit.logging.socket.max-read-length}") - public void setMax_Read_Length(String max_Read_Length) { - if(StringUtils.hasText(max_Read_Length)){ - Max_Read_Length = max_Read_Length; - } - } - - @Value("${somkit.logging.socket.read-interval}") - public void setRead_Interval(String read_Interval) { - if(StringUtils.hasText(read_Interval)){ - Read_Interval = read_Interval; - } - } -} diff --git a/src/main/java/cn/somkit/fmt/config/RocksDBConfig.java b/src/main/java/cn/somkit/fmt/config/RocksDBConfig.java deleted file mode 100644 index 56df54b..0000000 --- a/src/main/java/cn/somkit/fmt/config/RocksDBConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.somkit.fmt.config; - -import cn.somkit.fmt.utils.PathUtils; -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 = "./cache/rocksdb"; - - public static String RocksDB_Column_Family = "default"; - - @Value("${somkit.cache.rocks-db.path}") - public void setRocksDB_Path(String rocksDB_Path) { - if(StringUtils.hasText(rocksDB_Path)){ - //如果使用相对路径,转换为绝对路径 - if(rocksDB_Path.startsWith("./")){ - rocksDB_Path = PathUtils.resolve(rocksDB_Path); - } - RocksDB_Path = rocksDB_Path; - } - } - - @Value("${somkit.cache.rocks-db.column-family}") - public void setRocksDB_Column_Family(String rocksDB_Column_Family){ - if(StringUtils.hasText(rocksDB_Column_Family)){ - RocksDB_Column_Family = rocksDB_Column_Family; - } - } -} diff --git a/src/main/java/cn/somkit/fmt/entity/LoggerMessage.java b/src/main/java/cn/somkit/fmt/entity/LoggerMessage.java new file mode 100644 index 0000000..a25c154 --- /dev/null +++ b/src/main/java/cn/somkit/fmt/entity/LoggerMessage.java @@ -0,0 +1,49 @@ +package cn.somkit.fmt.entity; + +import java.time.LocalDateTime; + +public class LoggerMessage { + String level; + String loggerName; + String message; + String timestamp; + + public LoggerMessage(String level, String loggerName, String message, String timestamp) { + this.level = level; + this.loggerName = loggerName; + this.message = message; + this.timestamp = timestamp; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getLoggerName() { + return loggerName; + } + + public void setLoggerName(String loggerName) { + this.loggerName = loggerName; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } +} diff --git a/src/main/java/cn/somkit/fmt/filter/LogStashFilter.java b/src/main/java/cn/somkit/fmt/filter/LogStashFilter.java new file mode 100644 index 0000000..2c28cac --- /dev/null +++ b/src/main/java/cn/somkit/fmt/filter/LogStashFilter.java @@ -0,0 +1,44 @@ +package cn.somkit.fmt.filter; + +import ch.qos.logback.classic.Level; +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.somkit.fmt.entity.LoggerMessage; +import cn.somkit.fmt.utils.LoggerQueue; + +import java.time.Instant; +import java.time.ZoneId; + +public class LogStashFilter extends Filter { + + Level level; + + public LogStashFilter() { + } + + @Override + public FilterReply decide(ILoggingEvent e) { + LoggerMessage msg = new LoggerMessage( + e.getLevel().toString(), + e.getLoggerName(), + e.getFormattedMessage(), + DateUtil.format(Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime(), + "yyyy-MM-dd HH:mm:ss.SSS") + ); + LoggerQueue.getInstance().push(msg); // 单例阻塞队列 + return FilterReply.NEUTRAL; + } + + public void setLevel(String level) { + this.level = Level.toLevel(level); + } + + public void start() { + if (this.level != null) { + super.start(); + } + + } +} diff --git a/src/main/java/cn/somkit/fmt/socket/WebSocketAutoConfig.java b/src/main/java/cn/somkit/fmt/socket/WebSocketAutoConfig.java index 4e53b87..ea7c25c 100644 --- a/src/main/java/cn/somkit/fmt/socket/WebSocketAutoConfig.java +++ b/src/main/java/cn/somkit/fmt/socket/WebSocketAutoConfig.java @@ -18,7 +18,6 @@ public class WebSocketAutoConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketServerHandler(), "/socket/ws")//设置连接路径和处理 - .setAllowedOrigins("*")//允许跨域访问 - .addInterceptors(new WebSocketServerInterceptor());//设置拦截器 + .setAllowedOrigins("*");//允许跨域访问 } } diff --git a/src/main/java/cn/somkit/fmt/socket/WebSocketEntity.java b/src/main/java/cn/somkit/fmt/socket/WebSocketEntity.java deleted file mode 100644 index fd57553..0000000 --- a/src/main/java/cn/somkit/fmt/socket/WebSocketEntity.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.somkit.fmt.socket; - -public class WebSocketEntity { - - /** - * 发送类型 HeartBeat:心跳 Message:消息 - */ - private String send; - - private Object data; - - public String getSend() { - return send; - } - - public void setSend(String send) { - this.send = send; - } - - public Object getData() { - return data; - } - - public void setData(Object data) { - this.data = data; - } -} diff --git a/src/main/java/cn/somkit/fmt/socket/WebSocketEnums.java b/src/main/java/cn/somkit/fmt/socket/WebSocketEnums.java deleted file mode 100644 index a8b18ed..0000000 --- a/src/main/java/cn/somkit/fmt/socket/WebSocketEnums.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.somkit.fmt.socket; - -public enum WebSocketEnums { - - //消息来源 - SEND_TP_HEARTBEAT("HeartBeat", "心跳"), - SEND_TP_LOGGING("Logging", "日志") - ; - - private String code; - private String name; - - WebSocketEnums(){}; - - WebSocketEnums(String code, String name) { - this.code = code; - this.name = name; - } - - public String getCode() { - return code; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/cn/somkit/fmt/socket/WebSocketOnline.java b/src/main/java/cn/somkit/fmt/socket/WebSocketOnline.java deleted file mode 100644 index 8579af7..0000000 --- a/src/main/java/cn/somkit/fmt/socket/WebSocketOnline.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.somkit.fmt.socket; - -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -public class WebSocketOnline { - - //在线连接数 - private static final AtomicInteger onlineCount = new AtomicInteger(0); - //在线连接 - private static final Map onlineMap = new ConcurrentHashMap<>(); - //日志读取位置记录 - public static final Map logLengthMap = new ConcurrentHashMap<>(); - - public static synchronized int getOnlineCount() { - return onlineCount.get(); - } - - public static void setOnline(String id, WebSocketSession session) throws Exception{ - //如果已经存在,先把原来的连接关闭 - if(onlineMap.containsKey(id)){ - if(onlineMap.get(id).isOpen()){ - onlineMap.get(id).close(); - } - } - onlineMap.put(id, session); - onlineCount.set(getOnlineCount() + 1); - } - - public static WebSocketSession getOnline(String id) throws Exception{ - return onlineMap.get(id); - } - - public static List getOnlineList() throws Exception{ - List list = new ArrayList<>(); - for (String key : onlineMap.keySet()) { - list.add(onlineMap.get(key)); - } - return list; - } - - public static void closeOnline(String id) throws Exception{ - onlineMap.remove(id); - onlineCount.set(getOnlineCount() - 1); - } - - public static void sendMessage(String id, String entityJsonStr) throws Exception { - WebSocketSession session = getOnline(id); - if(session != null && session.isOpen()){ - session.sendMessage(new TextMessage(entityJsonStr)); - } - } -} diff --git a/src/main/java/cn/somkit/fmt/socket/WebSocketServerHandler.java b/src/main/java/cn/somkit/fmt/socket/WebSocketServerHandler.java index e3fc51c..15d399c 100644 --- a/src/main/java/cn/somkit/fmt/socket/WebSocketServerHandler.java +++ b/src/main/java/cn/somkit/fmt/socket/WebSocketServerHandler.java @@ -1,67 +1,53 @@ package cn.somkit.fmt.socket; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; -import cn.somkit.fmt.utils.ErrorUtil; -import cn.somkit.fmt.utils.LogSocketUtils; +import cn.metona.cache.Cache; +import cn.somkit.fmt.entity.LoggerMessage; +import cn.somkit.fmt.utils.LoggerQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketMessage; -import org.springframework.web.socket.WebSocketSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; -import java.util.Objects; +import java.io.IOException; +@Component public class WebSocketServerHandler implements WebSocketHandler { private final Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + private Cache cache; + @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { - String id = session.getAttributes().get("id").toString(); - WebSocketOnline.setOnline(id, session); - logger.info("有新窗口开始监听:" + id + ", 当前在线人数为:" + WebSocketOnline.getOnlineCount()); - try { - //返回一个心跳检测 - WebSocketEntity entity = new WebSocketEntity(); - entity.setSend(WebSocketEnums.SEND_TP_HEARTBEAT.getCode()); - entity.setData("心跳检测"); - WebSocketOnline.sendMessage(id, JSONUtil.toJsonStr(entity)); - }catch (Exception e){ - logger.error("连接成功发送心跳异常:{}", ErrorUtil.errorInfoToString(e)); - } + push(session); + } - //读取并实时发送日志 - LogSocketUtils.LoggingChannel(id); + 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))); + } + } } @Override public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { - String id = session.getAttributes().get("id").toString(); - String messageStr = message.getPayload().toString(); - WebSocketEntity entity = JSONUtil.toBean(messageStr, WebSocketEntity.class); - if(Objects.equals(WebSocketEnums.SEND_TP_HEARTBEAT.getCode(), entity.getSend())) { - //心跳检测消息,原路返回客户端 - WebSocketOnline.sendMessage(id, JSONUtil.toJsonStr(entity)); - } + push(session); } @Override - public void handleTransportError(WebSocketSession session, Throwable e) throws Exception { - String id = session.getAttributes().get("id").toString(); - //发生错误时主动关闭连接 - if(session.isOpen()){ - session.close(); - } - WebSocketOnline.closeOnline(id); - logger.error("{}连接发生错误:{}", id, ErrorUtil.errorInfoToString(e)); + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { - String id = session.getAttributes().get("id").toString(); - WebSocketOnline.closeOnline(id); - logger.info("连接关闭:{},状态:{},当前在线人数为:{}", id, closeStatus.toString(), WebSocketOnline.getOnlineCount()); + } @Override diff --git a/src/main/java/cn/somkit/fmt/socket/WebSocketServerInterceptor.java b/src/main/java/cn/somkit/fmt/socket/WebSocketServerInterceptor.java deleted file mode 100644 index 6fe0d12..0000000 --- a/src/main/java/cn/somkit/fmt/socket/WebSocketServerInterceptor.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.somkit.fmt.socket; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.HandshakeInterceptor; - -import java.util.Map; - -public class WebSocketServerInterceptor implements HandshakeInterceptor { - - @Override - public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler, Map attributes) throws Exception { - HttpServletRequest rs = ((ServletServerHttpRequest) request).getServletRequest(); - HttpServletResponse hp = ((ServletServerHttpResponse)response).getServletResponse(); - //request 参数放入 attributes中 - rs.getParameterMap().forEach((k, v) -> { - attributes.put(k, v[0]); - }); - return true; - } - - @Override - public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler, Exception exception) { - } -} diff --git a/src/main/java/cn/somkit/fmt/utils/AppContext.java b/src/main/java/cn/somkit/fmt/utils/AppContext.java new file mode 100644 index 0000000..25eea31 --- /dev/null +++ b/src/main/java/cn/somkit/fmt/utils/AppContext.java @@ -0,0 +1,29 @@ +package cn.somkit.fmt.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class AppContext implements ApplicationContextAware { + + private static ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } + + public static ApplicationContext getApplicationContext() { + return context; + } + + public static T getBean(Class clazz) { + return context.getBean(clazz); + } + + public static Object getBean(String name) { + return context.getBean(name); + } +} diff --git a/src/main/java/cn/somkit/fmt/utils/ErrorUtil.java b/src/main/java/cn/somkit/fmt/utils/ErrorUtil.java deleted file mode 100644 index 42c6512..0000000 --- a/src/main/java/cn/somkit/fmt/utils/ErrorUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.somkit.fmt.utils; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * 捕获报错日志处理工具类 - */ -public class ErrorUtil { - - /** - * Exception出错的栈信息转成字符串 - * 用于打印到日志中 - */ - public static String errorInfoToString(Throwable e) { - StringWriter sw = null; - PrintWriter pw = null; - try { - sw = new StringWriter(); - pw = new PrintWriter(sw); - // 将出错的栈信息输出到printWriter中 - e.printStackTrace(pw); - pw.flush(); - sw.flush(); - } finally { - if (sw != null) { - try { - sw.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - if (pw != null) { - pw.close(); - } - } - return sw.toString(); - } -} diff --git a/src/main/java/cn/somkit/fmt/utils/LogSocketUtils.java b/src/main/java/cn/somkit/fmt/utils/LogSocketUtils.java deleted file mode 100644 index 7a1c64f..0000000 --- a/src/main/java/cn/somkit/fmt/utils/LogSocketUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -package cn.somkit.fmt.utils; - -import cn.hutool.json.JSONUtil; -import cn.somkit.fmt.config.LogSocketConfig; -import cn.somkit.fmt.socket.WebSocketEntity; -import cn.somkit.fmt.socket.WebSocketEnums; -import cn.somkit.fmt.socket.WebSocketOnline; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.thymeleaf.util.StringUtils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.util.Arrays; - -public class LogSocketUtils { - - private static final Logger logger = LoggerFactory.getLogger(LogSocketUtils.class); - - public static void LoggingChannel(String socketSessionId) { - //获取日志信息 - new Thread(() -> { - try { - boolean first = true; - while (WebSocketOnline.getOnline(socketSessionId) != null) { - BufferedReader reader = null; - try { - //日志文件路径 - String filePath = ParamUtils.getLogFilePath(); - //字符流 - reader = new BufferedReader(new FileReader(filePath)); - Object[] lines = reader.lines().toArray(); - //只取从上次之后产生的日志 - Integer length = WebSocketOnline.logLengthMap.get(socketSessionId); - Object[] copyOfRange = Arrays.copyOfRange(lines, length == null ? 0 : length, lines.length); - //对日志进行着色,更加美观 PS:注意,这里要根据日志生成规则来操作 - for (int i = 0; i < copyOfRange.length; i++) { - String line = (String) copyOfRange[i]; - - //先转义 - line = line.replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll("\"", """); - - String[] stars = line.split("->"); - if(stars.length == 2){ - line = line.replace(stars[0], ""+stars[0]+""); - } - - //处理等级 - line = line.replace("[DEBUG ]", "[DEBUG]"); - line = line.replace("[INFO ]", "[INFO]"); - line = line.replace("[WARN ]", "[WARN]"); - line = line.replace("[ERROR ]", "[ERROR]"); - - copyOfRange[i] = line; - } - //存储最新一行开始 - WebSocketOnline.logLengthMap.put(socketSessionId, lines.length); - - //第一次如果太大,截取最新的200行就够了,避免传输的数据太大 - int maxLength = Integer.parseInt(ParamUtils.getMaxReadLength()); - if(first && copyOfRange.length > maxLength){ - copyOfRange = Arrays.copyOfRange(copyOfRange, copyOfRange.length - maxLength, copyOfRange.length); - first = false; - } - - String result = StringUtils.join(copyOfRange, "
"); - WebSocketEntity entity = new WebSocketEntity(); - entity.setSend(WebSocketEnums.SEND_TP_LOGGING.getCode()); - entity.setData(result); - WebSocketOnline.sendMessage(socketSessionId, JSONUtil.toJsonStr(entity)); - - //休眠一秒 - Thread.sleep(Long.parseLong(ParamUtils.getReadInterval())); - } catch (Exception e) { - logger.error("读取日志异常:{}", ErrorUtil.errorInfoToString(e)); - } finally { - try { - assert reader != null; - reader.close(); - } catch (IOException ioe) { - logger.error("字符流关闭异常:{}", ErrorUtil.errorInfoToString(ioe)); - } - } - } - } catch (Exception e) { - logger.error("读取日志异常:{}", ErrorUtil.errorInfoToString(e)); - } - }).start(); - } -} diff --git a/src/main/java/cn/somkit/fmt/utils/LoggerQueue.java b/src/main/java/cn/somkit/fmt/utils/LoggerQueue.java new file mode 100644 index 0000000..07754d7 --- /dev/null +++ b/src/main/java/cn/somkit/fmt/utils/LoggerQueue.java @@ -0,0 +1,28 @@ +package cn.somkit.fmt.utils; + +import cn.somkit.fmt.entity.LoggerMessage; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public final class LoggerQueue { + private static final LoggerQueue INSTANCE = new LoggerQueue(); + private final BlockingQueue queue = new LinkedBlockingQueue<>(100000); + + public static LoggerQueue getInstance() { + return INSTANCE; + } + + public void push(LoggerMessage msg) { + queue.offer(msg); // 非阻塞插入 + } + + public LoggerMessage poll() { + try { + return queue.take(); // 阻塞直到有数据 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } +} diff --git a/src/main/java/cn/somkit/fmt/utils/RocksDBUtils.java b/src/main/java/cn/somkit/fmt/utils/RocksDBUtils.java deleted file mode 100644 index 45313e0..0000000 --- a/src/main/java/cn/somkit/fmt/utils/RocksDBUtils.java +++ /dev/null @@ -1,231 +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 columnFamilyHandleMap = new ConcurrentHashMap<>(); - public static int GET_KEYS_BATCH_SIZE = 100000; - - static { - try { - String rocksDBPath = RocksDBConfig.RocksDB_Path; //RocksDB文件目录 - PathUtils.directory(rocksDBPath); // 创建RocksDB文件目录 - RocksDB.loadLibrary(); - Options options = new Options(); - options.setCreateIfMissing(true); //如果数据库不存在则创建 - List cfArr = RocksDB.listColumnFamilies(options, rocksDBPath); // 初始化所有已存在列族 - List 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 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 map) throws RocksDBException { - ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle - WriteOptions writeOptions = new WriteOptions(); - WriteBatch writeBatch = new WriteBatch(); - for (Map.Entry 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 multiGetAsMap(String cfName, List keys) throws RocksDBException { - Map map = new HashMap<>(keys.size()); - ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle - List columnFamilyHandles; - List keyBytes = keys.stream().map(String::getBytes).collect(Collectors.toList()); - columnFamilyHandles = IntStream.range(0, keys.size()).mapToObj(i -> columnFamilyHandle).collect(Collectors.toList()); - List 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 multiGetValueAsList(String cfName, List keys) throws RocksDBException { - List values = new ArrayList<>(keys.size()); - ColumnFamilyHandle columnFamilyHandle = cfAddIfNotExist(cfName); //获取列族Handle - List columnFamilyHandles = new ArrayList<>(); - List keyBytes = keys.stream().map(String::getBytes).collect(Collectors.toList()); - for (int i = 0; i < keys.size(); i++) { - columnFamilyHandles.add(columnFamilyHandle); - } - List 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 getAllKey(String cfName) throws RocksDBException { - List 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 getKeysFrom(String cfName, String lastKey) throws RocksDBException { - List 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 getAll(String cfName) throws RocksDBException { - Map 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; - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e546413..4fbcfbc 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,23 +15,52 @@ spring: multipart: max-file-size: 1024MB max-request-size: 10240MB + threads: + virtual: + enabled: true somkit: upload: - path: ./data/files #上传文件路径 可以使用绝对路径,也可使用相对路径 - temp-path: ./data/files/temp #临时文件存放地址 可以使用绝对路径,也可使用相对路径 - cache: - rocks-db: - path: ./cache/rocksdb # RocksDB缓存路径 可以使用绝对路径,也可使用相对路径 - column-family: default - logging: - socket: - #日志文件地址 - log-file-path: ./logs/fmt-server.log - #最大读取展示行数 - max-read-length: 500 - #读取间隔时间 毫秒 - read-interval: 1000 + path: + windows: D://data/install/upload + linux: /mnt/files logging: - config: classpath:logback-spring.xml \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 04ba28a..cc96ce5 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -3,15 +3,10 @@ - - - - - - - INFO + + DEBUG ${CONSOLE_LOG_PATTERN} @@ -19,43 +14,8 @@ - - - - ${LOG_HOME}/${APP_NAME}.log - - - - ${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log - - 365 - - - 100MB - - - - - [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] [%logger{50}] -> %msg%n - UTF-8 - - - - + - diff --git a/src/main/resources/static/common/js/LogMonitorAdaptive.js b/src/main/resources/static/common/js/LogMonitorAdaptive.js new file mode 100644 index 0000000..b6f279a --- /dev/null +++ b/src/main/resources/static/common/js/LogMonitorAdaptive.js @@ -0,0 +1,459 @@ +class LogMonitorAdaptive { + constructor(container, opts = {}) { + this.container = typeof container === 'string' + ? document.querySelector(container) + : container; + if (!this.container) throw new Error('容器未找到'); + + /* ========== 配置项及其含义 ========== */ + this.cfg = { + /* --- 尺寸相关 --- */ + width: null, // 日志容器宽度。null=100%,可填数字(px)或字符串('80%'/'20rem') + height: null, // 日志容器高度。null=100vh,可填数字(px)或字符串('400px'/'50vh') + + /* --- 日志条数与滚动 --- */ + maxLines: 5000, // 最大保留日志条数,超出后自动删除最旧日志 + autoScroll: true, // 出现新日志时是否自动滚动到底部 + + /* --- 主题与外观 --- */ + theme: 'dark', // 主题:'dark' | 'light',控制整体配色 + fontSize: 14, // 日志文字大小,单位 px + wordWrap: false, // 日志内容是否自动换行(true=换行,false=横向滚动) + + /* --- 日志内容格式 --- */ + showTimestamp: true, // 是否显示时间戳 + showLevel: true, // 是否显示日志级别标签 + timeFormat: 'HH:mm:ss.SSS', // 时间戳格式,可自定义 + + /* --- 日志级别 & 过滤 --- */ + levels: ['debug', 'info', 'warn', 'error', 'success', 'system'], // 可用日志级别 + // 过滤功能基于 levels;如只想显示 info/warn,可在这里删减 + + /* --- 功能开关 --- */ + enableFilter: true, // 顶部是否显示级别过滤复选框 + enableSearch: true, // 是否启用搜索框与高亮功能 + enableExport: true, // 是否允许导出日志文件 + enableClear: true, // 是否提供“清空”按钮 + enablePause: true, // 是否提供“暂停/继续”按钮 + enableThemeToggle: true, // 是否提供“切换主题”按钮 + enableFullscreen: true, // 是否提供“全屏”按钮 + enableFontSize: true, // 是否提供“字体大小 +/-”按钮 + enableWordWrap: true, // 是否提供“换行/不换行”切换按钮 + + /* --- 暂停/继续 的回调函数 --- */ + onTogglePause: () => {}, + + /* --- 创建完成 的回调函数 --- */ + onCreated: () => {}, + + ...opts + }; + + /* 强制确保 levels 是数组,防止 forEach 报错 */ + if (!Array.isArray(this.cfg.levels)) { + this.cfg.levels = ['info', 'warn', 'error']; + } + + this.isPaused = false; + this.isFullscreen = false; + this.logs = []; + this.filters = new Set(); + this.searchTerm = ''; + this.highlightIndex = -1; + this.highlightEls = []; + + this.initDOM(); + this.bindResize(); + this.bindGlobalEvents(); + + //执行回调函数 + if(this.cfg.onCreated && typeof this.cfg.onCreated === 'function'){ + this.cfg.onCreated(); + } + } + + /* ------------------------ 初始化 ------------------------ */ + initDOM() { + this.container.innerHTML = ''; + this.setContainerSize(); // 宽高自适应 + this.applyContainerStyle(); + + // 顶部导航栏 + this.navbar = document.createElement('div'); + Object.assign(this.navbar.style, { + display: 'flex', + alignItems: 'center', + padding: '8px 12px', + backgroundColor: this.cfg.theme === 'dark' ? '#2d2d30' : '#f3f3f3', + borderBottom: `1px solid ${this.cfg.theme === 'dark' ? '#555' : '#ccc'}`, + gap: '10px', + fontSize: '14px', + flexWrap: 'wrap' + }); + + // 按钮组 + if (this.cfg.enablePause) { + this.pauseBtn = this.createBtn('⏸ 暂停', () => this.togglePause(), {w: '72px'}); + this.navbar.appendChild(this.pauseBtn); + } + if (this.cfg.enableClear) { + this.navbar.appendChild(this.createBtn('🗑 清空', () => this.clear(), {w: '72px'})); + } + if (this.cfg.enableExport) { + this.navbar.appendChild(this.createBtn('💾 导出', () => this.export(), {w: '72px'})); + } + + // 主题切换按钮 + if (this.cfg.enableThemeToggle) { + this.themeBtn = this.createBtn( + this.cfg.theme === 'dark' ? '☀️ 明亮' : '🌙 暗黑', + () => this.toggleTheme(), + {w: '80px'} + ); + this.navbar.appendChild(this.themeBtn); + } + + // 字体大小 + if (this.cfg.enableFontSize) { + this.navbar.appendChild(this.createBtn('A-', () => this.changeFontSize(-1), {w: '36px'})); + this.navbar.appendChild(this.createBtn('A+', () => this.changeFontSize(1), {w: '36px'})); + } + + // 换行 + if (this.cfg.enableWordWrap) { + this.wrapBtn = this.createBtn( + this.cfg.wordWrap ? '⏎ 换行' : '⏎ 不换行', + () => this.toggleWordWrap(), + {w: '90px'} + ); + this.navbar.appendChild(this.wrapBtn); + } + + // 全屏 + if (this.cfg.enableFullscreen) { + this.navbar.appendChild(this.createBtn('⛶ 全屏', () => this.toggleFullscreen(), {w: '72px'})); + } + + // 过滤复选框 + if (this.cfg.enableFilter) { + this.cfg.levels.forEach(lvl => { + this.navbar.appendChild(this.createCheckbox(lvl)); + }); + } + + // 搜索框 + if (this.cfg.enableSearch) { + const searchWrap = document.createElement('span'); + searchWrap.style.marginLeft = 'auto'; + searchWrap.style.display = 'flex'; + searchWrap.style.alignItems = 'center'; + searchWrap.style.gap = '4px'; + + this.searchInput = document.createElement('input'); + Object.assign(this.searchInput.style, { + padding: '6px 8px', + fontSize: '14px', + border: `1px solid ${this.cfg.theme === 'dark' ? '#555' : '#bbb'}`, + borderRadius: '4px', + backgroundColor: this.cfg.theme === 'dark' ? '#3c3c3c' : '#fff', + color: 'inherit', + width: '260px' + }); + this.searchInput.placeholder = '搜索…'; + this.searchInput.oninput = () => { + this.searchTerm = this.searchInput.value.trim(); + this.renderVisible(); + }; + searchWrap.appendChild(this.searchInput); + + ['↑', '↓'].forEach((arr, idx) => { + searchWrap.appendChild( + this.createBtn(arr, () => this.navigateHighlight(idx), {w: '30px'}) + ); + }); + this.navbar.appendChild(searchWrap); + } + + // 日志视窗 + this.viewport = document.createElement('div'); + this.applyViewportStyle(); + this.container.appendChild(this.navbar); + this.container.appendChild(this.viewport); + + this.renderVisible(); + } + + /* ------------------------ 样式 ------------------------ */ + setContainerSize() { + // 如果用户未指定宽高,则自适应 + if (this.cfg.width === null) { + this.container.style.width = '100%'; + } else { + this.container.style.width = typeof this.cfg.width === 'number' ? `${this.cfg.width}px` : this.cfg.width; + } + + if (this.cfg.height === null) { + //this.container.style.height = '100vh'; + const bodyMargin = parseFloat(getComputedStyle(document.body).marginTop || 0) + + parseFloat(getComputedStyle(document.body).marginBottom || 0); + this.container.style.height = `calc(100vh - ${bodyMargin}px - 2px)`; + } else { + this.container.style.height = typeof this.cfg.height === 'number' ? `${this.cfg.height}px` : this.cfg.height; + } + } + + applyContainerStyle() { + Object.assign(this.container.style, { + display: 'flex', + flexDirection: 'column', + border: `1px solid ${this.cfg.theme === 'dark' ? '#444' : '#ccc'}`, + borderRadius: '6px', + fontFamily: 'Consolas, "Courier New", monospace', + fontSize: `${this.cfg.fontSize}px`, + lineHeight: 1.45, + backgroundColor: this.cfg.theme === 'dark' ? '#1e1e1e' : '#fafafa', + color: this.cfg.theme === 'dark' ? '#d4d4d4' : '#222', + overflow: 'hidden' + }); + } + + applyViewportStyle() { + Object.assign(this.viewport.style, { + flex: 1, + overflowY: 'auto', + padding: '8px', + whiteSpace: this.cfg.wordWrap ? 'pre-wrap' : 'pre', + wordBreak: 'break-word' + }); + } + + /* ------------------------ 工具 ------------------------ */ + createBtn(text, handler, {w} = {}) { + const btn = document.createElement('button'); + btn.textContent = text; + Object.assign(btn.style, { + padding: '6px 10px', + fontSize: '14px', + backgroundColor: this.cfg.theme === 'dark' ? '#444' : '#e7e7e7', + color: 'inherit', + border: `1px solid ${this.cfg.theme === 'dark' ? '#666' : '#ccc'}`, + borderRadius: '4px', + cursor: 'pointer', + minWidth: w || 'auto' + }); + btn.onmouseenter = () => btn.style.backgroundColor = this.cfg.theme === 'dark' ? '#555' : '#d0d0d0'; + btn.onmouseleave = () => btn.style.backgroundColor = this.cfg.theme === 'dark' ? '#444' : '#e7e7e7'; + btn.onclick = handler; + return btn; + } + + createCheckbox(level) { + const lbl = document.createElement('label'); + lbl.style.color = this.cfg.theme === 'dark' ? '#ccc' : '#333'; + lbl.style.fontSize = '13px'; + lbl.style.display = 'flex'; + lbl.style.alignItems = 'center'; + + const chk = document.createElement('input'); + chk.type = 'checkbox'; + chk.checked = true; + chk.style.margin = '0 4px'; + chk.onchange = () => { + if (chk.checked) this.filters.delete(level); + else this.filters.add(level); + this.renderVisible(); + }; + lbl.appendChild(chk); + lbl.append(level.toUpperCase()); + return lbl; + } + + /* ------------------------ 日志渲染 ------------------------ */ + log(message, level = 'info', ts = this.formatTime(new Date())) { + if (!this.cfg.levels.includes(level)) level = 'info'; + if (this.isPaused) return; + + ts = this.cfg.showTimestamp ? ts : ''; + this.logs.push({message, level, ts, id: Date.now() + Math.random()}); + + if (this.logs.length > this.cfg.maxLines) this.logs.shift(); + this.renderVisible(); + } + + renderVisible() { + this.viewport.innerHTML = ''; + this.highlightEls = []; + this.highlightIndex = -1; + + const filtered = this.logs.filter(l => !this.filters.has(l.level)); + const frag = document.createDocumentFragment(); + const regex = this.searchTerm ? new RegExp(`(${this.searchTerm})`, 'gi') : null; + + filtered.forEach(entry => { + const line = document.createElement('div'); + line.style.display = 'flex'; + line.style.margin = '1px 0'; + + if (this.cfg.showTimestamp && entry.ts) { + const tsSpan = document.createElement('span'); + tsSpan.textContent = entry.ts + ' '; + tsSpan.style.color = this.cfg.theme === 'dark' ? '#6a9955' : '#008000'; + tsSpan.style.minWidth = '90px'; + line.appendChild(tsSpan); + } + + if (this.cfg.showLevel) { + const lvlSpan = document.createElement('span'); + lvlSpan.textContent = `[${entry.level.toUpperCase()}]`; + const colors = { + debug: '#9c27b0', info: '#2196f3', warn: '#ff9800', + error: '#f44336', success: '#4caf50', system: '#00bcd4' + }; + lvlSpan.style.color = colors[entry.level] || colors.info; + lvlSpan.style.minWidth = '70px'; + lvlSpan.style.fontWeight = 'bold'; + line.appendChild(lvlSpan); + } + + const msgSpan = document.createElement('span'); + msgSpan.style.flex = 1; + let msg = entry.message; + if (regex) { + if (regex.test(entry.message)) { + line.style.backgroundColor = 'rgba(255,235,59,0.25)'; + this.highlightEls.push(line); + } + msg = entry.message.replace(regex, '$1'); + } + msgSpan.innerHTML = msg; + line.appendChild(msgSpan); + frag.appendChild(line); + }); + + this.viewport.appendChild(frag); + if (this.cfg.autoScroll && !this.isPaused) { + this.viewport.scrollTop = this.viewport.scrollHeight; + } + } + + /* ------------------------ 交互 ------------------------ */ + togglePause() { + this.isPaused = !this.isPaused; + this.pauseBtn.textContent = this.isPaused ? '▶ 继续' : '⏸ 暂停'; + this.pauseBtn.style.backgroundColor = this.isPaused + ? (this.cfg.theme === 'dark' ? '#d32f2f' : '#ff5252') + : (this.cfg.theme === 'dark' ? '#444' : '#e7e7e7'); + //执行回调函数 + if(this.cfg.onTogglePause && typeof this.cfg.onTogglePause === 'function'){ + this.cfg.onTogglePause(this.isPaused); + } + } + + toggleTheme() { + this.cfg.theme = this.cfg.theme === 'dark' ? 'light' : 'dark'; + this.themeBtn.textContent = this.cfg.theme === 'dark' ? '☀️ 明亮' : '🌙 暗黑'; + this.applyContainerStyle(); + this.navbar.style.backgroundColor = this.cfg.theme === 'dark' ? '#2d2d30' : '#f3f3f3'; + this.renderVisible(); + } + + changeFontSize(delta) { + this.cfg.fontSize = Math.max(10, Math.min(24, this.cfg.fontSize + delta)); + this.container.style.fontSize = `${this.cfg.fontSize}px`; + } + + toggleWordWrap() { + this.cfg.wordWrap = !this.cfg.wordWrap; + this.wrapBtn.textContent = this.cfg.wordWrap ? '⏎ 不换行' : '⏎ 换行'; + this.applyViewportStyle(); + } + + toggleFullscreen() { + if (!this.isFullscreen) { + if (this.container.requestFullscreen) this.container.requestFullscreen(); + else if (this.container.webkitRequestFullscreen) this.container.webkitRequestFullscreen(); + else if (this.container.msRequestFullscreen) this.container.msRequestFullscreen(); + } else { + if (document.exitFullscreen) document.exitFullscreen(); + else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); + else if (document.msExitFullscreen) document.msExitFullscreen(); + } + this.isFullscreen = !this.isFullscreen; + } + + navigateHighlight(dir) { + if (!this.highlightEls.length) return; + this.highlightIndex = (this.highlightIndex + (dir === 0 ? -1 : 1) + this.highlightEls.length) % this.highlightEls.length; + this.highlightEls.forEach((el, idx) => { + el.style.outline = idx === this.highlightIndex ? '2px solid #ffeb3b' : 'none'; + }); + this.highlightEls[this.highlightIndex].scrollIntoView({block: 'nearest', behavior: 'smooth'}); + } + + clear() { + this.logs = []; + this.renderVisible(); + } + + export() { + const lines = this.logs.map(l => `${l.ts} [${l.level.toUpperCase()}] ${l.message}`); + const blob = new Blob([lines.join('\n')], {type: 'text/plain;charset=utf-8'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `log_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`; + a.click(); + URL.revokeObjectURL(url); + } + + formatTime(date) { + const t = date.toTimeString().slice(0, 8); + const ms = String(date.getMilliseconds()).padStart(3, '0'); + return `${t}.${ms}`; + } + + bindResize() { + window.addEventListener('resize', () => { + this.setContainerSize(); // 响应窗口变化 + }); + } + + bindGlobalEvents() { + document.addEventListener('keydown', e => { + if (e.key === 'Escape' && this.searchInput) { + this.searchInput.value = ''; + this.searchTerm = ''; + this.renderVisible(); + } + if (e.ctrlKey && e.key === 'k') { + e.preventDefault(); + this.clear(); + } + }); + } + + /* 快捷方法 */ + info(msg, ts = this.formatTime(new Date())) { + this.log(msg, 'info', ts); + } + + warn(msg, ts = this.formatTime(new Date())) { + this.log(msg, 'warn', ts); + } + + error(msg, ts = this.formatTime(new Date())) { + this.log(msg, 'error', ts); + } + + success(msg, ts = this.formatTime(new Date())) { + this.log(msg, 'success', ts); + } + + debug(msg, ts = this.formatTime(new Date())) { + this.log(msg, 'debug', ts); + } + + system(msg, ts = this.formatTime(new Date())) { + this.log(msg, 'system', ts); + } +} \ No newline at end of file diff --git a/src/main/resources/static/common/js/fmt-socket.js b/src/main/resources/static/common/js/fmt-socket.js deleted file mode 100644 index 98eb7ef..0000000 --- a/src/main/resources/static/common/js/fmt-socket.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * WebSocket封装工具类 - *
- *     代码示例:
- *     let FmtSocket = new FmtSocket(
- *         {url:'websocket服务器地址',id:'唯一标识',timeout:心跳检测间隔时间(毫秒)}
- *     );
- *     //调用初始化方法
- *     FmtSocket.init((data) => {
- *         //初始化完成之后的回调函数 ata为监听到的数据
- *         //可以在这里做接收到消息后的逻辑处理
- *         console.log(data);
- *     });
- *      //调用发送消息方法 message 消息字符串或对象
- *     FmtSocket.sendMsg(message, () => {
- *         //发送消息成功之后的回调函数
- *     });
- * 
- */ -class FmtSocket { - id; - #options; - #ws; - #entity; - #deepProxyObj; - #proxyMsg; - /** - * 构造函数 - * @param {Object} options - */ - constructor(options) { - this.#options = this.#extend({ - url: 'http://127.0.0.1/socket/ws',//后台websocket服务地址 - id: this.#uuid(),//如果没传此参数,创建一个随机的UUID传给后台服务作为标识 - timeout: 30000 //心跳检测间隔时间 默认30秒 - }, options); - this.id = this.#options.id; - this.#ws = null; - //send:发送类型 HeartBeat:心跳 Logging:消息 - this.#entity = {send: null, data: null}; - } - - /** - * 初始化websocket - * @param {Function} callback (type, data) 回调函数 - */ - init(callback){ - if(callback && typeof callback === 'function'){ - this.#proxyMsg = []; - this.#deepProxyObj = this.#deepProxy(this.#proxyMsg,callback); - } - this.#createWebSocket(); - } - /** - * 对象、数组变化监听(增删改) - * @param {Object} obj - * @param {Function} callback - * @return {Object} - */ - #deepProxy(obj, callback){ - if (typeof obj === 'object') { - for (let key in obj) { - if (typeof obj[key] === 'object') { - obj[key] = this.#deepProxy(obj[key], callback); - } - } - } - return new Proxy(obj, { - /** - * @param {Object, Array} target 设置值的对象 - * @param {String} key 属性 - * @param {any} value 值 - * @param {Object} receiver this - */ - set: (target, key, value, receiver)=> { - if (typeof value === 'object') { - value = this.#deepProxy(value, callback); - } - let cbType = target[key] === undefined ? 'create' : 'modify'; - //排除数组修改length回调 - if (!(Array.isArray(target) && key === 'length')) { - if (cbType === 'create') { - callback(value); - } - } - return Reflect.set(target, key, value, receiver); - }, - deleteProperty(target, key) { - return Reflect.deleteProperty(target, key); - } - }); - } - #createWebSocket(){ - try { - if ('WebSocket' in window) { - this.#ws = new WebSocket((this.#options.url + "?id=" + this.#options.id) - .replace("http", "ws") - .replace("https", "wss")); - } else { - alert('浏览器不支持WebSocket通讯'); - return false; - } - this.#onopen(); - this.#onmessage(); - this.#onerror(); - this.#onclose(); - //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 - window.onbeforeunload = ()=> { - this.#ws.close(); - }; - } catch (e) { - console.log("WebSocket:链接失败", e); - alert('WebSocket:链接失败'); - } - } - /** - * 关闭websocket链接 - * @param {Function} callback - */ - closeWebSocket(callback){ - if(this.#ws){ - this.#ws.close(); - } - if(callback && typeof callback === 'function'){ - callback(); - } - } - /** - * 发送消息 - * @param {String, Object} message - * @param {Function} callback - */ - sendMsg(message, callback){ - if(!message || typeof message !== 'string' || typeof message !== 'object'){ - alert("FmtSocket sendMsg message is not null or message is not a string or object"); - return false; - } - if(callback && typeof callback !== 'function'){ - alert("FmtSocket sendMsg callback is not a function"); - return false; - } - this.#emptyObjectPropertyValues(this.#entity); - this.#entity.send = 'Message'; - this.#entity.data = message; - if (this.#ws.readyState === this.#ws.CONNECTING) { - // 正在开启状态,等待1s后重新调用 - let socket = this; - setTimeout(function () { - socket.#ws.send(JSON.stringify(socket.#entity)); - callback(); - }, 1000) - }else{ - this.#ws.send(JSON.stringify(this.#entity)); - callback(); - } - } - #onopen(){ - this.#ws.onopen = (event) => { - console.log("WebSocket:链接开启"); - } - } - #onmessage(){ - this.#ws.onmessage = (event) => { - this.#emptyObjectPropertyValues(this.#entity); - this.#entity = JSON.parse(event.data); - //心跳检测 - if(this.#entity.send === 'HeartBeat'){ - this.#heartBeat(); - } - if(this.#deepProxyObj){ - this.#deepProxyObj.splice(0);//赋值前先清空,保证消息数组里只有一条最新的数据 - let data = JSON.parse(JSON.stringify(this.#entity)); - this.#deepProxyObj.push(data); - } - } - } - #onerror(){ - this.#ws.onerror = (event) => { - console.log("WebSocket:发生错误", event); - } - } - #onclose(){ - this.#ws.onclose = (event) => { - console.log("WebSocket:链接关闭"); - } - } - #heartBeat(){ - setTimeout(()=> { - this.#emptyObjectPropertyValues(this.#entity); - if (this.#ws.readyState === this.#ws.CLOSING - || this.#ws.readyState === this.#ws.CLOSED){ - alert("WebSocket通讯服务链接已关闭"); - return false; - } - this.#entity.send = 'HeartBeat'; - this.#entity.data = '心跳检测'; - if (this.#ws.readyState === this.#ws.CONNECTING) { - // 正在开启状态,等待1s后重新调用 - setTimeout(()=> { - this.#ws.send(JSON.stringify(this.#entity)); - }, 1000) - }else{ - this.#ws.send(JSON.stringify(this.#entity)); - } - },this.#options.timeout); - } - /** - * 扩展,source相同属性的值去覆盖target, target如果没有这个属性就新增 - * @param {Object} target - * @param {Object} source - * @returns {Object} - */ - #extend(target, source){ - if(typeof target !== 'object') return null; - if(typeof source !== 'object') return null; - for (const key in source) { - // 使用for in会遍历数组所有的可枚举属性,包括原型。 - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - return target; - } - #uuid(len, radix){ - const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); - let uuid = [], i; - radix = radix || chars.length; - if (len) { - // Compact form - for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]; - } else { - // rfc4122, version 4 form - let r; - // rfc4122 requires these characters - uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; - uuid[14] = '4'; - // Fill in random data. At i==19 set the high bits of clock sequence as - // per rfc4122, sec. 4.1.5 - for (i = 0; i < 36; i++) { - if (!uuid[i]) { - r = 0 | Math.random() * 16; - uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r]; - } - } - } - return uuid.join(''); - } - #emptyObjectPropertyValues(person){ - Object.keys(person).forEach(key => (person[key] = null)); - } -} \ No newline at end of file diff --git a/src/main/resources/templates/download.html b/src/main/resources/templates/download.html index dbd5214..180062b 100644 --- a/src/main/resources/templates/download.html +++ b/src/main/resources/templates/download.html @@ -40,7 +40,7 @@
diff --git a/src/main/resources/templates/logging.html b/src/main/resources/templates/logging.html index 0ea0d26..3a31b6c 100644 --- a/src/main/resources/templates/logging.html +++ b/src/main/resources/templates/logging.html @@ -1,66 +1,72 @@ - + - - - - - - - - - + 在线日志 - - - -
-
-
- -
-
-
- 文件列表 - -
-
- 文件上传 - -
-
- 在线日志 - -
-
- 系统设置 - -
-
-
-
-
-
- -
- + +
- + diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html index 893e4fe..0254c11 100644 --- a/src/main/resources/templates/upload.html +++ b/src/main/resources/templates/upload.html @@ -33,7 +33,7 @@
diff --git a/src/test/java/cn/somkit/fmt/FmtApplicationTests.java b/src/test/java/cn/somkit/fmt/FmtApplicationTests.java deleted file mode 100644 index 02d4233..0000000 --- a/src/test/java/cn/somkit/fmt/FmtApplicationTests.java +++ /dev/null @@ -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() { - } - -}