前言
最近在工作中碰到一个需求,要求对于用户进行的增删改查操作,都做一个操作日志。最简单的方式就是每个增删改查操作完成之后,都调用一个封装好的保存日志的方法。本文介绍一下基于自定义切面+自定义注解
的实现方式,这种方案的优点就是对原来代码的侵入性低,并且是一种横向的拓展,和业务无关。
下面创建了一个demo项目,来演示一下具体的实现方式。
表设计
这里设计一个用户表(t_user),一个日志表(t_oper_log)。我们对用户表的数据进行增删改查,将操作日志保存到日志表。
用户表:
日志表:
代码实现
UserController
如下,现在有一个UserController,里面有增删改查四个方法,现在需要对这些方法,进行操作记录
java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/list")
public List<UserEntity> list() {
return userService.list();
}
@PostMapping("/save")
public boolean save(@RequestBody UserEntity userEntity) {
return userService.save(userEntity);
}
@PostMapping("/delete")
public boolean delete(@RequestParam("id") long id) {
return userService.removeById(id);
}
@PostMapping("/update")
public boolean update(@RequestBody UserEntity userEntity) {
return userService.updateById(userEntity);
}
}
自定义注解
这一步需要定义一个注解,将来会将他标注在需要进行操作记录的controller层方法上,可以指定模块名称、业务名称、业务类型。这里也可自行拓展其他的字段。
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
/**
* 模块名称
*
* @return
*/
String module() default "";
/**
* 业务名称
*
* @return
*/
String business() default "";
/**
* 业务类型
*
* @return
*/
BusinessType businessType() default BusinessType.OTHER;
}
java
public enum BusinessType {
SELECT,
SAVE,
DELETE,
UPDATE,
OTHER;
}
自定义切面
代码如下,使用ThreadLocal变量计算了请求耗时。
java
@Aspect
@Component
public class LogAspect {
@Autowired
private OperLogService operLogService;
//请求耗时
private static final ThreadLocal<Long> COST_TIME = new NamedThreadLocal<>("cost_time");
/**
* 前置通知
*
* @param joinPoint
* @param log
*/
@Before("@annotation(log)")
public void before(JoinPoint joinPoint, Log log) {
//设置初始值,用于计算请求耗时
COST_TIME.set(System.currentTimeMillis());
}
/**
* 返回通知
*
* @param joinPoint
* @param log
* @param result
*/
@AfterReturning(pointcut = "@annotation(log)", returning = "result")
public void afterReturning(JoinPoint joinPoint, Log log, Object result) {
insertLog(joinPoint, log, result, null);
}
/**
* 异常通知
*
* @param joinPoint
* @param log
* @param e
*/
@AfterThrowing(pointcut = "@annotation(log)", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Log log, Exception e) {
insertLog(joinPoint, log, null, e);
}
/**
* 日志记录
*
* @param joinPoint
* @param log
* @param result
* @param e
*/
private void insertLog(JoinPoint joinPoint, Log log, Object result, Exception e) {
try {
OperLogEntity entity = new OperLogEntity();
//基本信息
entity.setAddTime(new Date());
entity.setAddUser(((UserEntity) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId());
//业务信息
entity.setModule(log.module());
entity.setBusiness(log.business());
entity.setBusinessType(log.businessType().toString());
//请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
entity.setRequestIp(request.getLocalAddr().contains(":") ? "127.0.0.1" : request.getLocalAddr());
entity.setRequestUrl(request.getRequestURI());
entity.setRequestMethod(request.getMethod());
//方法信息
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
entity.setMethod(className + "." + methodName + "()");
//请求耗时
entity.setCostTime(System.currentTimeMillis() - COST_TIME.get());
//错误日志
entity.setStatus(BusinessStatus.SUCCESS.ordinal());
if (e != null) {
entity.setStatus(BusinessStatus.FAIL.ordinal());
entity.setErrMsg(e.getMessage().length() < 255 ? e.getMessage() : e.getMessage().substring(0, 255));
}
//保存日志
operLogService.save(entity);
} catch (Exception exception) {
e.printStackTrace();
} finally {
COST_TIME.remove();
}
}
}
使用注解
在需要进行记录的controller层方法上标注注解
java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/list")
@Log(module = "用户管理", business = "查询列表", businessType = BusinessType.SELECT)
public List<UserEntity> list() {
return userService.list();
}
@PostMapping("/save")
@Log(module = "用户管理", business = "保存用户", businessType = BusinessType.SAVE)
public boolean save(@RequestBody UserEntity userEntity) {
return userService.save(userEntity);
}
@PostMapping("/delete")
@Log(module = "用户管理", business = "删除用户", businessType = BusinessType.DELETE)
public boolean delete(@RequestParam("id") long id) {
return userService.removeById(id);
}
@PostMapping("/update")
@Log(module = "用户管理", business = "修改用户", businessType = BusinessType.UPDATE)
public boolean update(@RequestBody UserEntity userEntity) {
return userService.updateById(userEntity);
}
}
总结
以上,就通过自定义切面+自定义注解 完成了全局的操作日志记录。