切面aop记录系统操作日志 Java
切面原理
AOP切面获取操作日志的原理是通过在切点之前或之后执行日志记录的通知代码。每次目标方法执行时,切面会拦截执行过程,在执行前或执行后将相关信息记录到日志中,从而实现操作日志的获取。通过AOP的织入机制,可以将操作日志记录代码和业务代码进行分离,提高代码的可维护性和复用性
代码结构
主要代码:
MyLog
自定义注解记录系统操作日志,在方法上加注解如下,注解内容可以根据自己实际情况更改,我实在Impl层的方法上加的,这样就能保证到调接口使用到这个方法时,能够把对应的操作、操作时间、操作人等相关信息进行记录。**
@MyLog(title=“设备管理”,content=“对xx设备进行修改”)
**
/**
* @title: 自定义注解记录系统操作日志
* @Description: 自定义注解记录系统操作日志
* @Author: Momo
* @Date: 2023-11-23 11:14
*/
//Target注解决定 MyLog 注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ElementType.PARAMETER, ElementType.METHOD})
//Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让 MyLog 这个注解的生命周期一直程序运行时都存在
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
/**
* 模块标题
*/
String title() default "";
/**
* 日志内容
*/
String content() default "";
}
OperLog 实体类
这是对应数据库的实体类,存储的这些字段,可以根据自己系统的实际情况进行增减
/**
* @title: 操作日志记录实体类
* @Description: 操作日志记录表实体类
* @Author: Momo
* @Date: 2024-02-01 12:50
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperLog {
/**
* 唯一id
*/
private Integer id;
/**
* 操作人员
*/
private String operName;
/**
* 模块标题
*/
private String title;
/**
* 日志内容
*/
private String content;
/**
* 方法名称
*/
private String method;
/**
* ip地址
*/
private String ip;
/**
* 操作状态(0正常 1异常)
*/
private String status;
/**
* 错误信息
*/
private String errorMsg;
/**
* 操作时间
*/
private String operTime;
}
OperLogAspect 切面处理工具
import com.alibaba.fastjson.JSON;
import com.xtd.im.annotion.MyLog;
import com.xtd.im.entity.DO.OperLog;
import com.xtd.im.mapper.UserManagementMapper;
import com.xtd.im.utils.TokenUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @title: 切面处理工具
* @Description: 切面处理类,记录操作日志到数据库
* @Author: Momo
* @Date: 2024-02-01 13:44
*/
@Aspect
@Component
public class OperLogAspect {
@Resource
private UserManagementMapper operLogMapper;
//为了记录方法的执行时间
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 设置操作日志切入点,这里介绍两种方式:
* 1、基于注解切入(也就是打了自定义注解的方法才会切入)
* 2、基于包扫描切入
*/
@Pointcut("@annotation(com.xtd.im.annotion.MyLog)")//在注解的位置切入代码
//@Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")//从controller切入
public void operLogPoinCut() {
}
@Before("operLogPoinCut()")
public void beforMethod(JoinPoint point) {
startTime.set(System.currentTimeMillis());
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
*/
@Pointcut("execution(* com.xtd.im.controller..*.*(..))")
public void operExceptionLogPoinCut() {
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param result 返回结果
*/
@AfterReturning(value = "operLogPoinCut()", returning = "result")
public void saveOperLog(JoinPoint joinPoint, Object result) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
MyLog myLog = method.getAnnotation(MyLog.class);
OperLog operlog = new OperLog();
if (myLog != null) {
operlog.setTitle(myLog.title());//设置模块名称
operlog.setContent(myLog.content());//设置日志内容
}
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName + "()";
operlog.setMethod(methodName); //设置请求方法
String username = request.getSession().getAttribute("username").toString();
String token = request.getHeader("token");
operlog.setOperName(TokenUtils.getUsername(token));
// 获取用户名(真实环境中,肯定有工具类获取当前登录者的账号或ID的,或者从token中解析而来)
operlog.setIp(getIp(request)); // IP地址
operlog.setStatus("0");//操作状态(0正常 1异常)
//插入数据库
operLogMapper.insertLog(operlog);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
*/
@AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
OperLog operlog = new OperLog();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName + "()";
// 获取操作
MyLog myLog = method.getAnnotation(MyLog.class);
if (myLog != null) {
operlog.setTitle(myLog.title());//设置模块名称
operlog.setContent(myLog.content());//设置日志内容
}
operlog.setMethod(methodName); //设置请求方法
String token = request.getHeader("token");
operlog.setOperName(TokenUtils.getUsername(token));
operlog.setIp(getIp(request)); // IP地址
operlog.setStatus("1");//操作状态(0正常 1异常)
operlog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息
//插入数据库
operLogMapper.insertLog(operlog);
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 转换异常信息为字符串
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "\n");
}
String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
message = substring(message, 0, 2000);
return message;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (o != null) {
try {
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return params.trim();
}
//字符串截取
public static String substring(String str, int start, int end) {
if (str == null) {
return null;
} else {
if (end < 0) {
end += str.length();
}
if (start < 0) {
start += str.length();
}
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return "";
} else {
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
}
}
/**
* 转换request 请求参数
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> returnMap = new HashMap<>();
for (String key : paramMap.keySet()) {
returnMap.put(key, paramMap.get(key)[0]);
}
return returnMap;
}
//根据HttpServletRequest获取访问者的IP地址
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
TokenUtil 工具类
/**
* @title: token工具类
* @Description: token工具类
* @Author: Momo
* @Date: 2024-02-01 13:37
*/
public class TokenUtils {
// 令牌过期时间(毫秒),此处设置为30分钟
private static final long EXPIRE_DATE = 30 * 60 * 1000;
// 用于JWT加密的密钥
private static final String TOKEN_SECRET = "3A8Ii4F2DEyRqLFjNLy3uTRWmq16ZvRq";
/**
* 根据用户名和密码生成JWT Token
* @param username 用户名
* @param password 用户密码
* @return 返回生成的Token字符串
*/
public static String token(String username, String password) {
String token = "";
try {
// 计算Token的过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_DATE);
// 使用指定的密钥和算法进行加密
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// 设置Token的头部信息
Map<String, Object> header = new HashMap<>();
header.put("typ", "JWT");
header.put("alg", "HS256");
// 携带用户名和密码信息生成签名
token = JWT.create()
.withHeader(header) // 指定头部信息
.withClaim("username", username) // 包含username的信息
.withClaim("password", password) // 包含password的信息
.withExpiresAt(date) // 设置过期时间
.sign(algorithm); // 进行加密签名
} catch (Exception e) {
// 打印异常堆栈信息,并返回null
e.printStackTrace();
return null;
}
// 返回生成的Token
return token;
}
/**
* 检查Token是否已经过期
* @param token 需要检查的Token串
* @return true 如果Token已经过期,否则false
*/
public static boolean isTokenExpired(String token) {
// 如果Token的过期时间小于当前时间,则Token已经过期
return JWT.decode(token).getExpiresAt().before(new Date());
}
/**
* 验证Token是否合法
* @param token 需要验证的Token串
* @return true 如果Token验证通过,否则false
*/
public static boolean verify(String token) {
try {
// 根据密钥和算法创建Token验证对象
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
// 验证Token
DecodedJWT jwt = verifier.verify(token);
// 如果没有抛出异常,则验证通过,返回true
return true;
} catch (Exception e) {
// 如果验证过程中发生异常(如Token非法、过期等),打印异常堆栈信息,并返回false
e.printStackTrace();
return false;
}
}
/**
* @description: 解析token获取用户名
* @param token token令牌
* @return 用户名
* @Date: 2024-02-01 13:37
* @author: Momo
*/
public static String getUsername(String token){
//判空
if(StringUtils.isEmpty(token)) {
return "";
}
Claim username = null;
try {
DecodedJWT decode = JWT.decode(token);
Map<String, Claim> claims = decode.getClaims();
username = claims.get("username");
} catch (JWTDecodeException e) {
e.printStackTrace();
return "";
}
return username.asString();
}
}