欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用AOP实现日志管理

程序员文章站 2022-05-06 18:17:46
...

使用AOP实现日志管理

自定义注解

package com.yangzc.studentboot.common.annotation;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
	String value() default "";
}

给登录接口添加注解

    @Log("登录")
    @PostMapping("/login")
    @ResponseBody
    R ajaxLogin(String username, String password, String verify, HttpServletRequest request) {
        try {
            //从session中获取随机数
            String random = (String) request.getSession().getAttribute(RandomValidateCodeUtil.RANDOMCODEKEY);
            if (StringUtils.isBlank(verify)) {
                return R.error("请输入验证码");
            }
            if (random.equals(verify)) {
            } else {
                return R.error("请输入正确的验证码");
            }
        } catch (Exception e) {
            logger.error("验证码校验失败", e);
            return R.error("验证码校验失败");
        }
        password = MD5Utils.encrypt(username, password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return R.ok();
        } catch (AuthenticationException e) {
            return R.error("用户或密码错误");
        }
    }

使用Aspect记录操作日志

package com.yangzc.studentboot.common.aspect;

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

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import com.yangzc.studentboot.common.service.LogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.stereotype.Component;

import com.yangzc.studentboot.common.annotation.Log;
import com.yangzc.studentboot.common.domain.LogDO;
import com.yangzc.studentboot.common.utils.HttpContextUtils;
import com.yangzc.studentboot.common.utils.IPUtils;
import com.yangzc.studentboot.common.utils.JSONUtils;
import com.yangzc.studentboot.common.utils.ShiroUtils;
import com.yangzc.studentboot.system.domain.UserDO;
import org.springframework.web.multipart.MultipartFile;

@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    LogService logService;


    @Pointcut("@annotation(com.yangzc.studentboot.common.annotation.Log)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //异步保存日志
        saveLog(point, time);
        return result;
    }

    void saveLog(ProceedingJoinPoint joinPoint, long time) throws InterruptedException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogDO sysLog = new LogDO();
        Log syslog = method.getAnnotation(Log.class);
        if (syslog != null) {
            // 注解上的描述
            sysLog.setOperation(syslog.value());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        Object[] args = joinPoint.getArgs();
        Object[] arguments  = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
                continue;
            }
            arguments[i] = args[i];
        }
        try {
            String params = JSONUtils.beanToJson(arguments);
            sysLog.setParams(params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        // 用户名
        UserDO currUser = ShiroUtils.getUser();
        if (null == currUser) {
            if (null != sysLog.getParams()) {
                sysLog.setUserId(-1L);
                sysLog.setUsername(sysLog.getParams());
            } else {
                sysLog.setUserId(-1L);
                sysLog.setUsername("获取用户信息为空");
            }
        } else {
            sysLog.setUserId(ShiroUtils.getUserId());
            sysLog.setUsername(ShiroUtils.getUser().getUsername());
        }
        sysLog.setTime((int) time);
        // 系统当前时间
        Date date = new Date();
        sysLog.setCreateOn(date);
        // 保存系统日志
        logService.save(sysLog);
    }
}

日志查看接口

package com.yangzc.studentboot.common.controller;

import java.util.Map;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.yangzc.studentboot.common.domain.LogDO;
import com.yangzc.studentboot.common.domain.PageDO;
import com.yangzc.studentboot.common.service.LogService;
import com.yangzc.studentboot.common.utils.Query;
import com.yangzc.studentboot.common.utils.R;

@RequestMapping("/log")
@Controller
public class LogController {
	@Autowired
	LogService logService;
	String prefix = "log";

	@RequiresPermissions("log:list")
	@GetMapping("/list")
	String log() {
		return prefix + "/log";
	}

	@RequiresPermissions("log:list")
	@ResponseBody
	@GetMapping("/data")
	PageDO<LogDO> list(@RequestParam Map<String, Object> params) {
		Query query = new Query(params);
		PageDO<LogDO> page = logService.queryList(query);
		return page;
	}
	
	@ResponseBody
	@PostMapping("/remove")
	R remove(Long id) {
		if (logService.remove(id)>0) {
			return R.ok();
		}
		return R.error();
	}

	@ResponseBody
	@PostMapping("/batchRemove")
	R batchRemove(@RequestParam("ids[]") Long[] ids) {
		int r = logService.batchRemove(ids);
		if (r > 0) {
			return R.ok();
		}
		return R.error();
	}
}

日志查看页面(html)

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<meta charset="utf-8">
<head th:include="include :: header"></head>
<body class="gray-bg">
	<div class="wrapper wrapper-content">
		<div class="col-sm-12">
			<div class="ibox">
				<div class="ibox-body">
					<div class="fixed-table-toolbar">
						<div class="columns pull-left">
							<button type="button" class="btn  btn-danger"
								onclick="batchRemove()">
								<i class="fa fa-trash" aria-hidden="true"></i>删除
							</button>
						</div>
						<div class="columns pull-right" role="group">
							<button class="btn btn-success" onclick="reLoad()">
								<i class="fa fa-search" aria-hidden="true"></i>查询
							</button>
						</div>
						<div class="pull-right search col-md-2">
							<input id="searchOperation" type="text" class="form-control"
								placeholder="操作">
						</div>
						<div class="pull-right search col-md-2 nopadding">
							<input id="searchUsername" type="text" class="form-control"
								placeholder="用户">
						</div>
					</div>
					<table id="exampleTable" data-mobile-responsive="true">
					</table>
				</div>
			</div>
		</div>
	</div>
	<div th:include="include :: footer"></div>
	<script type="text/javascript" src="/app/log/log.js"></script>
</body>
</html>

日志查看页面(js)

var prefix = "/log"
$(function () {
    load();

});
$('#exampleTable').on('load-success.bs.table', function (e, data) {
    if (data.total && !data.rows.length) {
        $('#exampleTable').bootstrapTable('selectPage').bootstrapTable('refresh');
    }
});

function load() {
    $('#exampleTable')
        .bootstrapTable(
            {
                method: 'get', // 服务器数据的请求方式 get or post
                url: prefix + "/data", // 服务器数据的加载地址
                // showRefresh : true,
                // showToggle : true,
                // showColumns : true,
                iconSize: 'outline',
                toolbar: '#exampleToolbar',
                striped: true, // 设置为true会有隔行变色效果
                dataType: "json", // 服务器返回的数据类型
                pagination: true, // 设置为true会在底部显示分页条
                // queryParamsType : "limit",
                // //设置为limit则会发送符合RESTFull格式的参数
                singleSelect: false, // 设置为true将禁止多选
                // contentType : "application/x-www-form-urlencoded",
                // //发送到服务器的数据编码类型
                pageSize: 10, // 如果设置了分页,每页数据条数
                pageNumber: 1, // 如果设置了分布,首页页码
                // search : true, // 是否显示搜索框
                // showColumns : true, // 是否显示内容下拉框(选择显示的列)
                sidePagination: "server", // 设置在哪里进行分页,可选值为"client" 或者
                // "server"
                queryParams: function (params) {
                    return {
                        limit: params.limit,
                        offset: params.offset,
                        name: $('#searchName').val(),
                        sort: 'create_on',
                        order: 'desc',
                        operation: $("#searchOperation").val(),
                        username: $("#searchUsername").val()
                    };
                },
                // //请求服务器数据时,你可以通过重写参数的方式添加一些额外的参数,例如 toolbar 中的参数 如果
                // queryParamsType = 'limit' ,返回参数必须包含
                // limit, offset, search, sort, order 否则, 需要包含:
                // pageSize, pageNumber, searchText, sortName,
                // sortOrder.
                // 返回false将会终止请求
                columns: [
                    {
                        checkbox: true
                    },
                    {
                        field: 'id', // 列字段名
                        title: '序号' // 列标题
                    },
                    {
                        field: 'userId',
                        title: '用户Id'
                    },
                    {
                        field: 'username',
                        title: '用户名'
                    },
                    {
                        field: 'operation',
                        title: '操作'
                    },
                    {
                        field: 'time',
                        title: '用时'
                    },
                    {
                        field: 'method',
                        title: '方法'
                    },
                    {
                        field: 'params',
                        title: '参数'
                    },
                    {
                        field: 'ip',
                        title: 'IP地址'
                    },
                    {
                        field: 'createOn',
                        title: '创建时间'
                    },
                    {
                        title: '操作',
                        field: 'id',
                        align: 'center',
                        formatter: function (value, row, index) {
                            var e = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="编辑" οnclick="edit(\''
                                + row.userId
                                + '\')"><i class="fa fa-edit"></i></a> ';
                            var d = '<a class="btn btn-warning btn-sm" href="#" title="删除"  mce_href="#" οnclick="remove(\''
                                + row.id
                                + '\')"><i class="fa fa-remove"></i></a> ';
                            var f = '<a class="btn btn-success btn-sm" href="#" title="重置密码"  mce_href="#" οnclick="resetPwd(\''
                                + row.userId
                                + '\')"><i class="fa fa-key"></i></a> ';
                            return d;
                        }
                    }]
            });
}

function reLoad() {
    $('#exampleTable').bootstrapTable('refresh');
}

function remove(id) {
    layer.confirm('确定要删除选中的记录?', {
        btn: ['确定', '取消']
    }, function () {
        $.ajax({
            url: prefix + "/remove",
            type: "post",
            data: {
                'id': id
            },
            beforeSend: function (request) {
                index = layer.load();
            },
            success: function (r) {
                if (r.code == 0) {
                    layer.close(index);
                    layer.msg(r.msg);
                    reLoad();
                } else {
                    layer.msg(r.msg);
                }
            }
        });
    })
}

function batchRemove() {
    var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行,当没有选择的记录时,返回一个空数组
    if (rows.length == 0) {
        layer.msg("请选择要删除的数据");
        return;
    }
    layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", {
        btn: ['确定', '取消']
        // 按钮
    }, function () {
        var ids = new Array();
        // 遍历所有选择的行数据,取每条数据对应的ID
        $.each(rows, function (i, row) {
            ids[i] = row['id'];
        });
        $.ajax({
            type: 'POST',
            data: {
                "ids": ids
            },
            url: prefix + '/batchRemove',
            success: function (r) {
                if (r.code == 0) {
                    layer.msg(r.msg);
                    reLoad();
                } else {
                    layer.msg(r.msg);
                }
            }
        });
    }, function () {
    });
}

日志表映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.yangzc.studentboot.common.dao.LogDao">

	<select id="get" resultType="com.yangzc.studentboot.common.domain.LogDO">
		select `id`,`user_id`,`username`,`operation`,`time`,`method`,`params`,`ip`,`create_on` from sys_log where id = #{value}
	</select>

	<select id="list" resultType="com.yangzc.studentboot.common.domain.LogDO">
		select `id`,`user_id`,`username`,`operation`,`time`,`method`,`params`,`ip`,`create_on` from sys_log
        <where>  
		  		  <if test="id != null and id != ''"> and id = #{id} </if>
		  		  <if test="userId != null and userId != ''"> and user_id = #{userId} </if>
		  		  <if test="username != null and username != ''"> and username = #{username} </if>
		  		  <if test="operation != null and operation != ''"> and operation = #{operation} </if>
		  		  <if test="time != null and time != ''"> and time = #{time} </if>
		  		  <if test="method != null and method != ''"> and method = #{method} </if>
		  		  <if test="params != null and params != ''"> and params = #{params} </if>
		  		  <if test="ip != null and ip != ''"> and ip = #{ip} </if>
		  		  <if test="createOn != null and createOn != ''"> and create_on = #{createOn} </if>
		  		</where>
        <choose>
            <when test="sort != null and sort.trim() != ''">
                order by ${sort} ${order}
            </when>
			<otherwise>
                order by id desc
			</otherwise>
        </choose>
		<if test="offset != null and limit != null">
			limit #{offset}, #{limit}
		</if>
	</select>
	
 	<select id="count" resultType="int">
		select count(*) from sys_log
		 <where>  
		  		  <if test="id != null and id != ''"> and id = #{id} </if>
		  		  <if test="userId != null and userId != ''"> and user_id = #{userId} </if>
		  		  <if test="username != null and username != ''"> and username = #{username} </if>
		  		  <if test="operation != null and operation != ''"> and operation = #{operation} </if>
		  		  <if test="time != null and time != ''"> and time = #{time} </if>
		  		  <if test="method != null and method != ''"> and method = #{method} </if>
		  		  <if test="params != null and params != ''"> and params = #{params} </if>
		  		  <if test="ip != null and ip != ''"> and ip = #{ip} </if>
		  		  <if test="createOn != null and createOn != ''"> and create_on = #{createOn} </if>
		  		</where>
	</select>
	 
	<insert id="save" parameterType="com.yangzc.studentboot.common.domain.LogDO" useGeneratedKeys="true" keyProperty="id">
		insert into sys_log
		(
			`user_id`, 
			`username`, 
			`operation`, 
			`time`, 
			`method`, 
			`params`, 
			`ip`, 
			`create_on`
		)
		values
		(
			#{userId}, 
			#{username}, 
			#{operation}, 
			#{time}, 
			#{method}, 
			#{params}, 
			#{ip}, 
			#{createOn}
		)
	</insert>
	 
	<update id="update" parameterType="com.yangzc.studentboot.common.domain.LogDO">
		update sys_log 
		<set>
			<if test="userId != null">`user_id` = #{userId}, </if>
			<if test="username != null">`username` = #{username}, </if>
			<if test="operation != null">`operation` = #{operation}, </if>
			<if test="time != null">`time` = #{time}, </if>
			<if test="method != null">`method` = #{method}, </if>
			<if test="params != null">`params` = #{params}, </if>
			<if test="ip != null">`ip` = #{ip}, </if>
			<if test="creatOn != null">`create_on` = #{createOn}</if>
		</set>
		where id = #{id}
	</update>
	
	<delete id="remove">
		delete from sys_log where id = #{value}
	</delete>
	
	<delete id="batchRemove">
		delete from sys_log where id in 
		<foreach item="id" collection="array" open="(" separator="," close=")">
			#{id}
		</foreach>
	</delete>

</mapper>

github项目地址

https://github.com/yangzc23/studentboot

参考资料

[01] AOP拦截日志报错
[02] Springboot中Aspect实现切面(以记录日志为例)
[03] Spring Aspect的Execution表达式
[04] 再也不怕aop的原理了

微信扫一扫关注公众号
使用AOP实现日志管理
点击链接加入群聊

https://jq.qq.com/?_wv=1027&k=5eVEhfN
软件测试学习交流QQ群号:511619105

相关标签: 05软件开发