新增:新增系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等),新增操作日志引擎,记录 HTTP 请求信息

This commit is contained in:
Charles7c 2022-12-25 13:16:15 +08:00
parent 78e84e8941
commit 727850933f
28 changed files with 1523 additions and 12 deletions

View File

@ -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 # 认证相关 VOView Object
│ │ │ └─ service # 认证相关业务接口及实现类
│ │ │ └─ impl # 认证相关业务实现类
│ │ │ │ └─ properties # 系统认证相关配置属性
│ │ │ ├─ model # 系统认证相关模型
│ │ │ │ ├─ request # 系统认证相关请求对象
│ │ │ │ └─ vo # 系统认证相关 VOView 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 # 公共 VOView Object
│ └─ util # 公共工具类
│ └─ helper # 公共 Helper助手
│ ├─ helper # 公共 Helper助手
│ └─ holder # 公共 Holder持有者
```
### License

View File

@ -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>

View File

@ -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());
};
}
}

View File

@ -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"));
}
}

View File

@ -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);
}
};
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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
*

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View 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>

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<>();
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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> {}

View File

@ -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;
}

View File

@ -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 {
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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
View File

@ -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>