refactor: 💥 适配 ContiNew Starter Log(日志模块)
1.continew-starter 1.0.1-SNAPSHOT => 1.1.0-SNAPSHOT 2.日志表结构及相关管理 UI 变更
This commit is contained in:
parent
349899b4fc
commit
9bf015059b
@ -56,9 +56,9 @@ public class SysConstants {
|
|||||||
public static final String DEFAULT_PASSWORD = "123456";
|
public static final String DEFAULT_PASSWORD = "123456";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录 URI
|
* 账号登录 URI
|
||||||
*/
|
*/
|
||||||
public static final String LOGIN_URI = "/auth/login";
|
public static final String LOGIN_URI = "/auth/account";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退出 URI
|
* 退出 URI
|
||||||
|
@ -41,7 +41,6 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.util.NumberUtil;
|
import cn.hutool.core.util.NumberUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
import top.charles7c.continew.admin.common.util.holder.LogContextHolder;
|
|
||||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||||
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
||||||
import top.charles7c.continew.starter.core.exception.BusinessException;
|
import top.charles7c.continew.starter.core.exception.BusinessException;
|
||||||
@ -65,7 +64,6 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(BadRequestException.class)
|
@ExceptionHandler(BadRequestException.class)
|
||||||
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
|
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
|
||||||
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
|
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
|
||||||
LogContextHolder.setErrorMsg(e.getMessage());
|
|
||||||
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +75,6 @@ public class GlobalExceptionHandler {
|
|||||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||||
String errorMsg =
|
String errorMsg =
|
||||||
CollUtil.join(e.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
|
CollUtil.join(e.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
|
||||||
LogContextHolder.setErrorMsg(errorMsg);
|
|
||||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +86,6 @@ public class GlobalExceptionHandler {
|
|||||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||||
String errorMsg = CollUtil.join(e.getAllErrors(), StringConstants.CHINESE_COMMA,
|
String errorMsg = CollUtil.join(e.getAllErrors(), StringConstants.CHINESE_COMMA,
|
||||||
DefaultMessageSourceResolvable::getDefaultMessage);
|
DefaultMessageSourceResolvable::getDefaultMessage);
|
||||||
LogContextHolder.setErrorMsg(errorMsg);
|
|
||||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +97,6 @@ public class GlobalExceptionHandler {
|
|||||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||||
String errorMsg = ExceptionUtils
|
String errorMsg = ExceptionUtils
|
||||||
.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
|
.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
|
||||||
LogContextHolder.setErrorMsg(errorMsg);
|
|
||||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +108,6 @@ public class GlobalExceptionHandler {
|
|||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
String errorMsg = StrUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter().getParameterType());
|
String errorMsg = StrUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter().getParameterType());
|
||||||
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
|
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
|
||||||
LogContextHolder.setErrorMsg(errorMsg);
|
|
||||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +119,6 @@ public class GlobalExceptionHandler {
|
|||||||
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
|
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
|
||||||
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
|
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
|
||||||
String errorMsg = String.format("请上传小于 %sMB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
|
String errorMsg = String.format("请上传小于 %sMB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
|
||||||
LogContextHolder.setErrorMsg(errorMsg);
|
|
||||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +133,6 @@ public class GlobalExceptionHandler {
|
|||||||
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线。";
|
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线。";
|
||||||
default -> "您的登录状态已过期,请重新登录。";
|
default -> "您的登录状态已过期,请重新登录。";
|
||||||
};
|
};
|
||||||
LogContextHolder.setErrorMsg(errorMsg);
|
|
||||||
return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg);
|
return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +159,6 @@ public class GlobalExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||||
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
||||||
LogContextHolder.setErrorMsg(e.getMessage());
|
|
||||||
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
|
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
|
||||||
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
|
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
|
||||||
}
|
}
|
||||||
@ -178,7 +169,6 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
public R handleServiceException(BusinessException e, HttpServletRequest request) {
|
public R handleServiceException(BusinessException e, HttpServletRequest request) {
|
||||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||||
LogContextHolder.setErrorMsg(e.getMessage());
|
|
||||||
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +178,6 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(RuntimeException.class)
|
@ExceptionHandler(RuntimeException.class)
|
||||||
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||||
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
|
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
|
||||||
LogContextHolder.setException(e);
|
|
||||||
return R.fail(e.getMessage());
|
return R.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +187,6 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(Throwable.class)
|
@ExceptionHandler(Throwable.class)
|
||||||
public R handleException(Throwable e, HttpServletRequest request) {
|
public R handleException(Throwable e, HttpServletRequest request) {
|
||||||
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
||||||
LogContextHolder.setException(e);
|
|
||||||
return R.fail(e.getMessage());
|
return R.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -75,20 +75,25 @@ public class LoginUser implements Serializable {
|
|||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录 IP
|
* IP
|
||||||
*/
|
*/
|
||||||
private String clientIp;
|
private String ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录地点
|
* IP 归属地
|
||||||
*/
|
*/
|
||||||
private String location;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
*/
|
*/
|
||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
private String os;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录时间
|
* 登录时间
|
||||||
*/
|
*/
|
||||||
|
@ -26,13 +26,12 @@ import lombok.NoArgsConstructor;
|
|||||||
import cn.dev33.satoken.context.SaHolder;
|
import cn.dev33.satoken.context.SaHolder;
|
||||||
import cn.dev33.satoken.session.SaSession;
|
import cn.dev33.satoken.session.SaSession;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
|
||||||
import top.charles7c.continew.admin.common.constant.CacheConstants;
|
import top.charles7c.continew.admin.common.constant.CacheConstants;
|
||||||
import top.charles7c.continew.admin.common.model.dto.LogContext;
|
|
||||||
import top.charles7c.continew.admin.common.model.dto.LoginUser;
|
import top.charles7c.continew.admin.common.model.dto.LoginUser;
|
||||||
import top.charles7c.continew.admin.common.util.holder.LogContextHolder;
|
|
||||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||||
import top.charles7c.continew.starter.core.util.IpUtils;
|
import top.charles7c.continew.starter.core.util.IpUtils;
|
||||||
import top.charles7c.continew.starter.core.util.ServletUtils;
|
import top.charles7c.continew.starter.core.util.ServletUtils;
|
||||||
@ -58,11 +57,11 @@ public class LoginHelper {
|
|||||||
public static String login(LoginUser loginUser) {
|
public static String login(LoginUser loginUser) {
|
||||||
// 记录登录信息
|
// 记录登录信息
|
||||||
HttpServletRequest request = ServletUtils.getRequest();
|
HttpServletRequest request = ServletUtils.getRequest();
|
||||||
loginUser.setClientIp(JakartaServletUtil.getClientIP(request));
|
loginUser.setIp(JakartaServletUtil.getClientIP(request));
|
||||||
loginUser.setLocation(IpUtils.getCityInfo(loginUser.getClientIp()));
|
loginUser.setAddress(IpUtils.getAddress(loginUser.getIp()));
|
||||||
loginUser.setBrowser(ServletUtils.getBrowser(request));
|
loginUser.setBrowser(ServletUtils.getBrowser(request));
|
||||||
LogContext logContext = LogContextHolder.get();
|
loginUser.setLoginTime(LocalDateTime.now());
|
||||||
loginUser.setLoginTime(null != logContext ? logContext.getCreateTime() : LocalDateTime.now());
|
loginUser.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false));
|
||||||
// 登录并缓存用户信息
|
// 登录并缓存用户信息
|
||||||
StpUtil.login(loginUser.getId());
|
StpUtil.login(loginUser.getId());
|
||||||
SaHolder.getStorage().set(CacheConstants.LOGIN_USER_KEY, loginUser);
|
SaHolder.getStorage().set(CacheConstants.LOGIN_USER_KEY, loginUser);
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.continew.admin.common.util.holder;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import top.charles7c.continew.admin.common.model.dto.LogContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统日志上下文持有者
|
|
||||||
*
|
|
||||||
* @author Charles7c
|
|
||||||
* @since 2022/12/25 8:55
|
|
||||||
*/
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class LogContextHolder {
|
|
||||||
|
|
||||||
private static final ThreadLocal<LogContext> LOG_THREAD_LOCAL = new ThreadLocal<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 存储系统日志上下文
|
|
||||||
*
|
|
||||||
* @param logContext
|
|
||||||
* 系统日志上下文信息
|
|
||||||
*/
|
|
||||||
public static void set(LogContext logContext) {
|
|
||||||
LOG_THREAD_LOCAL.set(logContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统日志上下文
|
|
||||||
*
|
|
||||||
* @return 系统日志上下文信息
|
|
||||||
*/
|
|
||||||
public static LogContext get() {
|
|
||||||
return LOG_THREAD_LOCAL.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除系统日志上下文
|
|
||||||
*/
|
|
||||||
public static void remove() {
|
|
||||||
LOG_THREAD_LOCAL.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在系统日志上下文中保存异常信息
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
* 异常信息
|
|
||||||
*/
|
|
||||||
public static void setException(Throwable e) {
|
|
||||||
LogContext logContext = get();
|
|
||||||
if (null != logContext) {
|
|
||||||
logContext.setErrorMsg(e.getMessage());
|
|
||||||
logContext.setException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在系统日志上下文中保存错误信息(非未知异常不记录异常信息,只记录错误信息)
|
|
||||||
*
|
|
||||||
* @param errorMsg
|
|
||||||
* 错误信息
|
|
||||||
*/
|
|
||||||
public static void setErrorMsg(String errorMsg) {
|
|
||||||
LogContext logContext = get();
|
|
||||||
if (null != logContext) {
|
|
||||||
logContext.setErrorMsg(errorMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,6 +16,12 @@
|
|||||||
<description>系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等)</description>
|
<description>系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等)</description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- ContiNew Starter 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>top.charles7c.continew</groupId>
|
||||||
|
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) -->
|
<!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>top.charles7c.continew</groupId>
|
<groupId>top.charles7c.continew</groupId>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.continew.admin.monitor.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统日志注解(用于接口方法或类上,辅助 Spring Doc OpenAPI3 使用效果最佳)
|
|
||||||
*
|
|
||||||
* @author Charles7c
|
|
||||||
* @since 2022/12/23 20:00
|
|
||||||
*/
|
|
||||||
@Documented
|
|
||||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface Log {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志描述(仅用于接口方法上)
|
|
||||||
* <p>
|
|
||||||
* 读取顺序:(越靠后优先级越高)<br>
|
|
||||||
* 1、读取对应接口方法上的 @Operation(summary="描述") 内容<br>
|
|
||||||
* 2、读取对应接口方法上的 @Log("描述") 内容<br>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
String value() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所属模块(用于接口方法或类上)
|
|
||||||
* <p>
|
|
||||||
* 读取顺序:(越靠后优先级越高)<br>
|
|
||||||
* 1、读取对应接口类上的 @Tag(name = "模块") 内容<br>
|
|
||||||
* 2、读取对应接口类上的 @Log(module = "模块") 内容<br>
|
|
||||||
* 3、读取对应接口方法上的 @Log(module = "模块") 内容
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
String module() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否忽略日志记录(用于接口方法或类上)
|
|
||||||
*/
|
|
||||||
boolean ignore() default false;
|
|
||||||
}
|
|
@ -16,30 +16,29 @@
|
|||||||
|
|
||||||
package top.charles7c.continew.admin.monitor.config;
|
package top.charles7c.continew.admin.monitor.config;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.continew.admin.monitor.interceptor.LogInterceptor;
|
import top.charles7c.continew.admin.monitor.mapper.LogMapper;
|
||||||
|
import top.charles7c.continew.admin.system.service.UserService;
|
||||||
|
import top.charles7c.continew.starter.log.common.dao.LogDao;
|
||||||
|
import top.charles7c.continew.starter.log.httptracepro.autoconfigure.ConditionalOnEnabledLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监控模块 Web MVC 配置
|
* 日志配置
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2022/12/24 23:15
|
* @since 2022/12/24 23:15
|
||||||
*/
|
*/
|
||||||
@EnableWebMvc
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
@ConditionalOnEnabledLog
|
||||||
public class WebMvcMonitorConfiguration implements WebMvcConfigurer {
|
public class LogConfiguration {
|
||||||
|
|
||||||
private final LogInterceptor logInterceptor;
|
/**
|
||||||
|
* 日志持久层接口本地实现类
|
||||||
@Override
|
*/
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
@Bean
|
||||||
registry.addInterceptor(logInterceptor);
|
public LogDao logDao(UserService userService, LogMapper logMapper) {
|
||||||
|
return new LogDaoLocalImpl(userService, logMapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.continew.admin.monitor.config;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.SaManager;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HttpStatus;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
|
||||||
|
import top.charles7c.continew.admin.auth.model.req.AccountLoginReq;
|
||||||
|
import top.charles7c.continew.admin.common.constant.SysConstants;
|
||||||
|
import top.charles7c.continew.admin.monitor.enums.LogStatusEnum;
|
||||||
|
import top.charles7c.continew.admin.monitor.mapper.LogMapper;
|
||||||
|
import top.charles7c.continew.admin.monitor.model.entity.LogDO;
|
||||||
|
import top.charles7c.continew.admin.system.service.UserService;
|
||||||
|
import top.charles7c.continew.starter.core.constant.StringConstants;
|
||||||
|
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||||
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.dao.LogDao;
|
||||||
|
import top.charles7c.continew.starter.log.common.model.LogRecord;
|
||||||
|
import top.charles7c.continew.starter.log.common.model.LogRequest;
|
||||||
|
import top.charles7c.continew.starter.log.common.model.LogResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志持久层接口本地实现类
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/12/16 23:55
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LogDaoLocalImpl implements LogDao {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final LogMapper logMapper;
|
||||||
|
|
||||||
|
@Async
|
||||||
|
@Override
|
||||||
|
public void add(LogRecord logRecord) {
|
||||||
|
LogDO logDO = new LogDO();
|
||||||
|
logDO.setDescription(logRecord.getDescription());
|
||||||
|
String module = logRecord.getModule();
|
||||||
|
logDO.setModule(
|
||||||
|
StrUtil.isNotBlank(module) ? logRecord.getModule().replace("API", StringConstants.EMPTY).trim() : null);
|
||||||
|
logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault()));
|
||||||
|
logDO.setTimeTaken(logRecord.getTimeTaken().toMillis());
|
||||||
|
// 请求信息
|
||||||
|
LogRequest logRequest = logRecord.getRequest();
|
||||||
|
logDO.setRequestMethod(logRequest.getMethod());
|
||||||
|
String requestUrl = logRequest.getUri().toString();
|
||||||
|
logDO.setRequestUrl(requestUrl);
|
||||||
|
Map<String, List<String>> requestHeaders = logRequest.getHeaders();
|
||||||
|
logDO.setRequestHeaders(JSONUtil.toJsonStr(requestHeaders));
|
||||||
|
String requestBody = logRequest.getBody();
|
||||||
|
logDO.setRequestBody(requestBody);
|
||||||
|
logDO.setIp(logRequest.getIp());
|
||||||
|
logDO.setAddress(logRequest.getAddress());
|
||||||
|
logDO.setBrowser(logRequest.getBrowser());
|
||||||
|
logDO.setOs(StrUtil.subBefore(logRequest.getOs(), " or", false));
|
||||||
|
// 响应信息
|
||||||
|
LogResponse logResponse = logRecord.getResponse();
|
||||||
|
Integer statusCode = logResponse.getStatus();
|
||||||
|
logDO.setStatusCode(statusCode);
|
||||||
|
logDO.setResponseHeaders(JSONUtil.toJsonStr(logResponse.getHeaders()));
|
||||||
|
String responseBody = logResponse.getBody();
|
||||||
|
logDO.setResponseBody(responseBody);
|
||||||
|
// 状态
|
||||||
|
logDO.setStatus(statusCode >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : LogStatusEnum.SUCCESS);
|
||||||
|
if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) {
|
||||||
|
R result = JSONUtil.toBean(responseBody, R.class);
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
logDO.setStatus(LogStatusEnum.FAILURE);
|
||||||
|
logDO.setErrorMsg(result.getMsg());
|
||||||
|
}
|
||||||
|
// 操作人
|
||||||
|
if (StrUtil.contains(requestUrl, SysConstants.LOGOUT_URI)) {
|
||||||
|
Long loginId = Convert.toLong(result.getData(), -1L);
|
||||||
|
logDO.setCreateUser(-1 != loginId ? loginId : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 操作人
|
||||||
|
if (StrUtil.contains(requestUrl, SysConstants.LOGIN_URI)) {
|
||||||
|
AccountLoginReq loginReq = JSONUtil.toBean(requestBody, AccountLoginReq.class);
|
||||||
|
logDO.setCreateUser(
|
||||||
|
ExceptionUtils.exToNull(() -> userService.getByUsername(loginReq.getUsername()).getId()));
|
||||||
|
} else if (!StrUtil.contains(requestUrl, SysConstants.LOGOUT_URI) && MapUtil.isNotEmpty(requestHeaders)
|
||||||
|
&& requestHeaders.containsKey(HttpHeaders.AUTHORIZATION)) {
|
||||||
|
String authorization = requestHeaders.get(HttpHeaders.AUTHORIZATION).get(0);
|
||||||
|
String token = authorization.replace(SaManager.getConfig().getTokenPrefix() + StringConstants.SPACE,
|
||||||
|
StringConstants.EMPTY);
|
||||||
|
logDO.setCreateUser(Convert.toLong(StpUtil.getLoginIdByToken(token)));
|
||||||
|
}
|
||||||
|
logMapper.insert(logDO);
|
||||||
|
}
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.continew.admin.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.system")
|
|
||||||
public class LogProperties {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否启用系统日志
|
|
||||||
*/
|
|
||||||
private Boolean enabled;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否记录内网 IP 操作
|
|
||||||
*/
|
|
||||||
private Boolean includeInnerIp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 排除请求方式(哪些请求方式不记录系统日志)
|
|
||||||
*/
|
|
||||||
private List<String> excludeMethods = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 脱敏字段
|
|
||||||
*/
|
|
||||||
private List<String> desensitizeFields = new ArrayList<>();
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.continew.admin.monitor.filter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
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(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
|
||||||
@NonNull 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,380 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.continew.admin.monitor.interceptor;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
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.date.LocalDateTimeUtil;
|
|
||||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
|
||||||
import cn.hutool.http.HttpStatus;
|
|
||||||
import cn.hutool.json.JSONArray;
|
|
||||||
import cn.hutool.json.JSONObject;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
|
|
||||||
import top.charles7c.continew.admin.auth.model.req.AccountLoginReq;
|
|
||||||
import top.charles7c.continew.admin.common.constant.SysConstants;
|
|
||||||
import top.charles7c.continew.admin.common.model.dto.LogContext;
|
|
||||||
import top.charles7c.continew.admin.common.util.helper.LoginHelper;
|
|
||||||
import top.charles7c.continew.admin.common.util.holder.LogContextHolder;
|
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.admin.monitor.config.properties.LogProperties;
|
|
||||||
import top.charles7c.continew.admin.monitor.enums.LogStatusEnum;
|
|
||||||
import top.charles7c.continew.admin.monitor.model.entity.LogDO;
|
|
||||||
import top.charles7c.continew.admin.system.service.UserService;
|
|
||||||
import top.charles7c.continew.starter.core.constant.StringConstants;
|
|
||||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
|
||||||
import top.charles7c.continew.starter.core.util.IpUtils;
|
|
||||||
import top.charles7c.continew.starter.core.util.ServletUtils;
|
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统日志拦截器
|
|
||||||
*
|
|
||||||
* @author Charles7c
|
|
||||||
* @since 2022/12/24 21:14
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class LogInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
private final UserService userService;
|
|
||||||
private final LogProperties operationLogProperties;
|
|
||||||
private static final String ENCRYPT_SYMBOL = "****************";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
|
||||||
@NonNull Object handler) {
|
|
||||||
if (this.isNeedRecord(handler, request)) {
|
|
||||||
// 记录时间
|
|
||||||
this.logCreateTime();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
|
||||||
@NonNull Object handler, Exception e) {
|
|
||||||
// 记录请求耗时及异常信息
|
|
||||||
LogDO logDO = this.logElapsedTimeAndException();
|
|
||||||
if (null == logDO) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HandlerMethod handlerMethod = (HandlerMethod)handler;
|
|
||||||
// 记录所属模块
|
|
||||||
this.logModule(logDO, handlerMethod);
|
|
||||||
// 记录日志描述
|
|
||||||
this.logDescription(logDO, handlerMethod);
|
|
||||||
// 记录请求信息
|
|
||||||
this.logRequest(logDO, request);
|
|
||||||
// 记录响应信息
|
|
||||||
this.logResponse(logDO, response);
|
|
||||||
// 保存系统日志
|
|
||||||
SpringUtil.getApplicationContext().publishEvent(logDO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录时间
|
|
||||||
*/
|
|
||||||
private void logCreateTime() {
|
|
||||||
LogContext logContext = new LogContext();
|
|
||||||
logContext.setCreateUser(LoginHelper.getUserId());
|
|
||||||
logContext.setCreateTime(LocalDateTime.now());
|
|
||||||
LogContextHolder.set(logContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录请求耗时及异常详情
|
|
||||||
*
|
|
||||||
* @return 系统日志信息
|
|
||||||
*/
|
|
||||||
private LogDO logElapsedTimeAndException() {
|
|
||||||
LogContext logContext = LogContextHolder.get();
|
|
||||||
if (null == logContext) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
LogDO logDO = new LogDO();
|
|
||||||
logDO.setCreateTime(logContext.getCreateTime());
|
|
||||||
logDO.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(logDO.getCreateTime()));
|
|
||||||
logDO.setStatus(LogStatusEnum.SUCCESS);
|
|
||||||
// 记录错误信息(非未知异常不记录异常详情,只记录错误信息)
|
|
||||||
String errorMsg = logContext.getErrorMsg();
|
|
||||||
if (StrUtil.isNotBlank(errorMsg)) {
|
|
||||||
logDO.setStatus(LogStatusEnum.FAILURE);
|
|
||||||
logDO.setErrorMsg(errorMsg);
|
|
||||||
}
|
|
||||||
// 记录异常详情
|
|
||||||
Throwable exception = logContext.getException();
|
|
||||||
if (null != exception) {
|
|
||||||
logDO.setStatus(LogStatusEnum.FAILURE);
|
|
||||||
logDO.setExceptionDetail(ExceptionUtil.stacktraceToString(exception, -1));
|
|
||||||
}
|
|
||||||
return logDO;
|
|
||||||
} finally {
|
|
||||||
LogContextHolder.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录所属模块
|
|
||||||
*
|
|
||||||
* @param logDO
|
|
||||||
* 系统日志信息
|
|
||||||
* @param handlerMethod
|
|
||||||
* 处理器方法
|
|
||||||
*/
|
|
||||||
private void logModule(LogDO logDO, HandlerMethod handlerMethod) {
|
|
||||||
Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
|
|
||||||
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
|
|
||||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
|
||||||
// 例如:@Tag(name = "部门管理") -> 部门管理
|
|
||||||
// (本框架代码规范)例如:@Tag(name = "部门管理 API") -> 部门管理
|
|
||||||
if (null != classTag) {
|
|
||||||
String name = classTag.name();
|
|
||||||
logDO.setModule(
|
|
||||||
StrUtil.isNotBlank(name) ? name.replace("API", StringConstants.EMPTY).trim() : "请在该接口类上指定所属模块");
|
|
||||||
}
|
|
||||||
// 例如:@Log(module = "部门管理") -> 部门管理
|
|
||||||
if (null != classLog && StrUtil.isNotBlank(classLog.module())) {
|
|
||||||
logDO.setModule(classLog.module());
|
|
||||||
}
|
|
||||||
if (null != methodLog && StrUtil.isNotBlank(methodLog.module())) {
|
|
||||||
logDO.setModule(methodLog.module());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录日志描述
|
|
||||||
*
|
|
||||||
* @param logDO
|
|
||||||
* 系统日志信息
|
|
||||||
* @param handlerMethod
|
|
||||||
* 处理器方法
|
|
||||||
*/
|
|
||||||
private void logDescription(LogDO logDO, HandlerMethod handlerMethod) {
|
|
||||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
|
||||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
|
||||||
// 例如:@Operation(summary="新增部门") -> 新增部门
|
|
||||||
if (null != methodOperation) {
|
|
||||||
logDO.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
|
|
||||||
}
|
|
||||||
// 例如:@Log("新增部门") -> 新增部门
|
|
||||||
if (null != methodLog && StrUtil.isNotBlank(methodLog.value())) {
|
|
||||||
logDO.setDescription(methodLog.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录请求信息
|
|
||||||
*
|
|
||||||
* @param logDO
|
|
||||||
* 系统日志信息
|
|
||||||
* @param request
|
|
||||||
* 请求对象
|
|
||||||
*/
|
|
||||||
private void logRequest(LogDO logDO, HttpServletRequest request) {
|
|
||||||
logDO.setRequestUrl(StrUtil.isBlank(request.getQueryString()) ? request.getRequestURL().toString() : request
|
|
||||||
.getRequestURL().append(StringConstants.QUESTION_MARK).append(request.getQueryString()).toString());
|
|
||||||
String method = request.getMethod();
|
|
||||||
logDO.setRequestMethod(method);
|
|
||||||
logDO.setRequestHeaders(this.desensitize(JakartaServletUtil.getHeaderMap(request)));
|
|
||||||
String requestBody = this.getRequestBody(request);
|
|
||||||
logDO.setCreateUser(ObjectUtil.defaultIfNull(logDO.getCreateUser(), LoginHelper.getUserId()));
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
if (requestURI.startsWith("/oauth")) {
|
|
||||||
logDO.setCreateUser(null);
|
|
||||||
}
|
|
||||||
if (null == logDO.getCreateUser() && SysConstants.LOGIN_URI.equals(requestURI)) {
|
|
||||||
AccountLoginReq loginReq = JSONUtil.toBean(requestBody, AccountLoginReq.class);
|
|
||||||
logDO.setCreateUser(
|
|
||||||
ExceptionUtils.exToNull(() -> userService.getByUsername(loginReq.getUsername()).getId()));
|
|
||||||
}
|
|
||||||
if (StrUtil.isNotBlank(requestBody)) {
|
|
||||||
if (JSONUtil.isTypeJSONObject(requestBody)) {
|
|
||||||
requestBody = this.desensitize(JSONUtil.parseObj(requestBody));
|
|
||||||
} else if (JSONUtil.isTypeJSONArray(requestBody)) {
|
|
||||||
JSONArray requestBodyJsonArr = JSONUtil.parseArray(requestBody);
|
|
||||||
List<JSONObject> requestBodyJsonObjList = new ArrayList<>(requestBodyJsonArr.size());
|
|
||||||
for (Object requestBodyJsonObj : requestBodyJsonArr) {
|
|
||||||
requestBodyJsonObjList
|
|
||||||
.add(JSONUtil.parseObj(this.desensitize(JSONUtil.parseObj(requestBodyJsonObj))));
|
|
||||||
}
|
|
||||||
requestBody = JSONUtil.toJsonStr(requestBodyJsonObjList);
|
|
||||||
} else {
|
|
||||||
requestBody = this.desensitize(JakartaServletUtil.getParamMap(request));
|
|
||||||
}
|
|
||||||
logDO.setRequestBody(requestBody);
|
|
||||||
}
|
|
||||||
logDO.setClientIp(JakartaServletUtil.getClientIP(request));
|
|
||||||
logDO.setLocation(IpUtils.getCityInfo(logDO.getClientIp()));
|
|
||||||
logDO.setBrowser(ServletUtils.getBrowser(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录响应信息
|
|
||||||
*
|
|
||||||
* @param logDO
|
|
||||||
* 系统日志信息
|
|
||||||
* @param response
|
|
||||||
* 响应对象
|
|
||||||
*/
|
|
||||||
private void logResponse(LogDO logDO, HttpServletResponse response) {
|
|
||||||
int status = response.getStatus();
|
|
||||||
logDO.setStatusCode(status);
|
|
||||||
logDO.setStatus(status >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : logDO.getStatus());
|
|
||||||
logDO.setResponseHeaders(this.desensitize(JakartaServletUtil.getHeadersMap(response)));
|
|
||||||
// 响应体(不记录非 JSON 响应数据)
|
|
||||||
String responseBody = this.getResponseBody(response);
|
|
||||||
if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) {
|
|
||||||
logDO.setResponseBody(responseBody);
|
|
||||||
// 业务状态码优先级高
|
|
||||||
try {
|
|
||||||
R result = JSONUtil.toBean(responseBody, R.class);
|
|
||||||
logDO.setStatusCode(result.getCode());
|
|
||||||
logDO.setStatus(result.isSuccess() ? LogStatusEnum.SUCCESS : LogStatusEnum.FAILURE);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据脱敏
|
|
||||||
*
|
|
||||||
* @param waitDesensitizeData
|
|
||||||
* 待脱敏数据
|
|
||||||
* @return 脱敏后的 JSON 字符串数据
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private String desensitize(Map waitDesensitizeData) {
|
|
||||||
String desensitizeDataStr = JSONUtil.toJsonStr(waitDesensitizeData);
|
|
||||||
try {
|
|
||||||
if (CollUtil.isEmpty(waitDesensitizeData)) {
|
|
||||||
return desensitizeDataStr;
|
|
||||||
}
|
|
||||||
for (String desensitizeProperty : operationLogProperties.getDesensitizeFields()) {
|
|
||||||
waitDesensitizeData.computeIfPresent(desensitizeProperty, (k, v) -> ENCRYPT_SYMBOL);
|
|
||||||
waitDesensitizeData.computeIfPresent(desensitizeProperty.toLowerCase(), (k, v) -> ENCRYPT_SYMBOL);
|
|
||||||
waitDesensitizeData.computeIfPresent(desensitizeProperty.toUpperCase(), (k, v) -> ENCRYPT_SYMBOL);
|
|
||||||
}
|
|
||||||
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 (null != wrapper) {
|
|
||||||
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 (null != wrapper) {
|
|
||||||
responseBody = StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
|
||||||
}
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否要记录系统日志
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
* 处理器
|
|
||||||
* @param request
|
|
||||||
* 请求对象
|
|
||||||
* @return true 需要记录;false 不需要记录
|
|
||||||
*/
|
|
||||||
private boolean isNeedRecord(Object handler, HttpServletRequest request) {
|
|
||||||
// 1、未启用时,不需要记录系统日志
|
|
||||||
if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 2、检查是否需要记录内网 IP 操作
|
|
||||||
boolean isInnerIp = IpUtils.isInnerIp(JakartaServletUtil.getClientIP(request));
|
|
||||||
if (isInnerIp && Boolean.FALSE.equals(operationLogProperties.getIncludeInnerIp())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 3、排除不需要记录系统日志的接口
|
|
||||||
HandlerMethod handlerMethod = (HandlerMethod)handler;
|
|
||||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
|
||||||
// 3.1 如果接口方法上既没有 @Log 注解,也没有 @Operation 注解,则不记录系统日志
|
|
||||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
|
||||||
if (null == methodLog && null == methodOperation) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 3.2 请求方式不要求记录且接口方法上没有 @Log 注解,则不记录系统日志
|
|
||||||
if (null == methodLog && operationLogProperties.getExcludeMethods().contains(request.getMethod())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 3.3 如果接口被隐藏,不记录系统日志
|
|
||||||
if (null != methodOperation && methodOperation.hidden()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 3.4 如果接口方法或类上有 @Log 注解,但是要求忽略该接口,则不记录系统日志
|
|
||||||
if (null != methodLog && methodLog.ignore()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
|
|
||||||
return null == classLog || !classLog.ignore();
|
|
||||||
}
|
|
||||||
}
|
|
@ -92,40 +92,40 @@ public class LogDO implements Serializable {
|
|||||||
private String responseBody;
|
private String responseBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求耗时(ms)
|
* 耗时(ms)
|
||||||
*/
|
*/
|
||||||
private Long elapsedTime;
|
private Long timeTaken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作状态
|
* IP
|
||||||
*/
|
*/
|
||||||
private LogStatusEnum status;
|
private String ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端IP
|
* IP 归属地
|
||||||
*/
|
*/
|
||||||
private String clientIp;
|
private String address;
|
||||||
|
|
||||||
/**
|
|
||||||
* IP归属地
|
|
||||||
*/
|
|
||||||
private String location;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
*/
|
*/
|
||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
private String os;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private LogStatusEnum status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
private String errorMsg;
|
private String errorMsg;
|
||||||
|
|
||||||
/**
|
|
||||||
* 异常详情
|
|
||||||
*/
|
|
||||||
private String exceptionDetail;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建人
|
* 创建人
|
||||||
*/
|
*/
|
||||||
|
@ -53,13 +53,13 @@ public class LoginLogResp extends LogResp {
|
|||||||
* 登录 IP
|
* 登录 IP
|
||||||
*/
|
*/
|
||||||
@Schema(description = "登录 IP", example = "192.168.0.1")
|
@Schema(description = "登录 IP", example = "192.168.0.1")
|
||||||
private String clientIp;
|
private String ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录地点
|
* 登录地点
|
||||||
*/
|
*/
|
||||||
@Schema(description = "登录地点", example = "中国北京北京市")
|
@Schema(description = "登录地点", example = "中国北京北京市")
|
||||||
private String location;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
@ -67,6 +67,12 @@ public class LoginLogResp extends LogResp {
|
|||||||
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
|
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
|
||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作系统", example = "Windows 10")
|
||||||
|
private String os;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
|
@ -50,22 +50,16 @@ public class OperationLogResp extends LogResp {
|
|||||||
private String module;
|
private String module;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作状态
|
* 操作 IP
|
||||||
*/
|
*/
|
||||||
@Schema(description = "操作状态(1:成功;2:失败)", type = "Integer", allowableValues = {"1", "2"}, example = "1")
|
@Schema(description = "操作 IP", example = "192.168.0.1")
|
||||||
private LogStatusEnum status;
|
private String ip;
|
||||||
|
|
||||||
/**
|
|
||||||
* 操作IP
|
|
||||||
*/
|
|
||||||
@Schema(description = "操作IP", example = "192.168.0.1")
|
|
||||||
private String clientIp;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作地点
|
* 操作地点
|
||||||
*/
|
*/
|
||||||
@Schema(description = "操作地点", example = "中国北京北京市")
|
@Schema(description = "操作地点", example = "中国北京北京市")
|
||||||
private String location;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
@ -73,6 +67,12 @@ public class OperationLogResp extends LogResp {
|
|||||||
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
|
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
|
||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作状态
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作状态(1:成功;2:失败)", type = "Integer", allowableValues = {"1", "2"}, example = "1")
|
||||||
|
private LogStatusEnum status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误信息
|
* 错误信息
|
||||||
*/
|
*/
|
||||||
|
@ -56,7 +56,7 @@ public class SystemLogDetailResp extends LogResp {
|
|||||||
/**
|
/**
|
||||||
* 请求头
|
* 请求头
|
||||||
*/
|
*/
|
||||||
@Schema(description = "请求头", example = "{\"Origin\": \"https://cnadmin.charles7c.top\",...}")
|
@Schema(description = "请求头", example = "{\"Origin\": [\"https://cnadmin.charles7c.top\"],...}")
|
||||||
private String requestHeaders;
|
private String requestHeaders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,16 +78,16 @@ public class SystemLogDetailResp extends LogResp {
|
|||||||
private String responseBody;
|
private String responseBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端IP
|
* IP
|
||||||
*/
|
*/
|
||||||
@Schema(description = "客户端IP", example = "192.168.0.1")
|
@Schema(description = "IP", example = "192.168.0.1")
|
||||||
private String clientIp;
|
private String ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP归属地
|
* 地址
|
||||||
*/
|
*/
|
||||||
@Schema(description = "IP归属地", example = "中国北京北京市")
|
@Schema(description = "地址", example = "中国北京北京市")
|
||||||
private String location;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
@ -96,8 +96,14 @@ public class SystemLogDetailResp extends LogResp {
|
|||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求耗时(ms)
|
* 操作系统
|
||||||
*/
|
*/
|
||||||
@Schema(description = "请求耗时(ms)", example = "58")
|
@Schema(description = "操作系统", example = "Windows 10")
|
||||||
private Long elapsedTime;
|
private String os;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 耗时(ms)
|
||||||
|
*/
|
||||||
|
@Schema(description = "耗时(ms)", example = "58")
|
||||||
|
private Long timeTaken;
|
||||||
}
|
}
|
||||||
|
@ -54,16 +54,16 @@ public class SystemLogResp extends LogResp {
|
|||||||
private String requestUrl;
|
private String requestUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端IP
|
* IP
|
||||||
*/
|
*/
|
||||||
@Schema(description = "客户端IP", example = "192.168.0.1")
|
@Schema(description = "IP", example = "192.168.0.1")
|
||||||
private String clientIp;
|
private String ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP归属地
|
* 地址
|
||||||
*/
|
*/
|
||||||
@Schema(description = "IP归属地", example = "中国北京北京市")
|
@Schema(description = "地址", example = "中国北京北京市")
|
||||||
private String location;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
@ -72,20 +72,8 @@ public class SystemLogResp extends LogResp {
|
|||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求耗时(ms)
|
* 耗时(ms)
|
||||||
*/
|
*/
|
||||||
@Schema(description = "请求耗时(ms)", example = "58")
|
@Schema(description = "耗时(ms)", example = "58")
|
||||||
private Long elapsedTime;
|
private Long timeTaken;
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误信息
|
|
||||||
*/
|
|
||||||
@Schema(description = "错误信息")
|
|
||||||
private String errorMsg;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异常详情
|
|
||||||
*/
|
|
||||||
@Schema(description = "异常详情")
|
|
||||||
private String exceptionDetail;
|
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ import java.util.stream.Collectors;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.springframework.context.event.EventListener;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
@ -63,27 +61,18 @@ public class LogServiceImpl implements LogService {
|
|||||||
private final LogMapper logMapper;
|
private final LogMapper logMapper;
|
||||||
private final CommonUserService commonUserService;
|
private final CommonUserService commonUserService;
|
||||||
|
|
||||||
@Async
|
|
||||||
@EventListener
|
|
||||||
public void save(LogDO logDO) {
|
|
||||||
logMapper.insert(logDO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageDataResp<OperationLogResp> page(OperationLogQuery query, PageQuery pageQuery) {
|
public PageDataResp<OperationLogResp> page(OperationLogQuery query, PageQuery pageQuery) {
|
||||||
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
||||||
|
|
||||||
// 限定查询信息
|
// 限定查询信息
|
||||||
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(OperationLogResp.class);
|
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(OperationLogResp.class);
|
||||||
List<String> columnNameList =
|
List<String> columnNameList =
|
||||||
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
||||||
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
||||||
queryWrapper.select(columnNameList);
|
queryWrapper.select(columnNameList);
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||||
PageDataResp<OperationLogResp> pageDataResp = PageDataResp.build(page, OperationLogResp.class);
|
PageDataResp<OperationLogResp> pageDataResp = PageDataResp.build(page, OperationLogResp.class);
|
||||||
|
|
||||||
// 填充数据(如果是查询个人操作日志,只查询一次用户信息即可)
|
// 填充数据(如果是查询个人操作日志,只查询一次用户信息即可)
|
||||||
if (null != query.getUid()) {
|
if (null != query.getUid()) {
|
||||||
String nickname = ExceptionUtils.exToNull(() -> commonUserService.getNicknameById(query.getUid()));
|
String nickname = ExceptionUtils.exToNull(() -> commonUserService.getNicknameById(query.getUid()));
|
||||||
@ -98,18 +87,15 @@ public class LogServiceImpl implements LogService {
|
|||||||
public PageDataResp<LoginLogResp> page(LoginLogQuery query, PageQuery pageQuery) {
|
public PageDataResp<LoginLogResp> page(LoginLogQuery query, PageQuery pageQuery) {
|
||||||
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
||||||
queryWrapper.eq("module", "登录");
|
queryWrapper.eq("module", "登录");
|
||||||
|
|
||||||
// 限定查询信息
|
// 限定查询信息
|
||||||
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(LoginLogResp.class);
|
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(LoginLogResp.class);
|
||||||
List<String> columnNameList =
|
List<String> columnNameList =
|
||||||
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
||||||
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
||||||
queryWrapper.select(columnNameList);
|
queryWrapper.select(columnNameList);
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||||
PageDataResp<LoginLogResp> pageDataResp = PageDataResp.build(page, LoginLogResp.class);
|
PageDataResp<LoginLogResp> pageDataResp = PageDataResp.build(page, LoginLogResp.class);
|
||||||
|
|
||||||
// 填充数据
|
// 填充数据
|
||||||
pageDataResp.getList().forEach(this::fill);
|
pageDataResp.getList().forEach(this::fill);
|
||||||
return pageDataResp;
|
return pageDataResp;
|
||||||
@ -118,18 +104,15 @@ public class LogServiceImpl implements LogService {
|
|||||||
@Override
|
@Override
|
||||||
public PageDataResp<SystemLogResp> page(SystemLogQuery query, PageQuery pageQuery) {
|
public PageDataResp<SystemLogResp> page(SystemLogQuery query, PageQuery pageQuery) {
|
||||||
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
||||||
|
|
||||||
// 限定查询信息
|
// 限定查询信息
|
||||||
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(SystemLogResp.class);
|
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(SystemLogResp.class);
|
||||||
List<String> columnNameList =
|
List<String> columnNameList =
|
||||||
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
||||||
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
||||||
queryWrapper.select(columnNameList);
|
queryWrapper.select(columnNameList);
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||||
PageDataResp<SystemLogResp> pageDataResp = PageDataResp.build(page, SystemLogResp.class);
|
PageDataResp<SystemLogResp> pageDataResp = PageDataResp.build(page, SystemLogResp.class);
|
||||||
|
|
||||||
// 填充数据
|
// 填充数据
|
||||||
pageDataResp.getList().forEach(this::fill);
|
pageDataResp.getList().forEach(this::fill);
|
||||||
return pageDataResp;
|
return pageDataResp;
|
||||||
|
@ -66,13 +66,13 @@ public class OnlineUserResp implements Serializable {
|
|||||||
* 登录 IP
|
* 登录 IP
|
||||||
*/
|
*/
|
||||||
@Schema(description = "登录 IP", example = "192.168.0.1")
|
@Schema(description = "登录 IP", example = "192.168.0.1")
|
||||||
private String clientIp;
|
private String ip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录地点
|
* 登录地点
|
||||||
*/
|
*/
|
||||||
@Schema(description = "登录地点", example = "中国北京北京市")
|
@Schema(description = "登录地点", example = "中国北京北京市")
|
||||||
private String location;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浏览器
|
* 浏览器
|
||||||
@ -80,6 +80,12 @@ public class OnlineUserResp implements Serializable {
|
|||||||
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
|
@Schema(description = "浏览器", example = "Chrome 115.0.0.0")
|
||||||
private String browser;
|
private String browser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作系统
|
||||||
|
*/
|
||||||
|
@Schema(description = "操作系统", example = "Windows 10")
|
||||||
|
private String os;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录时间
|
* 登录时间
|
||||||
*/
|
*/
|
||||||
|
@ -5,9 +5,10 @@ const BASE_URL = '/monitor/log';
|
|||||||
|
|
||||||
export interface LogRecord {
|
export interface LogRecord {
|
||||||
id?: number;
|
id?: number;
|
||||||
clientIp: string;
|
ip: string;
|
||||||
location: string;
|
address: string;
|
||||||
browser: string;
|
browser: string;
|
||||||
|
os: string;
|
||||||
createTime: string;
|
createTime: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ export interface OperationLogRecord extends LogRecord {
|
|||||||
module: string;
|
module: string;
|
||||||
description: string;
|
description: string;
|
||||||
status: number;
|
status: number;
|
||||||
errorMsg: string;
|
errorMsgString: string;
|
||||||
createUserString: string;
|
createUserString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,8 +31,7 @@ export interface SystemLogRecord extends LogRecord {
|
|||||||
statusCode: number;
|
statusCode: number;
|
||||||
requestMethod: string;
|
requestMethod: string;
|
||||||
requestUrl: string;
|
requestUrl: string;
|
||||||
elapsedTime: number;
|
timeTaken: number;
|
||||||
exceptionDetail?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemLogDetailRecord extends SystemLogRecord {
|
export interface SystemLogDetailRecord extends SystemLogRecord {
|
||||||
|
@ -7,9 +7,10 @@ export interface DataRecord {
|
|||||||
token: string;
|
token: string;
|
||||||
username: string;
|
username: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
clientIp: string;
|
ip: string;
|
||||||
location: string;
|
address: string;
|
||||||
browser: string;
|
browser: string;
|
||||||
|
os: string;
|
||||||
loginTime: string;
|
loginTime: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,9 +72,10 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column title="登录 IP" data-index="clientIp" />
|
<a-table-column title="登录 IP" data-index="ip" />
|
||||||
<a-table-column title="登录地点" data-index="location" />
|
<a-table-column title="登录地点" data-index="address" />
|
||||||
<a-table-column title="浏览器" data-index="browser" />
|
<a-table-column title="浏览器" data-index="browser" />
|
||||||
|
<a-table-column title="终端系统" data-index="os" />
|
||||||
<a-table-column title="登录时间" data-index="createTime" />
|
<a-table-column title="登录时间" data-index="createTime" />
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
@ -83,8 +83,8 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column title="操作 IP" data-index="clientIp" />
|
<a-table-column title="操作 IP" data-index="ip" />
|
||||||
<a-table-column title="操作地点" data-index="location" />
|
<a-table-column title="操作地点" data-index="address" />
|
||||||
<a-table-column title="浏览器" data-index="browser" />
|
<a-table-column title="浏览器" data-index="browser" />
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
@ -74,21 +74,21 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column title="客户端 IP" data-index="clientIp" />
|
<a-table-column title="IP" data-index="ip" />
|
||||||
<a-table-column title="IP 归属地" data-index="location" />
|
<a-table-column title="地址" data-index="address" />
|
||||||
<a-table-column title="浏览器" data-index="browser" />
|
<a-table-column title="浏览器" data-index="browser" />
|
||||||
<a-table-column title="请求耗时">
|
<a-table-column title="耗时">
|
||||||
<template #cell="{ record }">
|
<template #cell="{ record }">
|
||||||
<a-tag v-if="record.elapsedTime > 500" color="red"
|
<a-tag v-if="record.timeTaken > 500" color="red"
|
||||||
>{{ record.elapsedTime }} ms</a-tag
|
>{{ record.timeTaken }} ms</a-tag
|
||||||
>
|
>
|
||||||
<a-tag v-else-if="record.elapsedTime > 200" color="orange"
|
<a-tag v-else-if="record.timeTaken > 200" color="orange"
|
||||||
>{{ record.elapsedTime }} ms</a-tag
|
>{{ record.timeTaken }} ms</a-tag
|
||||||
>
|
>
|
||||||
<a-tag v-else color="green">{{ record.elapsedTime }} ms</a-tag>
|
<a-tag v-else color="green">{{ record.timeTaken }} ms</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column title="创建时间" data-index="createTime" />
|
<a-table-column title="请求时间" data-index="createTime" />
|
||||||
<a-table-column title="操作" align="center">
|
<a-table-column title="操作" align="center">
|
||||||
<template #cell="{ record }">
|
<template #cell="{ record }">
|
||||||
<a-button
|
<a-button
|
||||||
@ -99,15 +99,6 @@
|
|||||||
>
|
>
|
||||||
<template #icon><icon-eye /></template>详情
|
<template #icon><icon-eye /></template>详情
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
|
||||||
v-if="record.exceptionDetail"
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
title="查看异常详情"
|
|
||||||
@click="toExceptionDetail(record)"
|
|
||||||
>
|
|
||||||
<template #icon><icon-bug /></template>异常
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
</template>
|
</template>
|
||||||
@ -125,11 +116,11 @@
|
|||||||
>
|
>
|
||||||
<div style="margin: 10px 0 0 10px">
|
<div style="margin: 10px 0 0 10px">
|
||||||
<a-descriptions title="基础信息" :column="2" bordered>
|
<a-descriptions title="基础信息" :column="2" bordered>
|
||||||
<a-descriptions-item label="客户端 IP">
|
<a-descriptions-item label="IP">
|
||||||
<a-skeleton v-if="loading" :animation="true">
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||||
</a-skeleton>
|
</a-skeleton>
|
||||||
<span v-else>{{ systemLog.clientIp }}</span>
|
<span v-else>{{ systemLog.ip }}</span>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="浏览器">
|
<a-descriptions-item label="浏览器">
|
||||||
<a-skeleton v-if="loading" :animation="true">
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
@ -137,34 +128,40 @@
|
|||||||
</a-skeleton>
|
</a-skeleton>
|
||||||
<span v-else>{{ systemLog.browser }}</span>
|
<span v-else>{{ systemLog.browser }}</span>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="IP 归属地">
|
<a-descriptions-item label="地址">
|
||||||
<a-skeleton v-if="loading" :animation="true">
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||||
</a-skeleton>
|
</a-skeleton>
|
||||||
<span v-else>{{ systemLog.location }}</span>
|
<span v-else>{{ systemLog.address }}</span>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="请求耗时">
|
<a-descriptions-item label="操作系统">
|
||||||
<a-skeleton v-if="loading" :animation="true">
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||||
</a-skeleton>
|
</a-skeleton>
|
||||||
<span v-else>
|
<span v-else>{{ systemLog.os }}</span>
|
||||||
<a-tag v-if="systemLog.elapsedTime > 500" color="red">
|
|
||||||
{{ systemLog.elapsedTime }} ms
|
|
||||||
</a-tag>
|
|
||||||
<a-tag v-else-if="systemLog.elapsedTime > 200" color="orange">
|
|
||||||
{{ systemLog.elapsedTime }} ms
|
|
||||||
</a-tag>
|
|
||||||
<a-tag v-else color="green"
|
|
||||||
>{{ systemLog.elapsedTime }} ms</a-tag
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="创建时间">
|
<a-descriptions-item label="请求时间">
|
||||||
<a-skeleton v-if="loading" :animation="true">
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||||
</a-skeleton>
|
</a-skeleton>
|
||||||
<span v-else>{{ systemLog.createTime }}</span>
|
<span v-else>{{ systemLog.createTime }}</span>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="耗时">
|
||||||
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
|
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||||
|
</a-skeleton>
|
||||||
|
<span v-else>
|
||||||
|
<a-tag v-if="systemLog.timeTaken > 500" color="red">
|
||||||
|
{{ systemLog.timeTaken }} ms
|
||||||
|
</a-tag>
|
||||||
|
<a-tag v-else-if="systemLog.timeTaken > 200" color="orange">
|
||||||
|
{{ systemLog.timeTaken }} ms
|
||||||
|
</a-tag>
|
||||||
|
<a-tag v-else color="green"
|
||||||
|
>{{ systemLog.timeTaken }} ms</a-tag
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
<a-descriptions
|
<a-descriptions
|
||||||
title="协议信息"
|
title="协议信息"
|
||||||
@ -256,20 +253,6 @@
|
|||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
</div>
|
</div>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
|
|
||||||
<!-- 异常详情区域 -->
|
|
||||||
<a-modal
|
|
||||||
title="异常详情"
|
|
||||||
:visible="exceptionDetailVisible"
|
|
||||||
width="83%"
|
|
||||||
:footer="false"
|
|
||||||
top="30px"
|
|
||||||
unmount-on-close
|
|
||||||
render-to-body
|
|
||||||
@cancel="handleExceptionDetailCancel"
|
|
||||||
>
|
|
||||||
<pre>{{ exceptionDetail }}</pre>
|
|
||||||
</a-modal>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -297,17 +280,16 @@
|
|||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
responseHeaders: '',
|
responseHeaders: '',
|
||||||
responseBody: '',
|
responseBody: '',
|
||||||
elapsedTime: 0,
|
timeTaken: 0,
|
||||||
clientIp: '',
|
ip: '',
|
||||||
location: '',
|
address: '',
|
||||||
browser: '',
|
browser: '',
|
||||||
|
os: '',
|
||||||
createTime: '',
|
createTime: '',
|
||||||
});
|
});
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const exceptionDetail = ref('');
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const exceptionDetailVisible = ref(false);
|
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
// 查询参数
|
// 查询参数
|
||||||
@ -362,24 +344,6 @@
|
|||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 查看异常详情
|
|
||||||
*
|
|
||||||
* @param record 记录信息
|
|
||||||
*/
|
|
||||||
const toExceptionDetail = async (record: SystemLogRecord) => {
|
|
||||||
exceptionDetail.value = record.exceptionDetail || '';
|
|
||||||
exceptionDetailVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭异常详情
|
|
||||||
*/
|
|
||||||
const handleExceptionDetailCancel = () => {
|
|
||||||
exceptionDetail.value = '';
|
|
||||||
exceptionDetailVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询
|
* 查询
|
||||||
*/
|
*/
|
||||||
|
@ -63,9 +63,10 @@
|
|||||||
{{ record.nickname }}({{ record.username }})
|
{{ record.nickname }}({{ record.username }})
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column title="登录 IP" data-index="clientIp" />
|
<a-table-column title="登录 IP" data-index="ip" />
|
||||||
<a-table-column title="登录地点" data-index="location" />
|
<a-table-column title="登录地点" data-index="address" />
|
||||||
<a-table-column title="浏览器" data-index="browser" />
|
<a-table-column title="浏览器" data-index="browser" />
|
||||||
|
<a-table-column title="终端系统" data-index="os" />
|
||||||
<a-table-column title="登录时间" data-index="loginTime" />
|
<a-table-column title="登录时间" data-index="loginTime" />
|
||||||
<a-table-column
|
<a-table-column
|
||||||
v-if="checkPermission(['monitor:online:user:delete'])"
|
v-if="checkPermission(['monitor:online:user:delete'])"
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</a-table-column>
|
</a-table-column>
|
||||||
<a-table-column title="操作 IP" data-index="clientIp" />
|
<a-table-column title="操作 IP" data-index="ip" />
|
||||||
<a-table-column title="操作地点" data-index="location" />
|
<a-table-column title="操作地点" data-index="address" />
|
||||||
<a-table-column title="浏览器" data-index="browser" />
|
<a-table-column title="浏览器" data-index="browser" />
|
||||||
</template>
|
</template>
|
||||||
<template #pagination-left>
|
<template #pagination-left>
|
||||||
|
@ -43,13 +43,13 @@ import top.charles7c.continew.admin.common.constant.CacheConstants;
|
|||||||
import top.charles7c.continew.admin.common.model.dto.LoginUser;
|
import top.charles7c.continew.admin.common.model.dto.LoginUser;
|
||||||
import top.charles7c.continew.admin.common.util.SecureUtils;
|
import top.charles7c.continew.admin.common.util.SecureUtils;
|
||||||
import top.charles7c.continew.admin.common.util.helper.LoginHelper;
|
import top.charles7c.continew.admin.common.util.helper.LoginHelper;
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.admin.system.model.resp.UserDetailResp;
|
import top.charles7c.continew.admin.system.model.resp.UserDetailResp;
|
||||||
import top.charles7c.continew.admin.system.service.UserService;
|
import top.charles7c.continew.admin.system.service.UserService;
|
||||||
import top.charles7c.continew.starter.cache.redisson.util.RedisUtils;
|
import top.charles7c.continew.starter.cache.redisson.util.RedisUtils;
|
||||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||||
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证 API
|
* 认证 API
|
||||||
@ -115,9 +115,10 @@ public class AuthController {
|
|||||||
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx",
|
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx",
|
||||||
in = ParameterIn.HEADER)
|
in = ParameterIn.HEADER)
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public R logout() {
|
public R<Object> logout() {
|
||||||
|
Object loginId = StpUtil.getLoginId(-1L);
|
||||||
StpUtil.logout();
|
StpUtil.logout();
|
||||||
return R.ok();
|
return R.ok(loginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Log(ignore = true)
|
@Log(ignore = true)
|
||||||
|
@ -32,10 +32,10 @@ import cn.dev33.satoken.stp.StpUtil;
|
|||||||
|
|
||||||
import top.charles7c.continew.admin.auth.model.resp.LoginResp;
|
import top.charles7c.continew.admin.auth.model.resp.LoginResp;
|
||||||
import top.charles7c.continew.admin.auth.service.LoginService;
|
import top.charles7c.continew.admin.auth.service.LoginService;
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
||||||
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||||
|
|
||||||
import me.zhyd.oauth.model.AuthCallback;
|
import me.zhyd.oauth.model.AuthCallback;
|
||||||
import me.zhyd.oauth.model.AuthResponse;
|
import me.zhyd.oauth.model.AuthResponse;
|
||||||
|
@ -45,7 +45,6 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import top.charles7c.continew.admin.common.config.properties.LocalStorageProperties;
|
import top.charles7c.continew.admin.common.config.properties.LocalStorageProperties;
|
||||||
import top.charles7c.continew.admin.common.constant.CacheConstants;
|
import top.charles7c.continew.admin.common.constant.CacheConstants;
|
||||||
import top.charles7c.continew.admin.common.model.resp.LabelValueResp;
|
import top.charles7c.continew.admin.common.model.resp.LabelValueResp;
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.admin.system.model.query.DeptQuery;
|
import top.charles7c.continew.admin.system.model.query.DeptQuery;
|
||||||
import top.charles7c.continew.admin.system.model.query.MenuQuery;
|
import top.charles7c.continew.admin.system.model.query.MenuQuery;
|
||||||
import top.charles7c.continew.admin.system.model.query.OptionQuery;
|
import top.charles7c.continew.admin.system.model.query.OptionQuery;
|
||||||
@ -59,6 +58,7 @@ import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
|||||||
import top.charles7c.continew.starter.data.mybatis.plus.enums.IBaseEnum;
|
import top.charles7c.continew.starter.data.mybatis.plus.enums.IBaseEnum;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
|
import top.charles7c.continew.starter.extension.crud.model.query.SortQuery;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公共 API
|
* 公共 API
|
||||||
|
@ -31,7 +31,6 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.admin.monitor.model.resp.DashboardAccessTrendResp;
|
import top.charles7c.continew.admin.monitor.model.resp.DashboardAccessTrendResp;
|
||||||
import top.charles7c.continew.admin.monitor.model.resp.DashboardGeoDistributionResp;
|
import top.charles7c.continew.admin.monitor.model.resp.DashboardGeoDistributionResp;
|
||||||
import top.charles7c.continew.admin.monitor.model.resp.DashboardPopularModuleResp;
|
import top.charles7c.continew.admin.monitor.model.resp.DashboardPopularModuleResp;
|
||||||
@ -40,6 +39,7 @@ import top.charles7c.continew.admin.monitor.service.DashboardService;
|
|||||||
import top.charles7c.continew.admin.system.model.resp.DashboardAnnouncementResp;
|
import top.charles7c.continew.admin.system.model.resp.DashboardAnnouncementResp;
|
||||||
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仪表盘 API
|
* 仪表盘 API
|
||||||
|
@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.admin.monitor.model.query.LoginLogQuery;
|
import top.charles7c.continew.admin.monitor.model.query.LoginLogQuery;
|
||||||
import top.charles7c.continew.admin.monitor.model.query.OperationLogQuery;
|
import top.charles7c.continew.admin.monitor.model.query.OperationLogQuery;
|
||||||
import top.charles7c.continew.admin.monitor.model.query.SystemLogQuery;
|
import top.charles7c.continew.admin.monitor.model.query.SystemLogQuery;
|
||||||
@ -41,6 +40,7 @@ import top.charles7c.continew.admin.monitor.service.LogService;
|
|||||||
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
|
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp;
|
import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志管理 API
|
* 日志管理 API
|
||||||
|
@ -29,7 +29,6 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import top.charles7c.continew.admin.common.util.helper.LoginHelper;
|
import top.charles7c.continew.admin.common.util.helper.LoginHelper;
|
||||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
|
||||||
import top.charles7c.continew.admin.system.model.query.MessageQuery;
|
import top.charles7c.continew.admin.system.model.query.MessageQuery;
|
||||||
import top.charles7c.continew.admin.system.model.resp.MessageResp;
|
import top.charles7c.continew.admin.system.model.resp.MessageResp;
|
||||||
import top.charles7c.continew.admin.system.model.resp.MessageUnreadResp;
|
import top.charles7c.continew.admin.system.model.resp.MessageUnreadResp;
|
||||||
@ -38,6 +37,7 @@ import top.charles7c.continew.admin.system.service.MessageUserService;
|
|||||||
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
|
import top.charles7c.continew.starter.extension.crud.model.query.PageQuery;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp;
|
import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp;
|
||||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||||
|
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息管理 API
|
* 消息管理 API
|
||||||
|
@ -25,19 +25,21 @@ project:
|
|||||||
--- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置)
|
--- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置)
|
||||||
logging:
|
logging:
|
||||||
config: classpath:logback-spring.xml
|
config: classpath:logback-spring.xml
|
||||||
## 系统日志配置
|
## 日志配置
|
||||||
system:
|
continew-starter:
|
||||||
# 是否启用系统日志
|
log:
|
||||||
enabled: true
|
enabled: true
|
||||||
# 是否记录内网 IP 操作
|
include:
|
||||||
includeInnerIp: true
|
- DESCRIPTION
|
||||||
# 排除请求方式
|
- MODULE
|
||||||
#excludeMethods:
|
- REQUEST_HEADERS
|
||||||
# - GET
|
- REQUEST_BODY
|
||||||
# 脱敏字段
|
- IP_ADDRESS
|
||||||
desensitizeFields:
|
- BROWSER
|
||||||
- password
|
- OS
|
||||||
- Authorization
|
- RESPONSE_HEADERS
|
||||||
|
- RESPONSE_BODY
|
||||||
|
- TIME_TAKEN
|
||||||
|
|
||||||
--- ### 接口文档配置
|
--- ### 接口文档配置
|
||||||
springdoc:
|
springdoc:
|
||||||
|
@ -123,17 +123,17 @@ CREATE TABLE IF NOT EXISTS `sys_log` (
|
|||||||
`status_code` int NOT NULL COMMENT '状态码',
|
`status_code` int NOT NULL COMMENT '状态码',
|
||||||
`response_headers` text DEFAULT NULL COMMENT '响应头',
|
`response_headers` text DEFAULT NULL COMMENT '响应头',
|
||||||
`response_body` mediumtext DEFAULT NULL COMMENT '响应体',
|
`response_body` mediumtext DEFAULT NULL COMMENT '响应体',
|
||||||
`elapsed_time` bigint(20) NOT NULL COMMENT '请求耗时(ms)',
|
`time_taken` bigint(20) NOT NULL COMMENT '耗时(ms)',
|
||||||
`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '操作状态(1:成功;2:失败)',
|
`ip` varchar(100) DEFAULT NULL COMMENT 'IP',
|
||||||
`client_ip` varchar(100) DEFAULT NULL COMMENT '客户端IP',
|
`address` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
|
||||||
`location` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
|
|
||||||
`browser` varchar(100) DEFAULT NULL COMMENT '浏览器',
|
`browser` varchar(100) DEFAULT NULL COMMENT '浏览器',
|
||||||
|
`os` varchar(100) DEFAULT NULL COMMENT '操作系统',
|
||||||
|
`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:成功;2:失败)',
|
||||||
`error_msg` text DEFAULT NULL COMMENT '错误信息',
|
`error_msg` text DEFAULT NULL COMMENT '错误信息',
|
||||||
`exception_detail` mediumtext DEFAULT NULL COMMENT '异常详情',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
INDEX `idx_module`(`module`) USING BTREE,
|
INDEX `idx_module`(`module`) USING BTREE,
|
||||||
INDEX `idx_client_ip`(`client_ip`) USING BTREE,
|
INDEX `idx_ip`(`ip`) USING BTREE,
|
||||||
INDEX `idx_create_time`(`create_time`) USING BTREE
|
INDEX `idx_create_time`(`create_time`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';
|
||||||
|
2
pom.xml
2
pom.xml
@ -12,7 +12,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>top.charles7c.continew</groupId>
|
<groupId>top.charles7c.continew</groupId>
|
||||||
<artifactId>continew-starter</artifactId>
|
<artifactId>continew-starter</artifactId>
|
||||||
<version>1.0.1-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>top.charles7c.continew</groupId>
|
<groupId>top.charles7c.continew</groupId>
|
||||||
|
Loading…
Reference in New Issue
Block a user