2583206853 1 år sedan
incheckning
0afa2027bb
46 ändrade filer med 1774 tillägg och 0 borttagningar
  1. 54 0
      pom.xml
  2. 8 0
      src/main/java/com/mxkj/monitor/AuthorizationException.java
  3. 12 0
      src/main/java/com/mxkj/monitor/LogGlobalException.java
  4. 112 0
      src/main/java/com/mxkj/monitor/LogServletUtils.java
  5. 17 0
      src/main/java/com/mxkj/monitor/config/AuthorizationConfig.java
  6. 35 0
      src/main/java/com/mxkj/monitor/config/DruidDataSourceConfig.java
  7. 66 0
      src/main/java/com/mxkj/monitor/config/DruidProperties.java
  8. 93 0
      src/main/java/com/mxkj/monitor/config/ElasticSearchConfig.java
  9. 47 0
      src/main/java/com/mxkj/monitor/config/MonitorRouteConfiguration.java
  10. 57 0
      src/main/java/com/mxkj/monitor/domain/MonitorLog.java
  11. 39 0
      src/main/java/com/mxkj/monitor/domain/MonitorLogWrapper.java
  12. 15 0
      src/main/java/com/mxkj/monitor/domain/TableMonitorLog.java
  13. 16 0
      src/main/java/com/mxkj/monitor/engine/DataEngine.java
  14. 30 0
      src/main/java/com/mxkj/monitor/engine/DataEngineInitializer.java
  15. 128 0
      src/main/java/com/mxkj/monitor/engine/impl/ElasticSearchDataEngine.java
  16. 48 0
      src/main/java/com/mxkj/monitor/engine/impl/MysqlDataEngine.java
  17. 53 0
      src/main/java/com/mxkj/monitor/engine/mysql/BaseOptional.java
  18. 64 0
      src/main/java/com/mxkj/monitor/engine/mysql/SQLConstant.java
  19. 62 0
      src/main/java/com/mxkj/monitor/engine/mysql/TableCreate.java
  20. 60 0
      src/main/java/com/mxkj/monitor/engine/mysql/TableInsert.java
  21. 196 0
      src/main/java/com/mxkj/monitor/engine/mysql/TableSelect.java
  22. 43 0
      src/main/java/com/mxkj/monitor/filter/FilterCatchMap.java
  23. 226 0
      src/main/java/com/mxkj/monitor/filter/LogMonitorFilter.java
  24. 68 0
      src/main/java/com/mxkj/monitor/handler/AuthHandler.java
  25. 130 0
      src/main/java/com/mxkj/monitor/handler/ExceptionHandler.java
  26. 78 0
      src/main/java/com/mxkj/monitor/handler/LogListHandler.java
  27. 14 0
      src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  28. 1 0
      src/main/resources/static/css/about.49b90404.css
  29. 1 0
      src/main/resources/static/css/app.831204d6.css
  30. 0 0
      src/main/resources/static/css/chunk-vendors.3a5a5cd6.css
  31. 0 0
      src/main/resources/static/css/chunk-vendors.e92fd658.css
  32. BIN
      src/main/resources/static/favicon.ico
  33. BIN
      src/main/resources/static/fonts/element-icons.f1a45d74.ttf
  34. BIN
      src/main/resources/static/fonts/element-icons.ff18efd1.woff
  35. BIN
      src/main/resources/static/img/BG1.408413ae.png
  36. 1 0
      src/main/resources/static/index.html
  37. 0 0
      src/main/resources/static/js/about.01929ad9.js
  38. 0 0
      src/main/resources/static/js/about.01929ad9.js.map
  39. 0 0
      src/main/resources/static/js/about.74a12c23.js
  40. 0 0
      src/main/resources/static/js/about.74a12c23.js.map
  41. 0 0
      src/main/resources/static/js/app.7d3c77bd.js
  42. 0 0
      src/main/resources/static/js/app.7d3c77bd.js.map
  43. 0 0
      src/main/resources/static/js/app.d04de73c.js
  44. 0 0
      src/main/resources/static/js/app.d04de73c.js.map
  45. 0 0
      src/main/resources/static/js/chunk-vendors.ae4ce3c9.js
  46. 0 0
      src/main/resources/static/js/chunk-vendors.ae4ce3c9.js.map

+ 54 - 0
pom.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>warst</artifactId>
+        <groupId>com.warst</groupId>
+        <version>3.6.2</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+        <artifactId>monitor-log</artifactId>
+
+        <dependencies>
+            <!-- SpringCloud Gateway -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-starter-gateway</artifactId>
+            </dependency>
+            <!-- Druid -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>1.2.16</version>
+            </dependency>
+            <!--   JSON     -->
+            <dependency>
+                <groupId>com.alibaba.fastjson2</groupId>
+                <artifactId>fastjson2</artifactId>
+            </dependency>
+            <!-- lombok-->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+            </dependency>
+            <!-- Mysql Connector -->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+            </dependency>
+            <!--  elasticSearch -->
+            <dependency>
+                <groupId>co.elastic.clients</groupId>
+                <artifactId>elasticsearch-java</artifactId>
+                <version>8.0.1</version>
+            </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-databind</artifactId>
+                <version>2.12.3</version>
+            </dependency>
+
+        </dependencies>
+    </project>

+ 8 - 0
src/main/java/com/mxkj/monitor/AuthorizationException.java

@@ -0,0 +1,8 @@
+package com.mxkj.monitor;
+
+public class AuthorizationException extends Exception {
+
+    public AuthorizationException(String msg){
+        super(msg);
+    }
+}

+ 12 - 0
src/main/java/com/mxkj/monitor/LogGlobalException.java

@@ -0,0 +1,12 @@
+package com.mxkj.monitor;
+
+public class LogGlobalException extends RuntimeException {
+
+    public LogGlobalException(String message){
+        super(message);
+    }
+
+    public LogGlobalException(Exception e){
+        super(e);
+    }
+}

+ 112 - 0
src/main/java/com/mxkj/monitor/LogServletUtils.java

@@ -0,0 +1,112 @@
+package com.mxkj.monitor;
+
+import com.alibaba.druid.support.json.JSONUtils;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * 客户端工具类
+ * 
+ * @author ruoyi
+ */
+public class LogServletUtils
+{
+
+    /**
+     * 设置webflux模型响应
+     *
+     * @param response ServerHttpResponse
+     * @param value 响应内容
+     * @return Mono<Void>
+     */
+    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value)
+    {
+        return webFluxResponseWriter(response, HttpStatus.OK, value, 500);
+    }
+
+    /**
+     * 设置webflux模型响应
+     *
+     * @param response ServerHttpResponse
+     * @param status http状态码
+     * @param code 响应状态码
+     * @param value 响应内容
+     * @return Mono<Void>
+     */
+    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code)
+    {
+        return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
+    }
+
+    public static String formatDateNow(){
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return dateFormat.format(new Date());
+    }
+
+    /**
+     * 设置webflux模型响应
+     *
+     * @param response ServerHttpResponse
+     * @param contentType content-type
+     * @param status http状态码
+     * @param code 响应状态码
+     * @param value 响应内容
+     * @return Mono<Void>
+     */
+    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code)
+    {
+        response.setStatusCode(status);
+        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
+        Map<String, Object> result = new HashMap<>();
+        result.put("code", code);
+        result.put("msg", value.toString());
+        result.put("data", null);
+        DataBuffer dataBuffer = response.bufferFactory().wrap(JSONUtils.toJSONString(result).getBytes());
+        return response.writeWith(Mono.just(dataBuffer));
+    }
+
+    /**
+     *  构建响应格式
+     * @return
+     */
+    public static Map<String, Object> buildResult(int code, String message, Object obj){
+        Map<String, Object> result = new HashMap<>();
+        result.put("code", code);
+        result.put("msg", message);
+        result.put("data", obj);
+        return result;
+    }
+
+    /**
+     *  非传递式的捕获,读取参数后,请求对象中参数为空
+     *  捕获请求参数
+     * @param request
+     * @return
+     */
+    public static AtomicReference<String> getRequestParams(ServerHttpRequest request){
+        AtomicReference<String> atoBody = new AtomicReference<>();
+        request.getBody().map(dataBuffer -> {
+            byte[] bytes = new byte[dataBuffer.readableByteCount()];
+            dataBuffer.read(bytes);
+            DataBufferUtils.release(dataBuffer);
+            String bodyStr = new String(bytes, StandardCharsets.UTF_8);
+            atoBody.set(bodyStr);
+            return Mono.empty();
+        }).next();
+        return atoBody;
+    }
+}

+ 17 - 0
src/main/java/com/mxkj/monitor/config/AuthorizationConfig.java

@@ -0,0 +1,17 @@
+package com.mxkj.monitor.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "mxkj.auth")
+public class AuthorizationConfig {
+
+    private String username;
+
+    private String password;
+
+    private int session;
+}

+ 35 - 0
src/main/java/com/mxkj/monitor/config/DruidDataSourceConfig.java

@@ -0,0 +1,35 @@
+package com.mxkj.monitor.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
+import com.mxkj.monitor.engine.DataEngine;
+import com.mxkj.monitor.engine.impl.MysqlDataEngine;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class DruidDataSourceConfig {
+
+
+    @Bean
+    @ConfigurationProperties("spring.datasource.dynamic.datasource.master")
+    @ConditionalOnProperty(prefix = "spring.datasource.dynamic.datasource.master", name = "enable", havingValue = "true")
+    public DataSource mysqlDataSource(DruidProperties druidProperties)
+    {
+        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+        return druidProperties.dataSource(dataSource);
+    }
+
+    @Bean
+    @ConditionalOnBean(value = DataSource.class)
+    public DataEngine getSqlDataEngine(){
+        return new MysqlDataEngine();
+    }
+
+
+}

+ 66 - 0
src/main/java/com/mxkj/monitor/config/DruidProperties.java

@@ -0,0 +1,66 @@
+package com.mxkj.monitor.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "spring.datasource.dynamic.druid")
+public class DruidProperties {
+
+    private int initialSize;
+
+    private int minIdle;
+
+    private int maxActive;
+
+    private int maxWait;
+
+    private int connectTimeout;
+
+    private int socketTimeout;
+
+    private int timeBetweenEvictionRunsMillis;
+
+    private String validationQuery;
+
+    private boolean testWhileIdle;
+
+    private boolean testOnBorrow;
+
+    private boolean testOnReturn;
+
+    public DruidDataSource dataSource(DruidDataSource datasource)
+    {
+        /** 配置初始化大小、最小、最大 */
+        datasource.setInitialSize(initialSize);
+        datasource.setMaxActive(maxActive);
+        datasource.setMinIdle(minIdle);
+
+        /** 配置获取连接等待超时的时间 */
+        datasource.setMaxWait(maxWait);
+
+        /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
+        datasource.setConnectTimeout(connectTimeout);
+
+        /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
+        datasource.setSocketTimeout(socketTimeout);
+
+        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
+        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+
+        /**
+         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
+         */
+        datasource.setValidationQuery(validationQuery);
+        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
+        datasource.setTestWhileIdle(testWhileIdle);
+        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnBorrow(testOnBorrow);
+        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnReturn(testOnReturn);
+        return datasource;
+    }
+}

+ 93 - 0
src/main/java/com/mxkj/monitor/config/ElasticSearchConfig.java

@@ -0,0 +1,93 @@
+package com.mxkj.monitor.config;
+
+import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.json.jackson.JacksonJsonpMapper;
+import co.elastic.clients.transport.ElasticsearchTransport;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
+import com.mxkj.monitor.engine.DataEngine;
+import com.mxkj.monitor.engine.impl.ElasticSearchDataEngine;
+import lombok.Data;
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestClientBuilder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "spring.elasticsearch")
+@ConditionalOnProperty(prefix = "spring.elasticsearch",  name = "enable", havingValue = "true")
+public class ElasticSearchConfig {
+
+    private String hostname;
+
+    private String port;
+
+    private String username;
+
+    private String password;
+
+    /**
+     *  同步客户端
+     * @return
+     */
+    @Bean
+    public ElasticsearchClient elasticsearchClient(){
+        ElasticsearchTransport transport = buildTransferPort();
+        return new ElasticsearchClient(transport);
+    }
+
+    /**
+     *  异步客户端
+     * @return
+     */
+    @Bean
+    public ElasticsearchAsyncClient elasticSearchAsyncClient(){
+        ElasticsearchTransport transport = buildTransferPort();
+        // And create the API client
+        return new ElasticsearchAsyncClient(transport);
+    }
+
+    @Bean
+    @Primary
+    @ConditionalOnBean(value = ElasticsearchClient.class)
+    public DataEngine getElasticDataEngine(){
+        return new ElasticSearchDataEngine();
+    }
+
+    private ElasticsearchTransport buildTransferPort(){
+        final CredentialsProvider credentialsProvider =
+                new BasicCredentialsProvider();
+        credentialsProvider.setCredentials(AuthScope.ANY,
+                new UsernamePasswordCredentials(username, password));
+
+        RestClientBuilder builder = RestClient.builder(
+                new HttpHost(hostname, Integer.valueOf(port)))
+                .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
+                    @Override
+                    public HttpAsyncClientBuilder customizeHttpClient(
+                            HttpAsyncClientBuilder httpClientBuilder) {
+                        return httpClientBuilder
+                                .setDefaultCredentialsProvider(credentialsProvider);
+                    }
+                });
+
+        RestClient restClient = builder.build();
+
+        // Create the transport with a Jackson mapper
+        return new RestClientTransport(
+                restClient, new JacksonJsonpMapper());
+    }
+
+}

+ 47 - 0
src/main/java/com/mxkj/monitor/config/MonitorRouteConfiguration.java

@@ -0,0 +1,47 @@
+package com.mxkj.monitor.config;
+
+import com.mxkj.monitor.handler.AuthHandler;
+import com.mxkj.monitor.handler.LogListHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.config.ResourceHandlerRegistry;
+import org.springframework.web.reactive.config.WebFluxConfigurer;
+import org.springframework.web.reactive.function.server.RequestPredicates;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+
+@Configuration
+public class MonitorRouteConfiguration implements WebFluxConfigurer {
+
+    @Autowired
+    private LogListHandler logListHandler;
+
+    @Autowired
+    private AuthHandler authHandler;
+
+    @Bean
+    public RouterFunction monitorListRouter()
+    {
+        return RouterFunctions.route(
+                RequestPredicates.GET("/mxkj/log").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
+                logListHandler);
+    }
+
+    @Bean
+    public RouterFunction monitorAuthRouter()
+    {
+        return RouterFunctions.route(
+                RequestPredicates.POST("/mxkj/login").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
+                authHandler);
+    }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        /** mxkj日志监控index 地址 */
+        registry.addResourceHandler("/mxkj/**")
+                .addResourceLocations("classpath:/static/");
+    }
+
+}

+ 57 - 0
src/main/java/com/mxkj/monitor/domain/MonitorLog.java

@@ -0,0 +1,57 @@
+package com.mxkj.monitor.domain;
+
+import lombok.Data;
+
+@Data
+public class MonitorLog {
+
+    /**
+     *  主键
+     */
+    private String id;
+
+    /**
+     *  用户名称
+     */
+    private String username;
+
+    /**
+     *  请求ip地址
+     */
+    private String operIp;
+
+    /**
+     *  请求资源地址
+     */
+    private String operUrl;
+
+    /**
+     *  请求方式
+     */
+    private String operType;
+
+    /**
+     *  请求参数
+     */
+    private String operParam;
+
+    /**
+     *  响应参数
+     */
+    private String jsonBody;
+
+    /**
+     *  操作状态
+     */
+    private String resCode;
+
+    /**
+     *  接口耗时
+     */
+    private String costTime;
+
+    /**
+     *  创建时间
+     */
+    private String createTime;
+}

+ 39 - 0
src/main/java/com/mxkj/monitor/domain/MonitorLogWrapper.java

@@ -0,0 +1,39 @@
+package com.mxkj.monitor.domain;
+
+public class MonitorLogWrapper {
+
+    private MonitorLog monitorLog;
+
+    private Long startTime;
+
+    private Long endTime;
+
+    public MonitorLogWrapper(MonitorLog monitorLog) {
+        this.monitorLog = monitorLog;
+        this.startTime = System.currentTimeMillis();
+    }
+
+    public MonitorLog getMonitorLog() {
+        return monitorLog;
+    }
+
+    public void setMonitorLog(MonitorLog monitorLog) {
+        this.monitorLog = monitorLog;
+    }
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Long startTime) {
+        this.startTime = startTime;
+    }
+
+    public Long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Long endTime) {
+        this.endTime = endTime;
+    }
+}

+ 15 - 0
src/main/java/com/mxkj/monitor/domain/TableMonitorLog.java

@@ -0,0 +1,15 @@
+package com.mxkj.monitor.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class TableMonitorLog {
+
+    private int count;
+
+    private List<MonitorLog> list;
+}

+ 16 - 0
src/main/java/com/mxkj/monitor/engine/DataEngine.java

@@ -0,0 +1,16 @@
+package com.mxkj.monitor.engine;
+
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.TableMonitorLog;
+
+public interface DataEngine {
+
+    String getName();
+
+    void initCreate() throws Exception;
+
+    void insert(MonitorLog monitorLog);
+
+    TableMonitorLog list(MonitorLog monitorLog, int page, int size);
+
+}

+ 30 - 0
src/main/java/com/mxkj/monitor/engine/DataEngineInitializer.java

@@ -0,0 +1,30 @@
+package com.mxkj.monitor.engine;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationListener;
+
+public class DataEngineInitializer implements ApplicationListener<ApplicationStartedEvent> {
+
+    private static final Logger log = LoggerFactory.getLogger(DataEngineInitializer.class);
+
+    @Autowired(required = false)
+    private DataEngine dataEngine;
+
+    // 容器加载完成后的事件
+    @Override
+    public void onApplicationEvent(ApplicationStartedEvent event) {
+        try {
+            if (dataEngine == null){
+                log.info("日志监控初始化成功,无引擎");
+                return;
+            }
+            dataEngine.initCreate();
+            log.info("日志监控初始化成功,{}", dataEngine.getName());
+        }catch (Exception e){
+            log.info("日志监控异常", e);
+        }
+    }
+}

+ 128 - 0
src/main/java/com/mxkj/monitor/engine/impl/ElasticSearchDataEngine.java

@@ -0,0 +1,128 @@
+package com.mxkj.monitor.engine.impl;
+
+import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery;
+import co.elastic.clients.elasticsearch.core.*;
+import co.elastic.clients.elasticsearch.core.search.Hit;
+import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
+import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
+import co.elastic.clients.elasticsearch.indices.ExistsRequest;
+import co.elastic.clients.transport.endpoints.BooleanResponse;
+import com.alibaba.druid.util.StringUtils;
+import com.mxkj.monitor.LogGlobalException;
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.TableMonitorLog;
+import com.mxkj.monitor.engine.DataEngine;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ElasticSearchDataEngine implements DataEngine {
+
+    @Autowired(required = false)
+    private ElasticsearchClient client;
+
+    @Autowired(required = false)
+    private ElasticsearchAsyncClient asyncClient;
+
+    /**
+     *  索引名称
+     */
+    private static final String INDEX = "monitors";
+
+
+    @Override
+    public String getName() {
+        return "Elasticsearch";
+    }
+
+    @Override
+    public void initCreate() throws IOException {
+        ExistsRequest request = new ExistsRequest.Builder()
+                .index(INDEX)
+                .build();
+        BooleanResponse booleanResponse = client.indices().exists(request);
+        // if not exsit
+        if (!booleanResponse.value()){
+            CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder()
+                    .index(INDEX)
+                    .build();
+            CreateIndexResponse response = client.indices().create(createIndexRequest);
+            if (response.acknowledged() == null || !response.acknowledged()){
+                throw new LogGlobalException("ES 索引创建失败");
+            }
+        }
+    }
+
+    @Override
+    public void insert(MonitorLog monitorLog) {
+        IndexRequest<MonitorLog> indexRequest = new IndexRequest.Builder<MonitorLog>()
+                .index(INDEX)
+                .document(monitorLog)
+                .build();
+
+        asyncClient.index(indexRequest);
+    }
+
+    @Override
+    public TableMonitorLog list(MonitorLog monitorLogBO, int page, int size) {
+
+        SearchRequest.Builder searchBuild = new SearchRequest.Builder()
+                .index(INDEX)
+                .from((page - 1) * size)
+                .size(size);
+
+        CountRequest.Builder countBuild = new CountRequest.Builder()
+                .index(INDEX);
+
+
+        Query query = null;
+        // 条件构造
+        if (!StringUtils.isEmpty(monitorLogBO.getOperType())){
+            query = Query.of(m -> m.match(k -> k.field("operType").query(monitorLogBO.getOperType())));
+        }
+
+        else if (!StringUtils.isEmpty(monitorLogBO.getOperParam())){
+            query = Query.of(m -> m.wildcard(WildcardQuery.of(w -> w.field("operParam")
+                    .value("*" + monitorLogBO.getOperParam()+"*"))));
+        }
+
+        else if (!StringUtils.isEmpty(monitorLogBO.getJsonBody())){
+            query = Query.of(m -> m.wildcard(WildcardQuery.of(w -> w.field("jsonBody")
+                    .value("*" + monitorLogBO.getJsonBody()+"*"))));
+        }
+
+        SearchRequest request = null;
+        CountRequest countRequest = null;
+        if (query != null){
+            request = searchBuild.query(query).build();
+            countRequest = countBuild.query(query).build();
+        }else {
+            request = searchBuild.build();
+            countRequest = countBuild.build();
+        }
+
+        // 查询
+        List<MonitorLog> result = new ArrayList<>();
+        int count = 0;
+        try {
+            CountResponse countResponse = client.count(countRequest);
+            count = (int) countResponse.count();
+            SearchResponse<MonitorLog> response = client.search(request, MonitorLog.class);
+            for (Hit<MonitorLog> hit : response.hits().hits()) {
+                MonitorLog monitorLog = hit.source();
+                if (monitorLog != null){
+                    monitorLog.setId(hit.id());
+                    result.add(monitorLog);
+                }
+            }
+        } catch (IOException e) {
+            throw new LogGlobalException("查询失败:" + e.getMessage());
+        }
+        return new TableMonitorLog(count, result);
+    }
+}

+ 48 - 0
src/main/java/com/mxkj/monitor/engine/impl/MysqlDataEngine.java

@@ -0,0 +1,48 @@
+package com.mxkj.monitor.engine.impl;
+
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.TableMonitorLog;
+import com.mxkj.monitor.engine.DataEngine;
+import com.mxkj.monitor.engine.mysql.TableCreate;
+import com.mxkj.monitor.engine.mysql.TableInsert;
+import com.mxkj.monitor.engine.mysql.TableSelect;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.List;
+
+public class MysqlDataEngine implements DataEngine {
+
+    @Autowired
+    private TableCreate tableCreate;
+    @Autowired
+    private TableInsert tableInsert;
+    @Autowired
+    private TableSelect tableSelect;
+
+    @Override
+    public String getName() {
+        return "Mysql";
+    }
+
+    @Override
+    public void initCreate() {
+        tableCreate.createTableIfPresent();
+    }
+
+    @Override
+    public void insert(MonitorLog monitorLog) {
+        tableInsert.insert(monitorLog);
+    }
+
+    @Override
+    public TableMonitorLog list(MonitorLog monitorLog, int page, int size) {
+        List<MonitorLog> list = null;
+        if (monitorLog == null){
+            list = tableSelect.select(page, size);
+        }else {
+            list = tableSelect.search(monitorLog, page, size);
+        }
+        int count = tableSelect.count(monitorLog);
+        return new TableMonitorLog(count, list);
+    }
+}

+ 53 - 0
src/main/java/com/mxkj/monitor/engine/mysql/BaseOptional.java

@@ -0,0 +1,53 @@
+package com.mxkj.monitor.engine.mysql;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.pool.DruidPooledConnection;
+import com.mxkj.monitor.LogGlobalException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.function.Consumer;
+
+@Component
+public class BaseOptional {
+
+    @Autowired(required = false)
+    private DruidDataSource dataSource;
+
+    /**
+     *  可关闭的执行语法块
+     * @param sql
+     * @param statementConsumer
+     * @return
+     * @throws SQLException
+     */
+    public void execute(String sql, Consumer<PreparedStatement> statementConsumer){
+        DruidPooledConnection connection = null;
+        PreparedStatement preparedStatement = null;
+        try {
+            connection = dataSource.getConnection();
+            preparedStatement = connection.prepareStatement(sql);
+            statementConsumer.accept(preparedStatement);
+        }catch (SQLException e){
+            throw new LogGlobalException(e);
+        }
+        finally {
+            if (preparedStatement != null){
+                try {
+                    preparedStatement.close();
+                } catch (SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (connection != null){
+                try {
+                    connection.close();
+                } catch (SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

+ 64 - 0
src/main/java/com/mxkj/monitor/engine/mysql/SQLConstant.java

@@ -0,0 +1,64 @@
+package com.mxkj.monitor.engine.mysql;
+
+public class SQLConstant {
+
+    public static final String TABLE_NAME = "mxkj_monitor_log";
+
+
+    public static final String SHOW_TABLE_SQL ="show tables";
+
+
+    /**
+     *  创建表的sql
+     *  id自增 Innodb引擎 UTF-8编码
+     */
+    public static final String CREATE_TALBLE_SQL =
+            "                 CREATE TABLE `" + TABLE_NAME + "`  (" +
+            "                `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '主键'," +
+            "                `username` varchar(255) NULL COMMENT '用户名'," +
+            "                `request_id` varchar(255) NULL COMMENT '请求对象的ID'," +
+            "                `oper_ip` varchar(255) NULL COMMENT '请求IP'," +
+            "                `oper_url` varchar(255) NULL COMMENT '请求地址'," +
+            "                `oper_type` varchar(255) NULL COMMENT '请求类型'," +
+            "                `oper_param` text NULL COMMENT '请求参数'," +
+            "                `json_body` text NULL COMMENT '返回结果'," +
+            "                `res_code` varchar(255) NULL COMMENT '返回状态码'," +
+            "                `cost_time` int(10) NULL COMMENT '耗时'," +
+            "                `create_time` datetime(0) NULL COMMENT '创建时间', " +
+            "                 PRIMARY KEY (`id`)   )" +
+            "                 DEFAULT CHARSET=utf8" +
+            "                 ENGINE=InnoDB" +
+            "                 AUTO_INCREMENT=1";
+
+
+    /**
+     *  插入日志数据
+     */
+    public static final String INSERT_SQL =
+            "insert into " + TABLE_NAME +" " +
+            "(username,  oper_ip, oper_url, oper_type, oper_param, json_body, res_code, cost_time, create_time)" +
+            " values " +
+            "(  ?,         ?,         ?,         ?,          ?,         ?,        ?,         ?,          ?);";
+
+    /**
+     *  计算数量
+     */
+    public static final String COUNT_SQL = "select count(1) from " + TABLE_NAME + " ";
+
+    /**
+     *  分页查询sql语句
+     */
+    public static final String SELECT_SQL =
+            "select " +
+            "id, username, oper_ip, oper_url, oper_type, oper_param, json_body, res_code, cost_time, create_time " +
+            "from " + TABLE_NAME + " " +
+            "limit ?, ?;";
+
+    /**
+     *  条件查询基础sql语句
+     */
+    public static final String SEARCH_BASE_SQL =
+            "select " +
+            "id, username, oper_ip, oper_url, oper_type, oper_param, json_body, res_code, cost_time, create_time " +
+            "from " + TABLE_NAME + " ";
+}

+ 62 - 0
src/main/java/com/mxkj/monitor/engine/mysql/TableCreate.java

@@ -0,0 +1,62 @@
+package com.mxkj.monitor.engine.mysql;
+
+import com.mxkj.monitor.LogGlobalException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@Component
+public class TableCreate {
+
+    private static final Logger log = LoggerFactory.getLogger(TableCreate.class);
+
+    @Autowired
+    private BaseOptional baseOptional;
+
+    /**
+     *  如果表不存在,则创建表
+     * @throws SQLException
+     */
+    public void createTableIfPresent() {
+        if (!isTableExist(SQLConstant.TABLE_NAME)){
+            String sql = SQLConstant.CREATE_TALBLE_SQL;
+            baseOptional.execute(sql, preparedStatement -> {
+                try {
+                    preparedStatement.execute();
+                } catch (SQLException e) {
+                    throw new LogGlobalException(e);
+                }
+            });
+        }
+    }
+
+    /**
+     *  检查表是否存在
+     * @param tableName
+     * @return
+     */
+    private boolean isTableExist(String tableName){
+        String sql = SQLConstant.SHOW_TABLE_SQL;
+        AtomicBoolean atomicBoolean = new AtomicBoolean(false);
+        baseOptional.execute(sql, preparedStatement -> {
+            try {
+                ResultSet set = preparedStatement.executeQuery(sql);
+                while (set.next()){
+                    String name = set.getString(1);
+                    if (tableName.equals(name)){
+                        atomicBoolean.set(true);
+                        break;
+                    }
+                }
+            }catch (SQLException e){
+                throw new LogGlobalException(e);
+            }
+        });
+        return atomicBoolean.get();
+    }
+}

+ 60 - 0
src/main/java/com/mxkj/monitor/engine/mysql/TableInsert.java

@@ -0,0 +1,60 @@
+package com.mxkj.monitor.engine.mysql;
+
+import com.mxkj.monitor.LogGlobalException;
+import com.mxkj.monitor.domain.MonitorLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class TableInsert {
+
+    private static final Logger log = LoggerFactory.getLogger(TableInsert.class);
+
+    @Autowired
+    private BaseOptional baseOptional;
+
+    /**
+     *  插入数据
+     * @param monitorLog
+     * @throws SQLException
+     */
+    public void insert(MonitorLog monitorLog){
+        String sql = SQLConstant.INSERT_SQL;
+        baseOptional.execute(sql, preparedStatement -> {
+            try {
+                String[] params = buildParamList(monitorLog);
+                for (int i = 1; i <= params.length; i++) {
+                    preparedStatement.setString(i, params[i - 1]);
+                }
+                preparedStatement.execute();
+            } catch (Exception e) {
+                throw new LogGlobalException(e);
+            }
+        });
+    }
+
+    /**
+     *  构建插入参数
+     * @param monitorLog
+     * @return
+     */
+    private String[] buildParamList(MonitorLog monitorLog) throws IllegalAccessException {
+        Class clazz = MonitorLog.class;
+        List<String> list = new ArrayList<>();
+        for (Field declaredField : clazz.getDeclaredFields()) {
+            declaredField.setAccessible(true);
+            if (!declaredField.getName().equals("id")){
+                String value = (String) declaredField.get(monitorLog);
+                list.add(value == null ? "" : value);
+            }
+        }
+        return list.toArray(new String[list.size()]);
+    }
+}

+ 196 - 0
src/main/java/com/mxkj/monitor/engine/mysql/TableSelect.java

@@ -0,0 +1,196 @@
+package com.mxkj.monitor.engine.mysql;
+
+import com.alibaba.druid.util.StringUtils;
+import com.mxkj.monitor.LogGlobalException;
+import com.mxkj.monitor.domain.MonitorLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class TableSelect {
+
+    private static final Logger log = LoggerFactory.getLogger(TableSelect.class);
+
+    @Autowired
+    private BaseOptional baseOptional;
+
+    public int count(MonitorLog monitorLog){
+        String baseSql = SQLConstant.COUNT_SQL;
+
+        List<String> values = new ArrayList<>();
+        String sql = buildWhere(monitorLog, baseSql, values);
+
+        AtomicInteger integer = new AtomicInteger();
+        baseOptional.execute(sql, preparedStatement -> {
+            try {
+                for (int i = 1; i <= values.size(); i++) {
+                    preparedStatement.setString(i, "%" + values.get(i - 1) + "%");
+                }
+                ResultSet set = preparedStatement.executeQuery();
+                while (set.next()){
+                    int count = set.getInt(1);
+                    integer.set(count);
+                }
+            } catch (Exception e) {
+                throw new LogGlobalException(e);
+            }
+        });
+        return integer.get();
+    }
+
+    /**
+     *  分页查询日志
+     * @param page
+     * @param size
+     * @return
+     */
+    public List<MonitorLog> select(int page, int size){
+        if (page < 1 || size <=0){
+            throw new LogGlobalException("非法参数:page:" + page + "  size:" + size);
+        }
+        String sql = SQLConstant.SELECT_SQL;
+        List<MonitorLog> list = new ArrayList<>();
+        baseOptional.execute(sql, preparedStatement -> {
+            try {
+                preparedStatement.setInt(1, page);
+                preparedStatement.setInt(2, size);
+                ResultSet set = preparedStatement.executeQuery();
+                mapper(set, list);
+            } catch (Exception e) {
+                throw new LogGlobalException(e);
+            }
+        });
+        return list;
+    }
+
+    /**
+     *  条件搜索查询
+     * @param monitorLog
+     * @param page
+     * @param size
+     * @return
+     */
+    public List<MonitorLog> search(MonitorLog monitorLog, int page, int size){
+        String baseSql = SQLConstant.SEARCH_BASE_SQL;
+        List<String> values = new ArrayList<>();
+        String sql = buildWhere(monitorLog, baseSql, values);
+        // 分页
+        String limit = " limit ?, ?;";
+        sql += limit;
+
+        // 执行
+        List<MonitorLog> list = new ArrayList<>();
+        baseOptional.execute(sql, preparedStatement -> {
+                    try {
+                        for (int i = 1; i <= values.size(); i++) {
+                            preparedStatement.setString(i, "%" + values.get(i - 1) + "%");
+                        }
+                        preparedStatement.setInt(values.size() + 1, page);
+                        preparedStatement.setInt(values.size() +2, size);
+
+                        ResultSet set = preparedStatement.executeQuery();
+                        mapper(set, list);
+                    } catch (Exception e) {
+                        throw new LogGlobalException(e);
+                    }
+        });
+        return list;
+    }
+
+
+    /**
+     *  驼峰转下划线
+     * @return
+     */
+    private static String toUnderlineName(String s) {
+        if (s == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            boolean nextUpperCase = true;
+            if (i < (s.length() - 1)) {
+                nextUpperCase = Character.isUpperCase(s.charAt(i + 1)) || Character.isDigit(s.charAt(i + 1));
+            }
+            if (Character.isUpperCase(c)) {
+                boolean flag = !upperCase || !nextUpperCase;
+                if (flag) {
+                    if (i > 0) sb.append("_");
+                }
+                upperCase = true;
+            } else {
+                upperCase = false;
+            }
+            sb.append(Character.toUpperCase(c));
+        }
+        return sb.toString();
+    }
+
+    /**
+     *  Mapper数据转换
+     * @param set
+     * @param list
+     * @throws Exception
+     */
+    private void mapper(ResultSet set, List<MonitorLog> list) throws Exception {
+        while (set.next()){
+            Class<MonitorLog> clazz = MonitorLog.class;
+            MonitorLog log = clazz.newInstance();
+            for (Field declaredField : clazz.getDeclaredFields()) {
+                declaredField.setAccessible(true);
+                declaredField.set(log, set.getString(toUnderlineName(declaredField.getName())));
+            }
+            list.add(log);
+        }
+    }
+
+    /**
+     *  构建where语句
+     * @param monitorLog
+     * @param baseSql
+     * @return
+     */
+    private String buildWhere(MonitorLog monitorLog, String baseSql, List<String> values){
+        String where = "where ";
+        String and = "and ";
+        Class<MonitorLog> clazz = MonitorLog.class;
+        List<String> conditionList = new ArrayList<>();
+        for (Field declaredField : clazz.getDeclaredFields()) {
+            declaredField.setAccessible(true);
+            String value = null;
+            try {
+                value = (String) declaredField.get(monitorLog);
+            } catch (IllegalAccessException e) {
+                throw new LogGlobalException(e);
+            }
+            if (!StringUtils.isEmpty(value)){
+                String properties = toUnderlineName(declaredField.getName());
+                conditionList.add(properties + " like ? ");
+                values.add(value);
+            }
+        }
+        // sql条件拼接
+        String sql = baseSql;
+        if (!conditionList.isEmpty()){
+            sql = baseSql + where;
+            for (int i = 0; i < conditionList.size(); i++) {
+                sql += conditionList.get(i);
+                if (i < conditionList.size() - 1){
+                    sql += and;
+                }
+            }
+        }
+        return sql;
+    }
+
+}

+ 43 - 0
src/main/java/com/mxkj/monitor/filter/FilterCatchMap.java

@@ -0,0 +1,43 @@
+package com.mxkj.monitor.filter;
+
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.MonitorLogWrapper;
+import com.mxkj.monitor.engine.DataEngine;
+
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class FilterCatchMap {
+
+    private static final ConcurrentHashMap<String, MonitorLogWrapper> map;
+
+    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1,
+            10, TimeUnit.MINUTES, new LinkedBlockingDeque<>());
+
+    static {
+        map = new ConcurrentHashMap<>();
+    }
+
+    public static void put(String requestId, MonitorLogWrapper monitorLogWrapper){
+        map.put(requestId, monitorLogWrapper);
+    }
+
+    public static Optional<MonitorLogWrapper> consumer(String requestId){
+        MonitorLogWrapper monitorLogWrapper = map.get(requestId);
+        if (monitorLogWrapper == null){
+            return Optional.empty();
+        }
+        map.remove(requestId);
+        return Optional.of(monitorLogWrapper);
+    }
+
+    public static void submit(DataEngine dataEngine, MonitorLog monitorLog){
+        if (dataEngine != null){
+            executor.submit(() -> dataEngine.insert(monitorLog));
+        }
+    }
+
+}

+ 226 - 0
src/main/java/com/mxkj/monitor/filter/LogMonitorFilter.java

@@ -0,0 +1,226 @@
+package com.mxkj.monitor.filter;
+
+import com.mxkj.monitor.LogServletUtils;
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.MonitorLogWrapper;
+import com.mxkj.monitor.engine.DataEngine;
+import org.reactivestreams.Publisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.zip.GZIPInputStream;
+
+@Component
+public class LogMonitorFilter implements GlobalFilter, Ordered {
+
+    private static final Logger log = LoggerFactory.getLogger(LogMonitorFilter.class);
+
+    @Autowired(required = false)
+    private DataEngine dataEngine;
+
+    @Override
+    public int getOrder() {
+        return -2000;
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+
+        MonitorLog monitorLog = new MonitorLog();
+        MonitorLogWrapper monitorLogWrapper = new MonitorLogWrapper(monitorLog);
+        try {
+
+            // 请求拦截
+            saveRequest(request, monitorLogWrapper);
+            // 响应拦截
+            ServerHttpResponse response = buildResponse(exchange.getResponse(), result -> {
+                saveResponse(exchange.getResponse(), result, monitorLogWrapper);
+                FilterCatchMap.consumer(request.getId());
+            });
+
+            if (request.getMethod() == null ){
+                return chain.filter(exchange.mutate().response(response).build());
+            }
+            // 拦截参数GET方式
+            if (request.getMethod().matches(HttpMethod.GET.toString())){
+                String param = request.getURI().getQuery();
+                monitorLog.setOperParam(param);
+            }
+
+            // 非POST方式
+            if (!request.getMethod().matches(HttpMethod.POST.toString())){
+                return chain.filter(exchange.mutate().response(response).build());
+            }
+
+            // content-type 为文件上传
+            String contentType = request.getHeaders().getFirst("Content-Type");
+            if (!Objects.isNull(contentType) && contentType.equals("multipart/form-data")){
+                return chain.filter(exchange.mutate().response(response).build());
+            }
+
+            // POST方式
+            return DataBufferUtils.join(request.getBody()).flatMap(dataBuffer -> {
+                byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                dataBuffer.read(bytes);
+                DataBufferUtils.release(dataBuffer);
+                Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
+                    DataBuffer buffer = response.bufferFactory().wrap(bytes);
+                    return Mono.just(buffer);
+                });
+                ServerHttpRequest mutateReq = new ServerHttpRequestDecorator(request) {
+                    @Override
+                    public Flux<DataBuffer> getBody() {
+                        return cachedFlux;
+                    }
+                };
+                String bodyStr = new String(bytes, StandardCharsets.UTF_8);
+                monitorLog.setOperParam(bodyStr);
+                return chain.filter(exchange.mutate().request(mutateReq).response(response).build());
+            });
+        }catch (Exception e){
+            log.error("【日志监控】异常", e);
+            return chain.filter(exchange.mutate().build());
+        }
+    }
+
+    /**
+     *  构建响应体
+     * @param response
+     * @return
+     */
+    private ServerHttpResponseDecorator buildResponse(ServerHttpResponse response, Consumer<String> jsonBodyConsumer){
+        DataBufferFactory bufferFactory = response.bufferFactory();
+        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
+
+            @Override
+            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
+                Flux<? extends DataBuffer> fluxBody = null;
+                if (body instanceof Mono){
+                    Mono<? extends DataBuffer> mono = (Mono) body;
+                    fluxBody = mono.flux();
+                }
+                if (body instanceof Flux) {
+                   fluxBody = (Flux<? extends DataBuffer>) body;
+                }
+                Mono<Void> mono = super.writeWith(fluxBody.buffer().map(dataBuffer -> {
+                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
+                    DataBuffer join = dataBufferFactory.join(dataBuffer);
+                    byte[] content = new byte[join.readableByteCount()];
+                    join.read(content);
+                    DataBufferUtils.release(join);
+                    String s = new String(content, StandardCharsets.UTF_8);
+
+                    // gzip编码数据
+                    List<String> strings = response.getHeaders().get(HttpHeaders.CONTENT_ENCODING);
+                    if (!CollectionUtils.isEmpty(strings) && strings.contains("gzip")) {
+                        GZIPInputStream gzipInputStream = null;
+                        try {
+                            gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(content), content.length);
+                            ByteArrayOutputStream out = new ByteArrayOutputStream();
+                            byte[] buffer = new byte[256];
+                            int n;
+                            while ((n = gzipInputStream.read(buffer)) >= 0) {
+                                out.write(buffer, 0, n);
+                            }
+                            s = new String(out.toByteArray(), "UTF-8");
+                        } catch (IOException e) {
+
+                        } finally {
+                            if (gzipInputStream != null) {
+                                try {
+                                    gzipInputStream.close();
+                                } catch (IOException e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                        }
+                    } else {
+                        s = new String(content, StandardCharsets.UTF_8);
+                    }
+                    try {
+                        jsonBodyConsumer.accept(s);
+                    }catch (Exception e){
+                        log.error("保存响应对象失败", e);
+                    }
+                    return bufferFactory.wrap(content);
+                }));
+                return mono;
+            }
+        };
+        return decoratedResponse;
+    }
+
+    /**
+     *  保存请求对象
+     * @param request
+     * @param monitorLogWrapper
+     */
+    private void saveRequest(ServerHttpRequest request, MonitorLogWrapper monitorLogWrapper){
+        InetSocketAddress inetAddress = request.getRemoteAddress();
+        MonitorLog monitorLog = monitorLogWrapper.getMonitorLog();
+        // ip
+        if (inetAddress != null){
+            monitorLog.setOperIp(inetAddress.getHostName());
+        }else {
+            monitorLog.setOperIp("未知");
+        }
+        // type
+        monitorLog.setOperType(request.getMethod().toString());
+        // path
+        monitorLog.setOperUrl(request.getPath().value());
+        // username
+        String username = "";
+        monitorLog.setUsername(username);
+        FilterCatchMap.put(request.getId(), monitorLogWrapper);
+    }
+
+    /**
+     *  保存响应对象
+     * @param monitorLogWrapper
+     */
+    private void saveResponse(ServerHttpResponse response, String result, MonitorLogWrapper monitorLogWrapper){
+        monitorLogWrapper.setEndTime(System.currentTimeMillis());
+        MonitorLog monitorLog = monitorLogWrapper.getMonitorLog();
+        // 响应码
+        if (response.getRawStatusCode() != null){
+            monitorLog.setResCode(String.valueOf(response.getRawStatusCode()));
+        }
+        // 耗时
+        long costTime = monitorLogWrapper.getEndTime() - monitorLogWrapper.getStartTime();
+        monitorLog.setCostTime(String.valueOf(costTime));
+        // 响应值
+        monitorLog.setJsonBody(result == null ? "" : result);
+        // 创建时间
+        monitorLogWrapper.getMonitorLog().setCreateTime(LogServletUtils.formatDateNow());
+        FilterCatchMap.submit(dataEngine, monitorLog);
+    }
+
+}

+ 68 - 0
src/main/java/com/mxkj/monitor/handler/AuthHandler.java

@@ -0,0 +1,68 @@
+package com.mxkj.monitor.handler;
+
+import com.mxkj.monitor.AuthorizationException;
+import com.mxkj.monitor.LogServletUtils;
+import com.mxkj.monitor.config.AuthorizationConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.reactive.config.WebFluxConfigurer;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.HandlerFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class AuthHandler implements HandlerFunction<ServerResponse>, WebFluxConfigurer {
+
+    /**
+     *  登录令牌容器,有时间限制
+     */
+    public static final Map<String, Long> LIMITED_TOKENS = new ConcurrentHashMap<>();
+
+    @Autowired(required = false)
+    private AuthorizationConfig authorizationConfig;
+
+    @Override
+    public Mono<ServerResponse> handle(ServerRequest request) {
+        ServerHttpRequest req = request.exchange().getRequest();
+        String query = req.getURI().getQuery();
+        String[] params = query.split("&");
+        if (params.length != 2){
+            Map<String, Object> result = LogServletUtils.buildResult(500, "参数不正确,例如:username=test&password=123456", null);
+            return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+        }
+        String username = params[0].split("=")[1];
+        String password = params[1].split("=")[1];
+        if (authorizationConfig.getUsername() == null || authorizationConfig.getPassword() == null){
+            Map<String, Object> result = LogServletUtils.buildResult(500, "账号未配置", null);
+            return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+        }
+        // 匹配账号密码 默认会话过期时间30分钟
+        if (authorizationConfig.getUsername().equals(username) && authorizationConfig.getPassword().equals(password)){
+            String uuid = UUID.randomUUID().toString();
+            int minites = authorizationConfig.getSession() == 0 ? 30 : authorizationConfig.getSession();
+            LIMITED_TOKENS.put(uuid, System.currentTimeMillis() + minites * 60 * 1000);
+            Map<String, Object> result = LogServletUtils.buildResult(200, "登录成功", uuid);
+            return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+        }
+        Map<String, Object> result = LogServletUtils.buildResult(500, "账号或者密码错误", null);
+        return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+    }
+
+    public static void checkAuth(ServerHttpRequest request) throws AuthorizationException {
+        String token = request.getHeaders().getFirst("Authorization");
+        if (token == null){
+            throw new AuthorizationException("登录失效或已过期");
+        }
+        Long time = LIMITED_TOKENS.get(token);
+        if (time == null || time < System.currentTimeMillis()){
+            LIMITED_TOKENS.remove(token);
+            throw new AuthorizationException("登录失效或已过期");
+        }
+    }
+}

+ 130 - 0
src/main/java/com/mxkj/monitor/handler/ExceptionHandler.java

@@ -0,0 +1,130 @@
+package com.mxkj.monitor.handler;
+
+import com.alibaba.druid.support.json.JSONUtils;
+import com.mxkj.monitor.LogServletUtils;
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.MonitorLogWrapper;
+import com.mxkj.monitor.engine.DataEngine;
+import com.mxkj.monitor.filter.FilterCatchMap;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.cloud.gateway.support.NotFoundException;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Configuration
+public class ExceptionHandler implements ErrorWebExceptionHandler, Ordered {
+
+    private static final Logger log = LoggerFactory.getLogger(ExceptionHandler.class);
+
+    @Autowired(required = false)
+    private DataEngine dataEngine;
+
+    @Override
+    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
+    {
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+
+        if (exchange.getResponse().isCommitted())
+        {
+            return Mono.error(ex);
+        }
+
+        String msg;
+
+        if (ex instanceof NotFoundException)
+        {
+            msg = "服务未找到";
+        }
+        else if (ex instanceof ResponseStatusException)
+        {
+            ResponseStatusException responseStatusException = (ResponseStatusException) ex;
+            msg = responseStatusException.getMessage();
+        }
+        else
+        {
+            msg = "内部服务器错误";
+        }
+        log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
+        saveMonitor(msg, request);
+        return LogServletUtils.webFluxResponseWriter(response, msg);
+    }
+
+    private void saveMonitor(String msg, ServerHttpRequest request){
+        Optional<MonitorLogWrapper> optional = FilterCatchMap.consumer(request.getId());
+        int code = 500;
+        MonitorLog monitorLog = null;
+        MonitorLogWrapper monitorLogWrapper = null;
+        Map<String, Object> result = LogServletUtils.buildResult(code, msg, null);
+        // 过滤器已捕获
+        if (optional.isPresent()){
+            monitorLogWrapper = optional.get();
+            monitorLog = monitorLogWrapper.getMonitorLog();
+            monitorLog.setCostTime(String.valueOf(System.currentTimeMillis() - monitorLogWrapper.getStartTime()));
+        }
+        // 过滤器未捕获
+        else {
+            monitorLog = new MonitorLog();
+            monitorLogWrapper = new MonitorLogWrapper(monitorLog);
+            // ip
+            InetSocketAddress inetAddress = request.getRemoteAddress();
+            if (inetAddress != null){
+                monitorLog.setOperIp(inetAddress.getHostName());
+            }else {
+                monitorLog.setOperIp("未知");
+            }
+            // type
+            if (request.getMethod() != null){
+                monitorLog.setOperType(request.getMethod().toString());
+
+                // param
+                String type = request.getMethod().toString().toUpperCase();
+                if (type.matches("GET")){
+                    String param = request.getURI().getQuery();
+                    monitorLog.setOperParam(param);
+                }
+                // JSON格式 APPLICATION_JSON
+                if (type.matches("POST")){
+                    String contentType = request.getHeaders().getFirst("Content-Type");
+                    if (ContentType.APPLICATION_JSON.toString().equalsIgnoreCase(contentType)){
+                        AtomicReference<String> atoBody = LogServletUtils.getRequestParams(request);
+                        monitorLog.setOperParam(atoBody.get());
+                    }
+                }
+            }
+            // path
+            monitorLog.setOperUrl(request.getPath().value());
+
+            monitorLog.setCostTime(String.valueOf(0));
+        }
+        monitorLog.setJsonBody(JSONUtils.toJSONString(result));
+        monitorLog.setResCode(String.valueOf(code));
+        // 创建时间
+        monitorLogWrapper.getMonitorLog().setCreateTime(LogServletUtils.formatDateNow());
+        FilterCatchMap.submit(dataEngine, monitorLog);
+    }
+
+    /**
+     *  设置要比原网关小,才能优先执行
+     *  原Gateway网关中GatewayExceptionHandler
+     * @return
+     */
+    @Override
+    public int getOrder() {
+        return -100;
+    }
+}

+ 78 - 0
src/main/java/com/mxkj/monitor/handler/LogListHandler.java

@@ -0,0 +1,78 @@
+package com.mxkj.monitor.handler;
+
+import com.mxkj.monitor.AuthorizationException;
+import com.mxkj.monitor.LogServletUtils;
+import com.mxkj.monitor.domain.MonitorLog;
+import com.mxkj.monitor.domain.TableMonitorLog;
+import com.mxkj.monitor.engine.DataEngine;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.HandlerFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+@Component
+public class LogListHandler  implements HandlerFunction<ServerResponse> {
+
+    @Autowired(required = false)
+    private DataEngine dataEngine;
+
+    @Override
+    public Mono<ServerResponse> handle(ServerRequest request) {
+        ServerHttpRequest req = request.exchange().getRequest();
+
+        // 授权校验
+        try {
+            AuthHandler.checkAuth(req);
+        } catch (AuthorizationException e) {
+            Map<String, Object> result = LogServletUtils.buildResult(401, e.getMessage(), null);
+            return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+        }
+
+        if (dataEngine == null){
+            Map<String, Object> result = LogServletUtils.buildResult(500, "监控未配置", null);
+            return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+        }
+
+        String query = req.getURI().getQuery();
+        String[] params = query.split("&");
+
+        // 默认分页参数
+        int page = 0;
+        int size = 10;
+        Class<MonitorLog> clazz = MonitorLog.class;
+        MonitorLog monitorLog = new MonitorLog();
+        for (String param : params) {
+            String key = param.split("=")[0];
+            String value = param.split("=")[1];
+            if (key.equals("page")){
+                page = Integer.valueOf(value);
+            }
+            if (key.equals("size")){
+                size = Integer.valueOf(value);
+            }
+            for (Field declaredField : clazz.getDeclaredFields()) {
+                if (declaredField.getName().equals(key)){
+                    try {
+                        declaredField.setAccessible(true);
+                        declaredField.set(monitorLog, value);
+                    } catch (IllegalAccessException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+                }
+            }
+        }
+
+        TableMonitorLog table = dataEngine.list(monitorLog, page, size);
+        Map<String, Object> result = LogServletUtils.buildResult(200, "成功", table);
+        return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(result));
+    }
+}

+ 14 - 0
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,14 @@
+com.mxkj.monitor.config.AuthorizationConfig
+com.mxkj.monitor.config.ElasticSearchConfig
+com.mxkj.monitor.config.DruidDataSourceConfig
+com.mxkj.monitor.config.DruidProperties
+com.mxkj.monitor.engine.DataEngineInitializer
+com.mxkj.monitor.config.MonitorRouteConfiguration
+com.mxkj.monitor.engine.mysql.BaseOptional
+com.mxkj.monitor.engine.mysql.TableCreate
+com.mxkj.monitor.engine.mysql.TableInsert
+com.mxkj.monitor.engine.mysql.TableSelect
+com.mxkj.monitor.filter.LogMonitorFilter
+com.mxkj.monitor.handler.ExceptionHandler
+com.mxkj.monitor.handler.LogListHandler
+com.mxkj.monitor.handler.AuthHandler

+ 1 - 0
src/main/resources/static/css/about.49b90404.css

@@ -0,0 +1 @@
+.app-container-right-content[data-v-68573f86]{height:100%!important;padding:15px}.search-header[data-v-68573f86]{margin-bottom:15px;padding:10px 20px}.body-content[data-v-68573f86],.search-header[data-v-68573f86]{border-radius:6px;background-color:#fff;box-shadow:0 5px 5px rgba(0,0,0,.1),0 0 10px 0 rgba(0,0,0,.2)}.body-content[data-v-68573f86]{height:100%;padding:20px}.mb8[data-v-68573f86]{margin-bottom:8px}.mr15[data-v-68573f86]{margin-right:15px}.pagina[data-v-68573f86]{margin-top:20px;text-align:right}.body-content[data-v-68573f86] .el-table__cell .cell{color:#515a6e}.body-content[data-v-68573f86] .el-table__header th{background-color:#f8f8f9}.login[data-v-ef4ccf9c]{display:flex;justify-content:center;align-items:center;height:100%}.title[data-v-ef4ccf9c]{margin:0 auto 30px auto;text-align:center;color:#707070}.login-img[data-v-ef4ccf9c]{position:absolute;top:0;left:0;width:100%;height:100%;z-index:-1}.login-form[data-v-ef4ccf9c]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;width:400px;padding:25px 25px 5px 25px}.login-form .el-input input[data-v-ef4ccf9c],.login-form .el-input[data-v-ef4ccf9c]{height:38px}.login-form .input-icon[data-v-ef4ccf9c]{height:39px;width:14px;margin-left:2px}.login-tip[data-v-ef4ccf9c]{font-size:13px;text-align:center;color:#bfbfbf}.login-code[data-v-ef4ccf9c]{width:33%;height:38px;float:right}.el-login-footer[data-v-ef4ccf9c]{height:40px;line-height:40px;position:fixed;bottom:0;width:100%;text-align:center;color:#fff;font-family:Arial;font-size:12px;letter-spacing:1px}.login-code-img[data-v-ef4ccf9c]{height:38px}

+ 1 - 0
src/main/resources/static/css/app.831204d6.css

@@ -0,0 +1 @@
+#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50}*{margin:0;padding:0}nav{padding:30px}nav a{font-weight:700;color:#2c3e50}nav a.router-link-exact-active{color:#42b983}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/css/chunk-vendors.3a5a5cd6.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/css/chunk-vendors.e92fd658.css


BIN
src/main/resources/static/favicon.ico


BIN
src/main/resources/static/fonts/element-icons.f1a45d74.ttf


BIN
src/main/resources/static/fonts/element-icons.ff18efd1.woff


BIN
src/main/resources/static/img/BG1.408413ae.png


+ 1 - 0
src/main/resources/static/index.html

@@ -0,0 +1 @@
+<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/mxkj/favicon.ico"><title>mxkj-pro</title><script defer="defer" src="/mxkj/js/chunk-vendors.ae4ce3c9.js"></script><script defer="defer" src="/mxkj/js/app.d04de73c.js"></script><link href="/mxkj/css/chunk-vendors.3a5a5cd6.css" rel="stylesheet"><link href="/mxkj/css/app.831204d6.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but mxkj-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/about.01929ad9.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/about.01929ad9.js.map


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/about.74a12c23.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/about.74a12c23.js.map


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/app.7d3c77bd.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/app.7d3c77bd.js.map


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/app.d04de73c.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/app.d04de73c.js.map


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/chunk-vendors.ae4ce3c9.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/main/resources/static/js/chunk-vendors.ae4ce3c9.js.map


Vissa filer visades inte eftersom för många filer har ändrats