本文为博主原创,未经允许不得转载:

  在项目开发已经完成多半的情况下,需要开发进行操作日志功能的开发,由于操作的重要性,需要记录下操作前的参数和请求时的参数,

在网上找了很多,没找到可行的方法.由于操作日志用注解方式的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实现操作日志记录,并记录请求参数与编辑前后字段的具体改变的更多相关文章

  1. 一文带你学会基于SpringAop实现操作日志的记录

    前言 大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案.大家可能会说,切,操作日志记录这么简单的东西,老生常谈了.不! 网上的操作日志一般就是记录操作人, ...

  2. 微软企业库5.0 学习之路——第九步、使用PolicyInjection模块进行AOP—PART4——建立自定义Call Handler实现用户操作日志记录

    在前面的Part3中, 我介绍Policy Injection模块中内置的Call Handler的使用方法,今天则继续介绍Call Handler——Custom Call Handler,通过建立 ...

  3. 【开源】OSharp3.0框架解说系列(6.2):操作日志与数据日志

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  4. 幸福框架:用户想看到的操作日志也要使用AOP吗?

    背景 日志无论是对于开发人员.运维人员和最终用户都是一笔财富,是不是所有类型的日志都要AOP呢?本着交流的目的,这里先说一些看法,希望大家多批评. 常见的日志类型 异常日志 概念:记录异常的日志. 考 ...

  5. tomcat与jetty接收请求参数的区别

    [场景] 服务端点对点通知.A服务发起请求B服务,B同步返回接收成功:然后B开始处理逻辑:B处理完成后异步通知给A:A接收请求并处理,同步回写响应给B:完成. [先上代码] 服务端(接收端)代码: i ...

  6. servlet-api api文档获取请求参数

    1.假如有个get请求后面带有的参数如下: a=b&a2=b2&a3=b3&a4=b4. 如果想获取所有的key,value.这个时候可以根据request的getQueryS ...

  7. SpringMVC 接收表单数据、数据绑定、解决请求参数中文乱码

    接收表单数据有3种方式. 1.使用简单类型接收表单数据(绑定简单数据类型) 表单: <form action="${pageContext.request.contextPath}/u ...

  8. springboot springmvc拦截器 拦截POST、PUT、DELETE请求参数和响应数据,并记录操作日志

    1.操作日志实体类 @Document(collection = "operation_log") @Getter @Setter @ToString public class O ...

  9. springAOP记录用户操作日志

    项目已经开发完成,需要加用户操作日志,如果返回去加也不太现实,所以使用springAOP来完成比较合适. 注解工具类: @Retention(RetentionPolicy.RUNTIME) @Tar ...

随机推荐

  1. pc端字体大小计算以及echart中字体大小计算

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  2. 2019春第六周作业Compile Summarize

    这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 在这里 我在这个课程的目标是 能够熟练掌握指针的用法 这个作业在那个具体方面帮助我实现目标 对指针的使用更加得心应手 参考文献与网址 C语 ...

  3. 正则re

    1.简介 其实re在基本模块里已经介绍过,但是在爬虫中re是非常重要的,所以在这里再进行详细描述. re在解析html内容时是效率最高的,但是也是最难的,一般来说,都是结合xpath和re使用,这样解 ...

  4. 我的FPGA之旅4---led流水灯

    [1]输入端口不能使用reg数据类型,因为reg类型对应的FPGA内部的寄存器.这样理解:reg寄存器具有记忆功能;而wire类型数据就相当于一根连线.input输入信号用wire连线进来就好:out ...

  5. Postman接口自动化测试实例用到的完整的SM2前端加密算法代码

    var __g__ = {}; !function(t,e){"object"==typeof exports?module.exports=exports=e():"f ...

  6. caffe 环境搭建

    1.VS安装 VS社区版(个人免费): http://download.microsoft.com/download/B/4/8/B4870509-05CB-447C-878F-2F80E4CB464 ...

  7. spider随机请求头和ip

    #创建爬虫 scrapy genspider randomIp_spider "taobao.com" #把需要请求的url放到一个混淆的url请求list中去,避免被监测到总是访 ...

  8. [PHP] 编写爬虫获取淘宝网上所有的商品分类以及关键属性 销售属性 非关键属性数据

    参考文章地址:https://blog.csdn.net/zhengzizhi/article/details/80716608 http://open.taobao.com/apitools/api ...

  9. 极致21点开发DAY4

    完成的内容:1.修改上一篇博文中的Bug  2.完成任务窗口逻辑 using System; using System.Collections.Generic; using UnityEngine; ...

  10. 《ASP.NET Core In Action》读书笔记系列二 ASP.NET Core 能用于什么样的应用,什么时候选择ASP.NET Core

    ASP.NET Core 能用于什么样的应用 ASP.NET Core 可以用作传统的web服务.RESTful服务.远程过程调用(RPC)服务.微服务,这归功于它的跨平台支持和轻量级设计.如下图所示 ...