使用AOP进行日志记录

需求:我们需要对用户账号进行审批,停用或启用,并且审批时我们需要对指定接口进行日志记录,记录到一个表内,包括记录编号和操作内容以备注

  • 用户表
-- auto-generated definition
create table t_user
(
    id       bigint auto_increment primary key,
    username varchar(100)  null,
    password varchar(100)  null,
    status   int default 0 null comment '1/0 停用/启用'
);


  • 操作记录表sql
create table audit_log
(
    id            bigint auto_increment comment 'id' primary key,
    create_time   timestamp default CURRENT_TIMESTAMP null,
    update_time   timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
    audit_no      varchar(255)                        null comment '操作编号',
    audit_content varchar(255)                        not null comment '操作内容',
    remark        varchar(255)                        null comment '备注'
);

我们现在的需求是将内容记录到该日志表内,所以我们需要记录的参数包括:操作编号、操作内容和备注,其他内容会自动生成。我们可以用AOP完成这个事情

首先我们每次操作时,需要知道用户的编号、我们需要操作的备注、内容、和操作的状态是停用或者还是启用,操作内容以及操作的状态和接口相关,所以我们只需要关心编号和备注。

因为操作的内容和状态都是与接口有关,所以我们将他作为注解实现

  • LogAudit
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAudit {
    /**
     * 操作 1/0  停用/启用
     * @return  int
     */
    int op() default 1;

    /**
     * 操作内容
     * @return pass
     */
    String content() default "PASS";
}

因为不同的接口我们需要的参数不同,但是都需要操作的编号和备注,这些是前端传进来的内容,所以我们将编号和备注抽出来。

  • BaseDTO
@Data
@ApiModel("基础dto")
public class BaseDTO {
    @ApiModelProperty("编号")
    private String auditNo;

    @ApiModelProperty("备注")
    private String remark;
}
  • 接着,我们传递需要的用户DTO
@Data
@ApiModel
public class UserDTO extends BaseDTO {
    private String username;
  
    private Integer status;
}
  • service
public interface UserService extends IService<User> {

    void auditUser(UserDTO user);
}
  • impl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    @LogAudit(op = 0,content = "START")
    @Transactional
    public void auditUser(UserDTO user) {
				LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(User::getStatus,user.getStatus()).
                eq(User::getUsername,user.getUsername());
        update(wrapper);
    }
}
  • 完成之后我们做我们的AOP增强,用来获取参数以及注解中的内容,进行封装
/**
 * @date 2023/9/13 15:42
 */
@Component
@Aspect
@Slf4j
public class LogAspect {

    @Autowired
    private AuditLogMapper logMapper;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Pointcut("@annotation(com.example.annotation.LogAudit)")
    private void pointCut(){};

    //后置切面
    @After(value = "pointCut()")
    @Transactional
    public void insertLog(JoinPoint joinPoint){
      
       //获取参数列表
        Object[] args = joinPoint.getArgs();
        AuditLog auditLog = new AuditLog();
        for (Object arg : args) {
            //	需要定位到有所需内容的参数处
            if(arg instanceof BaseDTO){
                BaseDTO baseDTO = (BaseDTO) arg;
                String auditNo = baseDTO.getAuditNo();
                String remark = baseDTO.getRemark();
                auditLog.setAuditNo(auditNo);
                auditLog.setRemark(remark);
                break;
            }
        }
        //获取注解对象 填充属性
        LogAudit annotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(LogAudit.class);
        int op = annotation.op();
        auditLog.setAuditContent("操作::"+op+",类型:"+annotation.content());
        auditLog.setCreateTime(new Date());
        auditLog.setUpdateTime(new Date());
        //默认传播行为,可以指定
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //声明式事物会失效,这里用编程式事物
        transactionTemplate.execute(t->{
            logMapper.insert(auditLog);
            return Boolean.TRUE;
        });

    }
}

我们写一个测试类

    @Autowired
    private UserService userService; 

    @Test
    public void aopTest(){
        UserDTO userDTO = new UserDTO();
        userDTO.setUsername("aaaa");
        userDTO.setAuditNo("NO0000000029");
        userDTO.setStatus(0);
        userDTO.setRemark("表现良好,再接再厉");
        userService.auditUser(userDTO);
    }

查看控制台日志

JDBC Connection [HikariProxyConnection@1486862157 wrapping com.mysql.cj.jdbc.ConnectionImpl@200d1a3d] will be managed by Spring
==>  Preparing: UPDATE t_user SET status=? WHERE (username = ?) 
==> Parameters: 0(Integer), aaaa(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@45e140ae]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@45e140ae] from current transaction
==>  Preparing: INSERT INTO audit_log ( create_time, update_time, audit_no, audit_content, remark ) VALUES ( ?, ?, ?, ?, ? ) 
==> Parameters: 2023-09-13 17:57:11.266(Timestamp), 2023-09-13 17:57:11.266(Timestamp), NO0000000029(String), 操作::0,类型:START(String), 表现良好,再接再厉(String)
<==    Updates: 1

数据库也是有内容的。

当然我们也可以使用@AfterReturning指定只有操作成功后才记录日志。

你可能感兴趣的:(java)