springAOP实现操作日志记录,并记录请求参数与编辑前后字段的具体改变
本文为博主原创,未经允许不得转载:
在项目开发已经完成多半的情况下,需要开发进行操作日志功能的开发,由于操作的重要性,需要记录下操作前的参数和请求时的参数,
在网上找了很多,没找到可行的方法.由于操作日志用注解方式的AOP记录操作日志比较便捷,所以想到了在注解中定义操作前查询数据
详情的bean,查询方法及参数,参数类型,在aop进行方法执行前,对指定的bean,方法,参数进行调用,获得修改前的参数,并进行保存.
此处需要注意:
1.在前面中调用指定bean的方法时,不可用反射进行调用,反射不能加载spring容器,无法获取指定的spring bean,下面方法中封装的获取spring bean的
工具类也需要配置为bean,而且被spring加载,才可以;
2.@Aspect注解的类一定要配置成bean,而且被spring加载,才可以,即同时配置@Component和@Aspect,或在spring的配置文件进行bean的配置
3.如果配置了bean,要检索component-scan扫描范围是否包括Aspect类;
一.定义切面执行的注解(该注解可根据自己实现的内容进行自定义)
1 import java.lang.annotation.Documented;
2 import java.lang.annotation.Retention;
3 import java.lang.annotation.Target;
4
5 import java.lang.annotation.ElementType;
6 import java.lang.annotation.RetentionPolicy;
7
8 @Target({ElementType.PARAMETER, ElementType.METHOD})
9 @Retention(RetentionPolicy.RUNTIME)
10 @Documented
11 public @interface SystemControllerLog {
12
13 /**查询模块*/
14 String module() default "";
15
16 /**查询模块名称*/
17 String methods() default "";
18
19 /**查询的bean名称*/
20 String serviceClass() default "";
21
22 /**查询单个详情的bean的方法*/
23 String queryMethod() default "";
24
25 /**查询详情的参数类型*/
26 String parameterType() default "";
27
28 /**从页面参数中解析出要查询的id,
29 * 如域名修改中要从参数中获取customerDomainId的值进行查询
30 */
31 String parameterKey() default "";
32
33 /**是否为批量类型操作*/
34 boolean paramIsArray() default false;
35
36 }
二.切面执行的方法
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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 org.springframework.util.ReflectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.suning.fucdn.common.RequestResult;
import com.suning.fucdn.common.enums.FucdnStrConstant;
import com.suning.fucdn.entity.log.SystemControllerLogInfo;
import com.suning.fucdn.impl.service.log.LogServiceImpl;
import com.suning.fucdn.vo.AdminUserVO; /**
*
* 〈一句话功能简述:操作日志切面记录操作〉<br>
* 〈功能详细描述〉
*
* @author xiang
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Component
@Aspect
public class ControllerLogAopAspect { private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAopAspect.class); //注入service,用来将日志信息保存在数据库
@Autowired
private LogServiceImpl logservice; //配置接入点,如果不知道怎么配置,可以百度一下规则
//指定controller的类进行切面 @Pointcut("execution(* com.controller..CustomerController.*(..))||execution(* com.controller.ManageController.*(..))")
@Pointcut("execution(* com.controller..*.*(..))")
private void controllerAspect(){
System.out.println("point cut start");
}//定义一个切入点 @SuppressWarnings({ "rawtypes", "unused" })
@Around("controllerAspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//常见日志实体对象
SystemControllerLogInfo log = new SystemControllerLogInfo();
//获取登录用户账户
HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //方法通知前获取时间,为什么要记录这个时间呢?当然是用来计算模块执行时间的
//获取系统时间
String time = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date());
log.setStartTime(time); //获取系统ip,这里用的是我自己的工具类,可自行网上查询获取ip方法
//String ip = GetLocalIp.localIp();
//log.setIP(ip); // 拦截的实体类,就是当前正在执行的controller
Object target = pjp.getTarget();
// 拦截的方法名称。当前正在执行的方法
String methodName = pjp.getSignature().getName();
// 拦截的方法参数
Object[] args = pjp.getArgs();
//String params = Arrays.toString(pjp.getArgs());
JSONArray operateParamArray = new JSONArray();
for (int i = 0; i < args.length; i++) {
Object paramsObj = args[i];
//通过该方法可查询对应的object属于什么类型:String type = paramsObj.getClass().getName();
if(paramsObj instanceof String || paramsObj instanceof JSONObject){
String str = (String) paramsObj;
//将其转为jsonobject
JSONObject dataJson = JSONObject.parseObject(str);
if(dataJson == null || dataJson.isEmpty() || "null".equals(dataJson)){
break;
}else{
operateParamArray.add(dataJson);
}
}else if(paramsObj instanceof Map){
//get请求,以map类型传参
//1.将object的map类型转为jsonobject类型
Map<String, Object> map = (Map<String, Object>) paramsObj;
JSONObject json =new JSONObject(map);
operateParamArray.add(json);
}
}
//设置请求参数
log.setOperateParams(operateParamArray.toJSONString());
// 拦截的放参数类型
Signature sig = pjp.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig; Class[] parameterTypes = msig.getMethod().getParameterTypes();
Object object = null;
// 获得被拦截的方法
Method method = null;
try {
method = target.getClass().getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e1) {
LOGGER.error("ControllerLogAopAspect around error",e1);
} catch (SecurityException e1) {
LOGGER.error("ControllerLogAopAspect around error",e1);
}
if (null != method) {
// 判断是否包含自定义的注解,说明一下这里的SystemLog就是我自己自定义的注解
if (method.isAnnotationPresent(SystemControllerLog.class)) { //此处需要对用户进行区分:1为admin user 2为customer user
// get session
HttpSession httpSession = httpRequest.getSession(true);
// 从session获取登录用户
AdminUserVO adminUserVO = (AdminUserVO) httpSession
.getAttribute(FucdnStrConstant.SESSION_KEY_ADMIN.getConstant());
long adminUserId = adminUserVO.getAdminUserId();
log.setUserId(String.valueOf(adminUserId)); SystemControllerLog systemlog = method.getAnnotation(SystemControllerLog.class); log.setModule(systemlog.module());
log.setMethod(systemlog.methods());
//请求查询操作前数据的spring bean
String serviceClass = systemlog.serviceClass();
//请求查询数据的方法
String queryMethod = systemlog.queryMethod();
//判断是否需要进行操作前的对象参数查询
if(StringUtils.isNotBlank(systemlog.parameterKey())
&&StringUtils.isNotBlank(systemlog.parameterType())
&&StringUtils.isNotBlank(systemlog.queryMethod())
&&StringUtils.isNotBlank(systemlog.serviceClass())){
boolean isArrayResult = systemlog.paramIsArray();
//参数类型
String paramType = systemlog.parameterType();
String key = systemlog.parameterKey(); if(isArrayResult){//批量操作
//JSONArray jsonarray = (JSONArray) object.get(key);
//从请求的参数中解析出查询key对应的value值
String value = "";
JSONArray beforeParamArray = new JSONArray();
for (int i = 0; i < operateParamArray.size(); i++) {
JSONObject params = operateParamArray.getJSONObject(i);
JSONArray paramArray = (JSONArray) params.get(key);
if (paramArray != null) {
for (int j = 0; j < paramArray.size(); j++) {
String paramId = paramArray.getString(j);
//在此处判断spring bean查询的方法参数类型
Object data = getOperateBeforeData(paramType, serviceClass, queryMethod, paramId);
JSONObject json = (JSONObject) JSON.toJSON(data);
beforeParamArray.add(json);
}
}
}
log.setBeforeParams(beforeParamArray.toJSONString()); }else{//单量操作 //从请求的参数中解析出查询key对应的value值
String value = "";
for (int i = 0; i < operateParamArray.size(); i++) {
JSONObject params = operateParamArray.getJSONObject(i);
value = params.getString(key);
if(StringUtils.isNotBlank(value)){
break;
}
}
//在此处获取操作前的spring bean的查询方法
Object data = getOperateBeforeData(paramType, serviceClass, queryMethod, value);
JSONObject beforeParam = (JSONObject) JSON.toJSON(data);
log.setBeforeParams(beforeParam.toJSONString());
}
} try {
//执行页面请求模块方法,并返回
object = pjp.proceed();
//获取系统时间
String endTime = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date());
log.setEndTime(endTime);
//将object 转化为controller封装返回的实体类:RequestResult
RequestResult requestResult = (RequestResult) object;
if(requestResult.isResult()){
//操作流程成功
if(StringUtils.isNotBlank(requestResult.getErrMsg())){
log.setResultMsg(requestResult.getErrMsg());
}else if(requestResult.getData() instanceof String){
log.setResultMsg((String) requestResult.getData());
}else{
log.setResultMsg("执行成功");
}
}else{
log.setResultMsg("失败");
}
//保存进数据库
logservice.saveLog(log);
} catch (Throwable e) {
String endTime = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date());
log.setEndTime(endTime); log.setResultMsg(e.getMessage());
logservice.saveLog(log);
}
} else {
//没有包含注解
object = pjp.proceed();
}
} else {
//不需要拦截直接执行
object = pjp.proceed();
}
return object;
} /**
*
* 功能描述: <br>
* 〈功能详细描述〉
*
* @param paramType:参数类型
* @param serviceClass:bean名称
* @param queryMethod:查询method
* @param value:查询id的value
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
public Object getOperateBeforeData(String paramType,String serviceClass,String queryMethod,String value){
Object obj = new Object();
//在此处解析请求的参数类型,根据id查询数据,id类型有四种:int,Integer,long,Long
if(paramType.equals("int")){
int id = Integer.parseInt(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); }else if(paramType.equals("Integer")){
Integer id = Integer.valueOf(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); }else if(paramType.equals("long")){
long id = Long.parseLong(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); }else if(paramType.equals("Long")){
Long id = Long.valueOf(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
}
return obj;
}
}
三.获取spring bean的工具类
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component; /**
* 获取spring容器,以访问容器中定义的其他bean
* xiang
* MOSTsView 3.0 2009-11-16
*/
@Component
public class SpringContextUtil implements ApplicationContextAware{ private static ApplicationContext applicationContext; /**
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
*/
public void setApplicationContext(ApplicationContext applicationContext){
SpringContextUtil.applicationContext = applicationContext;
} public static ApplicationContext getApplicationContext(){
return applicationContext;
} /**
* 获取对象
* @return Object 一个以所给名字注册的bean的实例 (service注解方式,自动生成以首字母小写的类名为bean name)
*/
public static Object getBean(String name) throws BeansException{
return applicationContext.getBean(name);
}
}
四.操作日志对应的实体类
1 public class SystemControllerLogInfo{
2
3 private long id;
4
5 /**用户id*/
6 private String userId;
7
8 /**用户类型*/
9 private int userType;
10
11 /**操作模块*/
12 private String module;
13
14 /**操作类型*/
15 private String method;
16
17 /**操作前参数*/
18 private String beforeParams;
19
20 /**操作时请求参数*/
21 private String operateParams;
22
23 /**开始时间*/
24 private String startTime;
25
26 /**结束时间*/
27 private String endTime;
28
29 /**操作状态描述*/
30 private int resultStatus;
31
32 /**操作结果描述*/
33 private String resultMsg;
五.进行注解切面调用
1 @ResponseBody
2 @RequestMapping(value = "/delete", method = { RequestMethod.POST })
3 @SystemControllerLog(module="域名管理",methods="域名删除",serviceClass="domainConfService",queryMethod="queryDomain",parameterType="Long",parameterKey="customerDomainId")
4 public RequestResult delete(@RequestBody String param) {
5 // 定义请求数据
6 RequestResult result = new RequestResult();
7 // 接收数据
8 CustomerDomain customerDomain = JSONObject.parseObject(param, CustomerDomain.class);
9 // 更新客户域名
10 try {
11 String data = domainConfService.deleteDomain(customerDomain.getId());
12 // 设置true
13 if (StringUtils.isBlank(data)) {
14 result.setData("删除成功");
15 } else {
16 result.setData(data);
17 }
18 result.setResult(true);
19 } catch (Exception e) {
20 // 记录错误信息,并返回
21 LOGGER.error("delete failed", e);
22 result.setErrMsg(e.getMessage());
23 }
24 // 返回
25 return result;
26 }
六.数据实例
补充:get请求参数类型解析和记录
JSONArray operateParamArray = new JSONArray();
for (int i = 0; i < args.length; i++) {
Object paramsObj = args[i];
//通过该方法可查询对应的object属于什么类型:String type = paramsObj.getClass().getName();
if(paramsObj instanceof String || paramsObj instanceof JSONObject){
String str = (String) paramsObj;
//将其转为jsonobject
JSONObject dataJson = JSONObject.parseObject(str);
if(dataJson == null || dataJson.isEmpty() || "null".equals(dataJson)){
break;
}else{
operateParamArray.add(dataJson);
}
}else if(paramsObj instanceof Map){
//get请求,以map类型传参
//1.将object的map类型转为jsonobject类型
Map<String, Object> map = (Map<String, Object>) paramsObj;
JSONObject json =new JSONObject(map);
operateParamArray.add(json);
}
}
get请求的controller示例:
@ResponseBody
@RequestMapping(value = "/add", method = { RequestMethod.GET })
@SystemControllerLog(module="域名管理",methods="域名新增")
public RequestResult addDomain(@RequestParam Map<String, String> paramMap) {
springAOP实现操作日志记录,并记录请求参数与编辑前后字段的具体改变的更多相关文章
- 一文带你学会基于SpringAop实现操作日志的记录
前言 大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案.大家可能会说,切,操作日志记录这么简单的东西,老生常谈了.不! 网上的操作日志一般就是记录操作人, ...
- 微软企业库5.0 学习之路——第九步、使用PolicyInjection模块进行AOP—PART4——建立自定义Call Handler实现用户操作日志记录
在前面的Part3中, 我介绍Policy Injection模块中内置的Call Handler的使用方法,今天则继续介绍Call Handler——Custom Call Handler,通过建立 ...
- 【开源】OSharp3.0框架解说系列(6.2):操作日志与数据日志
OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...
- 幸福框架:用户想看到的操作日志也要使用AOP吗?
背景 日志无论是对于开发人员.运维人员和最终用户都是一笔财富,是不是所有类型的日志都要AOP呢?本着交流的目的,这里先说一些看法,希望大家多批评. 常见的日志类型 异常日志 概念:记录异常的日志. 考 ...
- tomcat与jetty接收请求参数的区别
[场景] 服务端点对点通知.A服务发起请求B服务,B同步返回接收成功:然后B开始处理逻辑:B处理完成后异步通知给A:A接收请求并处理,同步回写响应给B:完成. [先上代码] 服务端(接收端)代码: i ...
- servlet-api api文档获取请求参数
1.假如有个get请求后面带有的参数如下: a=b&a2=b2&a3=b3&a4=b4. 如果想获取所有的key,value.这个时候可以根据request的getQueryS ...
- SpringMVC 接收表单数据、数据绑定、解决请求参数中文乱码
接收表单数据有3种方式. 1.使用简单类型接收表单数据(绑定简单数据类型) 表单: <form action="${pageContext.request.contextPath}/u ...
- springboot springmvc拦截器 拦截POST、PUT、DELETE请求参数和响应数据,并记录操作日志
1.操作日志实体类 @Document(collection = "operation_log") @Getter @Setter @ToString public class O ...
- springAOP记录用户操作日志
项目已经开发完成,需要加用户操作日志,如果返回去加也不太现实,所以使用springAOP来完成比较合适. 注解工具类: @Retention(RetentionPolicy.RUNTIME) @Tar ...
随机推荐
- HDU 2089 不要62【解题报告】
题目描述: 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客 ...
- MyBatis框架基于XML的配置
什么是MyBatis? 答:它是一个持久层框架 说的太简单了吗?那让我们来看一下官方的文档描述: MyBatis有什么作用呢? 1.持久层的零实现 2.可以自动将数据封装到对象里面不需要手工编写映射的 ...
- keycode简记表
keycode值 实际含义 48到57 0到9 65到90 a到z(A到Z) 112到135 F1到F24 8 BackSpace(退格) 9 Tab 13 Enter(回车) 20 Caps_Loc ...
- MATLAB多项式运算
序言 none 正文 1. 多项式的表示 在Matlab中,多项式用一个行向量表示, 行向量的元素值为多项式系数按幂次的降序排列, 如p(x)=x3-2x-5用P=[1,0,-2,-5]表示. 2. ...
- reduce函数
python中的reduce python中的reduce内建函数是一个二元操作函数,他用来将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给reduce中的函数 func()(必须是 ...
- 关于优先队列浅析(priority_queue)
优先队列 greater与less,自定义还有结构体(可以设置2层优先级) 模板; 下面废话不多说直接上程序 注释的很明白 #include<iostream> #include< ...
- .net如何引用该命名空间
一.在.Net中如何引用该命名空间 (1)System.Windows.Threading 该命名空间在程序集WindowsBase(WindowsBase.dll)下 (2)System.Windo ...
- php----------linux下安装php的swoole扩展
1.首先你已经安装好了php环境,这里就不介绍php环境的安装了.如果你是编译安装记得将php加入环境变量,以便于方便查看扩展是否安装成功. 2.我安装的php环境缺少了要给东西,详细看下图 如果你没 ...
- Fiddler抓包【2】_捕获设置
1.Fiddler抓web网站请求 手动设置方法一:Tools--->WinINET Options--->连接--->局域网设置--->代理服务器勾选后“高级”---> ...
- [批处理]使用IncrediBuilder加速自动化测试过程
背景 1.目前单机全部跑完一次测试的时间大概是5个小时 2.7个虚拟机进行分布式执行的时间大概为3.5小时(包含复制.收集日志的时间) 预期 期望通过IncrediBuilder的虚拟进程的功能在单机 ...