说明:

直接上效果图


java操作日志 mq java记录用户操作日志_环绕通知

系统日志列表

一个成熟的系统,应对用户的某些增删改操作,特别是管理员的增删改操作进行日志持久化处理。这些功能基本包括了用户的操作日志。那么我们要对一个完整的操作记录,其单位就是方法。通过AOP的环绕通知可以把切点的记录在内,得到日志并持久化处理。那么就不废话直接上设计了。

pojo

import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;

/**
 * 系统日志实体定义
 *
 */
@Entity
@Table(name = "ABC_SYSTEM_LOG")
@Data
public class SystemLog
{
    public static final int STATUS_NORMAL = 0;
    public static final int STATUS_PAUSE = 1;

    public static final String DEFAULT_OWNER_TYPE = "SYS";
    public static final String DEFAULT_OWNER = "0";

    /**
     * 标识
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    /**
     * 应用端
     */
    @Column(name = "app_name")
    @Size(max = 255)
    private String appName;
    /**
     * 日志类型:0为操作日志,1为异常日志
     */
    @Column(name = "log_type")
    private int logType;
    /**
     * 访问者/请求者
     */
    @Column(name = "user")
    @Size(max = 255)
    private String user;
    /**
     * 账号类型
     */
    @Column(name = "user_type")
    @Size(max = 255)
    private String userType;
    /**
     * 方法名称
     */
    @Column(name = "method_name")
    @Size(max = 255)
    private String methodName;
    /**
     * 请求参数
     */
    @Lob
    @Column(name = "request_params")
    private String requestParams;
    /**
     * 方法描述
     */
    @Column(name="method_description")
    @Size(max = 255)
    private String methodDescription;
    /**
     * 访问ip
     */
    @Column(name="request_ip")
    @Size(max = 255)
    private String requestIp;
    /**
     * 请求URL
     */
    @Column(name="request_uri")
    @Size(max = 255)
    private String requestUri;
    /**
     * 请求PATH
     */
    @Column(name="request_path")
    @Size(max = 255)
    private String requestPath;
    /**
     * 异常码
     */
    @Column(name="exception_code")
    @Size(max = 255)
    private String exceptionCode;
    /**
     * 异常描述
     */
    @Column(name="exception_detail")
    @Size(max = 255)
    private String exceptionDetail;
    /**
     * d
     */
    @Column(name="request_status")
    private int requestStatus;
    /**
     * 备注
     */
    @Lob
    @Column(name = "remark")
    private String remark;
    /**
     * 状态
     */
    @Column(name = "status")
    private int status = STATUS_NORMAL;
    /**
     * 所有者
     */
    @NotNull
    @Size(max = 32)
    @Column(name = "owner", updatable = false)
    private String owner = DEFAULT_OWNER;
    /**
     * 所有者类型
     */
    @Size(max = 32)
    @Column(name = "owner_type", updatable = false)
    private String ownerType = DEFAULT_OWNER_TYPE;
    /**
     * 创建人/时间,更新人/时间
     */
    @Column(name = "creator", nullable = false, updatable = false)
    private String creator;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_date", nullable = false, updatable = false)
    private Date creationDate = new Date();
    @Column(name = "modifier", insertable = false, updatable = true)
    private String modifier;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "modify_date", insertable = false, updatable = true)
    private Date modifyDate;

}

注解

import java.lang.annotation.*;


/**
 *自定义注解 拦截Controller
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
    String description() default "";
}

切点

import com.geek.abc.core.systemLog.SystemLog;
import com.geek.abc.core.systemLog.SystemLogManager;
import com.geek.abc.core.util.DateHelper;
import com.jade.bss.organization.account.AdminAccount;
import com.jade.bss.organization.customer.Customer;
import com.jade.framework.base.context.ApplicationContextUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 系统日志 切面
 * optimized by_liaoqian 20191212
 */
@Aspect
@Component
@SuppressAjWarnings("all")
@Slf4j
public class SystemLogAspect {

    private SystemLogManager getSystemLogManager() {
        return ApplicationContextUtils.getBean("bss_systemLog_manager");
    }



    /**
     * Service层切点
     */
    @Pointcut("@annotation(com.geek.abc.core.systemLog.aopHandle.SystemServiceLog)")
    public void serviceAspect() {
    }



    /**
     * Controller层切点
     */
    @Pointcut("@annotation(com.geek.abc.core.systemLog.aopHandle.SystemControllerLog)")
    public void controllerAspect() {
    }



    /**
     * 环绕通知 用于拦截controller层记录用户的操作
     * @param proceeding 切点
     */
    @Around("controllerAspect()")
    @SuppressAjWarnings("all")
    public Object aroundAdvice(ProceedingJoinPoint proceeding) throws Exception {
        Object[] args=proceeding.getArgs();
        Object result=null;
        String userName = "未识别";
        String userType = "未识别";
        String appName = "";
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (SecurityUtils.getSubject() != null) {
            if (SecurityUtils.getSubject().getPrincipal() != null) {
                String userClassStr = SecurityUtils.getSubject().getPrincipal().toString();
                if (userClassStr.contains("com.jade.bss.organization.account.AdminAccount")) {
                    AdminAccount adminAccount = (AdminAccount) SecurityUtils.getSubject().getPrincipal();
                    userName = adminAccount.getName();
                    userType = adminAccount.getType();
                } else if (SecurityUtils.getSubject().getPrincipal().toString().contains("com.jade.bss.organization.customer.Customer")) {
                    Customer customer = (Customer) SecurityUtils.getSubject().getPrincipal();
                    userName = customer.getName();
                    userType = customer.getOwnerType();
                }
            }

        }
        if (userType.equals("MEMBER")) {
            appName = "client";
        } else {
            appName = "web";
        }
        String params = "";
        System.out.println(proceeding.getArgs());
        if (proceeding.getArgs() != null && proceeding.getArgs().length > 0) {
            for (int i = 0; i < proceeding.getArgs().length; i++) {
                if (proceeding.getArgs()[i] != null) {
                    params += proceeding.getArgs()[i].toString() + ";";
                    if (proceeding.getArgs()[i].toString().contains("com.geek") || proceeding.getArgs()[i].toString().contains("com.jade.abc")) {
                        Object object = proceeding.getArgs()[i];
                        JSONObject json = JSONObject.fromObject(object);
                        params += json.toString();
                    }
                }
            }
        }

        SystemLog systemLog = new SystemLog();
        try {
            log.info("=====前置通知开始=====");
            System.out.println("请求方法:" + (proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()"));
            System.out.println("方法描述:" + getControllerMethodDescription(proceeding));
            System.out.println("请求路径:" + request.getRequestURI());
            System.out.println("请求参数:" + request.getQueryString());
            System.out.println("请求人:" + userName);
            System.out.println("请求人类型:" + userType);
            System.out.println("请求IP:" + request.getRemoteAddr());
            System.out.println("日期:" + DateHelper.getCurDateTime());

            // tip:执行连接点的方法 获取返回值 必不可少!!!
            result=proceeding.proceed(args);

            systemLog.setAppName(appName);
            systemLog.setCreator(userName);
            systemLog.setLogType(0);
            systemLog.setUser(userName);
            systemLog.setUserType(userType);
            systemLog.setMethodDescription(getControllerMethodDescription(proceeding));
            systemLog.setMethodName(proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()");
            systemLog.setExceptionCode(null);
            systemLog.setExceptionDetail(null);
            systemLog.setRequestIp(request.getRemoteAddr());
            systemLog.setRequestParams(request.getQueryString());
            systemLog.setRequestPath(request.getServletPath());
            systemLog.setRequestUri(request.getRequestURI());
            systemLog.setRequestStatus(200);
            systemLog.setRemark(params);

            // 初次登录,无法识别,所以通过截取参数获取,保留
            if ("用户登录".equals(systemLog.getMethodDescription())
                    && "未识别".equals(userName) && "未识别".equals(userType)) {
                String name = request.getQueryString().split("=")[2];
                systemLog.setAppName("client");
                systemLog.setCreator(name);
                systemLog.setUser(name);
                systemLog.setUserType("MEMBER");
            }

            log.info("=====后置通知结束=====");
        } catch (Throwable e) {
            log.error("=====异常通知开始=====");
            systemLog.setAppName(appName);
            systemLog.setCreator(userName);
            systemLog.setLogType(1);
            systemLog.setUser(userName);
            systemLog.setUserType(userType);
            systemLog.setMethodDescription(getControllerMethodDescription(proceeding));
            systemLog.setMethodName(proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()");
            systemLog.setExceptionCode(e.getClass().getName());
            systemLog.setExceptionDetail(e.getMessage());
            systemLog.setRequestIp(request.getRemoteAddr());
            systemLog.setRequestParams(request.getQueryString());
            systemLog.setRequestPath(request.getServletPath());
            systemLog.setRequestUri(request.getRequestURI());
            systemLog.setRequestStatus(500);
            systemLog.setRemark(params);
            log.error("=====异常通知结束=====");
        } finally {
            log.info("=====最终通知开始=====");
            getSystemLogManager().addSystemLog(systemLog);
        }
        return result;
    }



    /**
     * 获取注解中对方法的描述信息 用于service层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */

    public static String getServiceMthodDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemServiceLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemControllerLog.class).description();
                    break;
                }
            }
        }
        return description;
    }
}

重点在于切点的环绕通知写法,在切点的aroundAdvice方法内,由于采用的是远古的ssh项目,一些非必要的dao层和service层就没有写出来,那么controller的实现方式也特别简单。

事例

/**
     * 删除会员
     * @param id
     * @param response
     * @throws Exception
     */
    @RequestMapping(value = "/remove")
    @RequiresAuthentication
    @CheckPermission(permission = "member_remove")
    @SystemControllerLog(description = "删除会员信息")
    public void removeMember(@RequestParam(value = "id") long[] id, HttpServletResponse response) throws Exception {
        try {
            getMemberManager().removeMember( id );
            ResponseUtil.writeSuccessResult( response );
        } catch (BssException e) {
            e.printStackTrace();
            ResponseUtil.writeErrorResult( response, STATUS_ERROR, e.getMessage() );
        }
    }

就是一个简单的注解开发

@SystemControllerLog(description = "方法描述")

那么看一下实际运行图

操作正常下:


java操作日志 mq java记录用户操作日志_系统日志_02

标题

操作异常:


java操作日志 mq java记录用户操作日志_操作记录_03

标题

日志列表:


java操作日志 mq java记录用户操作日志_环绕通知_04

标题