文章目录
- 前言
- 一、基础资源配置
- 1.操作日志基本表[base_operation_log] 见附录1。
- 2.操作日志扩展表[base_operation_log_ext] 见附录2。
- 3.定义接口操作系统日志DTO:OptLogDTO
- 4.定义操作日志注解类WebLog
- 5.定义操作日志Aspect切面类SysLogAspect
- 6.定义异步监听日志事件类SysLogListener
- 8.定义系统日志事件SysLogEvent类
- 9.Web配置初始化SysLogListener
- 10. 资源实体内接口注入@WebLog
- 附录
前言
构建已上线系统, 以@注解方式对所有资源Restful标准接口注入,将资源Restful接口操作记录存在到操作日志基本表[base_operation_log记录接口操作详情如操作IP、操作人、请求方法 等等]操作日志扩展表[base_operation_log_ext记录接口的请求参数、响应体和异常描述],减少代码的侵入。
一、基础资源配置
1.操作日志基本表[base_operation_log] 见附录1。
2.操作日志扩展表[base_operation_log_ext] 见附录2。
3.定义接口操作系统日志DTO:OptLogDTO
记录所有资源实体的操作日志信息实。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode
@Accessors(chain = true)
public class OptLogDTO {
private static final long serialVersionUID = 1L;
/**
* 操作IP
*/
private String requestIp;
private Long basePoolNameHeader;
private Long extendPoolNameHeader;
/**
* 日志链路追踪id日志标志
*/
private String trace;
/**
* 日志类型
* #LogType{OPT:操作类型;EX:异常类型}
*/
private String type;
/**
* 操作人
*/
private String userName;
/**
* 操作描述
*/
private String description;
/**
* 类路径
*/
private String classPath;
/**
* 请求类型
*/
private String actionMethod;
/**
* 请求地址
*/
private String requestUri;
/**
* 请求类型
* #HttpMethod{GET:GET请求;POST:POST请求;PUT:PUT请求;DELETE:DELETE请求;PATCH:PATCH请求;TRACE:TRACE请求;HEAD:HEAD请求;OPTIONS:OPTIONS请求;}
*/
private String httpMethod;
/**
* 请求参数
*/
private String params;
/**
* 返回值
*/
private String result;
/**
* 异常描述
*/
private String exDetail;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 完成时间
*/
private LocalDateTime finishTime;
/**
* 消耗时间
*/
private Long consumingTime;
/**
* 浏览器
*/
private String ua;
private Long createdBy;
private Long createdOrgId;
private String token;
}
4.定义操作日志注解类WebLog
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebLog {
/**
* 是否启用 操作日志
* 禁用控制优先级:lamp.log.enabled = false > 控制器类上@WebLog(enabled = false) > 控制器方法上@WebLog(enabled = false)
*/
boolean enabled() default true;
/**
* 操作日志的描述, 支持spring 的 SpEL 表达式。
* @return {String}
*/
String value() default "";
/**
* 模块
*/
String modular() default "";
/**
* 是否拼接Controller类上@Api注解的描述值
* @return 是否拼接Controller类上的描述值
*/
boolean controllerApiValue() default true;
/**
* 是否记录方法的入参
*/
boolean request() default true;
/**
* 若设置了 request = false、requestByError = true,则方法报错时,依然记录请求的入参
* @return 当 request = false时, 方法报错记录请求参数
*/
boolean requestByError() default true;
/**
* 是否记录返回值
*/
boolean response() default true;
}
5.定义操作日志Aspect切面类SysLogAspect
1.定义controller切入点拦截规则:拦截标记WebLog注解和指定包下的方法
2.在接口方法三个时刻点[返回通知doAfterReturning、异常通知doAfterThrowable、执行方法之前doBefore] 进行拦截,构建OptLogDTO对象并发布订阅事件 publishEvent(sysLogDTO)
package top.tangyh.basic.log.aspect;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.tangyh.basic.annotation.log.WebLog;
import top.tangyh.basic.base.R;
import top.tangyh.basic.context.ContextConstants;
import top.tangyh.basic.context.ContextUtil;
import top.tangyh.basic.jackson.JsonUtil;
import top.tangyh.basic.log.event.SysLogEvent;
import top.tangyh.basic.log.util.LogUtil;
import top.tangyh.basic.log.util.ThreadLocalParam;
import top.tangyh.basic.model.log.OptLogDTO;
import top.tangyh.basic.utils.SpringUtils;
import top.tangyh.basic.utils.StrPool;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
/**
* 操作日志使用spring event异步入库
*/
@Slf4j
@Aspect
public class SysLogAspect {
public static final int MAX_LENGTH = 65535;
private static final ThreadLocal<OptLogDTO> THREAD_LOCAL = new ThreadLocal<>();
private static final String FORM_DATA_CONTENT_TYPE = "multipart/form-data";
/**
* 用于SpEL表达式解析.
*/
private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
/***
* 定义controller切入点拦截规则:拦截标记WebLog注解和指定包下的方法
* 2个表达式加起来才能拦截所有Controller 或者继承了BaseController的方法
*
* execution(public * top.tangyh.basic.base.controller.*.*(..)) 解释:
* 第一个* 任意返回类型
* 第二个* top.tangyh.basic.base.controller包下的所有类
* 第三个* 类下的所有方法
* ()中间的.. 任意参数
*
* \@annotation(top.tangyh.basic.annotation.log.WebLog) 解释:
* 标记了@WebLog 注解的方法
*/
@Pointcut("execution(public * top.tangyh.basic.base.controller.*.*(..)) || @annotation(top.tangyh.basic.annotation.log.WebLog)")
public void sysLogAspect() {
}
/**
* 返回通知doAfterReturning
*
* @param ret 返回值
* @param joinPoint 端点
*/
@AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
public void doAfterReturning(JoinPoint joinPoint, Object ret) {
tryCatch(p -> {
WebLog sysLog = LogUtil.getTargetAnnotation(joinPoint);
if (check(joinPoint, sysLog)) {
return;
}
R r = Convert.convert(R.class, ret);
OptLogDTO sysLogDTO = get();
if (r == null) {
sysLogDTO.setType("OPT");
if (sysLog.response()) {
sysLogDTO.setResult(getText(String.valueOf(ret == null ? StrPool.EMPTY : ret)));
}
} else {
if (r.getIsSuccess()) {
sysLogDTO.setType("OPT");
} else {
sysLogDTO.setType("EX");
sysLogDTO.setExDetail(r.getMsg());
}
if (sysLog.response()) {
sysLogDTO.setResult(getText(r.toString()));
}
}
publishEvent(sysLogDTO);
});
}
/**
* 异常通知doAfterThrowable
*
* @param joinPoint 端点
* @param e 异常
*/
@AfterThrowing(pointcut = "sysLogAspect()", throwing = "e")
public void doAfterThrowable(JoinPoint joinPoint, Throwable e) {
tryCatch((aaa) -> {
WebLog sysLog = LogUtil.getTargetAnnotation(joinPoint);
if (check(joinPoint, sysLog)) {
return;
}
OptLogDTO optLogDTO = get();
optLogDTO.setType("EX");
// 遇到错误时,请求参数若为空,则记录
if (!sysLog.request() && sysLog.requestByError() && StrUtil.isEmpty(optLogDTO.getParams())) {
Object[] args = joinPoint.getArgs();
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String strArgs = getArgs(args, request);
optLogDTO.setParams(getText(strArgs));
}
// 异常对象
optLogDTO.setExDetail(ExceptionUtil.stacktraceToString(e, MAX_LENGTH));
publishEvent(optLogDTO);
});
}
/**
* 执行方法之前doBefore
*
* @param joinPoint 端点
*/
@Before(value = "sysLogAspect()")
public void doBefore(JoinPoint joinPoint) {
tryCatch(val -> {
WebLog sysLog = LogUtil.getTargetAnnotation(joinPoint);
if (check(joinPoint, sysLog)) {
return;
}
OptLogDTO optLogDTO = buildOptLogDTO(joinPoint, sysLog);
THREAD_LOCAL.set(optLogDTO);
});
}
@NonNull
private OptLogDTO buildOptLogDTO(JoinPoint joinPoint, WebLog sysLog) {
// 开始时间
OptLogDTO optLogDTO = get();
optLogDTO.setCreatedBy(ContextUtil.getUserId());
setDescription(joinPoint, sysLog, optLogDTO);
// 类名
optLogDTO.setClassPath(joinPoint.getTarget().getClass().getName());
//获取执行的方法名
optLogDTO.setActionMethod(joinPoint.getSignature().getName());
HttpServletRequest request = setParams(joinPoint, sysLog, optLogDTO);
optLogDTO.setRequestIp(JakartaServletUtil.getClientIP(request));
optLogDTO.setRequestUri(URLUtil.getPath(request.getRequestURI()));
optLogDTO.setHttpMethod(request.getMethod());
optLogDTO.setUa(StrUtil.sub(request.getHeader("user-agent"), 0, 500));
if (ContextUtil.getBoot()) {
optLogDTO.setCreatedOrgId(ContextUtil.getCurrentCompanyId());
optLogDTO.setToken(ContextUtil.getToken());
} else {
optLogDTO.setToken(Convert.toStr(request.getHeader(ContextConstants.TOKEN_HEADER)));
optLogDTO.setCreatedOrgId(Convert.toLong(request.getHeader(ContextConstants.CURRENT_COMPANY_ID_HEADER)));
}
optLogDTO.setTrace(MDC.get(ContextConstants.TRACE_ID_HEADER));
if (StrUtil.isEmpty(optLogDTO.getTrace())) {
optLogDTO.setTrace(request.getHeader(ContextConstants.TRACE_ID_HEADER));
}
optLogDTO.setStartTime(LocalDateTime.now());
return optLogDTO;
}
@NonNull
private HttpServletRequest setParams(JoinPoint joinPoint, WebLog sysLog, OptLogDTO optLogDTO) {
// 参数
Object[] args = joinPoint.getArgs();
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes(), "只能在Spring Web环境使用@WebLog记录日志")).getRequest();
if (sysLog.request()) {
String strArgs = getArgs(args, request);
optLogDTO.setParams(getText(strArgs));
}
return request;
}
private void setDescription(JoinPoint joinPoint, WebLog sysLog, OptLogDTO optLogDTO) {
String controllerDescription = "";
Tag api = joinPoint.getTarget().getClass().getAnnotation(Tag.class);
if (api != null) {
controllerDescription = api.name();
}
String controllerMethodDescription = LogUtil.getDescribe(sysLog);
if (StrUtil.isNotEmpty(controllerMethodDescription) && StrUtil.contains(controllerMethodDescription, StrPool.HASH)) {
//获取方法参数值
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
controllerMethodDescription = getValBySpEl(controllerMethodDescription, methodSignature, args);
}
if (StrUtil.isEmpty(controllerDescription)) {
optLogDTO.setDescription(controllerMethodDescription);
} else {
if (sysLog.controllerApiValue()) {
optLogDTO.setDescription(controllerDescription + "-" + controllerMethodDescription);
} else {
optLogDTO.setDescription(controllerMethodDescription);
}
}
}
private OptLogDTO get() {
OptLogDTO sysLog = THREAD_LOCAL.get();
if (sysLog == null) {
return new OptLogDTO();
}
return sysLog;
}
private void tryCatch(Consumer<String> consumer) {
try {
consumer.accept("");
} catch (Exception e) {
log.warn("记录操作日志异常", e);
THREAD_LOCAL.remove();
}
}
private void publishEvent(OptLogDTO sysLog) {
sysLog.setFinishTime(LocalDateTime.now());
sysLog.setConsumingTime(sysLog.getStartTime().until(sysLog.getFinishTime(), ChronoUnit.MILLIS));
SpringUtils.publishEvent(new SysLogEvent(sysLog));
THREAD_LOCAL.remove();
}
/**
* 监测是否需要记录日志
*
* @param joinPoint 端点
* @param sysLog 操作日志
* @return true 表示不需要记录日志
*/
private boolean check(JoinPoint joinPoint, WebLog sysLog) {
if (sysLog == null || !sysLog.enabled()) {
return true;
}
// 读取目标类上的注解
WebLog targetClass = joinPoint.getTarget().getClass().getAnnotation(WebLog.class);
// 加上 sysLog == null 会导致父类上的方法永远需要记录日志
return targetClass != null && !targetClass.enabled();
}
/**
* 截取指定长度的字符串
*
* @param val 参数
* @return 截取文本
*/
private String getText(String val) {
return StrUtil.sub(val, 0, 65535);
}
private String getArgs(Object[] args, HttpServletRequest request) {
String strArgs = StrPool.EMPTY;
Object[] params = Arrays.stream(args).filter(item -> !(item instanceof ServletRequest || item instanceof ServletResponse)).toArray();
try {
if (!request.getContentType().contains(FORM_DATA_CONTENT_TYPE)) {
strArgs = JsonUtil.toJson(params);
}
} catch (Exception e) {
try {
strArgs = Arrays.toString(params);
} catch (Exception ex) {
log.warn("解析参数异常", ex);
}
}
return strArgs;
}
/**
* 解析spEL表达式
*/
private String getValBySpEl(String spEl, MethodSignature methodSignature, Object[] args) {
try {
//获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
if (paramNames != null && paramNames.length > 0) {
Expression expression = spelExpressionParser.parseExpression(spEl);
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
context.setVariable("p" + i, args[i]);
}
ThreadLocalParam tlp = new ThreadLocalParam();
BeanUtil.fillBeanWithMap(ContextUtil.getLocalMap(), tlp, true);
context.setVariable("threadLocal", tlp);
Object value = expression.getValue(context);
return value == null ? spEl : value.toString();
}
} catch (Exception e) {
log.warn("解析操作日志的el表达式出错", e);
}
return spEl;
}
}
6.定义异步监听日志事件类SysLogListener
@EventListener({SysLogEvent.class})监听SysLogEvent事件,当有发布事件publishEvent(sysLogDTO)发生,根据事件对象SysLogEvent 获取事件关联资源体(即OptLogDTO sysLog = (OptLogDTO)event.getSource()),并开始执行消费this.consumer.accept(sysLog)
import java.util.function.Consumer;
import lombok.Generated;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
public class SysLogListener {
private final Consumer<OptLogDTO> consumer;
@Async
@Order
@EventListener({SysLogEvent.class})
public void saveSysLog(SysLogEvent event) {
OptLogDTO sysLog = (OptLogDTO)event.getSource();
ContextUtil.setToken(sysLog.getToken());
this.consumer.accept(sysLog);
}
@Generated
public SysLogListener(final Consumer<OptLogDTO> consumer) {
this.consumer = consumer;
}
}
8.定义系统日志事件SysLogEvent类
import org.springframework.context.ApplicationEvent;
import top.tangyh.basic.model.log.OptLogDTO;
public class SysLogEvent extends ApplicationEvent {
public SysLogEvent(OptLogDTO source) {
super(source);
}
}
9.Web配置初始化SysLogListener
初始化SysLogListener监听类和消费动作的接口类BaseOperationLog-Service的保存(即消费事件资源)操作,
logApi.save(BeanPlusUtil.toBean(data, BaseOperationLogSaveVO.class)
其中:logApi为BaseOperationLogService 基础接口日志服务类
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.tangyh.basic.boot.config.BaseConfig;
import top.tangyh.basic.constant.Constants;
import top.tangyh.basic.log.event.SysLogListener;
import top.tangyh.basic.utils.BeanPlusUtil;
import top.tangyh.lamp.base.service.system.BaseOperationLogService;
import top.tangyh.lamp.base.vo.save.system.BaseOperationLogSaveVO;
/**
* 基础服务-Web配置SysLogListener
*/
@Configuration
public class BaseWebConfiguration extends BaseConfig {
/**
* lamp.log.enabled = true 并且 lamp.log.type=DB时实例该类
*/
@Bean
@ConditionalOnExpression("$lamp.log.enabled:true} && 'DB'.equals('${lamp.log.type:DB}')")
public SysLogListener sysLogListener(BaseOperationLogService logApi) {
return new SysLogListener(data -> logApi.save(BeanPlusUtil.toBean(data, BaseOperationLogSaveVO.class)));
}
}
yml配置:
lamp:
log: # 详情看:OptLogProperties
# 开启记录操作日志
enabled: true
# 记录到什么地方 DB:mysql LOGGER:日志文件
type: DB
package top.tangyh.lamp.base.service.system.impl;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.tangyh.basic.base.service.impl.SuperServiceImpl;
import top.tangyh.lamp.base.entity.system.BaseOperationLog;
import top.tangyh.lamp.base.entity.system.BaseOperationLogExt;
import top.tangyh.lamp.base.manager.system.BaseOperationLogManager;
import top.tangyh.lamp.base.mapper.system.BaseOperationLogExtMapper;
import top.tangyh.lamp.base.service.system.BaseOperationLogService;
import top.tangyh.lamp.base.vo.result.system.BaseOperationLogResultVO;
import top.tangyh.lamp.base.vo.save.system.BaseOperationLogSaveVO;
import java.time.LocalDateTime;
/**
* 业务实现类操作日志
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class BaseOperationLogServiceImpl extends SuperServiceImpl<BaseOperationLogManager, Long, BaseOperationLog> implements BaseOperationLogService {
private final BaseOperationLogExtMapper
baseOperationLogExtMapper;
@Override
public <SaveVO> BaseOperationLog save(SaveVO saveVO) {
BaseOperationLogSaveVO logSaveVO = (BaseOperationLogSaveVO) saveVO;
BaseOperationLogExt baseOperationLogExt = BeanUtil.toBean(saveVO, BaseOperationLogExt.class);
//存储扩展日志表
baseOperationLogExtMapper.insert(baseOperationLogExt);
logSaveVO.setId(baseOperationLogExt.getId());
return super.save(logSaveVO);//存储操作基础日志表
}
}
10. 资源实体内接口注入@WebLog
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/baseEmployee")
@Tag(name = "员工")
public class BaseEmployeeController ........ {
//response = false时日志扩展表不记录接口的response内容
@WebLog(value = "分页列表查询", response = false)
public R<IPage<BaseEmployeeResultVO>> page(@RequestBody
@Validated PageParams<BaseEmployeePageQuery> params) {
//todo:分页列表查询
return R.success(page);
}
}
附录
1.操作日志基本表[base_operation_log记录接口操作详情如操作IP、操作人、请求方法 等等]
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import top.tangyh.basic.base.entity.Entity;
import top.tangyh.lamp.base.enumeration.system.LogType;
import top.tangyh.lamp.model.enumeration.HttpMethod;
import java.time.LocalDateTime;
import static top.tangyh.lamp.model.constant.Condition.LIKE;
/**
* <p>
* 实体类 操作日志
*/
@Data
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("base_operation_log")
@AllArgsConstructor
public class BaseOperationLog extends Entity<Long> {
private static final long serialVersionUID = 1L;
/**
* 操作IP
*/
@TableField(value = "request_ip", condition = LIKE)
private String requestIp;
/**
* 日志类型;#LogType{OPT:操作类型;EX:异常类型}
*/
@TableField(value = "type")
private LogType type;
/**
* 操作人
*/
@TableField(value = "user_name", condition = LIKE)
private String userName;
/**
* 操作描述
*/
@TableField(value = "description", condition = LIKE)
private String description;
/**
* 类路径
*/
@TableField(value = "class_path", condition = LIKE)
private String classPath;
/**
* 请求方法
*/
@TableField(value = "action_method", condition = LIKE)
private String actionMethod;
/**
* 请求地址
*/
@TableField(value = "request_uri", condition = LIKE)
private String requestUri;
/**
* 请求类型;#HttpMethod{GET:GET请求;POST:POST请求;PUT:PUT请求;DELETE:DELETE请求;PATCH:PATCH请求;TRACE:TRACE请求;HEAD:HEAD请求;OPTIONS:OPTIONS请求;}
*/
@TableField(value = "http_method")
private HttpMethod httpMethod;
/**
* 开始时间
*/
@TableField(value = "start_time")
private LocalDateTime startTime;
/**
* 完成时间
*/
@TableField(value = "finish_time")
private LocalDateTime finishTime;
/**
* 消耗时间
*/
@TableField(value = "consuming_time")
private Long consumingTime;
/**
* 浏览器
*/
@TableField(value = "ua", condition = LIKE)
private String ua;
/**
* 创建人组织
*/
@TableField(value = "created_org_id")
private Long createdOrgId;
}
2.操作日志扩展表[base_operation_log_ext记录接口的请求参数、响应体和异常描述]。
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import top.tangyh.basic.base.entity.Entity;
import static top.tangyh.lamp.model.constant.Condition.LIKE;
/**
* <p>
* 实体类操作扩展日志
*/
@Data
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("base_operation_log_ext")
@AllArgsConstructor
public class BaseOperationLogExt extends Entity<Long> {
private static final long serialVersionUID = 1L;
/**
* 请求参数
*/
@TableField(value = "params", condition = LIKE)
private String params;
/**
* 返回值
*/
@TableField(value = "result", condition = LIKE)
private String result;
/**
* 异常描述
*/
@TableField(value = "ex_detail", condition = LIKE)
private String exDetail;
}
3.其它工具类参考 lamp-util
4.注意Consumer 的使用。
Consumer用于定义对单个输入参数进行操作且不返回结果的任务,核心方法是 void accept(T t),执行操作逻辑。Consumer 就是一个“吃掉”(消费)一个对象 T 并对其做点事情(如修改、打印、存储等)但不吐回(返回)任何东西的工具。
public class SysLogListener {
//1.1定义Consumer和待消息对象对象OptLogDTO
private final Consumer<OptLogDTO> consumer;
@Async
@Order
//监听到事件,开始执行消费操作
@EventListener({SysLogEvent.class})
public void saveSysLog(SysLogEvent event) {
OptLogDTO sysLog = (OptLogDTO)event.getSource();
ContextUtil.setToken(sysLog.getToken());
//3 抽象方法,等待执行 实例化(消费)的操作
this.consumer.accept(sysLog);
}
//1.2 构建Consumer和待消息对象OptLogDTO
@Generated
public SysLogListener(final Consumer<OptLogDTO> consumer) {
this.consumer = consumer;
}
}
@Configuration
public class BaseWebConfiguration extends BaseConfig {
@Bean
public SysLogListener sysLogListener(
BaseOperationLogService
logApi) {
//2 Lambda 方法实现类定义具体消费【即等待执行 实例化(消费)】
return new SysLogListener(data ->
logApi.save(BeanPlusUtil.toBean(data,
BaseOperationLogSaveVO.class)));
}
}
执行:BaseOperationLogService.save(..开始向数据表插入日志记录..)