15. SSM AOP环绕通知注解实现日志
来源:SegmentFault
时间:2023-02-24 21:00:28 232浏览 收藏
在IT行业这个发展更新速度很快的行业,只有不停止的学习,才不会被行业所淘汰。如果你是数据库学习者,那么本文《15. SSM AOP环绕通知注解实现日志》就很适合你!本篇内容主要包括15. SSM AOP环绕通知注解实现日志,希望对大家的知识积累有所帮助,助力实战开发!
问题分析
当我们在做一个后台管理系统的时候,很多时候都会需要一个专门的日志模块,来记录登录的用户的操作,其一可以便于监测数据变化,其二,也可以记录用户做的一些操作,便于我们追根溯源,其三,当我们系统出现问题的时候,也可以通过查看日志,找出问题出在哪里,比如Tomcat Localhost Log。
解决思路
我们都知道,Spring是一个轻量级的IOC和AOP容器,那么IOC其实就是Inverse of Control,即控制反转,就是将创建对象的权利交给Spring,由它给我们创建对象,对象默认是单例的;AOP其实就是Aspect Oriented Programming 即基于动态代理实现的面向切面编程,简单理解来说,就比如切西瓜,把西瓜切上两刀,然后在两个切面拼接进你想要加入的东西,然后再连起来。
那么我们有了AOP其实思路就会很明确了,只需要在要执行的目标方法之前和之后,插入我们想要插入的代码,就可以了
具体实现
1.日志Domain
需要说明一下,我这里的Getter&Setter方法因为使用的
Lombok插件,所以直接加上
@Data由插件底层帮我实现了,这个除了需要在idea里面下载
Lombok的插件,还需要导入pom依赖,这里把依赖附上
org.projectlombok lombok 1.18.8 provided
具体Domain如下
package com.arvin.crm.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
//系统日志记录
@Data
public class SystemLog extends BaseDomain {
//操作用户 应该是用户的姓名
private String opUser;
//操作时间
private Date opTime;
//登录ip
private String opIp;
//使用功能
private String function;
//操作参数信息
private String params;
//操作类型
private String operateType;
//操作结果
private String operateResult;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
public Date getOpTime() {
return opTime;
}
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
public void setOpTime(Date opTime) {
this.opTime = opTime;
}
}
2.Mapper层
2.1 SystemLogMapper
这个地方需要说明一下,我的SystemLogMapper里面什么都没有写,是因为我集成了
BaseMapper,即将公共的方法抽取到了
BaseMapper里面去
package com.arvin.crm.mapper; /* *@ClassName:EmployeeMapper *@Author:Arvin_yuan *@Date:2020/3/21 21:19 *@Description:TODO */ import com.arvin.crm.domain.SystemLog; public interface SystemLogMapper extends BaseMapper{ }
2.2 SystemLogMapper.xml
这里要说明一下的是,我的高级查询和分页是专门封装了一个BaseQuery和SystemLogQuery,因为感觉好像跟我们的主题没有大的关系,所以就没有贴代码上来,不过有需要的话,可以留言或者私信我,我私发或者改贴都可以
select s.id, s.opUser, s.opTime, s.opIp, s.function, s.params, s.operatetype, s.operateresult, t.companyName tcompanyName from t_systemLog s insert into t_systemLog( opUser, opTime, opIp, function, params, operatetype, operateresult ) values( #{opUser}, #{opTime}, #{opIp}, #{function}, #{params}, #{operatetype}, #{operateresult} delete from t_systemLog where id = #{id} select count(*) from t_systemLog and opUser like concat("%",#{opUser},"%") and opTime >= #{startTime} and opTime <= #{endTime} and opIp = #{opIp} and function like concat("%",#{function},"%") and params like concat("%",#{params},"%") and operatetype like concat("%",#{operatetype},"%") and operateresult like concat("%",#{operateresult},"%")
3.SystemLogService
3.1 ISystemLogService
package com.arvin.crm.service; /* *@ClassName:EmployeeService *@Author:Arvin_yuan *@Date:2020/3/21 21:31 *@Description:TODO */ import com.arvin.crm.domain.SystemLog; import java.sql.SQLException; public interface ISystemLogService extends BaseService{ }
3.2 SystemLogServiceImpl
package com.arvin.crm.service.impl; /* *@ClassName:SystemLogServiceImpl *@Author:Arvin_yuan *@Date:2020/3/31 2:33 *@Description:TODO */ import com.arvin.crm.domain.SystemLog; import com.arvin.crm.service.ISystemLogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class SystemLogServiceImpl extends BaseServiceImplimplements ISystemLogService { }
哈哈哈,看完是不是一脸懵逼,我这里面上面都没写,算了吧算了吧,还是把抽取的Base代码贴出来哈,上面的BaseQuery先不贴,哎,没贴是怕太长了,看起来不大美观,有需要找我哈
IBaseService
package com.arvin.crm.service; /* *@ClassName:BaseService *@Author:Arvin_yuan *@Date:2020/3/21 20:28 *@Description:TODO */ import com.arvin.crm.query.BaseQuery; import com.arvin.crm.utils.PageList; import java.io.Serializable; import java.util.List; public interface IBaseService{ //查找所有 List getAll(); //查找单个 T getOne(Serializable id); //保存 void save(T t); //删除 void delete(Serializable ids); //修改 void update(T t); //分页方法 PageList queryPage(BaseQuery baseQuery); }
BaseServiceImpl
package com.arvin.crm.service.impl; /* *@ClassName:BaseServiceImpl *@Author:Arvin_yuan *@Date:2020/3/21 20:28 *@Description:TODO */ import com.arvin.crm.mapper.BaseMapper; import com.arvin.crm.query.BaseQuery; import com.arvin.crm.service.BaseService; import com.arvin.crm.utils.PageList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; import java.util.List; @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) public class BaseServiceImplimplements BaseService { @Autowired private BaseMapper baseMapper; @Override public List getAll() { return baseMapper.getAll(); } @Override public T getOne(Serializable id) { return baseMapper.getOne(id); } @Override @Transactional public void save(T t) { baseMapper.save(t); } @Override @Transactional public void delete(Serializable id) { baseMapper.delete(id); } @Override @Transactional public void update(T t) { baseMapper.update(t); } //分页公共的方法 @Override public PageList queryPage(BaseQuery baseQuery) { PageList pageList = new PageList(); //总数 select count(*) from xxx where ? Long total = baseMapper.queryTotal(baseQuery); //select * from xxx where xxx limit List rows = baseMapper.queryData(baseQuery); pageList.setTotal(total); pageList.setRows(rows); return pageList; } }
4.SystemLogController
这个地方有必要说明一下,我只是写了查看、删除和删除多条日志,是因为吧,日志肯定不能做修改,那不然就没有真实性了,但你如果要直接修改数据库,那当我没说,哈哈哈
package com.arvin.crm.web.controller;
/*
*@ClassName:controller
*@Author:Arvin_yuan
*@Date:2020/3/21 21:43
*@Description:TODO
*/
import com.arvin.crm.aspect.SystemLogAnno;
import com.arvin.crm.domain.SystemLog;
import com.arvin.crm.query.SystemLogQuery;
import com.arvin.crm.service.ISystemLogService;
import com.arvin.crm.utils.AjaxResult;
import com.arvin.crm.utils.PageList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequestMapping("/systemLog")
@CrossOrigin//前后端分离支持注解
public class SystemLogController {
@Autowired
private ISystemLogService systemLogService;
@SystemLogAnno(operateType = "日志查看")
@ApiOperation(value = "查询所有日志", notes = "不需传入参数")
@RequestMapping(value = "/page", method = RequestMethod.PATCH)
@ResponseBody
public PageList queryPage(@RequestBody SystemLogQuery systemLogQuery){
System.out.println(systemLogQuery);
PageList pageList = systemLogService.queryPage(systemLogQuery);
for (Object row : pageList.getRows()) {
//Thu Mar 26 12:08:16 CST 2020
//Tue Apr 14 00:00:00 CST 2020
System.out.println(row);
}
return systemLogService.queryPage(systemLogQuery);
}
@SystemLogAnno(operateType = "日志删除")
@ApiOperation(value = "删除日志", notes = "传入日志id")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseBody
public AjaxResult delete(@PathVariable("id") Long id){
try {
systemLogService.delete(id);
return new AjaxResult();
} catch (Exception e) {
e.printStackTrace();
return new AjaxResult("网络错误,请重试");
}
}
/**
* 批量删除
* @param ids
* @return
*/
@SystemLogAnno(operateType = "删除多条日志")
@ApiOperation(value = "批量删除日志", notes = "传入日志id数组")
@RequestMapping(value = "/d/{ids}", method = RequestMethod.DELETE)
@ResponseBody
public AjaxResult deleteList(@PathVariable("ids") Long[] ids){
try {
for (Long id : ids) {
systemLogService.delete(id);
}
return new AjaxResult();
} catch (Exception e) {
e.printStackTrace();
return new AjaxResult("网络错误,请重试");
}
}
}
5.自定义注解
这里的话,这个注解里面我只是写了一句
String operateType() default "";,这个根据自己需要来写哈,你要写其他的内容,你高兴就好
package com.arvin.crm.aspect;
/*
*@ClassName:SystemLogAnno
*@Author:Arvin_yuan
*@Date:2020/3/31 18:50
*@Description:TODO
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER}) // 方法 + 参数 注解
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
public @interface SystemLogAnno {
String operateType() default "";// 记录日志的操作类型
}
这里需要来一条分割线,为什么呢?说实在话,因为下面的内容才是正儿八经的实现类,不,是实现功能的类哈,其他类都是辅助类
6.切面类SystemLogAspect
这个里面呢,我把我知道的都写在注释上面的,应该大致都能看懂
关于
获取IP的方法,写了3个,这个我是真不会啊,在网上找的,但是只有方法3我是成功了的,反正嘛,各人情况不同,也许我这行不通说不定哪位朋友拿过去就能执行呢?
我这里获取到的是
IPv4地址,如果需要IPv6或者其他地址,可以把
inetAddress.getHostAddress();这个结果打印出来,然后自己找就行,亲测可行,至于这个方法的注释,我是真写不出来,各位看官见谅
package com.arvin.crm.aspect;
/*
*@ClassName:LogAopAspect
*@Author:Arvin_yuan
*@Date:2020/3/31 18:53
*@Description:TODO
*/
import com.arvin.crm.domain.Employee;
import com.arvin.crm.domain.SystemLog;
import com.arvin.crm.service.ISystemLogService;
import com.sun.deploy.net.HttpRequest;
import com.sun.xml.internal.bind.CycleRecoverable;
import javafx.scene.control.ContextMenuBuilder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.omg.CORBA.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.sql.SQLException;
import java.util.Date;
import java.util.Enumeration;
@Component
@Aspect
public class SystemLogAopAspect {
@Autowired
private ISystemLogService systemLogService;// 系统日志的Service
@Autowired
private HttpServletRequest request;
@Around("@annotation(com.arvin.crm.aspect.LogAnno)")//这个是配置我们实现切面所需注解所在的类
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//准备一个结果集对象
Object result = null;
// 1.方法执行前的处理,相当于前置通知
// 获取方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取方法对象
Method method = methodSignature.getMethod();
// 获取方法名
String methodName = method.getName();
// 获取方法上面的注解
SystemLogAnno anno = method.getAnnotation(SystemLogAnno.class);
// 传入方法的参数
String parameters = method.getParameters().toString();
// 获取注解里面描述的类型 对应 String operateType() default "";
String operateType = anno.operateType();
// 获取session中存的当前登录用户信息
Employee employee = (Employee) request.getSession().getAttribute("USER_INFO");
// 创建一个切面对象来调用查询ip的方法
SystemLogAopAspect aop = new SystemLogAopAspect();
String ip = aop.getIpAddress();
// 设置参数进日志对象
// 创建一个日志对象(准备记录日志)
SystemLog systemLog = new SystemLog();
// 设置操作用户名
systemLog.setOpUser(employee.getUsername());
// 设置当前用户对应的租户
// systemLog.setTenant(employee.getTenant());
// 设置IP
systemLog.setOpIp(ip);
// 使用功能
systemLog.setFunction(methodName);
// 设置方法参数
systemLog.setParams(parameters);
// 操作类型
systemLog.setOperatetype(operateType);
try {
//这里执行注解对应的方法
result = joinPoint.proceed();
// 2.相当于后置通知(方法成功执行)
systemLog.setOperateresult("正常");// 设置操作结果
} catch (SQLException e) {
// 3.相当于异常通知部分
systemLog.setOperateresult("失败");// 设置操作结果
} finally {
// 4.相当于最终通知
try {
// 设置操作时间
systemLog.setOpTime(new Date());
systemLogService.addLog(systemLog);// 添加日志记录
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
//获取IP 方法一:
/*public String getIpAddress() {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}*/
// 获取IP 方法二:
/*public String getIpAddress() {
String ip = request.getHeader("X-Real-IP");
if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isEmpty(ip) && "unknown".equalsIgnoreCase(ip)) {
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
} else {
return request.getRemoteAddr();
}
}*/
//获取IP 方法三
public String getIpAddress() throws SocketException {
Enumeration e = NetworkInterface.getNetworkInterfaces();
String ip = null;
while (e.hasMoreElements()) {
NetworkInterface network = (NetworkInterface) e.nextElement();
Enumeration enumeration = network.getInetAddresses();
while (enumeration.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) enumeration.nextElement();
String hostAddress = inetAddress.getHostAddress();
if (hostAddress.length() == 11){
ip = hostAddress;
}
}
}
return ip;
}
}
7.Spring的applicationContext.xml配置
这里的东西是死的,就不多说了
8.补充Controller使用这个注解
需要在Springmvc的配置文件中进行配置
applicationContext-mvc.xml
结语
到这里基本就结束了,但是我配完了发现有一个问题,日志量非常大啊,很占空间,所以准备加个定时器定期清理,说实话早忘了,好在我写了blog,哈哈哈,在这里https://segmentfault.com/a/11...
本篇关于《15. SSM AOP环绕通知注解实现日志》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于数据库的相关知识,请关注golang学习网公众号!
-
499 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
101 收藏
-
数据库 · MySQL | 6分钟前 | 字符集 · 故障排查 · MySQL教程 · 索引优化 · 排序规则 · mysql 排序规则 索引优化 utf8mb4 collation MySQL 8.4294 收藏
-
数据库 · MySQL | 16分钟前 | binlog · 主从复制 · 故障排查 · MySQL教程 · DBA实战 · mysql DBA binlog 主从复制 MySQL 8.4 复制延迟 relay log119 收藏
-
数据库 · MySQL | 37分钟前 | MySQL教程 · 慢查询治理 · 索引优化 · 分区表 · DBA实战 · mysql 分区表 慢查询 索引优化 MySQL 8.4 partition pruning133 收藏
-
数据库 · MySQL | 1小时前 | 高并发 · 故障排查 · MySQL教程 · 事务隔离 · InnoDB锁 · mysql innodb 高并发 锁等待 MySQL 8.4 NOWAIT SKIP LOCKED439 收藏
-
数据库 · MySQL | 3小时前 | MySQL教程 · 慢查询治理 · 索引优化 · JSON查询 · InnoDB实战 · mysql JSON 慢查询 索引优化 MySQL 8.4 多值索引291 收藏
-
数据库 · MySQL | 22小时前 | InnoDB · 故障排查 · 生产实践 · MySQL教程 · 事务隔离 · mysql innodb Purge Lag History List 长事务 Undo326 收藏
-
数据库 · MySQL | 1天前 | 性能优化 · 执行计划 · 生产实践 · MySQL教程 · 索引优化 · mysql explain 索引优化 Index Condition Pushdown ICP179 收藏
-
189 收藏
-
数据库 · MySQL | 1天前 | 性能优化 · 执行计划 · 生产实践 · MySQL教程 · 数据库运维 · mysql 直方图 EXPLAIN ANALYZE Histogram 优化器统计信息419 收藏
-
388 收藏
-
数据库 · MySQL | 1天前 | 性能优化 · InnoDB · 生产实践 · MySQL教程 · 数据库运维 · mysql redo log innodb 性能优化 innodb_redo_log_capacity382 收藏
-
数据库 · MySQL | 1天前 | MySQL教程 · 数据库实战 · 在线DDL · ALTER TABLE · 元数据锁 · mysql innodb MySQL 8 在线 DDL ALTER TABLE MDL 元数据锁 INSTANT323 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习