最近刚换了份工作,这家公司主要做的是b端的产品,b端产品一般审计都比较严,所以免不了会有操作日志这个东西,但是我发现现在这家公司的操作日志都是这样收集的,看以下代码:

//记录操作日志 主子表分别添加
List<ContainerLogVO> containerLogVOS = BeanUtil.copyList(ContainerLogVO.class, listExecuteResult.getResult());
                            containerLogVOS.forEach(e -> {
                                e.setId(null);
                            });
                            ExecuteResult<List<ContainerLogVO>> listExecuteResult1 = containerLogService.addBatch(containerLogVOS);
                            if (!Constants.SUCCESS_CODE.equals(listExecuteResult1.getCode())) {
                                throw new Exception("容器日志保存异常");
                            }



//记录更新日志
GoodsBaseLogVO logVO = BeanUtil.copyPropertes(GoodsBaseLogVO.class, goodsBaseResult.getResult());
						logVO.setId(null);
						ExecuteResult<GoodsBaseLogVO> logVOExecuteResult = goodsBaseLogService.add(logVO);
						if(!Constants.SUCCESS_CODE.equals(logVOExecuteResult.getCode())){
							throw new Exception(logVOExecuteResult.getResultMessage());
						}

 随手粘了两处代码,这么写可不可以? 可以,但是太不面向对象了,O(∩_∩)O哈哈~

于是我就想着给他改造改造。

AOP的五大通知

先说aop的五个通知类型:

  • 前置通知 在目标方法执行前执行
  • 环绕通知 在目标方法执行之前和之后各执行一遍
  • 后置通知 在目标方法执行之后执行
  • 异常通知 在目标方法抛出异常后执行
  • 最终通知 在目标方法执行之后执行 目标方法无论是异常还是正常都会执行

如果方法出现异常,我这边是不需要记录操作日志的,所以我这边只选择后置通知就可以了(如果有特殊需求可以根据需要进行选择,也可以选择多个)

本次分享 只是给大家分享一个思路。

数据建模

android studio 日志打印_JSON

 这个表属于方法与日志生成的映射关系表 在给大家看看对应的配置

android studio 日志打印_android studio 日志打印_02

 首先 methodName 我们根据aop是完全可以取到对应的方法名称和映射的mapping,methodInfo这个字段就是配置对应的日志 每个?就相当于一个占位符与后面的paramArr对应,channel和paramName一起说 拿id为1的举例 就相当于是取入参中的vo。大概配置表就是这么一个概念。

在看日志表:

android studio 日志打印_java_03

 在看看日志表里面存储的信息

android studio 日志打印_mybatis_04

 具体实现

直接看代码

/**
 * @author Jacky
 * @create 2022-07-20-14:31
 */
@Component
@Aspect
public class UserLogAspect {

    private static final Logger log = LoggerFactory.getLogger(UserLogAspect.class);

    @Autowired
    private UserOperationLogExportService userOperationLogExportService;

    @Resource
    private UserColumnRelationDAO userColumnRelationDAO;

    @Autowired
    private MethodToLogExportService methodToLogExportService;

    @Pointcut("execution(* com.niu.wms.api..*.*(..))")
    public void allLog(){
    }

    @AfterReturning(value = "allLog()",returning = "ajaxInfo")
    public void saveLog(JoinPoint joinPoint, AjaxInfo ajaxInfo){
        // 拼接出对应的方法名以及映射的mapping
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        String methodName = method.getName();
        String allMapping = joinPoint.getTarget().getClass().getName().replace("Api", "").replace("Controller", "");
        String mapping = allMapping.substring(allMapping.lastIndexOf(".") + 1);
        // 根据对应的映射查询配置表 如配置表中无相关的配置则不记录
        MethodToLogVO methodToLogVO = methodToLogExportService.queryOneData(MethodToLogVO.builder().methodName(mapping + "/" + methodName).build()).getResult();
        if (Utility.isEmpty(methodToLogVO)){
            return;
        }
        String paramName = methodToLogVO.getParamName();
        JSONObject json = null;
        // 获取对应的参数 把对应的参数转成json格式
        if (methodToLogVO.getChannel().equals(0)){
            //参数名数组
            String[] parameters = signature.getParameterNames();
            //参数值
            Object[] args = joinPoint.getArgs();
            int voIndex = ArrayUtils.indexOf(parameters, paramName);
            if (voIndex==-1){
                return;
            }
            Object arg = args[voIndex];
            json = JSON.parseObject(JSON.toJSONString(arg));

        }else if (methodToLogVO.getChannel().equals(1)){
            Object data = ajaxInfo.getData();
            json = JSON.parseObject(JSON.toJSONString(data));
        }
        if (Utility.isEmpty(json)){
            return;
        }
        // 获取到参数之后进行占位符拼接赋值
        String paramArr = methodToLogVO.getParamArr();
        String[] split = paramArr.split(",");
        String methodInfo = methodToLogVO.getMethodInfo();
        String context = methodInfo;
        for (String s : split) {
             context = context.replaceFirst("\\?", json.getString(s));
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String userId = request.getHeader("userId");
        Map map = userColumnRelationDAO.queryUserInfo(Long.valueOf(userId));
        String realIP = IpUtil.getRealIP(request);
        String userType1 = request.getHeader("userType");
        userType1 = userType1 == null ? "1": userType1;
        Integer userType = Integer.parseInt(userType1);
        String requestParam = JSON.toJSONString(joinPoint.getArgs());
        String response = JSON.toJSONString(ajaxInfo);
        StringBuilder result = new StringBuilder();
        
        if (ajaxInfo.getCode()!=0){
            result.append("失败:");
            result.append(ajaxInfo.getMsg());
        }else{
            result.append("成功");
        }
        ExecuteResult<UserOperationLogVO> save = userOperationLogExportService.add(UserOperationLogVO.builder()
                .userNumber(map.getOrDefault("userNumber",userId).toString())
                .userName(map.getOrDefault("userName",userId).toString())
                .logType(userType)
                .hostAddress(realIP)
                .operationContent(context)
                .request(requestParam)
                .response(response)
                .operationTime(new Date())
                .result(result.toString())
                .build());
        if (!save.getCode().equals(Constants.SUCCESS_CODE)){
            log.error("日志保存失败");
            log.error("错误信息:"+save.getResultMessage());
        }


    }
}

大概就是这么个东西,主要是分享一个思路,也有很多可以优化的地方,大家可以根据系统情况自行优化。