SpringAOP注解方式记录操作日志(操作模块,操作功能,调用方法,主键信息等)支持多笔操作时记录

开源项目地址github开源链接

使用AOP切入的方式记录操作日志,本代码主要采用枚举作为记录方式,具体代码如下.

首先先定义先关枚举:

/**
 * 枚举公共接口
 * @author LeiYong
 *
 */
public interface EnumSuper {
	/**
	 * 获取值
	 * @return
	 */
	public String getValue();
	/**
	 * 获取描述信息
	 * @return
	 */
	public String getDiscription();
}

public enum LogOperateEnum implements EnumSuper{
	Select("0","查询"),Save("1","新增"),Update("2","修改"),Delete("3","删除");
	private String value;
	private String discription;
	
	LogOperateEnum(String value, String discription) {
		this.value = value;
		this.discription = discription;
	}

	@Override
	public String getValue() {
		return value;
	}

	@Override
	public String getDiscription() {
		return discription;
	}

	
}

public enum LogEnum implements EnumSuper{
	Sys("系统",""),
	Activity("活动",""),
	Shop("商城",""),
	Usr("用户",""),
	Scd("预约","")
	;
	private String value;
	private String discription;
	
	LogEnum(String value, String discription) {
		this.value = value;
		this.discription = discription;
	}

	@Override
	public String getValue() {
		return value;
	}

	@Override
	public String getDiscription() {
		return discription;
	}

	
}

接下来定义注解Logger

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Logger {
	/**
	 * 操作类型 0-查询 1-新增 2-修改 3-删除
	 * @return
	 */
	LogOperateEnum type();
	/**
	 * 所属模块
	 * @return
	 */
	LogEnum model();
	/**
	 * 业务主键
	 * @return
	 */
	String title() default "#pk";
}

最后LoggerAspect切面类

此处keyGenerate为主键生成,参考另一篇博文,使用redis生成数据库主键自增

使用redis生成数据库主键自增

package com.cykj.base.core.aop;

import java.lang.reflect.Method;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.cykj.base.common.annotation.Logger;
import com.cykj.base.common.system.SystemConfig;
import com.cykj.base.common.system.properties.AdminPropertiesConfig;
import com.cykj.base.common.util.PropertiesCacheUtil;
import com.cykj.base.common.util.StringUtil;
import com.cykj.base.core.model.log.LogOperater;
import com.cykj.base.core.model.sys.SysUser;
import com.cykj.base.core.model.usr.UsrUser;
import com.cykj.base.core.model.usr.UsrUserSlave;
import com.cykj.base.core.provide.log.LogOperaterService;
import com.cykj.base.core.util.AopUtil;
import com.cykj.base.core.util.WebUtils;

/**
 * 操作日志
 * @author LeiYong
 *
 */
@Aspect
@Component
public class LoggerAspect {
	
	@Autowired
	private LogOperaterService logOperaterService;
	@Pointcut("@annotation(com.cykj.base.common.annotation.Logger)")  
    private void loggerMethod(){}//定义一个切入点,此处Logger为Logger类全路径  
	/**
	 * 记录操作日志
	 * 
	 * @param jp
	 * @return
	 * @throws Throwable
	 */
	@Around("loggerMethod()")
	public Object logger(ProceedingJoinPoint jp) throws Throwable {
		
		Object result = null;
		Boolean logEnable = SystemConfig.LOG_ENABLE;
		result = AopUtil.executeJoinPointMethod(jp, jp.getArgs());
		if (logEnable&&result!=null) {
			LogOperater lo = null;
			SysUser su = getLoginSysUser();
			if (su!=null) {
				lo = getLogBySysUser(su, jp);
			}else {
				UsrUserSlave uu = getLoginUsrUser();
				lo = getLogByUsrUser(uu, jp);
			}
			if (lo!=null) {
				logOperaterService.insertSelective(lo);
			}
		}
		return result;
	}

	/**
	 * 记录系统用户操作日志
	 * @param user
	 * @param jp
	 * @return
	 */
	private LogOperater getLogBySysUser(SysUser user,ProceedingJoinPoint jp){
		LogOperater logOperater = getLogger(jp);
		if (logOperater!=null) {
			logOperater.setCreateBy(user.getPk());
			logOperater.setFrom("0");
			logOperater.setOrgPk(user.getOrgPk());
		}
		return logOperater;
	}
	/**
	 * 记录微信用户操作日志
	 * @param user
	 * @param jp
	 * @return
	 */
	private LogOperater getLogByUsrUser(UsrUser user,ProceedingJoinPoint jp){
		LogOperater logOperater = getLogger(jp);
		if (logOperater!=null) {
			logOperater.setCreateBy(user.getPk());
			logOperater.setFrom("1");
//			logOperater.setOrgPk(user.getOrgPk());
		}
		return logOperater;
	}

	private LogOperater getLogger(ProceedingJoinPoint jp){
		//获取切入点方法及参数
		Method method = AopUtil.getMethod(jp);
		Logger t = AopUtil.getMethodAnnotation(method, Logger.class);
		try {
			Boolean flag = PropertiesCacheUtil.loadProjectProperties(AdminPropertiesConfig.LOGGER_CONFIG).getBoolean(t.type().toString());
			//未配置或true都记录日志,仅为false时不记录
			if (flag!=null&&!flag) {
				return null;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String[] paramNames = AopUtil.getMethodParamNames(method);
		String pk = AopUtil.parseKeyByParam(t.title(),paramNames, jp.getArgs());
		//主键,机构pk,创建人,创建时间,操作类型,模块,处理方法,主键,操作内容
		//自增长主键
		LogOperater logOperater = new LogOperater();
		logOperater.setCreateTime(new Date());
		logOperater.setType(t.type().getValue());
		logOperater.setModel(t.model().getValue());
		logOperater.setFunc(t.model().getDiscription());
		logOperater.setMethod(method.getName());
		if (StringUtils.isNotBlank(pk)) {
			int index = pk.indexOf(",");
			if (index<0) {
				logOperater.setTitle(pk);
			}else{
				logOperater.setTitle(StringUtil.subString(pk, 0, index));
				int count = StringUtils.countMatches(pk, ",");
				String content = SystemConfig.LOG_CONTENT_LENGTH>pk.length()?pk:StringUtil.subString(pk, 0, SystemConfig.LOG_CONTENT_LENGTH)+"...";
				logOperater.setContent(count+"笔:"+content);
			}
		}
		return logOperater;
	}
	/**
	 * 获取系统登录用户
	 * @return
	 */
	private SysUser getLoginSysUser(){
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();    
		return WebUtils.getLoginUser(request);
	}
	/**
	 * 获取微信登录用户(未实现)
	 * @return
	 */
	private UsrUserSlave getLoginUsrUser(){
		return null;
	}
	
}


再在spring的xml中配置扫描到LoggerAspect所在package目录,大功告成

使用起来也非常方便,在需要加操作日志的方法上加个注解就OK了,如此则记录为系统模块,执行查询,主键为参数pk(此处使用springSpel表达式解析)

@Logger(model=LogEnum.Sys,type=LogOperateEnum.Select,title="#pk")
	public Json get(String orgPk,String pk) {}

最后附上AOPUtil代码

package com.cykj.base.core.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class AopUtil {
	private static ExpressionParser parser;
	/**
	 * 获取被拦截方法对象
	 * 
	 * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 而缓存的注解在实现类的方法上
	 * 所以应该使用反射获取当前对象的方法对象
	 */
	public static Method getMethod(ProceedingJoinPoint pjp) {
		// 获取参数的类型
		Class<?>[] parameterTypes = ((MethodSignature)pjp.getSignature()).getMethod().getParameterTypes();
		//此方法当参数传递null时会报错,故而改为采用上述方式
		// Object[] args = pjp.getArgs();
		// Class[] argTypes = new Class[pjp.getArgs().length];
		// for (int i = 0; i < args.length; i++) {
		// if (args[i]!=null) {
		// argTypes[i] = args[i].getClass();
		// }
		// }
		Method method = null;
		try {
			method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), parameterTypes);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return method;
	}
	/**
	 * 通过spring spel解析参数获取redis缓存key
	 * 
	 * @param keys
	 *            缓存keys
	 * @param paramNames
	 *            参数名
	 * @param args
	 *            参数列表
	 * @return
	 */
	public static String parseKeyByParam(String keys, String[] paramNames, Object[] args) {
		if (StringUtils.isBlank(keys)) {
			return "";
		}
		ExpressionParser parser = getParser();
		StandardEvaluationContext context = new StandardEvaluationContext();
		// 把方法参数放入SPEL上下文中
		for (int i = 0; i < paramNames.length; i++) {
			context.setVariable(paramNames[i], args[i]);
		}
		// 获取参数key
		StringBuffer sb = new StringBuffer();
		// for (int i = 0; i < keys.length; i++) {
		sb.append(parser.parseExpression(keys).getValue(context, String.class));
		// }
		return sb.toString();
	}

	/**
	 * 获取参数名列表
	 * 
	 * @param method
	 * @return
	 */
	public static String[] getMethodParamNames(Method method) {
		// 获取被拦截方法参数名列表(使用Spring支持类库)
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paraNameArr = u.getParameterNames(method);
		return paraNameArr;
	}

	/**
	 * 获取缓存主键
	 * @param jp
	 * @param clazz 
	 * @return
	 * @throws SecurityException 
	 * @throws NoSuchFieldException 
	 */
//	public static <T extends Annotation> String[] getCachedKey(T t,String[] paramNames, Class<T> clazz) throws NoSuchFieldException, SecurityException {
		Method method = getMethod(jp);
		T t = getMethodAnnotation(method, clazz);
//		// String fieldKey = null;
//		// if (cache.isParam()) {
//		String fieldKey = parseKeyByParam(ReflectionUtil.getField(t.getClass().getField("cachekey"), t).toString(), paramNames, jp.getArgs());
//		// }
//		return fieldKey.split(",");
//	}
	
	/**
	 * 获取方法注解
	 * @param method
	 * @param clazz 注解类型
	 * @return
	 */
	public static <T extends Annotation> T getMethodAnnotation(Method method,Class<T> clazz){
		T t = method.getAnnotation(clazz);
		return t;
	}
	/**
	 * 获取redis缓存key通过集合参数
	 * 
	 * @param keys
	 *            缓存keys
	 * @param params
	 *            参数名
	 * @param returnObj
	 *            参数列表
	 * @return
	 */
	private String parseKeyByReturn(String keys, Object returnObj) {
		ExpressionParser parser = getParser();
		StandardEvaluationContext context = new StandardEvaluationContext();
		// 把方法参数放入SPEL上下文中
		context.setVariable("obj", returnObj);
		// 获取参数key
		StringBuffer sb = new StringBuffer();
		sb.append(parser.parseExpression(keys).getValue(context, String.class));
		return sb.toString();
	}
	/**
	 * 使用SPEL进行key的解析
	 * 
	 * @return
	 */
	public synchronized static ExpressionParser getParser() {
		if (parser == null) {
			parser = new SpelExpressionParser();
		}
		return parser;
	}
	/**
	 * 执行切入点方法
	 * @param jp
	 * @param params 方法参数
	 * @return
	 */
	public static Object executeJoinPointMethod(ProceedingJoinPoint jp,Object[] params){
		Object obj = null;
		try {
			if (params==null||params.length==0) {
				obj = jp.proceed();
			}else{
				obj = jp.proceed(params);
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return obj;
	}
}



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值