使用AOP实现日志管理
程序员文章站
2022-05-06 18:17:46
...
自定义注解
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的原理了
微信扫一扫关注公众号
点击链接加入群聊
https://jq.qq.com/?_wv=1027&k=5eVEhfN
软件测试学习交流QQ群号:511619105
下一篇: PHP打包下载整个文件夹或多文件
推荐阅读
-
thinkPHP5使用Rabc实现权限管理
-
thinkPHP3.2使用RBAC实现权限管理的实现
-
如何使用向日葵远程控制软件实现远程控制、管理、操作等一系列项目的图文教程
-
使用apache的rotatelogs命令实现WebLogic启动命令的nohup的日志回滚
-
实现Nginx中使用PHP-FPM时记录PHP错误日志的配置方法
-
abp(net core)+easyui+efcore实现仓储管理系统——使用 WEBAPI实现CURD (十五)
-
使用注解实现IOC与AOP的配置
-
Spring-使用注解实现AOP(九)
-
abp(net core)+easyui+efcore实现仓储管理系统——使用 WEBAPI实现CURD (十二)
-
使用PHP实现蜘蛛访问日志统计