1、AOP切面
/**
* @Author wyq
* @Description 操作的日志记录切面
* @Date 2024/2/21 11:46
**/
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
@Component
@Aspect
@Slf4j
public class MyOperateLogAspect {
@Autowired
HttpServletRequest httpServletRequest;
/**
* 操作日志记录
*/
@Autowired
private MyOperatorLogBiz operatorLogBiz;
/**
* 声明切面
* 只要Controller的方法中有@log注解就切入 重要
*/
@Pointcut("@annotation(annotation.MyOpreatorLog)")
public void logPointCut() {
}
/**
* 处理完请求后执行
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
MyOpreatorLog opreatorLog = getAnnotationLog(joinPoint);
if (opreatorLog == null) {
return;
}
// 获取当前的用户
MyOperatorLogEntity logEntity = MyOperatorLogEntity.builder()
.requestType(httpServletRequest.getMethod())
.requestUrl(httpServletRequest.getRequestURI())
.build();
//设置异常信息
if(e == null){
//当不需要记录返回结果时,设值为空串 add-2023/09/12
logEntity.setRequestResult(opreatorLog.isSaveResultData()? JSON.toJSONString(jsonResult) : "-");
logEntity.setLogLevel(LogLevelConstants.LOG_LEVEL_INFO);
}else{
//记录错误原因,根据日志记录的时间戳,通过ELK查看报错详情
logEntity.setRequestResult(e.getMessage());
logEntity.setLogLevel(LogLevelConstants.LOG_LEVEL_ERROR);
}
//设置当前登录人信息
logEntity.setCrtTime(new Date());
logEntity.setCrtName("登录人名称");
logEntity.setCrtUser("登录人id");
logEntity.setCrtJobNo("登录人工号");
//处理设置注解上的参数
getControllerMethodDescription(joinPoint, opreatorLog, logEntity);
//保存您的日志数据到数据库(1.0使用openFeign,2.0使用restTemplate调用日志服务爆粗你接口)
operatorLogBiz.addRecord(logEntity);
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 是否存在注解,如果存在就获取
*/
private MyOpreatorLog getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(MyOpreatorLog.class);
}
return null;
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param opreatorLog 日志
* @param logEntity 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, MyOpreatorLog opreatorLog, MyOperatorLogEntity logEntity) throws Exception {
// 设置action动作
logEntity.setOperatorType(String.valueOf(opreatorLog.operatorType()));
// 设置菜单名称
logEntity.setMenuName(opreatorLog.menuName());
// 是否需要保存request,参数和值
if (opreatorLog.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, logEntity);
}
}
/**
* 获取请求的参数,放到log中
*
* @param logEntity 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, MyOperatorLogEntity logEntity) throws Exception {
String requestMethod = logEntity.getRequestType();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
logEntity.setRequestParam(StringUtils.substring(params, 0, 2000));
} else {
Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
logEntity.setRequestParam(StringUtils.substring(JSON.toJSONString(parameterMap), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
if (!isFilterObject(paramsArray[i])) {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
}
}
return params.trim();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
public boolean isFilterObject(final Object o) {
return o instanceof MultipartFile || o instanceof HttpServletRequest
|| o instanceof HttpServletResponse;
}
}
2、切入点
/**
* @Author wyq
* @Description
* @Date 2024/2/21 11:43
**/
import java.lang.annotation.*;
/**
* 日志管理 -- 操作日志注解
*/
@Documented
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyOpreatorLog {
/**
* 菜单路径
*/
String menuName() default "";
/**
* 操作类型
*/
OperatorTypeConstant operatorType();
/**
* 是否保存请求数据
*/
boolean isSaveRequestData() default true;
/**
* 是否保存返回结果,默认true add-2023/09/12
*/
boolean isSaveResultData() default true;
}
3、日志级别及操作类型
/**
* @Author wyq
* @Description 操作日志等级分类
* @Date 2024/2/21 12:00
**/
public interface LogLevelConstants {
String LOG_LEVEL_INFO = "INFO";
String LOG_LEVEL_WARN = "WARN";
String LOG_LEVEL_ERROR = "ERROR";
}
/**
* @Author wyq
* @Description 操作类型定制类
* @Date 2024/2/21 11:44
**/
public enum OperatorTypeConstant {
SELECT("SELECT","查询"),
ADD("ADD","新增"),
EDIT("EDIT","修改"),
DELETE("DELETE","删除"),
IMPORT("IMPORT","导入"),
EXPORT("EXPORT","导出"),
COUNT("COUNT","统计");
//操作标识
private String mark;
//操作名称
private String name;
OperatorTypeConstant(String mark,String name){
this.mark = mark;
this.name = name;
}
public String getName(){
return name;
}
public String getMark(){
return mark;
}
}
4、存储实体
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="MyOperatorLogMapper">
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="MyOperatorLogEntity" id="myTransportCostOperatorLogMap">
<result property="id" column="id"/>
<result property="operatorType" column="operator_type"/>
<result property="logLevel" column="log_level"/>
<result property="menuName" column="menu_name"/>
<result property="requestUrl" column="request_url"/>
<result property="requestType" column="request_type"/>
<result property="requestParam" column="request_param"/>
<result property="requestResult" column="request_result"/>
<result property="crtTime" column="crt_time"/>
<result property="crtUser" column="crt_user"/>
<result property="crtName" column="crt_name"/>
<result property="crtJobNo" column="crt_job_no"/>
<result property="crtHost" column="crt_host"/>
<result property="status" column="status"/>
</resultMap>
<sql id="Base_Column_List">
id, operator_type, log_level, menu_name, request_url, request_type, request_param, request_result, crt_time, crt_user, crt_name, crt_job_no, crt_host, status
</sql>
</mapper>
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* 日志管理--操作日志
*
* @author wyq
* @email 473664742@qq.com
* @date 2022-09-16 18:42:12
*/
@Table(name = "my_operator_log")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MyOperatorLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@Id
private String id;
/**
* 菜单名称
*/
@Column(name = "menu_name")
private String menuName;
/**
* 操作类型
*/
@Column(name = "operator_type")
private String operatorType;
/**
* 日志类型(INFO、WARN、ERROR)
*/
@Column(name = "log_level")
private String logLevel;
/**
* 请求url
*/
@Column(name = "request_url")
private String requestUrl;
/**
* 请求类型(GET、POST、PUT、DELETE)
*/
@Column(name = "request_type")
private String requestType;
/**
* 请求参数
*/
@Column(name = "request_param")
private String requestParam;
/**
* 请求结果(SUCCESS、FAIL)
*/
@Column(name = "request_result")
private String requestResult;
/**
* 创建时间
*/
@Column(name = "crt_time")
private Date crtTime;
/**
* 创建人
*/
@Column(name = "crt_user")
private String crtUser;
/**
* 创建人
*/
@Column(name = "crt_name")
private String crtName;
/**
* 创建工号
*/
@Column(name = "crt_job_no")
private String crtJobNo;
/**
* Mac/IP
*/
@Column(name = "crt_host")
private String crtHost;
/**
* 1:有效 -1:删除
*/
@Column(name = "status")
private Integer status;
}
5、引用方式
@MyOpreatorLog(menuName = “日志管理-操作日志-列表查询”,operatorType = OperatorTypeConstant.SELECT)
说明:
- 支持附件类型的请求,但不存储附件内容,仅存储参数
- menuName 可以接入到自己的权限系统通过url比较获取,没有权限的话,自己手写