添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
果断的围巾  ·  SQL ...·  7 月前    · 
大力的跑步机  ·  一文学完所有的Hive ...·  1 年前    · 

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天, 点击查看活动详情

在日常开发过程中,我们写的最多的莫过于各式各样的CRUD接口,通常我们需要知道接口的调用状态、时间、结果,以便于我们发现问题的时候去排查问题,但是在接口的实现方法里面手动写sql去完成记录日志,这种做法显然是大可不必的,记录日志这种简单的事情,还是让我们交给AOP来实现吧。

AOP切面

熟悉AOP的小伙伴都知道,AOP是基于jdk的动态代理和cglib代理,其实我们使用最多的本地事务、切面注解的方法,都是基于cglib的,通过定义@Aspect 修饰的切面类,来对前、中、后期、环绕、异常的情况进行切入,这里我只需要定义@AfterReturning,在接口调用后记录操作日志。

定义@OpeLog注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpeLog {
     * 操作模块
    String operationType() default "";
     * 操作项(例:更新)
    String operationItem() default "";
     * 操作内容(知道更新具体内容的 -【未审核】 改为【已审核】)不知道更新内容的 存储请求参数
    String operationContent() default "";
     * 业务单号 - 字段名
     * @return
    String[] businessNoField() default {};

定义切面类

·通过反射机制获取类的方法,并且获取方法的属性,在获取接口请求方式post、get不同的传参方式,拿到请求入参保存到数据库对应的reqParam字段上,最后将字段保存在operationLog对象里,调用insert方法进行保存。

@Aspect
@Slf4j
@Component
public class OperationLogAspect extends AbstractController {
    @Autowired
    private OperationLogMapper operationLogMapper;
    @AfterReturning(pointcut = "@annotation(com.xxx.annotation.OpeLog)", returning = "result")
    public void saveOperationLog(JoinPoint joinPoint, Object result) {
        try {
            //获取签名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取方法
            Method method = signature.getMethod();
            //通过方法获取注解信息
            OpeLog log = method.getAnnotation(OpeLog.class);
            String operationType = log.operationType();
            String operationItem = log.operationItem();
            String operationContent = log.operationContent();
            String[] businessNoField = log.businessNoField();
            OperationLog operationLog = new OperationLog();
            operationLog.setOperationType(operationType);
            operationLog.setOperationItem(operationItem);
            operationLog.setOperationContent(operationContent);
            //获取当前用户信息
            OpenSysUser currentUser = null;
            //获取请求参数
            Object[] args = joinPoint.getArgs();
            for (Object arg : args) {
                if (arg instanceof OpenSysUser) {
                    currentUser = (OpenSysUser) arg;
                    break;
            if (currentUser == null) {
                currentUser = this.getCurrentUser(false);
            operationLog.setOrgNo(currentUser.getOrgNo());
            operationLog.setOrgName(currentUser.getOrgName());
            operationLog.setMainOrgName(currentUser.getMainOrgName());
            operationLog.setMainOrgNo(currentUser.getMainOrgNo());
            operationLog.setCreateUserNo(currentUser.getAccount());
            operationLog.setCreateUserName(currentUser.getName());
            operationLog.setCreateUserPhone(currentUser.getPhone());
             * 保存请求参数
             * 排除httpRequest和httpResponse
            Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
            List<Object> logArgs = stream
                    .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof OpenSysUser)))
                    .collect(Collectors.toList());
            String reqParams = JSON.toJSONString(logArgs);
            operationLog.setReqParams(reqParams);
            List<String> fieldList = new ArrayList<>(Arrays.asList(businessNoField));
            if (!StringUtil.hasText(operationLog.getOperationContent())) {
                fieldList.add("operationContent");
            JSONArray jsonArray = JSONArray.parseArray(reqParams);
            for (int i = 0; i < fieldList.size(); i++) {
                for (int j = 0; j < jsonArray.size(); j++) {
                    String fieldName = fieldList.get(i);
                    Object obj = jsonArray.get(j);
                    if (obj != null) {
                        if (obj instanceof JSON) {
                            String jsonString = JSON.toJSONString(obj);
                            JSONObject jsonObject = JSON.parseObject(jsonString);
                            Object value = jsonObject.get(fieldName);
                            //设置属性值
                            this.setValue(i, operationLog, fieldName, value);
                        } else {
                            //设置属性值
                            this.setValue(i, operationLog, fieldName, obj);
                            // 因为get请求是按照属性的顺序设值的,所以设值完删掉该属性
                            jsonArray.remove(j);
                            break;
            this.operationLogMapper.insert(operationLog);
        } catch (Exception e) {
            log.error("保存日志异常!", e);
     * 设置属性值
     * @param i
     * @param operationLog
     * @param fieldName
     * @param value
    private void setValue(int i, OperationLog operationLog, String fieldName, Object value) {
        if (value != null && !"".equals(value)) {
            if (i == 0) {
                operationLog.setBusinessNo(String.valueOf(value));
            } else {
                ReflectUtil.setFieldValue(operationLog, fieldName, value);

日志表结构

Controller方法定义

在方法头加入@OpeLog注解,依次定义注解里面的属性,主要是对该方法的描述。

* 收款单导出 * @param @PostMapping("/export") @ApiOperationSupport(order = 2) @ApiOperation(value = "收款单-导出", notes = "传入receiptVoucherExportDTO对象") @OpeLog(operationType = "收款单", operationItem = "导出", operationContent = "导出收款单", businessNoField = {"receiptVoucherNo"}) public void export(@Valid @RequestBody ReceiptVoucherExportDTO receiptVoucherExportDTO, HttpServletResponse response) { log.info("导出收款单 【入参】 export receiptVoucherExportDTO={}", receiptVoucherExportDTO); receiptVoucherService.export(receiptVoucherExportDTO, this.getCurrentUser(true), response);

调用这个导出方法后,看控制台输出,执行完方法的时候记录操作日志

目前用这个切面记录操作日志的方式,还是比较常见的,通过mysql数据库进行操作记录的持久化,如果请求量比较大的情况下就不太支持这种方式了,还是建议使用ELK这样的日志搜索引擎,但是对于并发量不是很大的B端项目来说,这种记录操作日志的方式还是比较便捷的。

  • 私信