先说一下背景。
项目中要做好日志记录这个是很正常的事情,尤其是涉及到后台操作,要落实到记录每个操作由哪个用户哪个ip在什么时候用什么参数访问了什么方法。(这里的操作一般多是涉及到系统的更改,反正我设计的时候查询是不在此列的。多是对数据进行改动的操作,比如角色,子账号,用户的增删改。还有一些审核操作的处理等,酌情处理吧。)对了,我们是把日志保存到数据库。毕竟还是用传统数据库比较多,说真的,es至今为止没有在工作之中用到过。哈哈。
然后开始说具体的实现。
思路
首先说一下实现思路:因为我计划要实现的日志记录是要统计执行操作的时间的,所以我这里是用的Around注解。毕竟要知道执行前的时间和执行后的时间才能获取到执行用的时间。
科普小知识(切面注解类型)
想了想怕有人对这块不熟,所以我简单的列出来下:
-
@Before前置通知,相当于BeforeAdvice
-
@AfterReturning后置通知,相当于AfterReturningAdvice
-
@Around环绕通知,相当于MethodInterceptor
-
@AfterThrowing抛出通知,相当于ThrowAdvice
-
@After最终final通知,不管是否异常,该通知都会执行
切点
再其次如何切想好了,该接着考虑切哪里了。切点的定义因为我之前就说了增删改才记录,查是不记录的。所以我这里专门用一个注解来确定切点。而且专门写注解的好处是比较灵活,还能定义这个切点的具体内容。不过因为其实我这里就是简单的记录,所以写的也比较简单,我直接附上定义注解的代码:
/**
* 系统日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
这里value默认是空,但是如果在使用这个注解时传了value是可以正常取出来的,所以我是可以获得一个方法的简单中文描述的。下面附上几个使用的demo:
保存
修改
这里只要看@SysLog("")注解就行了。
最后回归正题:切点。自然就是有这个注释的方法啦。
@Pointcut("@annotation(io.renren.common.annotation.SysLog)")
public void logPointCut() {
}
实现
刚刚我们把怎么切,切哪里都定义好了。接下来开始要正式切了。切的时候的逻辑最开始就构思好了,切入方法的名称,参数,操作人的电脑ip,操作人的用户名。所以这些我都要在方法里具体实现。
但是因为这部分没啥难度,所以我直接贴代码:
/**
* 系统日志,切面处理类
*/
@Aspect
@Component
public class SysLogAspect {
@Autowired
private SysLogService sysLogService;//我自己定义的保存日志的dao
//切点就是被SysLog注释的方法
@Pointcut("@annotation(io.renren.common.annotation.SysLog)")
public void logPointCut() {
}
//方法执行前后都操作
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSysLog(point, time);
return result;
}
private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLogEntity sysLog = new SysLogEntity();
SysLog syslog = method.getAnnotation(SysLog.class);
if(syslog != null){
//注解上的描述
sysLog.setOperation(syslog.value());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//请求的参数
Object[] args = joinPoint.getArgs();
try{
String params = new Gson().toJson(args);
sysLog.setParams(params);
}catch (Exception e){
}
//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
//用户名
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
sysLog.setUsername(username);
sysLog.setTime(time);
sysLog.setCreateDate(new Date());
//保存系统日志
sysLogService.save(sysLog);
}
}
然后上面其实有好多是封装好了的工具类,比如说获取ip的方法。还有获取用户名是用token获取的。。用户名这个没法直接使用,但是获取ip 的我还是可以贴一下的,用得到更好,用不到就算了。
/**
* IP地址
*/
public class IPUtils {
/**
* 获取IP地址
*
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
logger.error("IPUtils ERROR ", e);
}
return ip;
}
}
反正做到这我想实现的日志记录是实现了。附上测试后的数据表示:
至于最后的数据库表字段啥的也没硬性要求,所以我也不附上了。
这篇笔记就记录到这里,如果稍微帮到你了记得点个喜欢点个关注!另外以上内容我是完全依照我需要实现的目的而做的,程序的事需求稍微不同可能实现就大不相同或者表现各异。所以大家可以酌情借鉴,有什么比较好玩新颖特别的需求也可以留言或者私信共通交流实现的方式方法思路!
另外打个广告:java技术交流群:130031711,欢迎各位萌新大佬踊跃加入!
网友评论