mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	新增:新增系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等),新增操作日志引擎,记录 HTTP 请求信息
This commit is contained in:
		
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| @@ -77,22 +77,43 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | ||||
|   │      │        └─ ContinewAdminApplication.java  # 启动入口 | ||||
|   │      └─ resources   # 工程配置目录 | ||||
|   │        └─ db.changelog.v0.0.1    # 数据库脚本文件 | ||||
|   ├─ continew-admin-system    # 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) | ||||
|   ├─ continew-admin-monitor  # 系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等) | ||||
|   │  └─ src | ||||
|   │    └─ main | ||||
|   │      ├─ java        # 工程源文件代码目录 | ||||
|   │      │  └─ top | ||||
|   │      │    └─ charles7c | ||||
|   │      │      └─ cnadmin | ||||
|   │      │        ├─ auth     # 认证相关业务及配置 | ||||
|   │      │        │  ├─ config    # 认证相关配置 | ||||
|   │      │        └─ monitor | ||||
|   │      │          ├─ annotation    # 系统监控相关注解 | ||||
|   │      │          ├─ config        # 系统监控相关配置 | ||||
|   │      │          │  └─ properties   # 系统监控相关配置属性 | ||||
|   │      │          ├─ enums         # 系统监控相关枚举 | ||||
|   │      │          ├─ filter        # 系统监控相关过滤器 | ||||
|   │      │          ├─ interceptor   # 系统监控相关拦截器 | ||||
|   │      │          ├─ mapper        # 系统监控相关 Mapper | ||||
|   │      │          ├─ model         # 系统监控相关模型 | ||||
|   │      │          │  └─ entity       # 系统监控相关实体对象 | ||||
|   │      │          └─ service       # 系统监控相关业务接口及实现类 | ||||
|   │      │             └─ impl         # 系统监控相关业务实现类 | ||||
|   │      └─ resources   # 工程配置目录 | ||||
|   │         └─ mapper        # MyBatis Mapper XML 文件目录 | ||||
|   ├─ continew-admin-system   # 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) | ||||
|   │  └─ src | ||||
|   │    └─ main | ||||
|   │      ├─ java        # 工程源文件代码目录 | ||||
|   │      │  └─ top | ||||
|   │      │    └─ charles7c | ||||
|   │      │      └─ cnadmin | ||||
|   │      │        ├─ auth     # 系统认证相关业务及配置 | ||||
|   │      │        │  ├─ config    # 系统认证相关配置 | ||||
|   │      │        │  │  ├─ satoken    # Sa-Token 配置 | ||||
|   │      │        │  │  └─ properties # 认证相关配置属性 | ||||
|   │      │        │  ├─ model     # 认证相关模型 | ||||
|   │      │        │  │  ├─ request    # 认证相关请求对象 | ||||
|   │      │        │  │  └─ vo         # 认证相关 VO(View Object) | ||||
|   │      │        │  └─ service   # 认证相关业务接口及实现类 | ||||
|   │      │        │     └─ impl       # 认证相关业务实现类 | ||||
|   │      │        │  │  └─ properties # 系统认证相关配置属性 | ||||
|   │      │        │  ├─ model     # 系统认证相关模型 | ||||
|   │      │        │  │  ├─ request    # 系统认证相关请求对象 | ||||
|   │      │        │  │  └─ vo         # 系统认证相关 VO(View Object) | ||||
|   │      │        │  └─ service   # 系统认证相关业务接口及实现类 | ||||
|   │      │        │     └─ impl       # 系统认证相关业务实现类 | ||||
|   │      │        └─ system   # 系统管理相关业务及配置 | ||||
|   │      │          ├─ mapper     # 系统管理相关 Mapper | ||||
|   │      │          ├─ model      # 系统管理相关模型 | ||||
| @@ -112,6 +133,7 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | ||||
|   │                ├─ config       # 公共配置 | ||||
|   │                │  ├─ jackson      # Jackson 配置 | ||||
|   │                │  ├─ mybatis      # MyBatis Plus 配置 | ||||
|   │                │  ├─ threadpool   # 线程池配置 | ||||
|   │                │  └─ properties   # 公共配置属性 | ||||
|   │                ├─ consts       # 公共常量 | ||||
|   │                ├─ exception    # 公共异常 | ||||
| @@ -121,7 +143,8 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | ||||
|   │                │  ├─ entity       # 公共实体对象 | ||||
|   │                │  └─ vo           # 公共 VO(View Object) | ||||
|   │                └─ util         # 公共工具类 | ||||
|   │                  └─ helper        # 公共 Helper(助手) | ||||
|   │                  ├─ helper        # 公共 Helper(助手) | ||||
|   │                  └─ holder        # 公共 Holder(持有者) | ||||
| ``` | ||||
|  | ||||
| ### License | ||||
|   | ||||
| @@ -110,6 +110,12 @@ limitations under the License. | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- ################ 工具库相关 ################ --> | ||||
|         <!-- 第三方封装 Ip2region(离线 IP 数据管理框架和定位库,支持亿级别的数据段,10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) --> | ||||
|         <dependency> | ||||
|             <groupId>net.dreamlu</groupId> | ||||
|             <artifactId>mica-ip2region</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Knife4j(前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案) --> | ||||
|         <dependency> | ||||
|             <groupId>com.github.xiaoymin</groupId> | ||||
|   | ||||
| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.config; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||||
| import org.springframework.scheduling.annotation.EnableAsync; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.exception.ServiceException; | ||||
|  | ||||
| /** | ||||
|  * 异步任务执行配置 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 22:33 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Configuration | ||||
| @RequiredArgsConstructor | ||||
| @EnableAsync(proxyTargetClass = true) | ||||
| public class AsyncConfiguration implements AsyncConfigurer { | ||||
|  | ||||
|     private final ScheduledExecutorService scheduledExecutorService; | ||||
|  | ||||
|     /** | ||||
|      * 异步任务执行时,使用 Java 内置线程池 | ||||
|      */ | ||||
|     @Override | ||||
|     public Executor getAsyncExecutor() { | ||||
|         return scheduledExecutorService; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 异步任务执行时的异常处理 | ||||
|      */ | ||||
|     @Override | ||||
|     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||||
|         return (throwable, method, objects) -> { | ||||
|             throwable.printStackTrace(); | ||||
|             StringBuilder sb = new StringBuilder(); | ||||
|             sb.append("Exception message - ").append(throwable.getMessage()).append(", Method name - ") | ||||
|                 .append(method.getName()); | ||||
|             if (ArrayUtil.isNotEmpty(objects)) { | ||||
|                 sb.append(", Parameter value - ").append(Arrays.toString(objects)); | ||||
|             } | ||||
|             throw new ServiceException(sb.toString()); | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -25,6 +25,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.boot.context.properties.NestedConfigurationProperty; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
|  | ||||
| /** | ||||
|  * 项目配置属性 | ||||
|  * | ||||
| @@ -72,4 +75,13 @@ public class ContinewAdminProperties { | ||||
|      */ | ||||
|     @NestedConfigurationProperty | ||||
|     private License license; | ||||
|  | ||||
|     /** | ||||
|      * 是否本地解析 IP 归属地 | ||||
|      */ | ||||
|     public static final boolean IP_ADDR_LOCAL_PARSE_ENABLED; | ||||
|  | ||||
|     static { | ||||
|         IP_ADDR_LOCAL_PARSE_ENABLED = Convert.toBool(SpringUtil.getProperty("continew-admin.ipAddrLocalParseEnabled")); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,85 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.config.threadpool; | ||||
|  | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.ScheduledThreadPoolExecutor; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import org.apache.commons.lang3.concurrent.BasicThreadFactory; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
|  | ||||
| /** | ||||
|  * 线程池配置 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 23:13 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Configuration | ||||
| @RequiredArgsConstructor | ||||
| public class ThreadPoolConfiguration { | ||||
|  | ||||
|     private final ThreadPoolProperties threadPoolProperties; | ||||
|     /** 核心(最小)线程数 = CPU 核心数 + 1 */ | ||||
|     private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1; | ||||
|  | ||||
|     /** | ||||
|      * Spring 内置线程池:ThreadPoolTaskExecutor | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true") | ||||
|     public ThreadPoolTaskExecutor threadPoolTaskExecutor() { | ||||
|         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||||
|         // 核心(最小)线程数 | ||||
|         executor.setCorePoolSize(corePoolSize); | ||||
|         // 最大线程数 | ||||
|         executor.setMaxPoolSize(corePoolSize * 2); | ||||
|         // 队列容量 | ||||
|         executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); | ||||
|         // 活跃时间 | ||||
|         executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); | ||||
|         // 配置当池内线程数已达到上限的时候,该如何处理新任务:不在新线程中执行任务,而是由调用者所在的线程来执行 | ||||
|         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | ||||
|         return executor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Java 内置线程池:ScheduledExecutorService(适用于执行周期性或定时任务) | ||||
|      */ | ||||
|     @Bean | ||||
|     public ScheduledExecutorService scheduledExecutorService() { | ||||
|         return new ScheduledThreadPoolExecutor(corePoolSize, | ||||
|             new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), | ||||
|             new ThreadPoolExecutor.CallerRunsPolicy()) { | ||||
|  | ||||
|             @Override | ||||
|             protected void afterExecute(Runnable runnable, Throwable throwable) { | ||||
|                 super.afterExecute(runnable, throwable); | ||||
|                 ExceptionUtils.printException(runnable, throwable); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.config.threadpool; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * 线程池配置属性 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 23:06 | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "thread-pool") | ||||
| public class ThreadPoolProperties { | ||||
|  | ||||
|     /** | ||||
|      * 队列容量 | ||||
|      */ | ||||
|     private int queueCapacity; | ||||
|  | ||||
|     /** | ||||
|      * 活跃时间 | ||||
|      */ | ||||
|     private int keepAliveSeconds; | ||||
| } | ||||
| @@ -38,9 +38,12 @@ import cn.dev33.satoken.exception.NotLoginException; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.exception.BadRequestException; | ||||
| import top.charles7c.cnadmin.common.exception.ServiceException; | ||||
| import top.charles7c.cnadmin.common.model.dto.OperationLog; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.StreamUtils; | ||||
| import top.charles7c.cnadmin.common.util.holder.LogContextHolder; | ||||
|  | ||||
| /** | ||||
|  * 全局异常处理器 | ||||
| @@ -58,6 +61,7 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||||
|     @ExceptionHandler(Exception.class) | ||||
|     public R handleException(Exception e, HttpServletRequest request) { | ||||
|         this.setException(e); | ||||
|         log.error("请求地址'{}',发生未知异常", request.getRequestURI(), e); | ||||
|         return R.fail(e.getMessage()); | ||||
|     } | ||||
| @@ -68,10 +72,22 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||||
|     @ExceptionHandler(RuntimeException.class) | ||||
|     public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { | ||||
|         this.setException(e); | ||||
|         log.error("请求地址'{}',发生系统异常", request.getRequestURI(), e); | ||||
|         return R.fail(e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 拦截业务异常 | ||||
|      */ | ||||
|     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||||
|     @ExceptionHandler(ServiceException.class) | ||||
|     public R handleServiceException(ServiceException e, HttpServletRequest request) { | ||||
|         this.setException(e); | ||||
|         log.error("请求地址'{}',发生业务异常", request.getRequestURI(), e); | ||||
|         return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 拦截自定义验证异常-错误请求 | ||||
|      */ | ||||
| @@ -147,4 +163,17 @@ public class GlobalExceptionHandler { | ||||
|         log.error("请求地址'{}',认证失败'{}',无法访问系统资源", request.getRequestURI(), e.getMessage()); | ||||
|         return R.fail(HttpStatus.UNAUTHORIZED.value(), "认证失败,无法访问系统资源"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 操作日志保存异常信息 | ||||
|      * | ||||
|      * @param e | ||||
|      *            异常信息 | ||||
|      */ | ||||
|     private void setException(Exception e) { | ||||
|         OperationLog operationLog = LogContextHolder.get(); | ||||
|         if (operationLog != null) { | ||||
|             operationLog.setException(e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,47 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.model.dto; | ||||
|  | ||||
| import java.util.Date; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * 操作日志 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/25 8:59 | ||||
|  */ | ||||
| @Data | ||||
| public class OperationLog { | ||||
|  | ||||
|     /** | ||||
|      * 操作人 | ||||
|      */ | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 操作时间 | ||||
|      */ | ||||
|     private Date createTime; | ||||
|  | ||||
|     /** | ||||
|      * 异常 | ||||
|      */ | ||||
|     private Exception exception; | ||||
|  | ||||
| } | ||||
| @@ -16,10 +16,14 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.util; | ||||
|  | ||||
| import java.util.concurrent.CancellationException; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| import java.util.concurrent.Future; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| /** | ||||
|  * 异常工具类 | ||||
| @@ -27,9 +31,38 @@ import lombok.NoArgsConstructor; | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/21 20:56 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ExceptionUtils { | ||||
|  | ||||
|     /** | ||||
|      * 打印线程异常信息 | ||||
|      * | ||||
|      * @param runnable | ||||
|      *            线程执行内容 | ||||
|      * @param throwable | ||||
|      *            异常 | ||||
|      */ | ||||
|     public static void printException(Runnable runnable, Throwable throwable) { | ||||
|         if (throwable == null && runnable instanceof Future<?>) { | ||||
|             try { | ||||
|                 Future<?> future = (Future<?>)runnable; | ||||
|                 if (future.isDone()) { | ||||
|                     future.get(); | ||||
|                 } | ||||
|             } catch (CancellationException e) { | ||||
|                 throwable = e; | ||||
|             } catch (ExecutionException e) { | ||||
|                 throwable = e.getCause(); | ||||
|             } catch (InterruptedException e) { | ||||
|                 Thread.currentThread().interrupt(); | ||||
|             } | ||||
|         } | ||||
|         if (throwable != null) { | ||||
|             log.error(throwable.getMessage(), throwable); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果有异常,返回 null | ||||
|      * | ||||
|   | ||||
| @@ -0,0 +1,111 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.util; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import cn.hutool.core.net.NetUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import cn.hutool.http.HtmlUtil; | ||||
| import cn.hutool.http.HttpUtil; | ||||
| import cn.hutool.json.JSONObject; | ||||
| import cn.hutool.json.JSONUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.config.properties.ContinewAdminProperties; | ||||
|  | ||||
| import net.dreamlu.mica.ip2region.core.Ip2regionSearcher; | ||||
| import net.dreamlu.mica.ip2region.core.IpInfo; | ||||
|  | ||||
| /** | ||||
|  * IP 工具类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 20:00 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class IpUtils { | ||||
|  | ||||
|     /** | ||||
|      * 太平洋网开放 API,查询 IP 归属地 | ||||
|      */ | ||||
|     private static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true"; | ||||
|  | ||||
|     /** | ||||
|      * 根据IP获取详细地址 | ||||
|      * | ||||
|      * @param ip | ||||
|      *            IP地址 | ||||
|      * @return 详细地址 | ||||
|      */ | ||||
|     public static String getCityInfo(String ip) { | ||||
|         if (ContinewAdminProperties.IP_ADDR_LOCAL_PARSE_ENABLED) { | ||||
|             return getLocalCityInfo(ip); | ||||
|         } else { | ||||
|             return getHttpCityInfo(ip); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 IP 获取详细地址(网络解析) | ||||
|      * | ||||
|      * @param ip | ||||
|      *            IP地址 | ||||
|      * @return 详细地址 | ||||
|      */ | ||||
|     public static String getHttpCityInfo(String ip) { | ||||
|         if (isInnerIP(ip)) { | ||||
|             return "内网IP"; | ||||
|         } | ||||
|         String api = String.format(IP_URL, ip); | ||||
|         JSONObject object = JSONUtil.parseObj(HttpUtil.get(api)); | ||||
|         return object.get("addr", String.class); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 IP 获取详细地址(本地解析) | ||||
|      * | ||||
|      * @param ip | ||||
|      *            IP 地址 | ||||
|      * @return 详细地址 | ||||
|      */ | ||||
|     public static String getLocalCityInfo(String ip) { | ||||
|         if (isInnerIP(ip)) { | ||||
|             return "内网IP"; | ||||
|         } | ||||
|         Ip2regionSearcher ip2regionSearcher = SpringUtil.getBean(Ip2regionSearcher.class); | ||||
|         IpInfo ipInfo = ip2regionSearcher.memorySearch(ip); | ||||
|         if (ipInfo != null) { | ||||
|             return ipInfo.getAddress(); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 是否为内网IPv4 | ||||
|      * | ||||
|      * @param ip | ||||
|      *            IP 地址 | ||||
|      * @return 是否为内网IP | ||||
|      */ | ||||
|     public static boolean isInnerIP(String ip) { | ||||
|         ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip); | ||||
|         return NetUtil.isInnerIP(ip); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,95 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.util; | ||||
|  | ||||
| import java.util.*; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import org.springframework.web.context.request.RequestContextHolder; | ||||
| import org.springframework.web.context.request.ServletRequestAttributes; | ||||
|  | ||||
| import cn.hutool.http.useragent.UserAgent; | ||||
| import cn.hutool.http.useragent.UserAgentUtil; | ||||
|  | ||||
| /** | ||||
|  * Servlet 工具类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 20:00 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ServletUtils { | ||||
|  | ||||
|     /** | ||||
|      * 获取请求对象 | ||||
|      * | ||||
|      * @return / | ||||
|      */ | ||||
|     public static HttpServletRequest getRequest() { | ||||
|         return getServletRequestAttributes().getRequest(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取响应对象 | ||||
|      * | ||||
|      * @return / | ||||
|      */ | ||||
|     public static HttpServletResponse getResponse() { | ||||
|         return getServletRequestAttributes().getResponse(); | ||||
|     } | ||||
|  | ||||
|     private static ServletRequestAttributes getServletRequestAttributes() { | ||||
|         return (ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取浏览器及其版本信息 | ||||
|      * | ||||
|      * @param request | ||||
|      *            请求信息 | ||||
|      * @return 浏览器及其版本信息 | ||||
|      */ | ||||
|     public static String getBrowser(HttpServletRequest request) { | ||||
|         if (request == null) { | ||||
|             return null; | ||||
|         } | ||||
|         UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); | ||||
|         return userAgent.getBrowser().getName() + " " + userAgent.getVersion(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取响应所有的头(header)信息 | ||||
|      * | ||||
|      * @param response | ||||
|      *            响应对象{@link HttpServletResponse} | ||||
|      * @return header值 | ||||
|      */ | ||||
|     public static Map<String, Collection<String>> getHeaderMap(HttpServletResponse response) { | ||||
|         final Map<String, Collection<String>> headerMap = new HashMap<>(); | ||||
|  | ||||
|         final Collection<String> names = response.getHeaderNames(); | ||||
|         for (String name : names) { | ||||
|             headerMap.put(name, response.getHeaders(name)); | ||||
|         } | ||||
|         return headerMap; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,60 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.util.holder; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.dto.OperationLog; | ||||
|  | ||||
| /** | ||||
|  * 操作日志上下文持有者 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/25 8:55 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class LogContextHolder { | ||||
|  | ||||
|     private static final ThreadLocal<OperationLog> LOG_THREAD_LOCAL = new ThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 存储操作日志 | ||||
|      * | ||||
|      * @param operationLog | ||||
|      *            操作日志信息 | ||||
|      */ | ||||
|     public static void set(OperationLog operationLog) { | ||||
|         LOG_THREAD_LOCAL.set(operationLog); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取操作日志 | ||||
|      * | ||||
|      * @return 操作日志信息 | ||||
|      */ | ||||
|     public static OperationLog get() { | ||||
|         return LOG_THREAD_LOCAL.get(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 移除操作日志 | ||||
|      */ | ||||
|     public static void remove() { | ||||
|         LOG_THREAD_LOCAL.remove(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										41
									
								
								continew-admin-monitor/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								continew-admin-monitor/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
| Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|  http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| --> | ||||
| <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"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|  | ||||
|     <parent> | ||||
|         <artifactId>continew-admin</artifactId> | ||||
|         <groupId>top.charles7c</groupId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
|  | ||||
|     <artifactId>continew-admin-monitor</artifactId> | ||||
|     <packaging>jar</packaging> | ||||
|  | ||||
|     <name>${project.artifactId}</name> | ||||
|     <description>系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等)</description> | ||||
|  | ||||
|     <dependencies> | ||||
|         <!-- 公共模块(存放公共工具类,公共配置等) --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c</groupId> | ||||
|             <artifactId>continew-admin-common</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| </project> | ||||
| @@ -0,0 +1,41 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 操作日志注解(用于接口方法或类上) | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 20:00 | ||||
|  */ | ||||
| @Documented | ||||
| @Target({ElementType.METHOD, ElementType.TYPE}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface Log { | ||||
|  | ||||
|     /** | ||||
|      * 操作日志描述 | ||||
|      */ | ||||
|     String value() default ""; | ||||
|  | ||||
|     /** | ||||
|      * 是否忽略日志记录 | ||||
|      */ | ||||
|     boolean ignore() default false; | ||||
| } | ||||
| @@ -0,0 +1,45 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.config; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.servlet.config.annotation.EnableWebMvc; | ||||
| import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
|  | ||||
| import top.charles7c.cnadmin.monitor.interceptor.LogInterceptor; | ||||
|  | ||||
| /** | ||||
|  * 监控模块 Web MVC 配置 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/24 23:15 | ||||
|  */ | ||||
| @EnableWebMvc | ||||
| @Configuration | ||||
| @RequiredArgsConstructor | ||||
| public class WebMvcMonitorConfiguration implements WebMvcConfigurer { | ||||
|  | ||||
|     private final LogInterceptor logInterceptor; | ||||
|  | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) { | ||||
|         registry.addInterceptor(logInterceptor); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.config.properties; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * 操作日志配置属性 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/24 23:04 | ||||
|  */ | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "logging.operation") | ||||
| public class LogProperties { | ||||
|  | ||||
|     /** | ||||
|      * 是否启用操作日志 | ||||
|      */ | ||||
|     private Boolean enabled = false; | ||||
|  | ||||
|     /** | ||||
|      * 脱敏字段 | ||||
|      */ | ||||
|     private List<String> desensitize = new ArrayList<>(); | ||||
|  | ||||
|     /** | ||||
|      * 不记录操作日志的请求方式 | ||||
|      */ | ||||
|     private List<String> excludeMethods = new ArrayList<>(); | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.enums; | ||||
|  | ||||
| import lombok.Getter; | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| /** | ||||
|  * 操作日志级别枚举 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/25 9:09 | ||||
|  */ | ||||
| @Getter | ||||
| @RequiredArgsConstructor | ||||
| public enum LogLevelEnum { | ||||
|  | ||||
|     /** 普通 */ | ||||
|     INFO("普通"), | ||||
|  | ||||
|     /** 错误 */ | ||||
|     ERROR("错误"),; | ||||
|  | ||||
|     /** 描述 */ | ||||
|     private final String description; | ||||
| } | ||||
| @@ -0,0 +1,80 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.filter; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import org.springframework.core.Ordered; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
| import org.springframework.web.util.ContentCachingRequestWrapper; | ||||
| import org.springframework.web.util.ContentCachingResponseWrapper; | ||||
| import org.springframework.web.util.WebUtils; | ||||
|  | ||||
| /** | ||||
|  * 操作日志过滤器(缓存请求和响应体过滤器) | ||||
|  * | ||||
|  * <p> | ||||
|  * 由于 requestBody 和 responseBody 分别对应的是 InputStream 和 OutputStream,由于流的特性,读取完之后就无法再被使用了。 所以,需要额外缓存一次流信息。 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/24 21:16 | ||||
|  */ | ||||
| @Component | ||||
| public class LogFilter extends OncePerRequestFilter implements Ordered { | ||||
|  | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         return Ordered.LOWEST_PRECEDENCE - 10; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) | ||||
|         throws ServletException, IOException { | ||||
|         // 包装流,可重复读取 | ||||
|         if (!(request instanceof ContentCachingRequestWrapper)) { | ||||
|             request = new ContentCachingRequestWrapper(request); | ||||
|         } | ||||
|         if (!(response instanceof ContentCachingResponseWrapper)) { | ||||
|             response = new ContentCachingResponseWrapper(response); | ||||
|         } | ||||
|  | ||||
|         filterChain.doFilter(request, response); | ||||
|         updateResponse(response); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新响应(不操作这一步,会导致接口响应空白) | ||||
|      * | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      * @throws IOException | ||||
|      *             / | ||||
|      */ | ||||
|     private void updateResponse(HttpServletResponse response) throws IOException { | ||||
|         ContentCachingResponseWrapper responseWrapper = | ||||
|             WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class); | ||||
|         Objects.requireNonNull(responseWrapper).copyBodyToResponse(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,293 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.interceptor; | ||||
|  | ||||
| import java.util.Date; | ||||
| import java.util.Map; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
|  | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| import org.springframework.core.annotation.AnnotationUtils; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.method.HandlerMethod; | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
| import org.springframework.web.util.ContentCachingRequestWrapper; | ||||
| import org.springframework.web.util.ContentCachingResponseWrapper; | ||||
| import org.springframework.web.util.WebUtils; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.exceptions.ExceptionUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.servlet.ServletUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import cn.hutool.json.JSONUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.dto.OperationLog; | ||||
| import top.charles7c.cnadmin.common.util.IpUtils; | ||||
| import top.charles7c.cnadmin.common.util.ServletUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
| import top.charles7c.cnadmin.common.util.holder.LogContextHolder; | ||||
| import top.charles7c.cnadmin.monitor.annotation.Log; | ||||
| import top.charles7c.cnadmin.monitor.config.properties.LogProperties; | ||||
| import top.charles7c.cnadmin.monitor.enums.LogLevelEnum; | ||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||
|  | ||||
| /** | ||||
|  * 操作日志拦截器 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/24 21:14 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Component | ||||
| @RequiredArgsConstructor | ||||
| public class LogInterceptor implements HandlerInterceptor { | ||||
|  | ||||
|     private final LogProperties operationLogProperties; | ||||
|  | ||||
|     @Override | ||||
|     public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, | ||||
|         @NotNull Object handler) { | ||||
|         if (!checkIsNeedRecord(handler, request)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 记录操作时间 | ||||
|         this.logCreateTime(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, | ||||
|         @NotNull Object handler, Exception e) { | ||||
|         // 记录请求耗时及异常信息 | ||||
|         SysLog sysLog = this.logElapsedTimeAndException(); | ||||
|         if (sysLog == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 记录描述 | ||||
|         this.logDescription(sysLog, handler); | ||||
|         // 记录请求信息 | ||||
|         this.logRequest(sysLog, request); | ||||
|         // 记录响应信息 | ||||
|         this.logResponse(sysLog, response); | ||||
|  | ||||
|         // 保存操作日志 | ||||
|         SpringUtil.getApplicationContext().publishEvent(sysLog); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录操作时间 | ||||
|      */ | ||||
|     private void logCreateTime() { | ||||
|         OperationLog operationLog = new OperationLog(); | ||||
|         operationLog.setCreateUser(LoginHelper.getUserId()); | ||||
|         operationLog.setCreateTime(new Date()); | ||||
|         LogContextHolder.set(operationLog); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录请求耗时及异常信息 | ||||
|      * | ||||
|      * @return 日志信息 | ||||
|      */ | ||||
|     private SysLog logElapsedTimeAndException() { | ||||
|         OperationLog operationLog = LogContextHolder.get(); | ||||
|         if (operationLog != null) { | ||||
|             LogContextHolder.remove(); | ||||
|             SysLog sysLog = new SysLog(); | ||||
|             sysLog.setCreateTime(operationLog.getCreateTime()); | ||||
|             sysLog.setElapsedTime(System.currentTimeMillis() - sysLog.getCreateTime().getTime()); | ||||
|             sysLog.setLogLevel(LogLevelEnum.INFO); | ||||
|  | ||||
|             // 记录异常信息 | ||||
|             Exception exception = operationLog.getException(); | ||||
|             if (exception != null) { | ||||
|                 sysLog.setLogLevel(LogLevelEnum.ERROR); | ||||
|                 sysLog.setException(ExceptionUtil.stacktraceToString(operationLog.getException(), -1)); | ||||
|             } | ||||
|             return sysLog; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录日志描述 | ||||
|      * | ||||
|      * @param sysLog | ||||
|      *            日志信息 | ||||
|      * @param handler | ||||
|      *            处理器 | ||||
|      */ | ||||
|     private void logDescription(@NotNull SysLog sysLog, Object handler) { | ||||
|         HandlerMethod handlerMethod = (HandlerMethod)handler; | ||||
|         Operation methodOperation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Operation.class); | ||||
|         Log methodLog = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Log.class); | ||||
|  | ||||
|         if (methodOperation != null) { | ||||
|             sysLog.setDescription( | ||||
|                 StrUtil.isNotBlank(methodOperation.summary()) ? methodOperation.summary() : "请在该接口方法上指定操作日志描述"); | ||||
|         } | ||||
|         // 例如:@Log("获取验证码") -> 获取验证码 | ||||
|         if (methodLog != null && StrUtil.isNotBlank(methodLog.value())) { | ||||
|             sysLog.setDescription(methodLog.value()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录请求信息 | ||||
|      * | ||||
|      * @param sysLog | ||||
|      *            日志信息 | ||||
|      * @param request | ||||
|      *            请求对象 | ||||
|      */ | ||||
|     private void logRequest(@NotNull SysLog sysLog, @NotNull HttpServletRequest request) { | ||||
|         sysLog.setRequestUrl(StrUtil.isBlank(request.getQueryString()) ? request.getRequestURL().toString() | ||||
|             : request.getRequestURL().append("?").append(request.getQueryString()).toString()); | ||||
|         sysLog.setRequestMethod(request.getMethod()); | ||||
|         sysLog.setRequestHeader(this.desensitize(ServletUtil.getHeaderMap(request))); | ||||
|         String requestBody = this.getRequestBody(request); | ||||
|         if (StrUtil.isNotBlank(requestBody)) { | ||||
|             sysLog.setRequestBody(this.desensitize( | ||||
|                 JSONUtil.isTypeJSON(requestBody) ? JSONUtil.parseObj(requestBody) : ServletUtil.getParamMap(request))); | ||||
|         } | ||||
|         sysLog.setRequestIp(ServletUtil.getClientIP(request)); | ||||
|         sysLog.setLocation(IpUtils.getCityInfo(sysLog.getRequestIp())); | ||||
|         sysLog.setBrowser(ServletUtils.getBrowser(request)); | ||||
|         sysLog.setCreateUser(sysLog.getCreateUser() == null ? LoginHelper.getUserId() : sysLog.getCreateUser()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录响应信息 | ||||
|      * | ||||
|      * @param sysLog | ||||
|      *            日志信息 | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      */ | ||||
|     private void logResponse(SysLog sysLog, HttpServletResponse response) { | ||||
|         sysLog.setStatusCode(response.getStatus()); | ||||
|         sysLog.setResponseHeader(this.desensitize(ServletUtils.getHeaderMap(response))); | ||||
|         // 响应体(不记录非 JSON 响应数据) | ||||
|         String responseBody = this.getResponseBody(response); | ||||
|         if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) { | ||||
|             sysLog.setResponseBody(responseBody); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 数据脱敏 | ||||
|      * | ||||
|      * @param waitDesensitizeData | ||||
|      *            待脱敏数据 | ||||
|      * @return 脱敏后的 JSON 字符串数据 | ||||
|      */ | ||||
|     private String desensitize(Map waitDesensitizeData) { | ||||
|         String desensitizeDataStr = JSONUtil.toJsonStr(waitDesensitizeData); | ||||
|         try { | ||||
|             if (CollUtil.isEmpty(waitDesensitizeData)) { | ||||
|                 return desensitizeDataStr; | ||||
|             } | ||||
|  | ||||
|             for (String desensitizeProperty : operationLogProperties.getDesensitize()) { | ||||
|                 waitDesensitizeData.computeIfPresent(desensitizeProperty, (k, v) -> "****************"); | ||||
|                 waitDesensitizeData.computeIfPresent(desensitizeProperty.toLowerCase(), (k, v) -> "****************"); | ||||
|                 waitDesensitizeData.computeIfPresent(desensitizeProperty.toUpperCase(), (k, v) -> "****************"); | ||||
|             } | ||||
|             return JSONUtil.toJsonStr(waitDesensitizeData); | ||||
|         } catch (Exception ignored) { | ||||
|         } | ||||
|         return desensitizeDataStr; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取请求体 | ||||
|      * | ||||
|      * @param request | ||||
|      *            请求对象 | ||||
|      * @return 请求体 | ||||
|      */ | ||||
|     private String getRequestBody(HttpServletRequest request) { | ||||
|         String requestBody = ""; | ||||
|         ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); | ||||
|         if (wrapper != null) { | ||||
|             requestBody = StrUtil.utf8Str(wrapper.getContentAsByteArray()); | ||||
|         } | ||||
|         return requestBody; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取响应体 | ||||
|      * | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      * @return 响应体 | ||||
|      */ | ||||
|     private String getResponseBody(HttpServletResponse response) { | ||||
|         String responseBody = ""; | ||||
|         ContentCachingResponseWrapper wrapper = | ||||
|             WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class); | ||||
|         if (wrapper != null) { | ||||
|             responseBody = StrUtil.utf8Str(wrapper.getContentAsByteArray()); | ||||
|         } | ||||
|         return responseBody; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查是否要记录操作日志 | ||||
|      * | ||||
|      * @param handler | ||||
|      *            / | ||||
|      * @param request | ||||
|      *            / | ||||
|      * @return true 需要记录,false 不需要记录 | ||||
|      */ | ||||
|     private boolean checkIsNeedRecord(Object handler, HttpServletRequest request) { | ||||
|         // 1、未启用时,不需要记录操作日志 | ||||
|         if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // 2、排除不需要记录日志的接口 | ||||
|         HandlerMethod handlerMethod = (HandlerMethod)handler; | ||||
|         Log methodLog = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Log.class); | ||||
|         // 2.1 请求方式不要求记录且请求上没有 @Log 注解,则不记录操作日志 | ||||
|         if (operationLogProperties.getExcludeMethods().contains(request.getMethod()) && methodLog == null) { | ||||
|             return false; | ||||
|         } | ||||
|         // 2.2 如果接口上既没有 @Log 注解,也没有 @Operation 注解,则不记录操作日志 | ||||
|         Operation methodOperation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Operation.class); | ||||
|         if (methodLog == null && methodOperation == null) { | ||||
|             return false; | ||||
|         } | ||||
|         // 2.3 如果接口被隐藏,不记录操作日志 | ||||
|         if (methodOperation != null && methodOperation.hidden()) { | ||||
|             return false; | ||||
|         } | ||||
|         // 2.4 如果接口上有 @Log 注解,但是要求忽略该接口,则不记录操作日志 | ||||
|         return methodLog == null || !methodLog.ignore(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.mapper; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
|  | ||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||
|  | ||||
| /** | ||||
|  * 操作日志 Mapper | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/22 21:47 | ||||
|  */ | ||||
| public interface LogMapper extends BaseMapper<SysLog> {} | ||||
| @@ -0,0 +1,126 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.model.entity; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
|  | ||||
| import top.charles7c.cnadmin.monitor.enums.LogLevelEnum; | ||||
|  | ||||
| /** | ||||
|  * 操作日志实体 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/25 9:11 | ||||
|  */ | ||||
| @Data | ||||
| @TableName("sys_log") | ||||
| public class SysLog implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 日志ID | ||||
|      */ | ||||
|     @TableId | ||||
|     private Long logId; | ||||
|  | ||||
|     /** | ||||
|      * 日志级别 | ||||
|      */ | ||||
|     private LogLevelEnum logLevel; | ||||
|  | ||||
|     /** | ||||
|      * 日志描述 | ||||
|      */ | ||||
|     private String description; | ||||
|  | ||||
|     /** | ||||
|      * 请求 URL | ||||
|      */ | ||||
|     private String requestUrl; | ||||
|  | ||||
|     /** | ||||
|      * 请求方式 | ||||
|      */ | ||||
|     private String requestMethod; | ||||
|  | ||||
|     /** | ||||
|      * 请求头 | ||||
|      */ | ||||
|     private String requestHeader; | ||||
|  | ||||
|     /** | ||||
|      * 请求体 | ||||
|      */ | ||||
|     private String requestBody; | ||||
|  | ||||
|     /** | ||||
|      * 状态码 | ||||
|      */ | ||||
|     private Integer statusCode; | ||||
|  | ||||
|     /** | ||||
|      * 响应头 | ||||
|      */ | ||||
|     private String responseHeader; | ||||
|  | ||||
|     /** | ||||
|      * 响应体 | ||||
|      */ | ||||
|     private String responseBody; | ||||
|  | ||||
|     /** | ||||
|      * 请求耗时(ms) | ||||
|      */ | ||||
|     private Long elapsedTime; | ||||
|  | ||||
|     /** | ||||
|      * 请求IP | ||||
|      */ | ||||
|     private String requestIp; | ||||
|  | ||||
|     /** | ||||
|      * 操作地址 | ||||
|      */ | ||||
|     private String location; | ||||
|  | ||||
|     /** | ||||
|      * 浏览器 | ||||
|      */ | ||||
|     private String browser; | ||||
|  | ||||
|     /** | ||||
|      * 异常 | ||||
|      */ | ||||
|     private String exception; | ||||
|  | ||||
|     /** | ||||
|      * 操作人 | ||||
|      */ | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 操作时间 | ||||
|      */ | ||||
|     private Date createTime; | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.service; | ||||
|  | ||||
| /** | ||||
|  * 操作日志业务接口 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 20:12 | ||||
|  */ | ||||
| public interface LogService { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,48 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.service.impl; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import org.springframework.context.event.EventListener; | ||||
| import org.springframework.scheduling.annotation.Async; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import top.charles7c.cnadmin.monitor.mapper.LogMapper; | ||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||
| import top.charles7c.cnadmin.monitor.service.LogService; | ||||
|  | ||||
| /** | ||||
|  * 操作日志业务实现类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2022/12/23 20:12 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class LogServiceImpl implements LogService { | ||||
|  | ||||
|     private final LogMapper logMapper; | ||||
|  | ||||
|     @Async | ||||
|     @EventListener | ||||
|     public void save(SysLog sysLog) { | ||||
|         logMapper.insert(sysLog); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | ||||
| <mapper namespace="top.charles7c.cnadmin.monitor.mapper.LogMapper"> | ||||
| </mapper> | ||||
| @@ -44,6 +44,12 @@ limitations under the License. | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等) --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c</groupId> | ||||
|             <artifactId>continew-admin-monitor</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c</groupId> | ||||
|   | ||||
| @@ -19,6 +19,8 @@ continew-admin: | ||||
|   license: | ||||
|     name: Apache-2.0 | ||||
|     url: https://github.com/Charles7c/continew-admin/blob/dev/LICENSE | ||||
|   # 是否本地解析 IP 归属地 | ||||
|   ipAddrLocalParseEnabled: false | ||||
|  | ||||
| --- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置) | ||||
| logging: | ||||
| @@ -27,6 +29,17 @@ logging: | ||||
|   file: | ||||
|     path: @logging.file.path@ | ||||
|   config: classpath:logback-spring.xml | ||||
|   ## 操作日志配置 | ||||
|   operation: | ||||
|     # 是否启用操作日志 | ||||
|     enabled: true | ||||
|     # 不记录操作日志的请求方式 | ||||
|     #excludeMethods: | ||||
|     #  - GET | ||||
|     # 脱敏字段 | ||||
|     desensitize: | ||||
|       - password | ||||
|       - Authorization | ||||
|  | ||||
| --- ### 接口文档配置 | ||||
| springdoc: | ||||
| @@ -169,4 +182,13 @@ spring: | ||||
|     # 反序列化配置(JSON -> Bean) | ||||
|     deserialization: | ||||
|       # 允许反序列化不存在的属性 | ||||
|       fail_on_unknown_properties: false | ||||
|       fail_on_unknown_properties: false | ||||
|  | ||||
| --- ### 线程池配置 | ||||
| thread-pool: | ||||
|   # 是否启用线程池 | ||||
|   enabled: true | ||||
|   # 队列容量 | ||||
|   queueCapacity: 128 | ||||
|   # 活跃时间 | ||||
|   keepAliveSeconds: 300 | ||||
|   | ||||
| @@ -22,4 +22,27 @@ CREATE TABLE IF NOT EXISTS `sys_user`  ( | ||||
|     UNIQUE INDEX `uk_email`(`email`) USING BTREE, | ||||
|     INDEX `idx_createUser`(`create_user`) USING BTREE, | ||||
|     INDEX `idx_updateUser`(`update_user`) USING BTREE | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; | ||||
|  | ||||
| -- changeset Charles7c:2 | ||||
| CREATE TABLE IF NOT EXISTS `sys_log` ( | ||||
|     `log_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '日志ID', | ||||
|     `log_level` varchar(255) DEFAULT NULL COMMENT '日志级别', | ||||
|     `description` varchar(255) DEFAULT NULL COMMENT '日志描述', | ||||
|     `request_url` varchar(512) NOT NULL DEFAULT '' COMMENT '请求URL', | ||||
|     `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式', | ||||
|     `request_header` text COMMENT '请求头', | ||||
|     `request_body` text DEFAULT NULL COMMENT '请求体', | ||||
|     `status_code` int(11) unsigned DEFAULT NULL COMMENT '状态码', | ||||
|     `response_header` text DEFAULT NULL COMMENT '响应头', | ||||
|     `response_body` text DEFAULT NULL COMMENT '响应体', | ||||
|     `elapsed_time` bigint(20) unsigned DEFAULT NULL COMMENT '请求耗时(ms)', | ||||
|     `request_ip` varchar(255) DEFAULT NULL COMMENT '请求IP', | ||||
|     `location` varchar(512) DEFAULT NULL COMMENT '操作地址', | ||||
|     `browser` varchar(255) DEFAULT NULL COMMENT '浏览器', | ||||
|     `exception` text DEFAULT NULL COMMENT '异常', | ||||
|     `create_user` bigint(20) unsigned DEFAULT NULL COMMENT '操作人', | ||||
|     `create_time` datetime NOT NULL COMMENT '操作时间', | ||||
|     PRIMARY KEY (`log_id`) USING BTREE, | ||||
|     INDEX `idx_createUser`(`create_user`) USING BTREE | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表'; | ||||
|   | ||||
							
								
								
									
										16
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -30,6 +30,7 @@ limitations under the License. | ||||
|  | ||||
|     <modules> | ||||
|         <module>continew-admin-webapi</module> | ||||
|         <module>continew-admin-monitor</module> | ||||
|         <module>continew-admin-system</module> | ||||
|         <module>continew-admin-common</module> | ||||
|     </modules> | ||||
| @@ -50,6 +51,7 @@ limitations under the License. | ||||
|         <p6spy.version>3.9.1</p6spy.version> | ||||
|  | ||||
|         <!-- ### 工具库相关 ### --> | ||||
|         <ip2region.version>2.7.6</ip2region.version> | ||||
|         <knife4j.version>4.0.0</knife4j.version> | ||||
|         <redisson.version>3.19.0</redisson.version> | ||||
|         <easy-captcha.version>1.6.2</easy-captcha.version> | ||||
| @@ -118,6 +120,13 @@ limitations under the License. | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- ################ 工具库相关 ################ --> | ||||
|             <!-- 第三方封装 Ip2region(离线 IP 数据管理框架和定位库,支持亿级别的数据段,10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) --> | ||||
|             <dependency> | ||||
|                 <groupId>net.dreamlu</groupId> | ||||
|                 <artifactId>mica-ip2region</artifactId> | ||||
|                 <version>${ip2region.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- Knife4j(前身是 swagger-bootstrap-ui,集 Swagger2 和 OpenAPI3 为一体的增强解决方案) --> | ||||
|             <dependency> | ||||
|                 <groupId>com.github.xiaoymin</groupId> | ||||
| @@ -156,6 +165,13 @@ limitations under the License. | ||||
|                 <version>${project.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- 系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等) --> | ||||
|             <dependency> | ||||
|                 <groupId>top.charles7c</groupId> | ||||
|                 <artifactId>continew-admin-monitor</artifactId> | ||||
|                 <version>${project.version}</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) --> | ||||
|             <dependency> | ||||
|                 <groupId>top.charles7c</groupId> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user