1 业务需求:今日,公司要求对操作的业务和日志统一做处理,需要把业务表数据相关信息存入日志表中,比如表名,方法名,业务id,操作操作时间modifyTIme等等。

除了在业务主动插入日志数据之外,有个比较好的方法就是用面向切面aop处理,明确跟业务逻辑分开,把业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

2 业务开发,这边处理的方式是用方式1【两种方式 1 利用注解方式 2 通过xml配置】

  • 2.1 定义切入点接口类
package com.hec.dup.facade.mgr.annotation;

import java.lang.annotation.*;

/**
 * 标记需要做业务日志的方法
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperateLogAnnotation {

    /**
     * 被修改的实体的唯一标识,例如:菜单实体的唯一标识为"id"
            */
    String key() default "id";

    /**
     * 业务模块类型,例如:"01菜单管理"
     */
    String moduleType()  default "";

    /**
     * 业务模块名称,例如:"菜单管理"
     */
    String moduleName()  default "";

    /**
     * 业务模块功能名称,例如:"新增菜单功能"
     */
    String functionName()  default "";

    /**
     * 操作日志内容,例如:"修改菜单"
     */
    String operateContent() default "";

    /**
     * 业务模块表名,例如:"SYS_MENU"
     */
    String tableName() default "";

}
  • 2.2 定义日志记录切面类
package com.hec.dup.facade.mgr.aspect;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hec.dup.beans.base.PrjUserEntity;
import com.hec.dup.beans.base.vo.PrjCurrUserInfo;
import com.hec.dup.beans.com.DupComOperateLogEntity;
import com.hec.dup.common.utils.UuidUtil;
import com.hec.dup.facade.base.IPrjUserService;
import com.hec.dup.facade.com.IDupComOperateLogService;
import com.hec.dup.facade.mgr.annotation.OperateLogAnnotation;
import org.apache.shiro.SecurityUtils;
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 java.lang.reflect.Method;
import java.util.Date;

/**
 * 日志记录切面
 * @author
 */
@Aspect
@Component
public class OperateLogAspect {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired(required = false)
    private IDupComOperateLogService operateLogService;
    @Autowired(required = false)
    private IPrjUserService prjUserService;

    /**
     * 通过AOP方式,拦解注解的日志     
     */
    @Pointcut(value = "@annotation(com.hec.dup.facade.mgr.annotation.OperateLogAnnotation)")
    public void logAspect() {
    }

        /**
         * 后置通知:如果需要访问其他建议类型的连接点上下文,则应使用JoinPoint参数类型而不是ProceedingJoinPoint。
        */
    @After("logAspect()")
    public Object recordLog(JoinPoint point) throws Throwable {       
        try {
            this.handle(point);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("日志记录出错!", e);
        }
        return result;
    }

    /**
     * 获取拦截方法参数,处理日志
     * @param point
     * @throws Exception
     */
    private void handle(ProceedingJoinPoint point) throws Exception {
        //获取拦截的方法名
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object[] params = point.getArgs();
        Object target = point.getTarget();
        Method method = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());

        //如果当前用户未登录,不做日志
        PrjCurrUserInfo userInfo = (PrjCurrUserInfo)SecurityUtils.getSubject().getPrincipal();
        if (null == userInfo) {
            return;
        }
        //获取拦截方法的参数,获取登录用户对象
        PrjUserEntity userEntity = userInfo.getUserEntity();
                /**
                // 拦截的实体类
        Object target = joinPoint.getTarget();
        // 拦截的方法名称
        String methodName = joinPoint.getSignature().getName();
        // 拦截的方法参数
        Object[] args = joinPoint.getArgs();
        // 拦截的参数类型
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        **/
        //获取注解日志内容
        OperateLogAnnotation annotation = method.getAnnotation(OperateLogAnnotation.class);
        //字典,替换成用table查询表注释
        //Class dictClass = annotation.dict();
        //通过表名,动态获取表字段名和注释
       /* Map<String,String> metaMap = new HashMap<>();
        //MetaTableVo metaTableVo = metaTableService.getByTableName(table);
        if(StringUtil.isNotEmpty(table)){
            List<MetaColumVo> metaColumns = metaColumService.queryByTable(table);
            if(metaColumns!=null && metaColumns.size()>0){
                for(MetaColumVo vo:metaColumns){
                    metaMap.put(vo.getColumnName(),vo.getColumnAlias());
                }
            }
        }*/

        StringBuilder sb = new StringBuilder();
        for (Object param : params) {
            sb.append(param);
            sb.append(" & ");
        }

        //如果涉及到修改,比对修改前后值的变化内容
        String logmsg ="";
        /*if (methodName.indexOf("update") != -1 || methodName.indexOf("modify") != -1
                || methodName.indexOf("edit") != -1) {
            Map<String, String> newMap = HttpKit.getRequestParameters(); //对象被修改后的内容
            Object oldObj = redisSupport.getObject(HttpKit.getRequest().getSession().getId());
            //Object oldObj = LogObjectHolder.me().get();  //获取为null
            logmsg = ContrastObjFactory.contrastObj(table,key,metaMap,newMap,oldObj);
        } else {
            logmsg=content;
        }*/
        //记录操作日志
        DupComOperateLogEntity operateLogEntity = getOperaLog( userEntity, target, method, logmsg, params );
        operateLogService.save(operateLogEntity);
    }

    /**
     * 封装操作日志实体对象
     *
     * @Date 2017/3/30 18:45
     */
    public static DupComOperateLogEntity getOperaLog(PrjUserEntity userEntity, Object target, Method method, String msg, Object[] params) {
        String classPath = target.getClass().getName(); //类名称,含路径
        String classMethod = method.getName(); //方法名(英文)
        OperateLogAnnotation annotation = method.getAnnotation( OperateLogAnnotation.class );

        DupComOperateLogEntity operaLog = new DupComOperateLogEntity();
        operaLog.setId( UuidUtil.getUuid() ); //主键
        operaLog.setClassPath( classPath ); //类名称,含路径
        operaLog.setClassMethod( classMethod ); //方法名(英文)
        operaLog.setIpHost( userEntity.getLastLoginIp() ); //IP地址
        operaLog.setOperateTime( new Date() ); //操作时间
        operaLog.setOperateUserId( userEntity.getId() );//用户ID
        operaLog.setOperateUserName( userEntity.getUserName() ); //用户名称
        operaLog.setUserAccount( userEntity.getAccount() ); //用户帐号
        operaLog.setUserType( userEntity.getUserType() ); //帐号类型
        operaLog.setModuleType( annotation.moduleType() );//业务模块类型
        operaLog.setModuleName( annotation.moduleName() );//业务模块名称
        operaLog.setFunctionName( annotation.functionName() );//业务模块名称
        operaLog.setTableName( annotation.tableName() ); //操作表名

        if (params != null && params.length > 0) {
            Map<String, Object> paramMap = object2Map( params[0] );
            operaLog.setTableId( paramMap.get( "id" ).toString() );
        }

        operaLog.setOperateContent( annotation.operateContent() ); //操作内容
        return operaLog;
    }

        /**
     * 实体对象转成Map
     * @param obj 实体对象
     * @return
     */
    public static Map<String, Object> object2Map(Object obj) {
        Map<String, Object> map = new HashMap<>();
        if (obj == null) {
            return map;
        }
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(obj));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

}
  • 2.3 定义applicationContext.xml配置
    Spring AOP 实现业务和异常日志记录实战

  • 2.4 接口添加注解,接口被调用的时候会调用被aop处理
    @OperateLogAnnotation(moduleType = "01")  //这边添加切入点接口的注解
    @RequestMapping(value = "testAop01")
    @ResponseBody
    public JsonResult testAop(BaseEntity baseEntity) {
        JsonResult jr = new JsonResult();
        baseEntity.setCreateTime( new Date() );
        baseEntity.setDbUser( "123456789," );
        baseEntity.setExport( true );
        jr.setData( baseEntity );
        return jr;
    }

3 注意事项:该方式主要是通过注解的方式,个人觉得比较便利,当然也可以通过另外一种方式xml,比如 AOP实现方式3——通过<aop:config>来配置 ,需要注意的是aop的执行顺序,可参考Spring AOP @Before @Around @After 等 advice 的执行顺序