需求:我们需要对用户账号进行审批,停用或启用,并且审批时我们需要对指定接口进行日志记录,记录到一个表内,包括记录编号和操作内容以备注
-- 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 停用/启用'
);
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完成这个事情
首先我们每次操作时,需要知道用户的编号、我们需要操作的备注、内容、和操作的状态是停用或者还是启用,操作内容以及操作的状态和接口相关,所以我们只需要关心编号和备注。
因为操作的内容和状态都是与接口有关,所以我们将他作为注解实现
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAudit {
/**
* 操作 1/0 停用/启用
* @return int
*/
int op() default 1;
/**
* 操作内容
* @return pass
*/
String content() default "PASS";
}
因为不同的接口我们需要的参数不同,但是都需要操作的编号和备注,这些是前端传进来的内容,所以我们将编号和备注抽出来。
@Data
@ApiModel("基础dto")
public class BaseDTO {
@ApiModelProperty("编号")
private String auditNo;
@ApiModelProperty("备注")
private String remark;
}
@Data
@ApiModel
public class UserDTO extends BaseDTO {
private String username;
private Integer status;
}
public interface UserService extends IService<User> {
void auditUser(UserDTO user);
}
@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);
}
}
/**
* @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
指定只有操作成功后才记录日志。