分布式通过AOP进行操作日志记录
一、公共类
定义业务操作类型
根据需要自定义,标识某方法执行的操作
/** * 业务操作类型 * * @author ruoyi */ public enum BusinessType { /** * 其它 */ OTHER, /** * 新增 */ INSERT, /** * 修改 */ UPDATE, /** * 删除 */ DELETE, /** * 授权 */ GRANT, /** * 导出 */ EXPORT, /** * 导入 */ IMPORT, /** * 强退 */ FORCE, /** * 生成代码 */ GENCODE, /** * 清空数据 */ CLEAN, }
定义操作人类别
根据需要定义操作人类型()
/** * 操作人类别 * * @author ruoyi */ public enum OperatorType { /** * 其它 */ OTHER, /** * 后台用户 */ MANAGE, /** * 手机端用户 */ MOBILE }
自定义操作日志记录注解
@Target
- @Target 说明了Annotation所修饰的对象范围
- 取值(ElementType)有:
- 1.CONSTRUCTOR:用于描述构造器
- 2.FIELD:用于描述域
- 3.LOCAL_VARIABLE:用于描述局部变量
- 4.METHOD:用于描述方法
- 5.PACKAGE:用于描述包
- 6.PARAMETER:用于描述参数
- 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
- 8.ANNOTATION_TYPE: 注释类型声明
- 9.TYPE_PARAMETER: 类型参数声明
- 10.TYPE_USE: 类型的使用
@Retention
- @Retention定义了该Annotation被保留的时间长短:
- 某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
- 取值(RetentionPoicy)有:
- 1.SOURCE:在源文件中有效(即源文件保留)
- 2.CLASS:在class文件中有效(即class保留)
- 3.RUNTIME:在运行时有效(即运行时保留)
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
@Documented
- @Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
import com.deerChain.log.enums.BusinessType; import com.deerChain.log.enums.OperatorType; import java.lang.annotation.*; /** * 自定义操作日志记录注解 * * @author ruoyi * */ @Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 模块 */ public String title() default ""; /** * 功能 */ public BusinessType businessType() default BusinessType.OTHER; /** * 操作人类别 */ public OperatorType operatorType() default OperatorType.MANAGE; /** * 是否保存请求的参数 */ public boolean isSaveRequestData() default true; }
定义日志类
定义需要记录的日志信息
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.SelectBeforeUpdate; import javax.persistence.*; import javax.validation.constraints.NotNull; import java.io.Serializable; /** * 操作日志 * @author Administrator * */ @Data @AllArgsConstructor @NoArgsConstructor @Entity @SelectBeforeUpdate @DynamicInsert @DynamicUpdate @Table(name="t_manager_log") public class ManagerLoginLog implements Serializable{ /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) //自动增长 @Column(name = "id",length =11) private Integer id; @Column(name="managerId",length=44) @NotNull private String managerId;//操作人ID @Column(name="createTime",length=10) @NotNull private Integer createTime;//添加时间 @Column(name = "ip") private String ip;//IP地址 @Column(name = "mapper") private String mapper;//操作的Api地址 @Column(name = "param") private String param;//传入参数 @Column(name = "businessType") private Integer businessType;//业务类型 @Column(name = "title") private String title;//操作模块 @Column(name = "operatorType") private Integer operatorType;//操作类别0=其他 1=后台 2=手机端 @Column(name = "jsonResult") private String jsonResult;//返回参数 @Column(name = "requestMethod") private String requestMethod;//请求方式 @Column(name = "errorMsg") private String errorMsg;//错误信息 @Column(name = "method") private String method;//方法名称 }
二、定义切面类
在需要使用日志记录的模块定义切面类
Spring AOP 相关注解
- @Aspect :将一个 java 类定义为切面类。
- @Pointcut :定义一个切入点,可以是一个规则表达式。
- @Before :在切入点开始处切入内容。
- @After :在切入点结尾处切入内容。
- @AfterReturning :在切入点 return 内容之后切入内容(可以用来对处理返回值做一些加工处理)。
- @Around :在切入点前后切入内容,并自己控制何时执行切入点自身的内容。
- @AfterThrowing :用来处理当切入内容部分抛出异常之后的处理逻辑。
其中 @Before 、 @After 、 @AfterReturning 、 @Around 、 @AfterThrowing 都属于通知。
import com.alibaba.fastjson.JSON; import net.sf.json.JSONObject; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; import redis.clients.jedis.Jedis; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.*; /** * 操作日志记录处理 * * @author ruoyi */ @Aspect @Component public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); @Autowired private ManagerFeign managerFeign; @Autowired private EnterpriseFeign enterpriseFeign; @Autowired private HttpServletRequest request; // 配置织入点 @Pointcut("@annotation(com.deerChain.log.annotation.Log)") 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 { // 获得注解 Log controllerLog = getAnnotationLog(joinPoint); if (controllerLog == null) { return; } // *========数据库日志=========*// ManagerLoginLog operLog = new ManagerLoginLog(); // 请求的地址 String ip = utils.getIPAddress(request); operLog.setIp(ip); // 返回参数 AjaxResult ajaxResult = (AjaxResult)jsonResult; operLog.setJsonResult("{ statusCode: "+ajaxResult.getStatusCode()+", message: "+ajaxResult.getMessage()+", data: "+ajaxResult.getData()+" }"); operLog.setCreateTime(utils.getNowTimeSecond()); operLog.setMapper(request.getRequestURI()); // 获取当前登录人id (该项目存在redis中,通过request取出前台传过来的token获得) EnterpriseAccount account = enterpriseAccount.getAccount(request); if (account != null) { if (!utils.isNullorEmpty(account.getId())) { operLog.setManagerId(account.getId()); } } if (e != null) { operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } // 设置方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 设置请求方式 operLog.setRequestMethod(request.getMethod()); // 管理端 operLog.setType(1); // 处理设置注解上的参数 getControllerMethodDescription(joinPoint, controllerLog, operLog); // 保存数据库 managerFeign.saveManagerLoginLog(operLog); } catch (Exception exp) { // 记录本地异常日志 log.error("==前置通知异常=="); log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 * * @param log 日志 * @param operLog 操作日志 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, ManagerLoginLog operLog) throws Exception { // 设置action动作 operLog.setBusinessType(log.businessType().ordinal()); // 设置标题 operLog.setTitle(log.title()); // 设置操作人类别 try { operLog.setOperatorType(IsWebOrPhone.isWebOrPhone(request)?2:1); } catch (Exception e) { operLog.setOperatorType(0); } // 是否需要保存request,参数和值 if (log.isSaveRequestData()) { //获得所有的参数名称 Enumeration<String> em = request.getParameterNames(); Map<String,Object> map = new HashMap<>(); while (em.hasMoreElements()) { String name = em.nextElement(); if(!"_".equals(name)){ String value = request.getParameter(name); map.put(name, value); } } String param; if(map.size() != 0){ JSONObject json = JSONObject.fromObject(map); param = json.toString(); }else{ param= HttpHelper.getBodyString(request); } operLog.setParam(param); // 获取参数的信息,传入到数据库中。 //setRequestValue(joinPoint, operLog); } } /** * 是否存在注解,如果存在就获取 */ private Log getAnnotationLog(JoinPoint joinPoint) throws Exception { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(Log.class); } return null; } }
三、使用@Log()注解为需要记录日志的方法添加日志
- title: 记录方法信息
- businessType: 记录该方法执行的操作
- operatorType: 记录操作人类型
@Log(title = "会员取消订单", businessType = BusinessType.UPDATE,operatorType = OperatorType.MOBILE) @PostMapping(value = "/orders/cancelling") public AjaxResult cancelling(@RequestParam("json")String json) throws Exception{ return ordersFeigin.cancelling(json); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)