Browse Source

feat: 系统日志

master
buaixuexideshitongxue 1 month ago
parent
commit
5cc2647273
  1. 446
      src/main/java/com/biutag/supervision/aop/SystemLogAspect.java
  2. 34
      src/main/java/com/biutag/supervision/controller/system/SystemLogController.java
  3. 37
      src/main/java/com/biutag/supervision/job/SystemLogCleanJob.java
  4. 12
      src/main/java/com/biutag/supervision/mapper/SystemLogMapper.java
  5. 69
      src/main/java/com/biutag/supervision/pojo/entity/SystemLog.java
  6. 43
      src/main/java/com/biutag/supervision/pojo/param/SystemLogQueryParam.java
  7. 78
      src/main/java/com/biutag/supervision/service/SystemLogService.java

446
src/main/java/com/biutag/supervision/aop/SystemLogAspect.java

@ -0,0 +1,446 @@
package com.biutag.supervision.aop;
import com.biutag.supervision.common.UserContextHolder;
import com.biutag.supervision.constants.RedisKeyConstants;
import com.biutag.supervision.pojo.Result;
import com.biutag.supervision.pojo.domain.AppUser;
import com.biutag.supervision.pojo.entity.SystemLog;
import com.biutag.supervision.pojo.model.UserAuth;
import com.biutag.supervision.pojo.vo.TokenVo;
import com.biutag.supervision.service.SystemLogService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* 统一记录进入 Controller 调用链的接口访问日志
* <p>
* 日志切面优先级高于现有参数检查切面因此参数检查抛出的业务异常也可以被记录
* Spring MVC 调用 Controller 之前就被鉴权拦截的请求不会进入本切面
*/
@Slf4j
@Aspect
@Component
@Order(-100)
@RequiredArgsConstructor
public class SystemLogAspect {
private static final int SUCCESS = 1;
private static final int FAILURE = 0;
private static final int MAX_PARAMS_LENGTH = 4000;
private static final int MAX_ERROR_LENGTH = 1000;
private static final String MASK_VALUE = "******";
private static final String TRUNCATED_MARK = "...[内容已截断]";
private final SystemLogService systemLogService;
private final ObjectMapper objectMapper;
private final RedisTemplate<Object, Object> redisTemplate;
/**
* 只拦截项目 Controller 包中的方法Service Mapper 调用不会触发日志递归
*/
@Pointcut("execution(* com.biutag.supervision.controller..*(..))")
public void controllerPointcut() {
}
/**
* 包裹 Controller 调用并在 finally 中保存日志确保业务异常场景也能记录
*/
@Around("controllerPointcut()")
public Object recordSystemLog(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = getRequestAttributes();
HttpServletRequest request = attributes == null ? null : attributes.getRequest();
HttpServletResponse response = attributes == null ? null : attributes.getResponse();
LocalDateTime logTime = LocalDateTime.now();
long startTime = System.nanoTime();
UserAuth currentUser = resolveCurrentUser();
String requestParams = buildRequestParams(joinPoint, request);
Object result = null;
Throwable throwable = null;
try {
result = joinPoint.proceed();
return result;
} catch (Throwable ex) {
throwable = ex;
throw ex;
} finally {
long executionTime = (System.nanoTime() - startTime) / 1_000_000L;
UserAuth logUser = currentUser == null ? resolveLoginUser(result) : currentUser;
saveLogSafely(request, response, logUser, requestParams, result, throwable, executionTime, logTime);
}
}
private ServletRequestAttributes getRequestAttributes() {
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attributes) {
return attributes;
}
return null;
}
/**
* 公开接口或外部接口可能没有后台登录态此时用户字段保持为空由前端显示为匿名调用
*/
private UserAuth resolveCurrentUser() {
try {
return UserContextHolder.getCurrentUser();
} catch (RuntimeException ignored) {
return null;
}
}
/**
* 登录请求进入 Controller 前尚未携带 Token登录成功后从返回值中临时提取 Token
* 再读取登录流程刚写入的 Redis 会话从而为账号登录和数字证书登录回填真实请求人
* App 登录已经直接返回 UserAuth优先复用该对象不保存任何响应内容
*/
private UserAuth resolveLoginUser(Object result) {
if (!(result instanceof Result<?> apiResult) || apiResult.getCode() != HttpServletResponse.SC_OK) {
return null;
}
Object data = apiResult.getData();
if (data instanceof AppUser appUser && appUser.getUser() != null) {
return appUser.getUser();
}
String token = null;
if (data instanceof TokenVo tokenVo) {
token = tokenVo.getToken();
} else if (data instanceof AppUser appUser) {
token = appUser.getToken();
}
if (token == null || token.isBlank()) {
return null;
}
try {
Object user = redisTemplate.opsForValue().get(String.format(RedisKeyConstants.LOGIN_USERINFO_KEY, token));
return user instanceof UserAuth userAuth ? userAuth : null;
} catch (RuntimeException ex) {
log.warn("登录日志请求人解析失败:{}", ex.getMessage());
return null;
}
}
/**
* 同时采集原始 query/form 参数和 Controller 方法参数以覆盖查询路径变量及 JSON 请求体
*/
private String buildRequestParams(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
try {
ObjectNode rootNode = objectMapper.createObjectNode();
appendRequestParameters(rootNode, request);
appendMethodArguments(rootNode, joinPoint);
sanitizeNode(rootNode);
if (rootNode.isEmpty()) {
return null;
}
return truncate(objectMapper.writeValueAsString(rootNode), MAX_PARAMS_LENGTH, TRUNCATED_MARK);
} catch (Exception ex) {
log.warn("系统日志请求参数解析失败:{}", ex.getMessage());
return "[请求参数解析失败]";
}
}
private void appendRequestParameters(ObjectNode rootNode, HttpServletRequest request) {
if (request == null || request.getParameterMap().isEmpty()) {
return;
}
ObjectNode queryNode = rootNode.putObject("query");
request.getParameterMap().forEach((key, values) -> {
if (values == null || values.length == 0) {
queryNode.putNull(key);
} else if (values.length == 1) {
queryNode.put(key, values[0]);
} else {
ArrayNode arrayNode = queryNode.putArray(key);
for (String value : values) {
arrayNode.add(value);
}
}
});
}
private void appendMethodArguments(ObjectNode rootNode, ProceedingJoinPoint joinPoint) {
Object[] arguments = joinPoint.getArgs();
if (arguments == null || arguments.length == 0) {
return;
}
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
ObjectNode argumentNode = rootNode.putObject("arguments");
for (int index = 0; index < arguments.length; index++) {
Object argument = arguments[index];
if (shouldIgnoreArgument(argument)) {
continue;
}
String parameterName = parameterNames != null && index < parameterNames.length
? parameterNames[index]
: "arg" + index;
argumentNode.set(parameterName, convertArgument(argument));
}
if (argumentNode.isEmpty()) {
rootNode.remove("arguments");
}
}
/**
* Servlet校验结果和流对象不可序列化也不属于业务请求参数统一跳过
*/
private boolean shouldIgnoreArgument(Object argument) {
return argument instanceof ServletRequest
|| argument instanceof ServletResponse
|| argument instanceof BindingResult
|| argument instanceof InputStream
|| argument instanceof OutputStream
|| argument instanceof Reader
|| argument instanceof Writer;
}
private JsonNode convertArgument(Object argument) {
if (argument == null) {
return objectMapper.getNodeFactory().nullNode();
}
if (argument instanceof MultipartFile file) {
return buildFileNode(file);
}
if (argument instanceof byte[] bytes) {
return objectMapper.getNodeFactory().textNode("[二进制内容已忽略,大小:" + bytes.length + "字节]");
}
if (argument.getClass().isArray()) {
ArrayNode arrayNode = objectMapper.createArrayNode();
int length = Array.getLength(argument);
for (int index = 0; index < length; index++) {
arrayNode.add(convertArgument(Array.get(argument, index)));
}
return arrayNode;
}
if (argument instanceof Iterable<?> iterable) {
ArrayNode arrayNode = objectMapper.createArrayNode();
for (Object item : iterable) {
arrayNode.add(convertArgument(item));
}
return arrayNode;
}
if (argument instanceof Map<?, ?> map) {
ObjectNode mapNode = objectMapper.createObjectNode();
map.forEach((key, value) -> mapNode.set(String.valueOf(key), convertArgument(value)));
return mapNode;
}
return objectMapper.valueToTree(argument);
}
/**
* 上传文件只记录元信息禁止读取或序列化文件内容
*/
private ObjectNode buildFileNode(MultipartFile file) {
ObjectNode fileNode = objectMapper.createObjectNode();
fileNode.put("fieldName", file.getName());
fileNode.put("originalFilename", file.getOriginalFilename());
fileNode.put("size", file.getSize());
fileNode.put("contentType", file.getContentType());
return fileNode;
}
/**
* 递归处理对象数组和嵌套 JSON 字符串凭证字段统一替换为固定掩码
*/
private JsonNode sanitizeNode(JsonNode node) {
if (node == null) {
return objectMapper.getNodeFactory().nullNode();
}
if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
List<String> fieldNames = new ArrayList<>();
objectNode.fieldNames().forEachRemaining(fieldNames::add);
for (String fieldName : fieldNames) {
if (isCredentialField(fieldName)) {
objectNode.put(fieldName, MASK_VALUE);
} else {
objectNode.set(fieldName, sanitizeNode(objectNode.get(fieldName)));
}
}
return objectNode;
} else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
for (int index = 0; index < arrayNode.size(); index++) {
arrayNode.set(index, sanitizeNode(arrayNode.get(index)));
}
return arrayNode;
} else if (node.isTextual()) {
return sanitizeJsonText(node);
}
return node;
}
/**
* APP 转发参数中的 body 可能是 JSON 字符串需要再次解析后脱敏避免内部密码被遗漏
*/
private JsonNode sanitizeJsonText(JsonNode valueNode) {
String value = valueNode.textValue();
if (value == null) {
return valueNode;
}
String trimmedValue = value.trim();
if (!(trimmedValue.startsWith("{") || trimmedValue.startsWith("["))) {
return valueNode;
}
try {
JsonNode nestedNode = objectMapper.readTree(trimmedValue);
JsonNode sanitizedNode = sanitizeNode(nestedNode);
return objectMapper.getNodeFactory().textNode(objectMapper.writeValueAsString(sanitizedNode));
} catch (Exception ignored) {
// 普通字符串可能恰好以大括号开头,解析失败时保留原值即可。
return valueNode;
}
}
private boolean isCredentialField(String fieldName) {
String normalizedName = fieldName.toLowerCase(Locale.ROOT).replace("_", "").replace("-", "");
return normalizedName.contains("password")
|| normalizedName.contains("passwd")
|| normalizedName.contains("pwd")
|| normalizedName.contains("token")
|| normalizedName.contains("authorization")
|| normalizedName.contains("cookie")
|| normalizedName.contains("secret")
|| normalizedName.contains("signeddata");
}
/**
* 日志写入属于附加能力任何日志异常都不能改变原业务接口的返回或异常
*/
private void saveLogSafely(HttpServletRequest request,
HttpServletResponse response,
UserAuth currentUser,
String requestParams,
Object result,
Throwable throwable,
long executionTime,
LocalDateTime logTime) {
if (request == null) {
return;
}
try {
int status = resolveStatus(response, result, throwable);
SystemLog systemLog = new SystemLog();
if (currentUser != null) {
systemLog.setUserId(currentUser.getUserId());
systemLog.setUserName(currentUser.getUserName());
systemLog.setNickName(currentUser.getNickName());
}
systemLog.setRequestUri(request.getRequestURI());
systemLog.setRequestMethod(request.getMethod());
systemLog.setRequestParams(requestParams);
systemLog.setSourceIp(resolveSourceIp(request));
systemLog.setExecutionTime(executionTime);
systemLog.setStatus(status);
systemLog.setErrorMessage(resolveErrorMessage(response, result, throwable, status));
systemLog.setLogTime(logTime);
systemLogService.save(systemLog);
} catch (Exception ex) {
log.error("系统访问日志保存失败:{}", ex.getMessage(), ex);
}
}
private int resolveStatus(HttpServletResponse response, Object result, Throwable throwable) {
if (throwable != null) {
return FAILURE;
}
if (response != null && response.getStatus() >= HttpServletResponse.SC_BAD_REQUEST) {
return FAILURE;
}
if (result instanceof Result<?> apiResult && apiResult.getCode() != HttpServletResponse.SC_OK) {
return FAILURE;
}
return SUCCESS;
}
private String resolveErrorMessage(HttpServletResponse response,
Object result,
Throwable throwable,
int status) {
if (status == SUCCESS) {
return null;
}
String message = null;
if (throwable != null) {
message = throwable.getMessage();
if (message == null || message.isBlank()) {
message = throwable.getClass().getSimpleName();
}
} else if (result instanceof Result<?> apiResult) {
message = apiResult.getMessage();
}
if ((message == null || message.isBlank()) && response != null) {
message = "HTTP " + response.getStatus();
}
return truncate(message, MAX_ERROR_LENGTH, TRUNCATED_MARK);
}
/**
* 兼容常见反向代理X-Forwarded-For 存在多级地址时取第一个有效值
*/
private String resolveSourceIp(HttpServletRequest request) {
String forwardedFor = request.getHeader("X-Forwarded-For");
if (isValidIpHeader(forwardedFor)) {
for (String ip : forwardedFor.split(",")) {
if (isValidIpHeader(ip)) {
return ip.trim();
}
}
}
String realIp = request.getHeader("X-Real-IP");
if (isValidIpHeader(realIp)) {
return realIp.trim();
}
return request.getRemoteAddr();
}
private boolean isValidIpHeader(String value) {
return value != null && !value.isBlank() && !"unknown".equalsIgnoreCase(value.trim());
}
private String truncate(String value, int maxLength, String mark) {
if (value == null || value.length() <= maxLength) {
return value;
}
int contentLength = Math.max(0, maxLength - mark.length());
return value.substring(0, contentLength) + mark;
}
}

34
src/main/java/com/biutag/supervision/controller/system/SystemLogController.java

@ -0,0 +1,34 @@
package com.biutag.supervision.controller.system;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.biutag.supervision.pojo.Result;
import com.biutag.supervision.pojo.entity.SystemLog;
import com.biutag.supervision.pojo.param.SystemLogQueryParam;
import com.biutag.supervision.service.SystemLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 系统访问日志查询接口
*/
@Tag(name = "系统日志")
@RequiredArgsConstructor
@RequestMapping("systemLog")
@RestController
public class SystemLogController {
private final SystemLogService systemLogService;
/**
* 分页查询系统访问日志
*/
@Operation(summary = "分页查询系统日志")
@GetMapping
public Result<Page<SystemLog>> list(SystemLogQueryParam queryParam) {
return Result.success(systemLogService.pageByQuery(queryParam));
}
}

37
src/main/java/com/biutag/supervision/job/SystemLogCleanJob.java

@ -0,0 +1,37 @@
package com.biutag.supervision.job;
import com.biutag.supervision.service.SystemLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 系统访问日志定时清理任务只保留最近四个月的数据
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SystemLogCleanJob {
private static final int RETENTION_MONTHS = 4;
private final SystemLogService systemLogService;
/**
* 清理四个月以前的日志
* 当前处于上线观察期未注册定时调度确认启用后再增加每天凌晨 03:30 @Scheduled 注解
* 启用调度后单次清理失败只记录错误信息不影响后续执行
* @Scheduled(cron = "0 30 3 * * ?")
*/
public void cleanExpiredLogs() {
LocalDateTime expireTime = LocalDateTime.now().minusMonths(RETENTION_MONTHS);
try {
int removedCount = systemLogService.removeBefore(expireTime);
log.info("系统访问日志清理完成,截止时间:{},删除数量:{}", expireTime, removedCount);
} catch (RuntimeException ex) {
log.error("系统访问日志清理失败,截止时间:{},失败原因:{}", expireTime, ex.getMessage(), ex);
}
}
}

12
src/main/java/com/biutag/supervision/mapper/SystemLogMapper.java

@ -0,0 +1,12 @@
package com.biutag.supervision.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.biutag.supervision.pojo.entity.SystemLog;
/**
* 系统访问日志数据访问接口
*/
@DS("master")
public interface SystemLogMapper extends BaseMapper<SystemLog> {
}

69
src/main/java/com/biutag/supervision/pojo/entity/SystemLog.java

@ -0,0 +1,69 @@
package com.biutag.supervision.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* 系统接口访问日志
*/
@Schema(description = "系统接口访问日志")
@TableName("system_log")
@Getter
@Setter
public class SystemLog {
@Schema(description = "日志主键")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "请求人用户ID")
@TableField("user_id")
private String userId;
@Schema(description = "请求人登录名")
@TableField("user_name")
private String userName;
@Schema(description = "请求人姓名")
@TableField("nick_name")
private String nickName;
@Schema(description = "请求接口地址")
@TableField("request_uri")
private String requestUri;
@Schema(description = "HTTP访问方式", example = "GET")
@TableField("request_method")
private String requestMethod;
@Schema(description = "脱敏并截断后的请求参数,最多4000字符")
@TableField("request_params")
private String requestParams;
@Schema(description = "请求来源IP")
@TableField("source_ip")
private String sourceIp;
@Schema(description = "Controller调用链执行耗时,单位:毫秒")
@TableField("execution_time")
private Long executionTime;
@Schema(description = "执行状态:1-成功,0-失败", allowableValues = {"0", "1"}, example = "1")
@TableField("status")
private Integer status;
@Schema(description = "异常或失败摘要,最多1000字符")
@TableField("error_message")
private String errorMessage;
@Schema(description = "请求开始时间")
@TableField("log_time")
private LocalDateTime logTime;
}

43
src/main/java/com/biutag/supervision/pojo/param/SystemLogQueryParam.java

@ -0,0 +1,43 @@
package com.biutag.supervision.pojo.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* 系统日志分页查询参数
*/
@Schema(description = "系统日志分页查询参数")
@Getter
@Setter
public class SystemLogQueryParam extends BasePage {
@Schema(description = "日志类型:login-登录日志,operation-操作日志", allowableValues = {"login", "operation"})
private String logType;
@Schema(description = "请求人登录名或姓名,支持模糊查询")
private String requester;
@Schema(description = "接口地址,支持模糊查询")
private String requestUri;
@Schema(description = "HTTP访问方式", example = "GET")
private String requestMethod;
@Schema(description = "来源IP,支持模糊查询")
private String sourceIp;
@Schema(description = "执行状态:1-成功,0-失败", allowableValues = {"0", "1"})
private Integer status;
@Schema(description = "日志开始时间,格式:yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
@Schema(description = "日志结束时间,格式:yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endTime;
}

78
src/main/java/com/biutag/supervision/service/SystemLogService.java

@ -0,0 +1,78 @@
package com.biutag.supervision.service;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.biutag.supervision.mapper.SystemLogMapper;
import com.biutag.supervision.pojo.entity.SystemLog;
import com.biutag.supervision.pojo.param.SystemLogQueryParam;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Set;
/**
* 系统访问日志服务
*/
@Service
public class SystemLogService extends ServiceImpl<SystemLogMapper, SystemLog> {
private static final String LOG_TYPE_LOGIN = "login";
private static final String LOG_TYPE_OPERATION = "operation";
/** 实际完成登录动作并创建登录态的接口。 */
private static final Set<String> LOGIN_REQUEST_URIS = Set.of(
"/login",
"/jit/p7certAuth",
"/app/login"
);
/**
* 按页面查询条件检索系统访问日志
*
* @param queryParam 分页及筛选条件
* @return 日志分页数据
*/
public Page<SystemLog> pageByQuery(SystemLogQueryParam queryParam) {
LambdaQueryWrapper<SystemLog> queryWrapper = new LambdaQueryWrapper<>();
if (StrUtil.isNotBlank(queryParam.getRequester())) {
queryWrapper.and(wrapper -> wrapper
.like(SystemLog::getUserName, queryParam.getRequester())
.or()
.like(SystemLog::getNickName, queryParam.getRequester()));
}
// 登录日志和操作日志使用同一张表,通过登录接口白名单进行互斥分类。
if (LOG_TYPE_LOGIN.equals(queryParam.getLogType())) {
queryWrapper.in(SystemLog::getRequestUri, LOGIN_REQUEST_URIS);
} else if (LOG_TYPE_OPERATION.equals(queryParam.getLogType())) {
queryWrapper.notIn(SystemLog::getRequestUri, LOGIN_REQUEST_URIS);
}
queryWrapper
.like(StrUtil.isNotBlank(queryParam.getRequestUri()), SystemLog::getRequestUri, queryParam.getRequestUri())
.eq(StrUtil.isNotBlank(queryParam.getRequestMethod()), SystemLog::getRequestMethod, queryParam.getRequestMethod())
.like(StrUtil.isNotBlank(queryParam.getSourceIp()), SystemLog::getSourceIp, queryParam.getSourceIp())
.eq(Objects.nonNull(queryParam.getStatus()), SystemLog::getStatus, queryParam.getStatus())
.ge(Objects.nonNull(queryParam.getStartTime()), SystemLog::getLogTime, queryParam.getStartTime())
.le(Objects.nonNull(queryParam.getEndTime()), SystemLog::getLogTime, queryParam.getEndTime())
.orderByDesc(SystemLog::getLogTime)
.orderByDesc(SystemLog::getId);
return page(Page.of(queryParam.getCurrent(), queryParam.getSize()), queryWrapper);
}
/**
* 删除截止时间之前的系统访问日志
*
* @param expireTime 日志保留截止时间
* @return 实际删除条数
*/
public int removeBefore(LocalDateTime expireTime) {
return baseMapper.delete(new LambdaQueryWrapper<SystemLog>()
.lt(SystemLog::getLogTime, expireTime));
}
}
Loading…
Cancel
Save