RBAC从零开始--操作日志记录

本文介绍了一种系统操作日志的记录方案,包括定义注解、配置线程池、实现AOP切面来拦截标记的方法,并将日志数据写入数据库。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  首先,得利用上篇的工具类代码生成,OperateLogInfoMapper、OperateLogInfo、OperateLogInfoExample、OperateLogInfoMapper.xml还有user表的,放到对应的包下,代码有的没有判空这个不太好,最好自己可以加上非空判断!


  设计日志记录注解SystemLog(在annotation包下),控制层的方法要记录操作日志,就需要增加该注解,具体代码如下:
  

package com.xll.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 进行系统日志控制的注释,如果Controller里面方法加上了该注解,
 * 则表示该方法需要进行操作日志记录,该注释只作用在方法上
 *
 * @author lonely.xia
 * @date 2017/10/15
 */

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemLog {

    /**
     * 描述业务操作功能 例:登录、查看用户等等
     * @return
     */
    String description() default "";
}

spring-mvc.xml配置文件中加上

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="5" />
        <property name="maxPoolSize" value="10" />
        <property name="WaitForTasksToCompleteOnShutdown" value="true" />
    </bean>

编写SystemLogAspect的AOP类,用来拦截被SystemLog注解的方法

package com.xll.aop;

import com.xll.annotation.SystemLog;
import com.xll.model.OperateLogInfo;
import com.xll.model.User;
import com.xll.service.OperateLogInfoService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 系统日志切面,针对使用了SystemLog注释的方法进行拦截
 * 之后将日志数据写入数据库
 */
@Component
@Aspect
public class SystemLogAspect {

    private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);

    /** 记录每个用户刚开始访问方法的时间 */
    private static final ThreadLocal<Date> BEGIN_TIME_THREAD_LOCAL =
            new NamedThreadLocal<Date>("ThreadLocal beginTime");

    private static final ThreadLocal<OperateLogInfo> LOG_THREAD_LOCAL =
            new NamedThreadLocal<OperateLogInfo>("ThreadLocal log");


    @Autowired(required=false)
    private HttpServletRequest request;

    /** spring框架自带的线程池 */
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;


    @Resource
    private OperateLogInfoService operateLogInfoService;

    public SystemLogAspect() {}

    /**
     * 对使用SystemLog注释的方法进行拦截
     */
    @Pointcut("@annotation(com.xll.annotation.SystemLog)")
    public void systemLogAspectCtrl(){}


    /**
     * 前置通知 用于拦截记录用户的操作的开始时间
     *
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("systemLogAspectCtrl()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException {

        Date beginTime = new Date();

        BEGIN_TIME_THREAD_LOCAL.set(beginTime);

        if (LOG.isDebugEnabled()){
            LOG.debug("开始计时: {},URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                    .format(beginTime), request.getRequestURI());
        }
    }

    /**
     * 后置通知 用于拦截用户操作
     *
     * @param joinPoint 切点
     */
    @After("systemLogAspectCtrl()")
    public void doAfter(JoinPoint joinPoint) {

        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");

        if (user != null) {
            OperateLogInfo operateLogInfo = new OperateLogInfo();

            operateLogInfo.setUid(user.getId());
            operateLogInfo.setUname(user.getName());
            operateLogInfo.setStatus(true);
            operateLogInfo.setFromIp(request.getRemoteAddr());
            operateLogInfo.setMethod(request.getMethod());
            operateLogInfo.setUri(request.getRequestURI());
            operateLogInfo.setLogType("info");
            operateLogInfo.setOperateFunc(getMethodDescription(joinPoint));
            operateLogInfo.setVisitMethod(getMethod(joinPoint));
            operateLogInfo.setMethodCostTime(new Date().getTime() - BEGIN_TIME_THREAD_LOCAL.get().getTime() + "");

            // 开启新线程进行日志记录
            taskExecutor.execute(new SaveLogThread(operateLogInfo , operateLogInfoService));

            LOG_THREAD_LOCAL.set(operateLogInfo);

        }

    }


    /**
     * 获取注解中对方法的描述信息
     *
     * @param joinPoint 切点
     * @return description
     */
    private String getMethodDescription(JoinPoint joinPoint) {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        Method method = signature.getMethod();

        SystemLog systemLog = method.getAnnotation(SystemLog.class);

        String description = systemLog.description();

        return description;
    }

    private String getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        Method method = signature.getMethod();

        return method.getName();
    }


    /**
     * 异常通知 记录操作报错日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "systemLogAspectCtrl()" , throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint , Throwable e) {

        OperateLogInfo operateLogInfo = LOG_THREAD_LOCAL.get();

        operateLogInfo.setLogType("error");
        operateLogInfo.setVisitMethodErrorInfo(e.getMessage());

        taskExecutor.execute(new UpdateLogThread(operateLogInfo , operateLogInfoService));
    }

    /**
     * 保存日志线程
     */
    private static class SaveLogThread implements Runnable {

        private OperateLogInfo operateLogInfo;
        private OperateLogInfoService operateLogInfoService;


        public SaveLogThread(OperateLogInfo operateLogInfo, OperateLogInfoService operateLogInfoService) {
            this.operateLogInfo = operateLogInfo;
            this.operateLogInfoService = operateLogInfoService;
        }

        @Override
        public void run() {
            //这里会将插入后的记录ID给,写入operateLogInfo所指的对象实例中(如何实现看OperateLogInfoMapper.xml中insert)
            operateLogInfoService.insert(operateLogInfo);
        }
    }

    /**
     * 日志更新线程
     */
    private static class UpdateLogThread extends Thread {

        private OperateLogInfo operateLogInfo;
        private OperateLogInfoService operateLogInfoService;

        public UpdateLogThread(OperateLogInfo operateLogInfo, OperateLogInfoService operateLogInfoService) {
            super(UpdateLogThread.class.getSimpleName());
            this.operateLogInfo = operateLogInfo;
            this.operateLogInfoService = operateLogInfoService;
        }

        @Override
        public void run() {
            operateLogInfoService.update(operateLogInfo);
        }
    }
}

OperateLogInfoMapper.xml中的insert(useGeneratedKeys=”true” keyProperty=”id”会将ID返回)

<insert id="insert" useGeneratedKeys="true" keyProperty="id" parameterType="com.xll.model.OperateLogInfo">
    insert into operate_log_info (id, uid, uname, 
      create_time, update_time, status, 
      from_ip, operate_func, visit_method, 
      visit_method_error_info, login_out_time, method_cost_time, 
      log_type, uri, method
      )
    values (#{id,jdbcType=INTEGER}, #{uid,jdbcType=INTEGER}, #{uname,jdbcType=VARCHAR}, 
      #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=BIT}, 
      #{fromIp,jdbcType=VARCHAR}, #{operateFunc,jdbcType=VARCHAR}, #{visitMethod,jdbcType=VARCHAR}, 
      #{visitMethodErrorInfo,jdbcType=VARCHAR}, #{loginOutTime,jdbcType=TIMESTAMP}, #{methodCostTime,jdbcType=VARCHAR}, 
      #{logType,jdbcType=VARCHAR}, #{uri,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR}
      )
  </insert>

OperateLogInfoService.java接口

package com.xll.service;

import com.xll.model.OperateLogInfo;

/**
 * Created by lonely.xia on 2017/10/16.
 */
public interface OperateLogInfoService {
    int update(OperateLogInfo operateLogInfo);
    int insert(OperateLogInfo operateLogInfo);
}

OperateLogInfoServiceImpl.java实现类

package com.xll.service.impl;

import com.xll.mapper.OperateLogInfoMapper;
import com.xll.model.OperateLogInfo;
import com.xll.service.OperateLogInfoService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * Created by lonely.xia on 2017/10/16.
 */

@Service("operateLogInfoService")
public class OperateLogInfoServiceImpl implements OperateLogInfoService {

    @Resource
    private OperateLogInfoMapper operateLogInfoDao;

    @Override
    public int update(OperateLogInfo operateLogInfo) {
        return operateLogInfoDao.updateByPrimaryKeySelective(operateLogInfo);
    }

    @Override
    public int insert(OperateLogInfo operateLogInfo) {
        int count = operateLogInfoDao.insertSelective(operateLogInfo);
        return count;
    }
}

UserController.java

package com.xll.controller;

import com.xll.annotation.SystemLog;
import com.xll.dto.UserDTO;
import com.xll.enums.PageEnum;
import com.xll.enums.ResponseEnum;
import com.xll.model.Role;
import com.xll.model.User;
import com.xll.model.UserRole;
import com.xll.service.RoleService;
import com.xll.service.UserRoleService;
import com.xll.service.UserService;
import com.xll.util.BootstrapTablePage;
import com.xll.util.GeneralResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by lonely.xia on 2017/10/10.
 */

@Controller
@RequestMapping("/user")
public class UserController {

    private static final Logger LOG = LoggerFactory.getLogger(UserController.class);

    @Resource
    private UserService userService;

    @SystemLog(description = "伪登录")
    @RequestMapping(value = "/vlogin" , method = RequestMethod.GET)
    public String vlogin(HttpSession httpSession
            , HttpServletRequest request , HttpServletResponse response, @RequestParam Integer uid) {
        User user = userService.vlogin(uid);

        if (user != null) {
            httpSession.setAttribute("user" , user);
            request.setAttribute("page" , PageEnum.INDEX.getCode());
        } else {
            request.setAttribute("page" , PageEnum.LOGIN_HINT.getCode());
        }

        return "./../../index";
    }
}

UserServiceImpl.java

package com.xll.service.impl;

import com.xll.mapper.UserMapper;
import com.xll.model.RoleExample;
import com.xll.model.User;
import com.xll.model.UserExample;
import com.xll.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;

/**
 * Created by lonely.xia on 2017/10/10.
 */
@Service("userService")
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userDao;

    public User vlogin(int uid) {

       User user = userDao.selectByPrimaryKey(uid);

        return user;

    }
}

PageEnum.java

package com.xll.enums;

/**
 * Created by lonely.xia on 2017/10/12.
 */
public enum PageEnum {

    LOGIN_HINT("登录提示" , -1) ,
    INDEX("首页" , 0) ,
    TEST_ONE("测试页面一" , 1) ,
    TEST_TWO("测试页面二" , 2) ,
    TEST_THREE("测试页面三" , 3) ,
    TEST_FOUR("测试页面四" , 4) ,
    USER_LIST("用户列表" , 10) ,
    ROLE_LIST("角色列表" , 20) ,
    ACCESS_LIST("权限列表" , 30) ,
    NONE_ACCESS("无访问权限" , -9999);

    private String name;
    private int code;

    private PageEnum(String name , int code) {
        this.name = name;
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public int getCode() {
        return code;
    }
}

访问http://localhost:8080/user/vlogin/uid=xxx,就会在数据库插入一条数据:
这里写图片描述


下篇实现首页内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值