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,只要在保存文档之前将数据拦截处理就可以实现字段填充。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Spring Boot中使用MongoDB,需要执行以下步骤: 1. 添加MongoDB依赖项 在Maven项目中,可以在pom.xml文件中添加以下依赖项: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> ``` 2. 配置MongoDB连接信息 在application.properties文件中添加MongoDB连接信息,例如: ``` spring.data.mongodb.uri=mongodb://localhost:27017/mydatabase ``` 其中,`mydatabase`是数据库的名称。 3. 创建MongoDB实体类 创建一个Java类,用于表示MongoDB中的一个文档。可以使用`@Document`注解来指定MongoDB中的集合名称,例如: ``` @Document(collection = "users") public class User { @Id private String id; private String name; private int age; // getter和setter方法 } ``` 在这个例子中,`User`类表示MongoDB中的`users`集合,包含`id`、`name`和`age`三个字段。 4. 创建MongoDB数据访问对象 创建一个接口,用于定义MongoDB的数据访问操作。可以使用Spring Data MongoDB提供的一些方法来实现常见的CRUD操作。例如: ``` @Repository public interface UserRepository extends MongoRepository<User, String> { List<User> findByName(String name); } ``` 在这个例子中,`UserRepository`接口继承`MongoRepository`,并且定义了一个`findByName`方法,用于按照`name`字段查询`User`对象。 5. 使用MongoDB数据访问对象 在需要使用MongoDB的地方,可以注入`UserRepository`对象,并且使用其提供的方法来进行数据访问。例如: ``` @Service public class UserService { @Autowired private UserRepository userRepository; public void saveUser(User user) { userRepository.save(user); } public List<User> findUsersByName(String name) { return userRepository.findByName(name); } } ``` 在这个例子中,`UserService`类注入了`UserRepository`对象,并且使用其提供的`save`和`findByName`方法来进行数据访问。 以上就是在Spring Boot中配置MongoDB的步骤。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值