操作日志的记录
为什么要有日志?
因为我们不光要记录代码的运行,如(logback log4j),而且还应该记录用户的行为,这叫做业务运行日志
例如:记录 zhangsan 在项目中 调用了哪个方法, 什么时间调用的 。访问的ip地址, 访问了哪些数据,做了什么操作,以此当程序出现问题的时候更利于我们进行错误的排查!
业务运行日志的作用
- 记录用户的行为 用于后续的分析
- 记录用户的所有的操作
业务运行日志最常用的使用场景:记录管理员所有的行为操作, 可以用于业务分析,事故恢复
日志实现的思路
1.我们需要记录哪些数据 存入数据库
这里列出一个我所用的表结构,如下所示:
字段 | 含义 |
---|---|
log_id | 主键 |
log_date | 时间 |
log_content | 操作内容 例如:查询全部菜单信息 添加用户数据 |
log_name_id | 用户的id |
log_ip | 用户的ip地址 |
log_type | 操作类型 |
2.在项目中什么位置记录
日志记录是一个数据库的添加操作 是一段代码
通常,我们在Controller方法进行后置增强
如下图所示,我们在需要记录操作的controller上使用aop配置一个切入点,以此来记录用户所进行的操作
3.如何实现记录功能
实现方式:AOP
4.Aop日志记录 具体代码实现
aop的使用流程,这里使用注解式aop来实现
具体步骤:
-
设置切入点
- 可以切在方法上
- 可以切在注解上
@Transactional 事务注解 注解加在类上 aop 切在注解上
-
写增强 日志记录增强
- 获取日志的相关信息
用户的id ip地址, 时间, 操作的描述, 类型等信息 - 将日志对象 添加到数据库
- 获取日志的相关信息
增强方法的编写
增强方法中获取session
因为我们是通过aop来获取用户的请求的,所以就需要通过当前的请求拿到session,进而去获取用户的信息。
但是,操作的描述如何获取呢?
比如 执行的方法不同 描述是不一样的
login 管理员登录
selectAllMenu 查询了所有的菜单
解决方案:使用自定义注解:
-
在 目标 方法上添加自定义注解 (@Log) 如下
-
在增强中获取注解(@Log)的value 和 type
代码实现
自定义日志注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 元注解:加在自定义注解上的注解
* @Target 定义注解可以添加的位置 METHOD 方法上 type 类上
* @Retention RUNTIME 运行时 不管编译 还是 运行 这个注解都可以用
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
/**
* 写法类似于接口的方法 后面可以通过default 关键字给默认值
* 用法类似于属性
* @return
*/
String value() default "";
String type() default "";
}
这里要注意什么是元注解,和 注解属性的定义方式
2. 在目标方法上使用注解
3. 在增强方法中获取注解的value 和 type
/**
* 操作的描述
*
* 执行的方法不同 描述是不一样的
* login 管理员登录
* selectAllGuru 查询了所有的上师
*
* 获取注解的值
*/
// 1.通过连接点获取方法签名 被切入方法的所有信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 2.获取被切入方法对象
Method method = signature.getMethod();
// 3.获取方法上的注解
LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
// 4.获取注解的值
String value = annotation.value();
完整的aop的代码实现
package com.tourism.hu.config;
/**
* @author 马超伟
* @PROJECT_NAME: fzll
* @Description:
* @date 15:29
* @Copyright: All rights Reserved, Designed By Huerdai
* Copyright: Copyright(C) 2019-2020
* Company Huerdai Henan LTD.
*/
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tourism.hu.entity.CustomerInfo;
import com.tourism.hu.entity.CustomerLoginLog;
import com.tourism.hu.service.ICustomerInfoService;
import com.tourism.hu.service.ICustomerLoginLogService;
import com.tourism.hu.util.IpAddressUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/*** @Aspect 标记当前类为功能增强类 切面类 *
* @Configuration 标记当前类为配置类 这个注解包含了@Component的功能
*/
@Aspect
@Configuration
public class LogAop {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private ICustomerInfoService iCustomerInfoService;
@Resource
private RedisTemplate redisTemplate;
@Resource
private ICustomerLoginLogService iCustomerLoginLogService;
/**
* JoinPoint 连接点 就是切入点 通过这个对象可以获取切入点的相关所有信息 例如:被切入的方法和注解
*
* @param joinPoint ** 切入点的设置 切注解 @annotation *
*/
@After("@annotation(com.tourism.hu.config.Log)")
public void logAfter(JoinPoint joinPoint) {
//new 一个日志的实体,用来保存日志信息
CustomerLoginLog loginLog = new CustomerLoginLog();
// 1.获取日志相关的信息 用户的id session ip 时间 操作的描述 类型 ctrl+H
/**
* 获取用户id
* 为什么不能装配session?因为服务器有多个session
* 通过 ServletRequestAttributes 可以获取当前请求
* 当前请求可以获取当前会话的session
*/
//获取用户的请求
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//得到session
HttpSession session = request.getSession();
String sessionid = session.getId();
//通过sessionid去获取用户信息
Object obj = redisTemplate.opsForValue().get(sessionid);
String customerId = "";
if(obj!=null) {
customerId=obj.toString();
}
//拿到用户对象
CustomerInfo customerInfo = iCustomerInfoService.getOne(new QueryWrapper<CustomerInfo>().eq("id", customerId));
if (customerInfo!=null){
//将用户的id 存入到日志实体中
loginLog.setCustomerId(customerInfo.getCustomerId());
}
loginLog.setLoginTime(LocalDateTime.now());
/**
* 获取用户的ip
* 通过工具类 ip
*/
loginLog.setLoginIp(IpAddressUtil.getIp());
/**
* 操作的描述
* 执行的方法不同 描述是不一样的
* login 管理员登录
* 获取注解的值
*/
// 1.通过连接点获取方法签名 被切入方法的所有信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 2.获取被切入方法对象
Method method = signature.getMethod();
// 3.获取方法上的注解
Log annotation = method.getAnnotation(Log.class);
// 4.获取注解的值
String value = annotation.value();
loginLog.setLogContent(value);
// 获取注解的类型
String type = annotation.type();
if (type!=null){
loginLog.setLoginType(type);
}
// 2.将日志对象 添加到数据库
System.out.println(loginLog);
logger.debug("loginLog===="+loginLog);
boolean save = iCustomerLoginLogService.save(loginLog);
logger.debug("保存日志------"+save);
}
}
所用到的工具类
获取ip地址的工具类IpAddressUtil
public static String getIp() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
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);
}
//使用代理,则获取第一个IP地址
if(StringUtils.isNotEmpty(ip) && ip.length() > 15) {
if(ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return ip;
}