SpringBoot应用自定义注解记录操作日志

在项目里我们常常有一种需求,要在系统中记录用户的操作情况,需要记录用户请求的路径,表单参数等信息。针对这类需求,倘若对所有的请求都记录操作日志,当在访问量大并且系统功能点多的情况下日志量可能也会非常可观,然而其中一些可有可无的操作并不是我们非常需要关注的,这种情况下我们可以根据需要,选择性的对部分重要业务的操作进行记录,同时我希望这样的操作日志可以由框架统一来处理,不需要在开发业务模块的过程中过多的关注记录日志的过程,所以这里我们可以通过自定义注解,以及AOP的方式来将日志记录在数据库中或者文件当中。这里我们的业务非常的简单就记录到数据库中,方便展示。

1.首先自定义注解

/**
 * 定义操作日志注解
 * @author admin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SysOperationLog {
  String value() default "";
}

这里@Target(ElementType.METHOD)标记表示我们此处的自定义注解应用在方法上。当然,还有其他的类型:
@Target(ElementType.TYPE) //类、接口、枚举、注解
@Target(ElementType.METHOD) //方法
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE) //局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) //包

2.定义切面,实现自定义日志

@Aspect
@Order(5)
@Component
public class SysOperationLogAspect {

	@Autowired
	private SysLogService sysLogService;

	public static final String ACCOUNT = "匿名用户";
	public static final String LOGOUT_URI = "/logout";
	public static final String SHORT_PKG = "c.s.s.c";

	//保存线程共享变量,开始执行时间
	ThreadLocal<Long> startTime = new ThreadLocal<>();
	//日志ID
	ThreadLocal<String> logId = new ThreadLocal<>();

	//定义一个切入点,匹配带有@SysOperationLog注解的方法
	@Pointcut("@annotation(com.story.storyvueweb.config.log.SysOperationLog)")
	private void storySysLog() {
	}

	/**
	 * 环绕通知
	 * @param joinPoint
	 */
	@Around("storySysLog()")
	public Object advice(ProceedingJoinPoint joinPoint) {
		Object o = null;
		try {
			o = joinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		// 返回请求的结果
		return o;
	}

	/**
	 * 在请求的方法执行前执行
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Before("storySysLog()&&@annotation(SysOperationLog)")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();

		//记下请求起始时间
		startTime.set(System.currentTimeMillis());
		//生成logid
		logId.set(UUID.randomUUID().toString());
		
		SpiritLoginUser loginUser = LoginUserUtils.loginUser();
		final String method = request.getMethod();  //请求方法
		final String url = request.getRequestURL().toString();
		final String uri = request.getRequestURI();
		final String ip = IPUtils.getIpAddr(request);
		final String account = loginUser != null ? loginUser.getAccount() : "匿名用户";
		final String clazz = joinPoint.getSignature().getDeclaringTypeName()
				.replaceAll("com.story.storyvueweb.controller", SHORT_PKG);
		final String methodName = joinPoint.getSignature().getName();
		final Object[] args = joinPoint.getArgs();
		final String params = Arrays.toString(args);
		sysLogService.recordLog(new SysLogPO(logId.get(),account, ip, method, url, uri, clazz, methodName,
				DateUtils.currentDate(), null, params));
	}

	/**
	 * 在方法执行后环绕结束后执行
	 */
	@After("storySysLog()")
	public void doAfter() {
		// 计算出本次请求用时,更新到日志中
		sysLogService.recordLog(logId.get(), System.currentTimeMillis() - startTime.get());
	}
}

@Around环绕通知中的ProceedingJoinPoint表示连接点对象,可以通过反射执行目标对象连接点,也就是请求访问的方法。
@Before中的JoinPoint用以获取连接点运行时的参数,签名对象,上下文等信息。

这里我们已经在切面中将需要的参数放到了一个封装好的对象中,后面主要通过SysLogService来做,它的接口定义很简单:

public interface SysLogService extends StoryAbstractService<SysLogReqDTO, SysLogRespDTO,Long> {

	/**
	 * 记录日志
	 * @param entity
	 */
	public void recordLog(SysLogPO entity);
	
	/**
	 * 更新日志中耗时
	 * @param id
	 * @param spendTime  持续时间
	 */
	public void recordLog(String id, Long spendTime);
}

日志我这里放在了数据库中,当然也可以选择放在文件中,我们只需要修改实现类就可以了。

这里也简单的看下我们的SysLog对象定义:

@Data
@Entity
@Table(name="t_sys_log")
public class SysLogPO {
	private String id;
	private String account;
	private String ip;
	private String requestMethod;
	private String url;
	private String uri;
	private String clazz;
	private String methodName;
	private Date visitTime;
	private Long spendTime;
	private String params;

	public SysLogPO(String id, String account, String ip, String requestMethod, String url, String uri, String clazz,String methodName, Date visitTime, Long spendTime, String params) {
		super();
		this.id = id;
		this.account = account;
		this.ip = ip;
		this.requestMethod = requestMethod;
		this.url = url;
		this.uri = uri;
		this.clazz = clazz;
		this.methodName = methodName;
		this.visitTime = visitTime;
		this.spendTime = spendTime;
		this.params = params;
	}

	public SysLogPO() {
		super();
	}
}

3.在方法中使用注解

@RestController
@RequestMapping("/sysmgr/user")
public class UserController {

	@SysOperationLog
	@PostMapping(value = "/update_user")
	public BaseResp<UserRespDTO> updateUser(@RequestBody UserReqDTO userVO) {
		BaseResp<UserRespDTO> result = new BaseResp<>();
		return result;
	}
}

我们只需要在方法声明上增加一个注解@SysOperationLog,就可以在被请求时记录下请求的参数以及耗时。

最后我们可以通过查询,呈现在页面上的效果就是这样:
SpringBoot应用自定义注解记录操作日志
日志详细

发表评论

电子邮件地址不会被公开。 必填项已用*标注