feat(audit): полноценный writer в audit_log + admin UI (audit + outbox DLQ)
До этого audit_log entity была определена, но никто туда не писал — реальный аудит шёл только через Hibernate Envers (без user/IP/trace). Теперь: - AuditLogger service пишет в той же транзакции что и CRUD (DictionaryRecordService). Захватывает: user (JWT sub либо preferred_username), scope, IP (X-Forwarded-For), UA, trace_id (MDC), request_id. Не ломает основной flow при сбое — только громко логирует. - AuditLogRepository: гибкий search() с nullable фильтрами (dictionary, user, action, businessKey, from, to) + Pageable. - AuditAdminController: GET /admin/audit (paginated) + /admin/audit/export.csv (cap 10k строк против OOM). Frontend: - /audit route: фильтр-панель, таблица с цветными бейджами (CREATE/UPDATE/CLOSE), expand-row с JSON before/after diff, footer IP/UA/req_id, кнопка экспорта CSV. Pagination 50/100/200. - /outbox route: stat-cards pending/DLQ + DLQ-таблица с last_error. - Nav links + i18n (RU + EN).
This commit is contained in:
+1
-1
@@ -77,7 +77,7 @@ public class AuditLog {
|
||||
@Column(name = "user_agent", columnDefinition = "TEXT")
|
||||
private String userAgent;
|
||||
|
||||
protected AuditLog() {}
|
||||
public AuditLog() {}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public OffsetDateTime getEventTime() { return eventTime; }
|
||||
|
||||
+26
@@ -1,6 +1,10 @@
|
||||
package cloud.nstart.terravault.ordinis.domain.audit;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
@@ -15,4 +19,26 @@ public interface AuditLogRepository extends JpaRepository<AuditLog, Long> {
|
||||
List<AuditLog> findByUserIdOrderByEventTimeDesc(String userId);
|
||||
|
||||
List<AuditLog> findByEventTimeBetweenOrderByEventTimeDesc(OffsetDateTime from, OffsetDateTime to);
|
||||
|
||||
/**
|
||||
* Гибкий запрос для admin UI: фильтры nullable, NULL = «не фильтруем».
|
||||
* Сортировка задаётся {@link Pageable}.
|
||||
*/
|
||||
@Query("""
|
||||
SELECT a FROM AuditLog a
|
||||
WHERE (:dictionaryName IS NULL OR a.dictionaryName = :dictionaryName)
|
||||
AND (:userId IS NULL OR a.userId = :userId)
|
||||
AND (:action IS NULL OR a.action = :action)
|
||||
AND (:businessKey IS NULL OR a.businessKey = :businessKey)
|
||||
AND (:from IS NULL OR a.eventTime >= :from)
|
||||
AND (:to IS NULL OR a.eventTime <= :to)
|
||||
""")
|
||||
Page<AuditLog> search(
|
||||
@Param("dictionaryName") String dictionaryName,
|
||||
@Param("userId") String userId,
|
||||
@Param("action") String action,
|
||||
@Param("businessKey") String businessKey,
|
||||
@Param("from") OffsetDateTime from,
|
||||
@Param("to") OffsetDateTime to,
|
||||
Pageable pageable);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user