分布式通过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);
    }

posted @ 2021-12-30 14:02  初学者入坑  阅读(66)  评论(0)    收藏  举报  来源