From a6993b35fd392905a0cc8bd495977d5eeb569639 Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Sun, 3 Nov 2024 00:32:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=97=A5=E5=BF=97=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=A3=80=E7=B4=A2=E5=92=8C=E6=97=A5=E5=BF=97=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8B=E8=BD=BD=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/JwtAuthenticationFilter.java | 9 +- .../vmp/conf/security/WebSecurityConfig.java | 14 +- .../iot/vmp/conf/webLog/LogChannel.java | 10 +- .../iot/vmp/service/ILogService.java | 12 + .../iot/vmp/service/bean/LogFileInfo.java | 20 ++ .../iot/vmp/service/impl/LogServiceImpl.java | 111 +++++++ .../iot/vmp/vmanager/log/LogController.java | 90 ++++++ web_src/package.json | 1 + web_src/src/components/operations.vue | 12 +- .../components/operationsForHistoryLog.vue | 291 ++++++++++++++++++ .../src/components/operationsForLogFile.vue | 144 --------- .../src/components/operationsForRealLog.vue | 52 +++- 12 files changed, 601 insertions(+), 165 deletions(-) create mode 100644 src/main/java/com/genersoft/iot/vmp/service/ILogService.java create mode 100644 src/main/java/com/genersoft/iot/vmp/service/bean/LogFileInfo.java create mode 100644 src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java create mode 100755 src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java create mode 100755 web_src/src/components/operationsForHistoryLog.vue delete mode 100755 web_src/src/components/operationsForLogFile.vue diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java index cf44ad2e..3246097a 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java @@ -25,6 +25,8 @@ import java.util.ArrayList; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final static String WSHeader = "sec-websocket-protocol"; + @Autowired private UserSetting userSetting; @@ -56,11 +58,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String jwt = request.getHeader(JwtUtils.getHeader()); // 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的 // 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口 - System.out.println("sec-websocket-protocol==" + request.getHeader("sec-websocket-protocol")); + + // websocket 鉴权信息默认存储在这里 + String secWebsocketProtocolHeader = request.getHeader(WSHeader); if (StringUtils.isBlank(jwt)) { - String secWebsocketProtocolHeader = request.getHeader("sec-websocket-protocol"); + if (secWebsocketProtocolHeader != null) { jwt = secWebsocketProtocolHeader; + response.setHeader(WSHeader, secWebsocketProtocolHeader); }else { jwt = request.getParameter(JwtUtils.getHeader()); } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index 982a3b1a..2bdad1ff 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -25,6 +25,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; /** * 配置Spring Security @@ -104,6 +105,16 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { + + List defaultExcludes = userSetting.getInterfaceAuthenticationExcludes(); + defaultExcludes.add("/api/user/login"); + defaultExcludes.add("/index/hook/**"); + defaultExcludes.add("/api/device/query/snap/**"); + defaultExcludes.add("/index/hook/abl/**"); + defaultExcludes.add("/swagger-ui/**"); + defaultExcludes.add("/doc.html#/**"); +// defaultExcludes.add("/channel/log"); + http.headers().contentTypeOptions().disable() .and().cors().configurationSource(configurationSource()) .and().csrf().disable() @@ -114,8 +125,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .and() .authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll() - .antMatchers("/api/user/login", "/index/hook/**","/index/hook/abl/**", "/swagger-ui/**", "/doc.html#/**").permitAll() + .antMatchers(defaultExcludes.toArray(new String[0])).permitAll() .anyRequest().authenticated() // 异常处理器 .and() diff --git a/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java b/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java index 8396de6d..efa3bc11 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java @@ -5,10 +5,11 @@ import lombok.extern.slf4j.Slf4j; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -@ServerEndpoint(value = "/channel/log") + @ServerEndpoint(value = "/channel/log") @Slf4j public class LogChannel { @@ -30,6 +31,7 @@ public class LogChannel { public void onOpen(Session session, EndpointConfig endpointConfig) { this.session = session; this.session.setMaxIdleTimeout(0); + System.out.println(); CHANNELS.put(this.session.getId(), this); log.info("[Web-Log] 连接已建立: id={}", this.session.getId()); @@ -45,8 +47,10 @@ public class LogChannel { @OnError public void onError(Throwable throwable) throws IOException { - log.info("[Web-Log] 连接错误: id={}, err= ", this.session.getId(), throwable); - this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage())); + log.info("[Web-Log] 连接错误: id={}, err= {}", this.session.getId(), throwable.getMessage()); + if (this.session.isOpen()) { + this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage())); + } } /** diff --git a/src/main/java/com/genersoft/iot/vmp/service/ILogService.java b/src/main/java/com/genersoft/iot/vmp/service/ILogService.java new file mode 100644 index 00000000..ef6161c0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/ILogService.java @@ -0,0 +1,12 @@ +package com.genersoft.iot.vmp.service; + +import com.genersoft.iot.vmp.service.bean.LogFileInfo; + +import java.io.File; +import java.util.List; + +public interface ILogService { + List queryList(String query, String startTime, String endTime); + + File getFileByName(String fileName); +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/LogFileInfo.java b/src/main/java/com/genersoft/iot/vmp/service/bean/LogFileInfo.java new file mode 100644 index 00000000..e71046e4 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/LogFileInfo.java @@ -0,0 +1,20 @@ +package com.genersoft.iot.vmp.service.bean; + +import lombok.Data; + +@Data +public class LogFileInfo { + + private String fileName; + private String startTime; + private String endTime; + + public static LogFileInfo getInstance(String fileName, String startTime, String endTime) { + LogFileInfo logFileInfo = new LogFileInfo(); + logFileInfo.setFileName(fileName); + logFileInfo.setStartTime(startTime); + logFileInfo.setEndTime(endTime); + return logFileInfo; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java new file mode 100644 index 00000000..e23c8440 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java @@ -0,0 +1,111 @@ +package com.genersoft.iot.vmp.service.impl; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.core.rolling.RollingFileAppender; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.service.ILogService; +import com.genersoft.iot.vmp.service.bean.LogFileInfo; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.input.ReversedLinesFileReader; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Service +@Slf4j +public class LogServiceImpl implements ILogService { + + @Override + public List queryList(String query, String startTime, String endTime) { + File logFile = getLogDir(); + if (logFile == null && !logFile.exists()) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取日志文件目录失败"); + } + File[] files = logFile.listFiles(); + List result = new ArrayList<>(); + if (files == null || files.length == 0) { + return result; + } + for (File file : files) { + LogFileInfo logFileInfo = new LogFileInfo(); + logFileInfo.setFileName(file.getName()); + if (query != null && !file.getName().contains(query)) { + continue; + } + // 读取文件创建时间作为开始时间,修改时间为结束时间 + + Long startTimestamp = null; + if (startTime != null) { + startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + } + Long endTimestamp = null; + if (startTime != null) { + endTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + } + try { + String[] fileAttributes = getFileAttributes(file); + if (fileAttributes == null) { + continue; + } + logFileInfo.setStartTime(fileAttributes[0]); + logFileInfo.setEndTime(fileAttributes[1]); + if (startTimestamp != null && startTimestamp > DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(fileAttributes[0])) { + continue; + } + if (endTimestamp != null && endTimestamp < DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(fileAttributes[1])) { + continue; + } + } catch (IOException e) { + log.error("[读取日志文件列表] 获取创建时间和修改时间失败", e); + continue; + } + result.add(logFileInfo); + + } + return result; + } + + private File getLogDir() { + Logger logger = (Logger) LoggerFactory.getLogger("com.genersoft.iot.vmp"); + RollingFileAppender rollingFileAppender = (RollingFileAppender) logger.getAppender("RollingFile"); + File rollingFile = new File(rollingFileAppender.getFile()); + return rollingFile.getParentFile(); + } + + String[] getFileAttributes(File file) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + String startLine = bufferedReader.readLine(); + if (startLine== null) { + return null; + } + String startTime = startLine.substring(0, 19); + + + String lastLine = ""; + try (ReversedLinesFileReader reversedLinesReader = new ReversedLinesFileReader(file, Charset.defaultCharset())) { + lastLine = reversedLinesReader.readLine(); + } catch (Exception e) { + log.error("file read error, msg:{}", e.getMessage(), e); + } + String endTime = lastLine.substring(0, 19); + return new String[]{startTime, endTime}; + } + + @Override + public File getFileByName(String fileName) { + File logDir = getLogDir(); + + return new File(logDir, fileName); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java new file mode 100755 index 00000000..640f2b4f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java @@ -0,0 +1,90 @@ +package com.genersoft.iot.vmp.vmanager.log; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.core.rolling.RollingFileAppender; +import com.alibaba.fastjson2.JSONArray; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.conf.security.JwtUtils; +import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService; +import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.service.IMediaServerService; +import com.genersoft.iot.vmp.service.ILogService; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; +import com.genersoft.iot.vmp.service.bean.LogFileInfo; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl; +import com.github.pagehelper.PageInfo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +@SuppressWarnings("rawtypes") +@Tag(name = "日志文件查询接口") +@Slf4j +@RestController +@RequestMapping("/api/log") +public class LogController { + + @Autowired + private ILogService logService; + + + @ResponseBody + @GetMapping("/list") + @Operation(summary = "分页查询日志文件", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "query", description = "检索内容", required = false) + @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false) + @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false) + public List queryList(@RequestParam(required = false) String query, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime + + ) { + return logService.queryList(query, startTime, endTime); + } + + /** + * 下载指定日志文件 + */ + @ResponseBody + @GetMapping("/file") + public void downloadFile(HttpServletResponse response, @RequestParam(required = true) String fileName) { + try { + File file = logService.getFileByName(fileName); + if (file == null || !file.exists() || !file.isFile()) { + throw new ControllerException(ErrorCode.ERROR400); + } + final InputStream in = Files.newInputStream(file.toPath()); + response.setContentType(MediaType.TEXT_PLAIN_VALUE); + ServletOutputStream outputStream = response.getOutputStream(); + IOUtils.copy(in, response.getOutputStream()); + in.close(); + outputStream.close(); + } catch (IOException e) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + } + +} diff --git a/web_src/package.json b/web_src/package.json index 0acfbd33..0a6a1d55 100644 --- a/web_src/package.json +++ b/web_src/package.json @@ -25,6 +25,7 @@ "postcss-pxtorem": "^5.1.1", "screenfull": "5.1.0", "slicedToArray": "link:@babel/runtime/helpers/slicedToArray", + "strip-ansi": "^7.1.0", "uuid": "^8.3.2", "v-charts": "^1.19.0", "vue": "^2.6.11", diff --git a/web_src/src/components/operations.vue b/web_src/src/components/operations.vue index 9dfb8279..c94c29d8 100755 --- a/web_src/src/components/operations.vue +++ b/web_src/src/components/operations.vue @@ -8,7 +8,7 @@ - 日志文件 + 历史日志 实时日志 @@ -18,8 +18,9 @@ - + + @@ -28,20 +29,21 @@ + + diff --git a/web_src/src/components/operationsForLogFile.vue b/web_src/src/components/operationsForLogFile.vue deleted file mode 100755 index e9c4238e..00000000 --- a/web_src/src/components/operationsForLogFile.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - - - diff --git a/web_src/src/components/operationsForRealLog.vue b/web_src/src/components/operationsForRealLog.vue index a9c3ef1f..52d4d2ea 100755 --- a/web_src/src/components/operationsForRealLog.vue +++ b/web_src/src/components/operationsForRealLog.vue @@ -1,11 +1,14 @@