spring boot自定义mongo审计

x33g5p2x  于2022-02-12 转载在 Go  
字(5.1k)|赞(0)|评价(0)|浏览(319)

问题出现

以前通过@EnableMongoAuditing、@CreateDate、@LastModifiedDate进行实体类创建时间、修改时间的自动管理。
但为了实现多数据源的管理以及切换,自己覆盖了mongoTemplate的bean,发现应用所有数据库操作都出现"Couldn't find PersistentEntity"错误,去掉@EnableMongoAuditing注解后才能正常使用,但@CreateDate、@LastModifiedDate失效,创建时间、修改时间这些都要自己去设置,太麻烦了也容易漏,为了方便使用以及偷懒,需要实现所有数据库存储更新操作能自动插入、更新公用字段,如创建时间、修改时间、创建者、修改者信息、对所有数据库数据逻辑删除

解决的思路

为了实现这个功能,翻了下MongoTemplate的源码,下面是关键的几个方法

MongoTemplate.class

    public <T> T save(T objectToSave, String collectionName) {
        Assert.notNull(objectToSave, "Object to save must not be null!");
        Assert.hasText(collectionName, "Collection name must not be null or empty!");
        AdaptibleEntity<T> source = this.operations.forEntity(objectToSave, this.mongoConverter.getConversionService());
        return source.isVersionedEntity() ? this.doSaveVersioned(source, collectionName) : this.doSave(collectionName, objectToSave, this.mongoConverter);
    }

    protected <T> T doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
        objectToSave = ((BeforeConvertEvent)this.maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName))).getSource();
        objectToSave = this.maybeCallBeforeConvert(objectToSave, collectionName);
        AdaptibleEntity<T> entity = this.operations.forEntity(objectToSave, this.mongoConverter.getConversionService());
        entity.assertUpdateableIdIfNotSet();
        MappedDocument mapped = entity.toMappedDocument(writer);
        Document dbDoc = mapped.getDocument();
        this.maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName));
        objectToSave = this.maybeCallBeforeSave(objectToSave, dbDoc, collectionName);
        Object id = this.saveDocument(collectionName, dbDoc, objectToSave.getClass());
        T saved = this.populateIdIfNecessary(objectToSave, id);
        this.maybeEmitEvent(new AfterSaveEvent(saved, dbDoc, collectionName));
        return this.maybeCallAfterSave(saved, dbDoc, collectionName);
    }

   protected <T> T maybeCallBeforeConvert(T object, String collection) {
        return this.entityCallbacks != null ? this.entityCallbacks.callback(BeforeConvertCallback.class, object, new Object[]{collection}) : object;
    }

   protected <T> T maybeCallBeforeSave(T object, Document document, String collection) {
        return this.entityCallbacks != null ? this.entityCallbacks.callback(BeforeSaveCallback.class, object, new Object[]{document, collection}) : object;
    }

查看MongoTemplate的源码会发现,新增以及更新操作调用的save方法,在执行前会有BeforeConvertEvent、BeforeSaveEvent两个事件,我们只需要在这两个事件里面将我们需要保存的内容做下处理,就可以实现所有数据库新增、更新操作都自动加上创建时间这些信息,而且原来MongoAudit只有几个固定的字段能用,自定义BeforeConvertCallback事件后,完全可以按自己的需要多管理几个字段。
具体要实现一个接口,并且将自定义的callback处理set到要用的mongTemplate上面

@FunctionalInterface
public interface BeforeConvertCallback<T> extends EntityCallback<T> {
    T onBeforeConvert(T var1, String var2);
}

具体实现

BeforeConvertEvent事件自定义处理

class BeforeConvert implements BeforeConvertCallback<Object> {

        @NotNull
        @Override
        public Object onBeforeConvert(Object o, String s) {
            log.info("before convert callback");

            Map<String, Field> fieldMap = ReflectUtil.getFieldMap(o.getClass());
            String userName = SecurityUtils.getUsername();
            Date now = new Date();

            if (fieldMap.containsKey("id")) {
                Field id = fieldMap.get("id");

                if (id == null || StringUtils.isBlank((String) ReflectUtil.getFieldValue(o, id))) {
                    //没有id时为新增
                    //创建时间
                    if (fieldMap.containsKey("createTime")) {
                        ReflectUtil.setFieldValue(o, "createTime", now);
                    }
                    //创建者
                    if (fieldMap.containsKey("createUser") && StringUtils.isNotBlank(userName)) {
                        ReflectUtil.setFieldValue(o, "createUser", userName);
                    }
                }
            }
            //更新日期
            if (fieldMap.containsKey("updateTime")) {
                ReflectUtil.setFieldValue(o, "updateTime", now);
            }
            //更新者
            if (fieldMap.containsKey("updateUser") && StringUtils.isNotBlank(userName)) {
                ReflectUtil.setFieldValue(o, "updateUser", userName);
            }
            //这里还可以加上其他各种需要设置的值

            return o;
        }
    }
@Bean(name = "mongoTemplate")
    public DynamicMongoTemplate dynamicMongoTemplate() {
        Iterator<SimpleMongoClientDatabaseFactory> iterator = MONGO_CLIENT_DB_FACTORY_MAP.values().iterator();
        DynamicMongoTemplate mongoTemplate = new DynamicMongoTemplate(iterator.next());
	//将自定义事件处理放进mongoTemplate中
        mongoTemplate.setEntityCallbacks(EntityCallbacks.create(new BeforeConvert()));
        return mongoTemplate;
    }
   @Bean(name = "mongoDbFactory")
    public MongoDatabaseFactory mongoDbFactory() {
        Iterator<SimpleMongoClientDatabaseFactory> iterator = MONGO_CLIENT_DB_FACTORY_MAP.values().iterator();
        return iterator.next();
    }

最后的一些话

一开始我自定义BeforeSaveCallback,在里面修改了object的内容,但发现并没有保存在数据库中。在官方的文档中说,在这事件里面修改object内容只是临时的不会做持久化,需要修改转换后document的内容才会保存进数据库,官方原话:Entity callback method invoked before a domain object is saved. Can return either the same or a modified instance of the domain object and can modify Document contents. This method is called after converting the entity to a Document so effectively the document is used as outcome of invoking this callback. Changes to the domain object are not taken into account for saving, only changes to the document. Only transient fields of the entity should be changed in this callback. To change persistent the entity before being converted, use the BeforeConvertCallback.
官方文档
为了避免实体转document后字段名发生改变不好找,就选用了BeforeConvertEvent,在转换前进行了处理,但其实在BeforeSaveEvent处理也是可以的

事件的发生顺序

Event:
BeforeDeleteEvent

AfterDeleteEvent

BeforeConvertEvent

BeforeSaveEvent

AfterSaveEvent

AfterLoadEvent

相关文章

微信公众号

最新文章

更多