springboot + 操作日志添加

31 篇文章 0 订阅
23 篇文章 0 订阅

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等依赖

<repositories> <!-- 添加多仓库,提升依赖加载速度,有些依赖可能在默认maven配置中的仓库找不到-->
    <repository>
			<id>springsource-repos</id>
			<name>SpringSource Repository</name>
			<url>http://repo.spring.io/release/</url>
		</repository>
</repositories>


<dependencies>        
        <dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>2.0.5</version>
		</dependency>
        <dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
             <!-- 版本依赖springboot版本-->
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
       <!-- lombok版本依赖springboot版本-->
		</dependency>
        <!--spring切面aop依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<!-- mybatisPlus 核心库 -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.1</version>
		</dependency>
        <dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
				<version>2.2.2</version> 
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<!-- <version>2.17.2</version> log4j版本依赖springboot版本-->
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-web</artifactId>
			<!--<version>2.17.2</version> log4j版本依赖springboot版本-->
		</dependency>
</dependencies> 

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<SysLog> {
}

6. LogService 以及LogServiceImpl 

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

public interface LogService extends IService<SysLog> {
}
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<LogMapper, SysLog> 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<String,Object> obj = (Map<String, Object>) 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<String, String> 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<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        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<SysLog> page = new Page<>(log.getPageNo(), null!=log.getSize()?log.getSize():10); //分页参数, 每页log.getPageSize()条,第log.getPageNo()页
		QueryWrapper<SysLog> 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<SysLog> 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只需修改页面参数)+ 分页

<div class="row">
    <div class="x_panel">
        <div class="x_title">
            <h2>操作日志</h2>
            <div class="clearfix"></div>
        </div>
        <div class="col-sm-12">
            <div class="col-sm-2">
                <input class="form-control" id="username" placeholder="用户名" type="text"/>
            </div>
            <div class="col-sm-4">
                <input class="form-control" id="operation" placeholder="操作" type="text"/>
            </div>

            <div class="input-group col-sm-6">
                <input class="form-control" id="createDateSt" placeholder="格式:2020/05/20" type="date"/>
                <span class="input-group-addon">-</span>
                <input class="form-control" id="createDateEd" placeholder="格式:2020/05/20" type="date"/>
                <span class="input-group-addon btn" onclick="toPage(1);">查询</span>
            </div>
        </div>

        <#if logs??>
        <div class="x_content">
            <div class="table-responsive" style="overflow-x: auto; overflow-y: auto; height: 500px; width:100%;">
                <table border=1 class="table table-striped jambo_table bulk_action">
                    <thead>
                    <tr class="headings column-title">
                        <th>用户</th>
                        <th>操作</th>
                        <th>方法名</th>
                        <th>参数</th>
                        <th>IP</th>
                        <th>操作时间</th>
                    </tr>
                    </thead>
                    <tbody>
                    <#list logs.records as log>
                    <tr>
                        <td>${(log.username)!}</td>
                        <td>${(log.operation)!}</td>
                        <td>${(log.method)!}</td>
                        <td>${(log.params)!}</td>
                        <td>${(log.ip)!}</td>
                        <td>${(log.createdate?string("yyyy-MM-dd HH:mm"))!}</td>
                    </tr>
                    </#list>
                    </tbody>
                </table>
            </div>

            <div class="row col-sm-6">
                共${(logs.total)!}条数据,每页
                <input id="size" style="width:50px" value="${(logs.size)!}" onchange="toPage(1)" oninput="value=value.replace(/[^\d]/g,'')" />
                条,当前第${(logs.current)!}页, 共${(logs.pages)!}页

                <#if logs.current?number gte 2>
                    <a href="#" onclick="toPage(1)" style="color:#00527f">首页</a>
                    <a href="#" onclick="toPage('${logs.current-1}')">上一页</a>
                </#if>
                <#if (logs.current?number != logs.pages?number) >
                    <a href="#" onclick="toPage('${logs.current+1}')">下一页</a>
                    <a href="#" onclick="toPage('${(logs.pages)!}')" style="color:#00527f">末页</a>
                </#if>

            </div>
    </#if>

</div>
</div>

10. 页面效果

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

往事不堪回首..

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值