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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iBase4J是Java语言的分布式系统架构。使用Spring整合的开源框架。 iBase4J特点: 使用Maven对项目进行模块化管理,提高项目的易开发性、扩展性。 系统包括4个子系统:系统管理Service、系统管理Web、业务Service、业务Web。 系统管理:包括用户管理、权限管理、数据字典、系统参数管理等等。 业务相关:您的业务开发。 可以无限的扩展子系统,子系统之间使用Dubbo或MQ进行通信。 iBase4J主要功能: 1、数据库:Druid数据库连接池,监控数据库访问性能,统计SQL的执行性能。数据库密码加密,加密方式请查看PropertiesUtil,decryptProperties属性配置需要解密的key。 2、持久层:mybatis持久化,使用MyBatis-Plus优化,减少sql开发量;aop切换数据库实现读写分离。Transaction注解事务。 3、MVC:基于spring mvc注解,Rest风格Controller。Exception统一管理。 4、调度:Spring+quartz, 可以查询、修改周期、暂停、删除、新增、立即执行,查询执行记录等。 5、基于session的国际化提示信息,职责链模式的本地语言拦截器,Shiro登录、URL权限管理。会话管理,强制结束会话。 6、缓存和Session:注解redis缓存数据;shiro实现redis分布式session同步,重启服务会话不丢失。 7、多系统交互:Dubbo,ActiveMQ多系统交互,ftp/sftp/fastdafs发送文件到独立服务器,使文件服务分离。 8、前后端分离:没有权限的文件只用nginx代理即可。 9、日志:log4j2打印日志,业务日志和调试日志分开打印。同时基于时间和文件大小分割日志文件。 10、QQ、微信、新浪微博第三方登录。 11、工具类:excel导入导出,汉字转拼音,身份证号码验证,数字转大写人民币,FTP/SFTP/fastDFS上传下载,发送邮件,redis缓存,加密等等。 技术选型: 核心框架:Sring boot + Spring Framework + Dubbo + ibase4j-common 安全框架:Apache Shiro 任务调度:Spring + Quartz 持久层框架:MyBatis + MyBatis-Plus 数据库连接池:Alibaba Druid 缓存框架:Redis 会话管理:Spring-Session 日志管理:SLF4J、Log4j2 前端框架:Angular JS + Bootstrap + Jquery 启动说明: * 项目依赖activemq、Redis和ZooKeeper服务。 * 使用nginx代理UI:修改配置里的UI目录后重启nginx。 * 启动方法: SysServiceApplication.java SysWebApplication.java * 测试环境打包命令: clean package -P test * 生产环境打包命令: clean package -P product

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值