Как заставить AuditorAware работать с Spring Data Mongo Reactive

Spring Security 5 предоставляет ReactiveSecurityContextHolder для извлечения SecurityContext из контекста Reactive, но когда я хочу реализовать AuditorAware и получить работу для прослушивания автоматически, но не работает. В настоящее время я не могу найти вариант Reactive для AuditorAware.

@Bean
public AuditorAware<Username> auditor() {
    return () -> ReactiveSecurityContextHolder.getContext()
        .map(SecurityContext::getAuthentication)
        .log()
        .filter(a -> a != null && a.isAuthenticated())
        .map(Authentication::getPrincipal)
        .cast(UserDetails.class)
        .map(auth -> new Username(auth.getName()))
        .switchIfEmpty(Mono.empty())
        .blockOptional();
}

Я добавил @EnableMongoAuduting в класс загрузки Application.

В классе документов Mongo. Я добавил аннотации, связанные с прослушиванием.

@CreatedDate
private LocalDateTime createdDate;

@CreatedBy
private Username author;

Когда я добавляю сообщение, createdDate заполняется, но автор не указан.

{"id":"5a49ccdb9222971f40a4ada1","title":"my first post","content":"content of my first post","createdDate":"2018-01-01T13:53:31.234","author":null}

Полные коды находятся здесь на основе Spring Boot 2.0. 0.М7.

Обновление: Spring Boot 2.4.0-M2/Spring Data Common 2.4.0-M2/Spring Data Mongo 3.1.0-M2 включает в себя ReactiveAuditorAware, проверьте этот новый пример, Примечание: используйте @EnableReactiveMongoAuditing для активации Это.


person Hantsy    schedule 01.01.2018    source источник
comment
Пока нет поддержки аудита для реактивного использования.   -  person mp911de    schedule 03.01.2018


Ответы (3)


Я публикую другое решение, которое учитывает входной идентификатор для поддержки операций обновления:

@Component
@RequiredArgsConstructor
public class AuditCallback implements ReactiveBeforeConvertCallback<AuditableEntity> {

    private final ReactiveMongoTemplate mongoTemplate;

    private Mono<?> exists(Object id, Class<?> entityClass) {
        if (id == null) {
            return Mono.empty();
        }
        return mongoTemplate.findById(id, entityClass);
    }

    @Override
    public Publisher<AuditableEntity> onBeforeConvert(AuditableEntity entity, String collection) {
        var securityContext = ReactiveSecurityContextHolder.getContext();
        return securityContext
                .zipWith(exists(entity.getId(), entity.getClass()))
                .map(tuple2 -> {
                    var auditableEntity = (AuditableEntity) tuple2.getT2();
                    auditableEntity.setLastModifiedBy(tuple2.getT1().getAuthentication().getName());
                    auditableEntity.setLastModifiedDate(Instant.now());
                    return auditableEntity;
                })
                .switchIfEmpty(Mono.zip(securityContext, Mono.just(entity))
                        .map(tuple2 -> {
                            var auditableEntity = (AuditableEntity) tuple2.getT2();
                            String principal = tuple2.getT1().getAuthentication().getName();
                            Instant now = Instant.now();
                            auditableEntity.setLastModifiedBy(principal);
                            auditableEntity.setCreatedBy(principal);
                            auditableEntity.setLastModifiedDate(now);
                            auditableEntity.setCreatedDate(now);
                            return auditableEntity;
                        }));
    }
}
person Adam Ostrožlík    schedule 03.07.2020

Устарело: см. решение для обновления в исходном сообщении.

Прежде чем будет предоставлен официальный реактивный AuditAware, существует альтернатива для их реализации с помощью специфичного для Spring Data Mongo ReactiveBeforeConvertCallback.

  1. Не используйте @EnableMongoAuditing
  2. Реализуйте свой собственный ReactiveBeforeConvertCallback, здесь я использую PersistentEntity интерфейс для тех сущностей, которые необходимо проверить.
public class PersistentEntityCallback implements ReactiveBeforeConvertCallback<PersistentEntity> {

    @Override
    public Publisher<PersistentEntity> onBeforeConvert(PersistentEntity entity, String collection) {
        var user = ReactiveSecurityContextHolder.getContext()
                .map(SecurityContext::getAuthentication)
                .filter(it -> it != null && it.isAuthenticated())
                .map(Authentication::getPrincipal)
                .cast(UserDetails.class)
                .map(userDetails -> new Username(userDetails.getUsername()))
                .switchIfEmpty(Mono.empty());

        var currentTime = LocalDateTime.now();

        if (entity.getId() == null) {
            entity.setCreatedDate(currentTime);
        }
        entity.setLastModifiedDate(currentTime);

        return user
                .map(u -> {
                            if (entity.getId() == null) {
                                entity.setCreatedBy(u);
                            }
                            entity.setLastModifiedBy(u);

                            return entity;
                        }
                )
                .defaultIfEmpty(entity);
    }
}

Проверьте полные коды здесь.

person Hantsy    schedule 02.06.2020
comment
Это может не сработать, если код поддерживает вставку/обновление через ту же конечную точку и метод сохранения базы данных. Если кто-то отправит DTO с идентификатором, который будет сопоставлен с созданным ДОКУМЕНТОМ и не существует в базе данных, все поля, зависящие от идентификатора, будут пустыми. - person Adam Ostrožlík; 03.07.2020
comment
Пожалуйста, проверьте полные коды, решение, которое я опубликовал, не заботится о вашем DTO, просто отслеживайте документ, над которым вы работаете. - person Hantsy; 03.07.2020
comment
Проблема в том, что если построенному объекту уже назначен идентификатор, но он не существует в базе данных, ваше решение, я думаю, не будет работать. Мне не хватает здесь какой-то процедуры для проверки репозитория, существует ли идентификатор. - person Adam Ostrožlík; 03.07.2020
comment
Вы можете добавить логику, чтобы удовлетворить свой бизнес, верно? Лично я всегда использовал Spring Data для генерации идентификатора. Или поделитесь своим рабочим решением в качестве дополнительного ответа, это поможет другим. - person Hantsy; 03.07.2020

Чтобы атрибут createdBy был заполнен, вам нужно связать свой bean-компонент auditAware с аннотацией @EnableMongoAuditing.

В своем классе MongoConfig определите свой компонент:

@Bean(name = "auditorAware")
public AuditorAware<String> auditor() {
    ....
}

и используйте его в аннотации:

@Configuration
@EnableMongoAuditing(auditorAwareRef="auditorAware")
class MongoConfig {
    ....
}
person Richard Sinelle    schedule 05.01.2018
comment
Пока нет поддержки аудита для реактивного использования. В настоящее время аудиторы извлекаются из ThreadLocal, что неприменимо для реактивного использования из-за постоянного переключения потоков и отложенного выполнения. - person mp911de; 05.01.2018
comment
@RichardSinelle Вы пробовали эту функцию аудита в реактивном приложении? - person Hantsy; 06.01.2018
comment
Я начинаю реализовывать прототип, вдохновленный кодом, который вы предоставили, спасибо. Компонент представляет собой SimpleReactiveMongoRepository. я держу тебя в курсе - person Richard Sinelle; 09.01.2018