注解 + AOP 的方式记录日志到 t_ops_sync_log 表
要实现通过注解 + AOP 的方式记录日志到 t_ops_sync_log
表,我们可以按照以下步骤进行:
1. 首先创建自定义注解
这个注解将用于标记需要记录日志的方法,并可以通过属性传递一些基本信息
2. 创建切面类
通过切面捕获被注解标记的方法,在环绕通知中记录方法执行的详细信息
import java.lang.annotation.*;/*** 数据同步日志注解*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpsSyncLog {/*** 操作名*/String opsName() default "";/*** 是否记录方法参数*/boolean recordParams() default false;
}
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.stereotype.Component;
import org.springframework.util.StringUtils;import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.UUID;/*** 数据同步日志切面*/
@Slf4j
@Aspect
@Component
public class OpsSyncLogAspect {@Resourceprivate IOpsSyncLogService opsSyncLogService;/*** 定义切点:拦截所有标注了OpsSyncLog注解的方法*/@Pointcut("@annotation(com.yourpackage.OpsSyncLog)")public void opsSyncLogPointCut() {}/*** 环绕通知:记录方法执行的详细信息*/@Around("opsSyncLogPointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 构建日志对象TOpsSyncLog syncLog = new TOpsSyncLog();syncLog.setId(UUID.randomUUID().toString().replaceAll("-", ""));syncLog.setCreateTime(new Date());syncLog.setUpdateTime(new Date());syncLog.setIsDelete(0);syncLog.setVersion(1);syncLog.setStatus(0); // 默认状态// 获取注解信息MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();OpsSyncLog opsSyncLog = method.getAnnotation(OpsSyncLog.class);// 设置操作名if (StringUtils.hasText(opsSyncLog.opsName())) {syncLog.setOpsName(opsSyncLog.opsName());} else {// 如果没有指定操作名,使用类名+方法名String className = joinPoint.getTarget().getClass().getSimpleName();syncLog.setOpsName(className + "." + method.getName());}syncLog.setOpsTime(new Date());// 记录开始时间long startTime = System.currentTimeMillis();Object result = null;try {// 执行目标方法result = joinPoint.proceed();// 方法执行成功syncLog.setResult("1"); // 1表示成功syncLog.setStatus(1); // 成功状态return result;} catch (Throwable e) {// 方法执行异常syncLog.setResult("0"); // 0表示失败syncLog.setStatus(2); // 失败状态// 记录异常信息,避免过长String errorMsg = e.getMessage();if (errorMsg != null && errorMsg.length() > 500) {errorMsg = errorMsg.substring(0, 500);}syncLog.setErrMsg(errorMsg);throw e; // 继续抛出异常,不影响原有业务逻辑} finally {// 计算运行时长long endTime = System.currentTimeMillis();syncLog.setRunningTime((int) (endTime - startTime));// 异步保存日志,不阻塞主流程saveLogAsync(syncLog);}}/*** 异步保存日志*/private void saveLogAsync(TOpsSyncLog syncLog) {try {// 这里可以使用线程池或@Async注解实现异步opsSyncLogService.save(syncLog);} catch (Exception e) {// 日志保存失败不影响主业务,只记录错误日志log.error("保存同步日志失败", e);}}
}
import java.util.Date;/*** 数据同步日志实体类*/
public class TOpsSyncLog {private String id;private String opsName;private Date opsTime;private Integer runningTime;private Integer runTimes;private Integer dataSize;private String result;private String errMsg;private String createBy;private Date createTime;private String updateBy;private Date updateTime;private Integer status;private Integer isDelete;private Integer version;// getter和setter方法public String getId() {return id;}public void setId(String id) {this.id = id;}public String getOpsName() {return opsName;}public void setOpsName(String opsName) {this.opsName = opsName;}public Date getOpsTime() {return opsTime;}public void setOpsTime(Date opsTime) {this.opsTime = opsTime;}public Integer getRunningTime() {return runningTime;}public void setRunningTime(Integer runningTime) {this.runningTime = runningTime;}public Integer getRunTimes() {return runTimes;}public void setRunTimes(Integer runTimes) {this.runTimes = runTimes;}public Integer getDataSize() {return dataSize;}public void setDataSize(Integer dataSize) {this.dataSize = dataSize;}public String getResult() {return result;}public void setResult(String result) {this.result = result;}public String getErrMsg() {return errMsg;}public void setErrMsg(String errMsg) {this.errMsg = errMsg;}public String getCreateBy() {return createBy;}public void setCreateBy(String createBy) {this.createBy = createBy;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public String getUpdateBy() {return updateBy;}public void setUpdateBy(String updateBy) {this.updateBy = updateBy;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime = updateTime;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status = status;}public Integer getIsDelete() {return isDelete;}public void setIsDelete(Integer isDelete) {this.isDelete = isDelete;}public Integer getVersion() {return version;}public void setVersion(Integer version) {this.version = version;}
}
import org.springframework.stereotype.Service;/*** 使用示例*/
@Service
public class DataSyncService {/*** 被注解标记的方法会自动记录日志*/@OpsSyncLog(opsName = "用户数据同步")public void syncUserData() {// 业务逻辑实现try {// 模拟数据同步操作Thread.sleep(100);System.out.println("用户数据同步完成");} catch (InterruptedException e) {Thread.currentThread().interrupt();}}/*** 带参数的同步方法*/@OpsSyncLog(opsName = "订单数据同步")public void syncOrderData(int batchSize) {// 业务逻辑实现System.out.println("同步了" + batchSize + "条订单数据");}
}
实现说明
自定义注解
OpsSyncLog
:- 用于标记需要记录日志的方法
- 可以指定操作名称,便于日志查询
切面类
OpsSyncLogAspect
:- 使用
@Around
环绕通知,可以捕获方法执行前后的信息 - 记录方法执行时间、执行结果、异常信息等
- 通过
finally
确保无论方法成功还是失败都会记录日志 - 采用异步方式保存日志,避免影响主业务执行
- 日志保存失败时仅记录错误日志,不影响主流程
- 使用
关键特性:
- 完全不侵入业务代码,只需添加注解即可
- 异常安全:即使日志记录失败,也不会影响原方法执行
- 性能考虑:异步保存日志,避免阻塞主流程
- 自动记录:操作时间、运行时长、执行结果、异常信息等关键信息
扩展建议:
- 可以根据需要扩展注解属性,如添加
dataSize
等字段的获取方式 - 可以通过 SpEL 表达式从方法参数中提取更多信息
- 对于
runTimes
等需要业务逻辑提供的字段,可以考虑通过 ThreadLocal 传递
- 可以根据需要扩展注解属性,如添加
使用时,只需在需要记录日志的方法上添加 @OpsSyncLog
注解即可,例如示例中的 syncUserData
和 syncOrderData
方法。