本文为博主原创,未经允许不得转载:
在项目开发已经完成多半的情况下,需要开发进行操作日志功能的开发,由于操作的重要性,需要记录下操作前的参数和请求时的参数,
在网上找了很多,没找到可行的方法.由于操作日志用注解方式的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) {