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";
|
||||
|
||||
/**
|
||||
* 登录 URI
|
||||
* 账号登录 URI
|
||||
*/
|
||||
public static final String LOGIN_URI = "/auth/login";
|
||||
public static final String LOGIN_URI = "/auth/account";
|
||||
|
||||
/**
|
||||
* 退出 URI
|
||||
|
@ -41,7 +41,6 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
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.exception.BadRequestException;
|
||||
import top.charles7c.continew.starter.core.exception.BusinessException;
|
||||
@ -65,7 +64,6 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
|
||||
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
|
||||
LogContextHolder.setErrorMsg(e.getMessage());
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
||||
}
|
||||
|
||||
@ -77,7 +75,6 @@ public class GlobalExceptionHandler {
|
||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||
String errorMsg =
|
||||
CollUtil.join(e.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
|
||||
LogContextHolder.setErrorMsg(errorMsg);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
@ -89,7 +86,6 @@ public class GlobalExceptionHandler {
|
||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||
String errorMsg = CollUtil.join(e.getAllErrors(), StringConstants.CHINESE_COMMA,
|
||||
DefaultMessageSourceResolvable::getDefaultMessage);
|
||||
LogContextHolder.setErrorMsg(errorMsg);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
@ -101,7 +97,6 @@ public class GlobalExceptionHandler {
|
||||
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
|
||||
String errorMsg = ExceptionUtils
|
||||
.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
|
||||
LogContextHolder.setErrorMsg(errorMsg);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
@ -113,7 +108,6 @@ public class GlobalExceptionHandler {
|
||||
HttpServletRequest request) {
|
||||
String errorMsg = StrUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter().getParameterType());
|
||||
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
|
||||
LogContextHolder.setErrorMsg(errorMsg);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
@ -125,7 +119,6 @@ public class GlobalExceptionHandler {
|
||||
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
|
||||
String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for");
|
||||
String errorMsg = String.format("请上传小于 %sMB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024);
|
||||
LogContextHolder.setErrorMsg(errorMsg);
|
||||
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
|
||||
}
|
||||
|
||||
@ -140,7 +133,6 @@ public class GlobalExceptionHandler {
|
||||
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线。";
|
||||
default -> "您的登录状态已过期,请重新登录。";
|
||||
};
|
||||
LogContextHolder.setErrorMsg(errorMsg);
|
||||
return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg);
|
||||
}
|
||||
|
||||
@ -167,7 +159,6 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
||||
LogContextHolder.setErrorMsg(e.getMessage());
|
||||
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
|
||||
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
|
||||
}
|
||||
@ -178,7 +169,6 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public R handleServiceException(BusinessException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||
LogContextHolder.setErrorMsg(e.getMessage());
|
||||
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
||||
}
|
||||
|
||||
@ -188,7 +178,6 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
|
||||
LogContextHolder.setException(e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
@ -198,7 +187,6 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public R handleException(Throwable e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
||||
LogContextHolder.setException(e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
@ -75,20 +75,25 @@ public class LoginUser implements Serializable {
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 登录 IP
|
||||
* IP
|
||||
*/
|
||||
private String clientIp;
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 登录地点
|
||||
* IP 归属地
|
||||
*/
|
||||
private String location;
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 浏览器
|
||||
*/
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 登录时间
|
||||
*/
|
||||
|
@ -26,13 +26,12 @@ import lombok.NoArgsConstructor;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
||||
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.util.holder.LogContextHolder;
|
||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||
import top.charles7c.continew.starter.core.util.IpUtils;
|
||||
import top.charles7c.continew.starter.core.util.ServletUtils;
|
||||
@ -58,11 +57,11 @@ public class LoginHelper {
|
||||
public static String login(LoginUser loginUser) {
|
||||
// 记录登录信息
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
loginUser.setClientIp(JakartaServletUtil.getClientIP(request));
|
||||
loginUser.setLocation(IpUtils.getCityInfo(loginUser.getClientIp()));
|
||||
loginUser.setIp(JakartaServletUtil.getClientIP(request));
|
||||
loginUser.setAddress(IpUtils.getAddress(loginUser.getIp()));
|
||||
loginUser.setBrowser(ServletUtils.getBrowser(request));
|
||||
LogContext logContext = LogContextHolder.get();
|
||||
loginUser.setLoginTime(null != logContext ? logContext.getCreateTime() : LocalDateTime.now());
|
||||
loginUser.setLoginTime(LocalDateTime.now());
|
||||
loginUser.setOs(StrUtil.subBefore(ServletUtils.getOs(request), " or", false));
|
||||
// 登录并缓存用户信息
|
||||
StpUtil.login(loginUser.getId());
|
||||
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>
|
||||
|
||||
<dependencies>
|
||||
<!-- ContiNew Starter 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版) -->
|
||||
<dependency>
|
||||
<groupId>top.charles7c.continew</groupId>
|
||||
<artifactId>continew-starter-log-httptrace-pro</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) -->
|
||||
<dependency>
|
||||
<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;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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
|
||||
* @since 2022/12/24 23:15
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class WebMvcMonitorConfiguration implements WebMvcConfigurer {
|
||||
@ConditionalOnEnabledLog
|
||||
public class LogConfiguration {
|
||||
|
||||
private final LogInterceptor logInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(logInterceptor);
|
||||
/**
|
||||
* 日志持久层接口本地实现类
|
||||
*/
|
||||
@Bean
|
||||
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;
|
||||
|
||||
/**
|
||||
* 请求耗时(ms)
|
||||
* 耗时(ms)
|
||||
*/
|
||||
private Long elapsedTime;
|
||||
private Long timeTaken;
|
||||
|
||||
/**
|
||||
* 操作状态
|
||||
* IP
|
||||
*/
|
||||
private LogStatusEnum status;
|
||||
|
||||
/**
|
||||
* 客户端IP
|
||||
*/
|
||||
private String clientIp;
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* IP 归属地
|
||||
*/
|
||||
private String location;
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 浏览器
|
||||
*/
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private LogStatusEnum status;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
/**
|
||||
* 异常详情
|
||||
*/
|
||||
private String exceptionDetail;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
|
@ -53,13 +53,13 @@ public class LoginLogResp extends LogResp {
|
||||
* 登录 IP
|
||||
*/
|
||||
@Schema(description = "登录 IP", example = "192.168.0.1")
|
||||
private String clientIp;
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 登录地点
|
||||
*/
|
||||
@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")
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
@Schema(description = "操作系统", example = "Windows 10")
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
|
@ -49,23 +49,17 @@ public class OperationLogResp extends LogResp {
|
||||
@Schema(description = "所属模块", example = "部门管理")
|
||||
private String module;
|
||||
|
||||
/**
|
||||
* 操作状态
|
||||
*/
|
||||
@Schema(description = "操作状态(1:成功;2:失败)", type = "Integer", allowableValues = {"1", "2"}, example = "1")
|
||||
private LogStatusEnum status;
|
||||
|
||||
/**
|
||||
* 操作 IP
|
||||
*/
|
||||
@Schema(description = "操作 IP", example = "192.168.0.1")
|
||||
private String clientIp;
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 操作地点
|
||||
*/
|
||||
@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")
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -78,16 +78,16 @@ public class SystemLogDetailResp extends LogResp {
|
||||
private String responseBody;
|
||||
|
||||
/**
|
||||
* 客户端IP
|
||||
* IP
|
||||
*/
|
||||
@Schema(description = "客户端IP", example = "192.168.0.1")
|
||||
private String clientIp;
|
||||
@Schema(description = "IP", example = "192.168.0.1")
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* IP归属地
|
||||
* 地址
|
||||
*/
|
||||
@Schema(description = "IP归属地", example = "中国北京北京市")
|
||||
private String location;
|
||||
@Schema(description = "地址", example = "中国北京北京市")
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 浏览器
|
||||
@ -96,8 +96,14 @@ public class SystemLogDetailResp extends LogResp {
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 请求耗时(ms)
|
||||
* 操作系统
|
||||
*/
|
||||
@Schema(description = "请求耗时(ms)", example = "58")
|
||||
private Long elapsedTime;
|
||||
@Schema(description = "操作系统", example = "Windows 10")
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 耗时(ms)
|
||||
*/
|
||||
@Schema(description = "耗时(ms)", example = "58")
|
||||
private Long timeTaken;
|
||||
}
|
||||
|
@ -54,16 +54,16 @@ public class SystemLogResp extends LogResp {
|
||||
private String requestUrl;
|
||||
|
||||
/**
|
||||
* 客户端IP
|
||||
* IP
|
||||
*/
|
||||
@Schema(description = "客户端IP", example = "192.168.0.1")
|
||||
private String clientIp;
|
||||
@Schema(description = "IP", example = "192.168.0.1")
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* IP归属地
|
||||
* 地址
|
||||
*/
|
||||
@Schema(description = "IP归属地", example = "中国北京北京市")
|
||||
private String location;
|
||||
@Schema(description = "地址", example = "中国北京北京市")
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 浏览器
|
||||
@ -72,20 +72,8 @@ public class SystemLogResp extends LogResp {
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 请求耗时(ms)
|
||||
* 耗时(ms)
|
||||
*/
|
||||
@Schema(description = "请求耗时(ms)", example = "58")
|
||||
private Long elapsedTime;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMsg;
|
||||
|
||||
/**
|
||||
* 异常详情
|
||||
*/
|
||||
@Schema(description = "异常详情")
|
||||
private String exceptionDetail;
|
||||
@Schema(description = "耗时(ms)", example = "58")
|
||||
private Long timeTaken;
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ import java.util.stream.Collectors;
|
||||
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 com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
@ -63,27 +61,18 @@ public class LogServiceImpl implements LogService {
|
||||
private final LogMapper logMapper;
|
||||
private final CommonUserService commonUserService;
|
||||
|
||||
@Async
|
||||
@EventListener
|
||||
public void save(LogDO logDO) {
|
||||
logMapper.insert(logDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageDataResp<OperationLogResp> page(OperationLogQuery query, PageQuery pageQuery) {
|
||||
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
||||
|
||||
// 限定查询信息
|
||||
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(OperationLogResp.class);
|
||||
List<String> columnNameList =
|
||||
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
||||
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
||||
queryWrapper.select(columnNameList);
|
||||
|
||||
// 分页查询
|
||||
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||
PageDataResp<OperationLogResp> pageDataResp = PageDataResp.build(page, OperationLogResp.class);
|
||||
|
||||
// 填充数据(如果是查询个人操作日志,只查询一次用户信息即可)
|
||||
if (null != 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) {
|
||||
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
||||
queryWrapper.eq("module", "登录");
|
||||
|
||||
// 限定查询信息
|
||||
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(LoginLogResp.class);
|
||||
List<String> columnNameList =
|
||||
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
||||
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
||||
queryWrapper.select(columnNameList);
|
||||
|
||||
// 分页查询
|
||||
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||
PageDataResp<LoginLogResp> pageDataResp = PageDataResp.build(page, LoginLogResp.class);
|
||||
|
||||
// 填充数据
|
||||
pageDataResp.getList().forEach(this::fill);
|
||||
return pageDataResp;
|
||||
@ -118,18 +104,15 @@ public class LogServiceImpl implements LogService {
|
||||
@Override
|
||||
public PageDataResp<SystemLogResp> page(SystemLogQuery query, PageQuery pageQuery) {
|
||||
QueryWrapper<LogDO> queryWrapper = QueryHelper.build(query);
|
||||
|
||||
// 限定查询信息
|
||||
List<String> fieldNameList = ReflectUtils.getNonStaticFieldsName(SystemLogResp.class);
|
||||
List<String> columnNameList =
|
||||
fieldNameList.stream().filter(n -> !n.endsWith(SysConstants.DESCRIPTION_FIELD_SUFFIX))
|
||||
.map(StrUtil::toUnderlineCase).collect(Collectors.toList());
|
||||
queryWrapper.select(columnNameList);
|
||||
|
||||
// 分页查询
|
||||
IPage<LogDO> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper);
|
||||
PageDataResp<SystemLogResp> pageDataResp = PageDataResp.build(page, SystemLogResp.class);
|
||||
|
||||
// 填充数据
|
||||
pageDataResp.getList().forEach(this::fill);
|
||||
return pageDataResp;
|
||||
|
@ -66,13 +66,13 @@ public class OnlineUserResp implements Serializable {
|
||||
* 登录 IP
|
||||
*/
|
||||
@Schema(description = "登录 IP", example = "192.168.0.1")
|
||||
private String clientIp;
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 登录地点
|
||||
*/
|
||||
@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")
|
||||
private String browser;
|
||||
|
||||
/**
|
||||
* 操作系统
|
||||
*/
|
||||
@Schema(description = "操作系统", example = "Windows 10")
|
||||
private String os;
|
||||
|
||||
/**
|
||||
* 登录时间
|
||||
*/
|
||||
|
@ -5,9 +5,10 @@ const BASE_URL = '/monitor/log';
|
||||
|
||||
export interface LogRecord {
|
||||
id?: number;
|
||||
clientIp: string;
|
||||
location: string;
|
||||
ip: string;
|
||||
address: string;
|
||||
browser: string;
|
||||
os: string;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
@ -22,7 +23,7 @@ export interface OperationLogRecord extends LogRecord {
|
||||
module: string;
|
||||
description: string;
|
||||
status: number;
|
||||
errorMsg: string;
|
||||
errorMsgString: string;
|
||||
createUserString: string;
|
||||
}
|
||||
|
||||
@ -30,8 +31,7 @@ export interface SystemLogRecord extends LogRecord {
|
||||
statusCode: number;
|
||||
requestMethod: string;
|
||||
requestUrl: string;
|
||||
elapsedTime: number;
|
||||
exceptionDetail?: string;
|
||||
timeTaken: number;
|
||||
}
|
||||
|
||||
export interface SystemLogDetailRecord extends SystemLogRecord {
|
||||
|
@ -7,9 +7,10 @@ export interface DataRecord {
|
||||
token: string;
|
||||
username: string;
|
||||
nickname: string;
|
||||
clientIp: string;
|
||||
location: string;
|
||||
ip: string;
|
||||
address: string;
|
||||
browser: string;
|
||||
os: string;
|
||||
loginTime: string;
|
||||
}
|
||||
|
||||
|
@ -72,9 +72,10 @@
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="登录 IP" data-index="clientIp" />
|
||||
<a-table-column title="登录地点" data-index="location" />
|
||||
<a-table-column title="登录 IP" data-index="ip" />
|
||||
<a-table-column title="登录地点" data-index="address" />
|
||||
<a-table-column title="浏览器" data-index="browser" />
|
||||
<a-table-column title="终端系统" data-index="os" />
|
||||
<a-table-column title="登录时间" data-index="createTime" />
|
||||
</template>
|
||||
</a-table>
|
||||
|
@ -83,8 +83,8 @@
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作 IP" data-index="clientIp" />
|
||||
<a-table-column title="操作地点" data-index="location" />
|
||||
<a-table-column title="操作 IP" data-index="ip" />
|
||||
<a-table-column title="操作地点" data-index="address" />
|
||||
<a-table-column title="浏览器" data-index="browser" />
|
||||
</template>
|
||||
</a-table>
|
||||
|
@ -74,21 +74,21 @@
|
||||
}}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="客户端 IP" data-index="clientIp" />
|
||||
<a-table-column title="IP 归属地" data-index="location" />
|
||||
<a-table-column title="IP" data-index="ip" />
|
||||
<a-table-column title="地址" data-index="address" />
|
||||
<a-table-column title="浏览器" data-index="browser" />
|
||||
<a-table-column title="请求耗时">
|
||||
<a-table-column title="耗时">
|
||||
<template #cell="{ record }">
|
||||
<a-tag v-if="record.elapsedTime > 500" color="red"
|
||||
>{{ record.elapsedTime }} ms</a-tag
|
||||
<a-tag v-if="record.timeTaken > 500" color="red"
|
||||
>{{ record.timeTaken }} ms</a-tag
|
||||
>
|
||||
<a-tag v-else-if="record.elapsedTime > 200" color="orange"
|
||||
>{{ record.elapsedTime }} ms</a-tag
|
||||
<a-tag v-else-if="record.timeTaken > 200" color="orange"
|
||||
>{{ 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>
|
||||
</a-table-column>
|
||||
<a-table-column title="创建时间" data-index="createTime" />
|
||||
<a-table-column title="请求时间" data-index="createTime" />
|
||||
<a-table-column title="操作" align="center">
|
||||
<template #cell="{ record }">
|
||||
<a-button
|
||||
@ -99,15 +99,6 @@
|
||||
>
|
||||
<template #icon><icon-eye /></template>详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.exceptionDetail"
|
||||
type="text"
|
||||
size="small"
|
||||
title="查看异常详情"
|
||||
@click="toExceptionDetail(record)"
|
||||
>
|
||||
<template #icon><icon-bug /></template>异常
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
@ -125,11 +116,11 @@
|
||||
>
|
||||
<div style="margin: 10px 0 0 10px">
|
||||
<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-line :widths="['200px']" :rows="1" />
|
||||
</a-skeleton>
|
||||
<span v-else>{{ systemLog.clientIp }}</span>
|
||||
<span v-else>{{ systemLog.ip }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="浏览器">
|
||||
<a-skeleton v-if="loading" :animation="true">
|
||||
@ -137,34 +128,40 @@
|
||||
</a-skeleton>
|
||||
<span v-else>{{ systemLog.browser }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="IP 归属地">
|
||||
<a-descriptions-item label="地址">
|
||||
<a-skeleton v-if="loading" :animation="true">
|
||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||
</a-skeleton>
|
||||
<span v-else>{{ systemLog.location }}</span>
|
||||
<span v-else>{{ systemLog.address }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="请求耗时">
|
||||
<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.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>
|
||||
<span v-else>{{ systemLog.os }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
<a-descriptions-item label="请求时间">
|
||||
<a-skeleton v-if="loading" :animation="true">
|
||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||
</a-skeleton>
|
||||
<span v-else>{{ systemLog.createTime }}</span>
|
||||
</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
|
||||
title="协议信息"
|
||||
@ -256,20 +253,6 @@
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
@ -297,17 +280,16 @@
|
||||
statusCode: 200,
|
||||
responseHeaders: '',
|
||||
responseBody: '',
|
||||
elapsedTime: 0,
|
||||
clientIp: '',
|
||||
location: '',
|
||||
timeTaken: 0,
|
||||
ip: '',
|
||||
address: '',
|
||||
browser: '',
|
||||
os: '',
|
||||
createTime: '',
|
||||
});
|
||||
const total = ref(0);
|
||||
const exceptionDetail = ref('');
|
||||
const loading = ref(false);
|
||||
const visible = ref(false);
|
||||
const exceptionDetailVisible = ref(false);
|
||||
|
||||
const data = reactive({
|
||||
// 查询参数
|
||||
@ -362,24 +344,6 @@
|
||||
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 }})
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="登录 IP" data-index="clientIp" />
|
||||
<a-table-column title="登录地点" data-index="location" />
|
||||
<a-table-column title="登录 IP" data-index="ip" />
|
||||
<a-table-column title="登录地点" data-index="address" />
|
||||
<a-table-column title="浏览器" data-index="browser" />
|
||||
<a-table-column title="终端系统" data-index="os" />
|
||||
<a-table-column title="登录时间" data-index="loginTime" />
|
||||
<a-table-column
|
||||
v-if="checkPermission(['monitor:online:user:delete'])"
|
||||
|
@ -40,8 +40,8 @@
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="操作 IP" data-index="clientIp" />
|
||||
<a-table-column title="操作地点" data-index="location" />
|
||||
<a-table-column title="操作 IP" data-index="ip" />
|
||||
<a-table-column title="操作地点" data-index="address" />
|
||||
<a-table-column title="浏览器" data-index="browser" />
|
||||
</template>
|
||||
<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.util.SecureUtils;
|
||||
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.service.UserService;
|
||||
import top.charles7c.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.charles7c.continew.starter.core.util.ExceptionUtils;
|
||||
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||
|
||||
/**
|
||||
* 认证 API
|
||||
@ -115,9 +115,10 @@ public class AuthController {
|
||||
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx",
|
||||
in = ParameterIn.HEADER)
|
||||
@PostMapping("/logout")
|
||||
public R logout() {
|
||||
public R<Object> logout() {
|
||||
Object loginId = StpUtil.getLoginId(-1L);
|
||||
StpUtil.logout();
|
||||
return R.ok();
|
||||
return R.ok(loginId);
|
||||
}
|
||||
|
||||
@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.service.LoginService;
|
||||
import top.charles7c.continew.admin.monitor.annotation.Log;
|
||||
import top.charles7c.continew.starter.core.exception.BadRequestException;
|
||||
import top.charles7c.continew.starter.core.util.validate.ValidationUtils;
|
||||
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.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.constant.CacheConstants;
|
||||
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.MenuQuery;
|
||||
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.extension.crud.model.query.SortQuery;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||
|
||||
/**
|
||||
* 公共 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.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.DashboardGeoDistributionResp;
|
||||
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.starter.core.util.validate.ValidationUtils;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||
|
||||
/**
|
||||
* 仪表盘 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.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.OperationLogQuery;
|
||||
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.resp.PageDataResp;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||
|
||||
/**
|
||||
* 日志管理 API
|
||||
|
@ -29,7 +29,6 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
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.resp.MessageResp;
|
||||
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.resp.PageDataResp;
|
||||
import top.charles7c.continew.starter.extension.crud.model.resp.R;
|
||||
import top.charles7c.continew.starter.log.common.annotation.Log;
|
||||
|
||||
/**
|
||||
* 消息管理 API
|
||||
|
@ -25,19 +25,21 @@ project:
|
||||
--- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置)
|
||||
logging:
|
||||
config: classpath:logback-spring.xml
|
||||
## 系统日志配置
|
||||
system:
|
||||
# 是否启用系统日志
|
||||
## 日志配置
|
||||
continew-starter:
|
||||
log:
|
||||
enabled: true
|
||||
# 是否记录内网 IP 操作
|
||||
includeInnerIp: true
|
||||
# 排除请求方式
|
||||
#excludeMethods:
|
||||
# - GET
|
||||
# 脱敏字段
|
||||
desensitizeFields:
|
||||
- password
|
||||
- Authorization
|
||||
include:
|
||||
- DESCRIPTION
|
||||
- MODULE
|
||||
- REQUEST_HEADERS
|
||||
- REQUEST_BODY
|
||||
- IP_ADDRESS
|
||||
- BROWSER
|
||||
- OS
|
||||
- RESPONSE_HEADERS
|
||||
- RESPONSE_BODY
|
||||
- TIME_TAKEN
|
||||
|
||||
--- ### 接口文档配置
|
||||
springdoc:
|
||||
|
@ -123,17 +123,17 @@ CREATE TABLE IF NOT EXISTS `sys_log` (
|
||||
`status_code` int NOT NULL COMMENT '状态码',
|
||||
`response_headers` text DEFAULT NULL COMMENT '响应头',
|
||||
`response_body` mediumtext DEFAULT NULL COMMENT '响应体',
|
||||
`elapsed_time` bigint(20) NOT NULL COMMENT '请求耗时(ms)',
|
||||
`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '操作状态(1:成功;2:失败)',
|
||||
`client_ip` varchar(100) DEFAULT NULL COMMENT '客户端IP',
|
||||
`location` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
|
||||
`time_taken` bigint(20) NOT NULL COMMENT '耗时(ms)',
|
||||
`ip` varchar(100) DEFAULT NULL COMMENT 'IP',
|
||||
`address` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
|
||||
`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 '错误信息',
|
||||
`exception_detail` mediumtext DEFAULT NULL COMMENT '异常详情',
|
||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) 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
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';
|
||||
|
Loading…
Reference in New Issue
Block a user