springboot + 操作日志添加

1. 操作日志表postgresql脚本:

CREATE TABLE public.sys_log (
	id varchar(32) NOT NULL,
	username varchar(20) NULL,
	operation varchar(20) NULL,
	"method" varchar(100) NULL,
	params text NULL,
	ip varchar(20) NULL,
	createdate timestamp NULL
);
--添加两索引,提升查询速度
CREATE INDEX sys_log_createdate_idx ON public.sys_log USING btree (createdate);
CREATE INDEX sys_log_operation_idx ON public.sys_log USING btree (operation);

2. 集成mybatisplus等依赖

 
    
			springsource-repos
			SpringSource Repository
			http://repo.spring.io/release/
		



        
        
			com.alibaba
			fastjson
			2.0.5
		
        
			org.postgresql
			postgresql
             
		
		
			org.projectlombok
			lombok
			true
       
		
        
		
			org.springframework.boot
			spring-boot-starter-aop
		
		
		
			com.baomidou
			mybatis-plus-boot-starter
			3.5.1
		
        
			org.mybatis.spring.boot
			mybatis-spring-boot-starter
				2.2.2 
		
		
			org.apache.logging.log4j
			log4j-core
			
		
		
			org.apache.logging.log4j
			log4j-web
			
		
 

3. 添加mybatisplus分页配置类  ---必须添加,否则无法使用mybatisplus自带分页

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
        return interceptor;
    }
}

4. 实体类 SysLog 


import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

//SysLog  对应的表名就是 sys_log  mybatisplus自动驼峰命名对应 , 如果表名是syslog那么类名就是Syslog
@Data
public class SysLog implements Serializable {
    private Long id;
    private String username; //用户名
    private String operation; //操作
    private String method; //方法名
    private String params; //参数
    private String ip; //ip地址
    private Date createdate; //操作时间

    //非数据库字段必须 添加注解@TableField(exist = false),因mybatisplus会给表字段自动映射
    @TableField(exist = false)
    Integer pageNo;//第几页数据
    @TableField(exist = false)
    Integer size;//每页多少条数据
    @TableField(exist = false)
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createDateSt; //操作时间  -- 开始
    @TableField(exist = false)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDateEd; //操作时间 --结束
}

5. LogMapper 

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxx.entity.SysLog;
import org.springframework.stereotype.Repository;

@Repository //表明这个类具有对对象进行CRUD(增删改查)的功能
public interface LogMapper extends BaseMapper {
}

6. LogService 以及LogServiceImpl 

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxx.entity.SysLog;

public interface LogService extends IService {
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxs.entity.SysLog;
import com.xxx.mapper.LogMapper;
import com.xxx.service.LogService;
import org.springframework.stereotype.Service;

@Service
public class LogServiceImpl extends ServiceImpl implements LogService {
}

********************以下为插入操作日志******************************

5.AOP面向切入(插入操作日志相关类): AnnotationResolver 、MyLog 、SysLogAspect

import java.lang.annotation.*;

/**
 * 自定义注解类
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface MyLog {
    String value() default "";
}

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * 获取注解的详细信息 注解的值
 */
public class AnnotationResolver {

    private static AnnotationResolver resolver;
    public static AnnotationResolver newInstance() {
        if (resolver == null) {
            return resolver = new AnnotationResolver();
        } else {
            return resolver;
        }
    }
    /**
     * 解析注解上的值
     *
     * @param joinPoint
     * @param str       需要解析的字符串
     * @return
     */
    public Object resolver(JoinPoint joinPoint, String str) {
        if (str == null) {
            return null;
        }
        Object value = null;
        // 如果name匹配上了#,则表示为map,使用map.get方法
        if (str.startsWith("Map.")) {
            String newStr = str.replaceAll("Map.", "").replaceAll("", "");
            if(newStr.contains(".")){
                try {
                    value = complexResolverMap(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            String newStr = str.replaceAll("", "");
            // 复杂类型 调用对象.get属性()方法
            if (newStr.contains(".")) {
                try {
                    value = complexResolver(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = simpleResolverPost(joinPoint, newStr);
            }
        }
        return value;
    }

    private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {

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

        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");

        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Object obj = args[i];
                Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
                Object value = dmethod.invoke(args[i]);
                return getValue(value, 1, strs);
            }
        }
        return null;
    }
    private Object complexResolverMap(JoinPoint joinPoint, String str) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");
        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Map obj = (Map) args[i];
                return obj.get(strs[1]);
            }
        }
        return null;
    }

    private Object getValue(Object obj, int index, String[] strs) {
        try {
            if (obj != null && index < strs.length - 1) {
                Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
                obj = method.invoke(obj);
                getValue(obj, index + 1, strs);
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMethodName(String name) {
        return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
    }
    private Object simpleResolver(JoinPoint joinPoint, String str) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < names.length; i++) {
            if (str.equals(names[i])) {
                return args[i];
            }
        }
        return null;
    }

    private Object simpleResolverPost(JoinPoint joinPoint, String str) {
        return joinPoint.getArgs()[0];
    }
}

注意:  SysLogAspect 类中切入位置需要修改成自己注解类所在包路径 ,否则切入无效

import com.alibaba.fastjson2.JSON;
import com.xxx.entity.SessionBean;
import com.xxx.entity.SysLog;
import com.xxx.mapper.LogMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 系统日志:切面处理类
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {

    @Autowired
    private LogMapper dao;

    //定义切点 @Pointcut
    //在注解的位置切入代码  ---------------需要修改成自己注解类所在包路径
    @Pointcut("@annotation(com.xxx.MyLog)")
    public void logPoinCut() {
    }

    //切面 配置通知
    @AfterReturning("logPoinCut()")
    public void saveSysLog(JoinPoint joinPoint) {

        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        HttpSession session = request.getSession();

        //保存日志
        SysLog sysLog = new SysLog();
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();

        //获取操作
        MyLog myLog = method.getAnnotation(MyLog.class);
        if (myLog != null) {
            String value = myLog.value();
            sysLog.setOperation(value);//保存获取的操作
        }

        //获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //获取请求的方法名
        String methodName = method.getName();
        sysLog.setMethod(className + "." + methodName + "()");

        //请求的参数
        Map rtnMap = converMap(request.getParameterMap());
        // 将参数所在的数组转换成json
        String params = JSON.toJSONString(rtnMap);
        sysLog.setParams(params);

        sysLog.setCreatedate(new Date());

//-----------------------------用户名信息可根据实际存储进行修改-------------
        //操作用户 --登录时有把用户的信息保存在session中,可以直接取出
        SessionBean sessionBean = (SessionBean)session.getAttribute("sid");
        if(sessionBean!=null){//如果为空 ,就是未登录
            sysLog.setUsername(sessionBean.getUserName());
        }
//----------------------------------------------------------------
        //获取用户ip地址
        sysLog.setIp(getIpAddr(request));

        //调用service保存SysLog实体类到数据库
        dao.insert(sysLog);
    }


    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map converMap(Map paramMap) {
        Map rtnMap = new HashMap();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 获取IP地址
     *
     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Exception e) {
            log.error("IPUtils ERROR ", e);
        }
        return ip;
    }

}

 注意:在接口上添加@MyLog(value = "操作说明"),该操作会插入到操作日志表中

-------------------以上为操作日志插入操作------------------------------

-------------------以下为操作日志查询操作------------------------------

7.  LogController

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xxx.entity.SysLog;
import com.xxx.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Controller
public class LogController extends BasicController {
	@Autowired
	LogService logService;

	@RequestMapping("toLog") //跳转操作日志页面
	public String toLog(HttpServletResponse response) {
		setHeaderSuccess(response);
		return "Log";
	}

	@RequestMapping("logPager") //查询操作日志分页数据
	public String logPager(SysLog log, HttpServletRequest request, HttpServletResponse response) {

		Page page = new Page<>(log.getPageNo(), null!=log.getSize()?log.getSize():10); //分页参数, 每页log.getPageSize()条,第log.getPageNo()页
		QueryWrapper wrapper = new QueryWrapper();//实体参数
		if(StringUtils.isNotBlank(log.getOperation())){ //操作
			wrapper.lambda().like(SysLog::getOperation, log.getOperation());
		}
		if(StringUtils.isNotBlank(log.getUsername())){ //用户名
			wrapper.lambda().like(SysLog::getUsername, log.getUsername());
		}
		if(null != log.getCreateDateSt()){ //操作开始时间
			wrapper.ge("createdate", log.getCreateDateSt());
		}
		if(null != log.getCreateDateEd()){//操作结束时间
			wrapper.le("createdate", log.getCreateDateEd());
		}

		wrapper.orderByDesc("createdate");
		IPage logs = logService.page(page,wrapper);
		request.setAttribute("logs", logs);
		setHeaderSuccess(response);
		return "Log";
	}
}

8. js


function toLog() {
    	var postData = {};
    	$.ajax({
    		type : "post",
    		url : "/toLog",
    		data : postData,
    		beforeSend: function (XMLHttpRequest) {
          		XMLHttpRequest.setRequestHeader("requestType", "ajax");
            },
    		success : function(data,textStatus,response) {
    			handleResponseData(data,response,null);
    			var createDateSt = getAfter2Date();
                var createDateEd = getServerDate();

//    		    alert(createDateSt  +  "- " + createDateEd);
                $("#createDateSt").val(createDateSt);//初始化时间 页面加载默认设置当前日期的前两天
                $("#createDateEd").val(createDateEd);//初始化时间 页面加载默认当天日期
    		}
    	});
    }

    function toPage(flag){
        var operation = $("#operation").val();
        var username = $("#username").val();
        var createDateSt = $("#createDateSt").val();
        var createDateEd = $("#createDateEd").val();
        var tempCreateDateEd = createDateEd.length==10?createDateEd+' 23:59:59':'';
        var size = $("#size").val();//每页显示条数
        var postData = {"pageNo":flag, "size":size,
        "operation":operation, "username":username,
        "createDateSt":createDateSt, "createDateEd":tempCreateDateEd};

        $.ajax({
            type : "post",
            url : "/logPager",
            data : postData,
            beforeSend: function (XMLHttpRequest) {
                XMLHttpRequest.setRequestHeader("requestType", "ajax");
            },
            success : function(data,textStatus,response) {
                handleResponseData(data,response,null);
                $("#operation").val(operation);
                $("#username").val(username);
                $("#createDateSt").val(createDateSt);
                $("#createDateEd").val(createDateEd);
            },
            error : function(data) {
                reLogin();
            }
        });
    }

// 获取服务器端日期年月日
function getServerDate() {
	var time, year, month, date;
	time = new Date($.ajax({
		type : "HEAD",
		async : false,
		cache : false
	}).getResponseHeader("Date"));
	year = time.getFullYear();// 年
	// 小于10的数在前面加上0
	month = (time.getMonth() + 1) < 10 ? ("0" + (time.getMonth() + 1)) : (time
			.getMonth() + 1);// 月
	date = time.getDate() < 10 ? ("0" + time.getDate()) : time.getDate();// 日
	// 拼成自己想要的日期格式
	time = year + "-" + month + "-" + date;
	return time;
}

// 获取当前时间的前两天日期
function getAfter2Date() {
	var time = new Date();
    time.setTime(time.getTime()-24*60*60*1000*2);
    // 小于10的数在前面加上0
    var	month = (time.getMonth() + 1) < 10 ? ("0" + (time.getMonth() + 1)) : (time.getMonth() + 1);// 月
    var	date = time.getDate() < 10 ? ("0" + time.getDate()) : time.getDate();// 日
	return time.getFullYear()+ "-" + month + "-" + date;
}

9. freemarker (.ftlh文件,也可改成html只需修改页面参数)+ 分页

操作日志

- 查询
<#if logs??>
<#list logs.records as log>
用户 操作 方法名 参数 IP 操作时间
${(log.username)!} ${(log.operation)!} ${(log.method)!} ${(log.params)!} ${(log.ip)!} ${(log.createdate?string("yyyy-MM-dd HH:mm"))!}
共${(logs.total)!}条数据,每页 条,当前第${(logs.current)!}页, 共${(logs.pages)!}页 <#if logs.current?number gte 2> 首页 上一页 <#if (logs.current?number != logs.pages?number) > 下一页 末页

10. 页面效果

springboot + 操作日志添加_第1张图片

你可能感兴趣的:(springboot,java,前端集,spring,boot,postgresql,后端,操作日志,mybatisplus自带分页)