Spring boot MongoDB 实现自定义审计字段

这篇具有很好参考价值的文章主要介绍了Spring boot MongoDB 实现自定义审计字段。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

	之前的文章实现了自定义Repository基类和业务基类,现在有了新的需求,就是一些公共字段的填充,例如:创建时间、更新时间、创建人、更新人等字段,`spring-boot-starter-data-mongodb`中已经提供类似的审计注解,例如:`@CreatedDate`、`@CreatedBy`、`@LastModifiedDate`、`@LastModifiedBy`,但是这些注解只能在Repository的接口中使用,也就是说只能在JPA场景下使用,例如使用了`MongoTemplate`就无法使用这些注解,而且这些注解并不能满足我们实际的业务场景,有时需要自定义审计字段,例如多租户下的租户ID。

AuditorAware是什么?

AuditorAware是Spring Data提供的一个接口,用于提供当前执行数据库操作的"审计员"的信息。"审计员"可以是当前操作的用户、系统的默认用户或其他相关信息,用于记录和跟踪数据的变更历史。

具体来说,AuditorAware的作用是为实体类中标记了@CreatedBy@LastModifiedBy注解的属性提供值。

AuditorAware接口有一个方法:

Optional<T> getCurrentAuditor();

我们只需要重写此方法即可,假设我们的用户ID为String类型,具体操作如下:

public class MongoAuditorAware implements AuditorAware<String> {
    @NotNull
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of(UserContext.getUserId());//此处设置系统的用户唯一标志或其他标志字段
    }
}

使MongoTemplate也支持@CreatedDate@CreatedBy@LastModifiedDate@LastModifiedBy

自定义AuditingMongoEventListener.java继承AbstractMongoEventListener并重写onBeforeConvert方法。

import com.learning.mongodb.crud.annotations.TenantId;
import com.learning.mongodb.crud.constant.MongodbConstant;
import com.learning.mongodb.crud.helper.UserContext;
import lombok.SneakyThrows;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.Objects;

/**
 * 自定义审计字段方式一:mongoDB审计字段监听
 */
@Component
public class AuditingMongoEventListener extends AbstractMongoEventListener {


    @SneakyThrows
    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        Object source = event.getSource();
        Field id = ReflectionUtils.findField(source.getClass(), MongodbConstant.ID);
        Date date = new Date();
        if (Objects.nonNull(id) && valueIsNotEmpty(source, id)) {
            //修改
            ReflectionUtils.doWithFields(source.getClass(), field -> {
                handleLastModifiedDate(source, field, date);
                handleLastModifiedBy(source, field);
            });
        } else {
            //新增
            ReflectionUtils.doWithFields(source.getClass(), field -> {
                handleCreatedBy(source, field);
                handleCreatedDate(source, field, date);
                handleLastModifiedDate(source, field, date);
                handleLastModifiedBy(source, field);
                handleTenantId(source, field);
            });
        }
    }

    private void handleCreatedDate(Object source, Field field, Date time) throws IllegalAccessException {
        if (canBeFilled(field,CreatedDate.class)) {
            field.setAccessible(true);
            field.set(source, time);
        }
    }

    private void handleCreatedBy(Object source, Field field) throws IllegalAccessException {
        if (canBeFilled(field,CreatedBy.class)) {
            field.setAccessible(true);
            field.set(source, UserContext.getUserId());
        }
    }

    private void handleTenantId(Object source, Field field) throws IllegalAccessException {
        if (canBeFilled(field,TenantId.class)) {
            field.setAccessible(true);
            field.set(source, UserContext.getTenantId());
        }
    }

    private void handleLastModifiedBy(Object source, Field field) throws IllegalAccessException {
        if (canBeFilled(field,LastModifiedBy.class)) {
            field.setAccessible(true);
            field.set(source, UserContext.getUserId());
        }
    }

    private void handleLastModifiedDate(Object source, Field field, Date time) throws IllegalAccessException {
        if (canBeFilled(field,LastModifiedDate.class)) {
            field.setAccessible(true);
            field.set(source, time);
        }
    }


    /**
     * 判断属性是否为空
     *
     * @param source 对象
     * @param field  对象属性
     * @return 不为空
     * @throws IllegalAccessException 异常
     */
    private boolean valueIsNotEmpty(Object source, Field field) throws IllegalAccessException {
        ReflectionUtils.makeAccessible(field);
        return Objects.nonNull(field.get(source));
    }

    /**
     * 是否可以填充值
     * @param field 属性
     * @param annotationType 注解类型
     * @return 是否可以填充
     */
    private boolean canBeFilled(Field field, Class<? extends Annotation> annotationType) {
        return Objects.nonNull(AnnotationUtils.getAnnotation(field, annotationType));
    }
}

除了上面这种方式,还可以通过实现BeforeConvertCallback类并重写onBeforeConvert方法,用法和继承AbstractMongoEventListener是一样的,示例代码如下:

import com.learning.mongodb.crud.annotations.TenantId;
import com.learning.mongodb.crud.constant.MongodbConstant;
import com.learning.mongodb.crud.helper.UserContext;
import com.sun.istack.internal.NotNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.Objects;

/**
 * 保存修改之前数据处理
 */
@Slf4j
@Component
public class BeforeConvert implements BeforeConvertCallback<Object> {

   @SneakyThrows
    @NotNull
    @Override
    public Object onBeforeConvert(@NotNull Object source, @NotNull String s) {
        Field id = ReflectionUtils.findField(source.getClass(), MongodbConstant.ID);
        Date date = new Date();
        if (Objects.nonNull(id) && valueIsNotEmpty(source, id)) {
            //修改
            ReflectionUtils.doWithFields(source.getClass(), field -> {
                handleLastModifiedDate(source, field, date);
                handleLastModifiedBy(source, field);
            });
        } else {
            //新增
            ReflectionUtils.doWithFields(source.getClass(), field -> {
                handleCreatedBy(source, field);
                handleCreatedDate(source, field, date);
                handleLastModifiedDate(source, field, date);
                handleLastModifiedBy(source, field);
                handleTenantId(source, field);
            });
        }
        return source;
    }
	//...
}

使用

自定义租户注解TenantId.java

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE })
public @interface TenantId {
}

UserContext.java

public class UserContext {
    /**
     * 获取系统唯一标志
     * @return 用户ID
     */
    public static String getUserId() {
        return "admin";
    }

    /**
     * 获取租户ID
     * @return 租户ID
     */
    public static String getTenantId() {
        return "tenant1";
    }
}

Book.java

@Document(collection = "Books")
@Data
public class Book {
    @Id
    private ObjectId id;
    @CreatedDate
    private Date createDate;
    @CreatedBy
    private String createBy;
    @LastModifiedDate
    private Date modifiedDate;
    @LastModifiedBy
    private String modifiedBy;
    @TenantId
    private String tenantId;
}

验证

curl --location 'http://localhost:8080/book' \
--header 'Content-Type: application/json' \
--data '{
    "name":"C Primer Plus 第9版",
    "price":53.9
}'

结果

{
    "id": "64a29a683e4b3f0f3a6b491f",
    "name": "C Primer Plus 第9版",
    "price": 53.9,
    "createDate": "2023-07-03T09:52:40.604+00:00",
    "createBy": "admin",
    "modifiedDate": "2023-07-03T09:52:40.604+00:00",
    "modifiedBy": "admin",
    "tenantId": "tenant1"
}

总结

无论使用MongoRepository还是MongoTemplate,只要在保存文档之前将数据拦截处理就可以实现字段填充。文章来源地址https://www.toymoban.com/news/detail-533211.html

到了这里,关于Spring boot MongoDB 实现自定义审计字段的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • “深入探究Spring Boot:实现微服务架构的利器“

    标题:深入探究Spring Boot:实现微服务架构的利器 摘要:本文将深入探讨Spring Boot作为实现微服务架构的利器。我们将介绍Spring Boot的特点、优势以及如何使用它来构建高效、可扩展的微服务应用。同时,我们还将通过示例代码演示如何使用Spring Boot创建一个简单的微服务应用

    2024年02月16日
    浏览(32)
  • 企业级信息系统开发——Spring Boot加载自定义配置文件

    设置项目元数据 添加项目依赖 设置项目编码为utf8(尤其注意复选框) 在 resources 下创建 myconfig.properties 文件 在 net.shuai.boot 包里创建配置类 config.StudentConfig 点开测试类 ConfigDemo01ApplicationTests 编写测试方法,注入学生配置实体,创建 testStudentConfig() 测试方法,在里面输出学生

    2024年02月07日
    浏览(34)
  • 自定义错误页面在Spring Boot中的实现

    引言 在SpringBoot中,常用的异常处理有两种:一种是 BasicErrorController ,另一种是 @ControllerAdvice 。 BasicErrorController 用于处理 非Controller 抛出的异常,而 @ControllerAdvice 用于处理 Controller 抛出的异常,对于非Controller抛出的异常它是不会管的。但是,如果是 Controller层 调用Service层

    2024年01月25日
    浏览(31)
  • Spring Boot 优雅实现多租户架构,so easy~!

    多租户架构是指在一个应用中支持多个租户(Tenant)同时访问,每个租户拥有独立的资源和数据,并且彼此之间完全隔离。通俗来说,多租户就是把一个应用按照客户的需求“分割”成多个独立的实例,每个实例互不干扰。 更好地满足不同租户的个性化需求。 可以降低运维

    2024年02月09日
    浏览(30)
  • spring boot集成jasypt 并 实现自定义加解密

    由于项目中的配置文件 配置的地方过多,现将配置文件统一放到nacos上集中管理 且密码使用加密的方式放在配置文件中 项目中组件使用的版本环境如下 spring cloud 2021.0.5 spring cloud alibaba 2021.0.5.0 spring boot 2.6.13 配置文件的加密使用 加密库 jasypt 引入maven依赖 添加配置 使用jasy

    2024年02月11日
    浏览(32)
  • Spring Boot如何实现微服务架构中的API网关?

    随着微服务架构的流行,越来越多的企业开始构建自己的微服务系统。在这种情况下,API网关变得尤为重要。API网关是微服务架构中的一个组件,它可以帮助我们管理和路由所有的API请求。Spring Boot提供了一些工具和框架,可以帮助我们轻松地实现API网关。在本文中,我们将

    2024年02月07日
    浏览(45)
  • Spring Boot使用@Async实现异步调用:自定义线程池

    第一步,先在Spring Boot主类中定义一个线程池,比如: 上面我们通过使用​​ ThreadPoolTaskExecutor ​​创建了一个线程池,同时设置了以下这些参数: 核心线程数10:线程池创建时候初始化的线程数 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核

    2024年02月14日
    浏览(33)
  • Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单

       一、立项管理 1、招标立项申请 功能点:招标类项目立项申请入口,用户可以保存为草稿,提交。 2、非招标立项申请 功能点:非招标立项申请入口、用户可以保存为草稿、提交。 3、采购立项列表 功能点:对草稿进行编辑,驳回的立项编辑,在途流程查看。 二、项目管

    2024年02月17日
    浏览(36)
  • Spring Boot学习随笔- 实现AOP(JoinPoint、ProceedingJoinPoint、自定义注解类实现切面)

    学习视频:【编程不良人】2021年SpringBoot最新最全教程 问题 现有业务层开发存在问题 额外功能代码存在大量冗余 每个方法都需要书写一遍额外功能代码不利于项目维护 Spring中的AOP AOP:Aspect 切面 + Oriented 面向 Programmaing 面向切面编程 Aspect(切面) = Advice(通知) + Pointcut(

    2024年02月04日
    浏览(33)
  • Spring Boot 整合 MongoDB 实战

    MongoDB是一种NoSQL数据库,而Spring Boot是一个用于快速构建Java应用程序的开发框架。本文将介绍如何使用Spring Boot整合MongoDB,实现数据的持久化和操作。通过本文的学习,读者将了解到Spring Boot和MongoDB的基本概念和用途,并理解为什么选择使用它们进行整合。 在开始整合之前,

    2024年02月14日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包