Files
ai-security-xdr/haobang-security-dm/syslog-consumer-rule/src/main/java/com/common/service/StatsProjectionOutboxWriter.java
T

71 lines
3.0 KiB
Java
Raw Normal View History

package com.common.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/** Writes one aggregated outbox row in the same transaction as the corresponding fact batch. */
@Component
public class StatsProjectionOutboxWriter {
private final JdbcTemplate jdbc;
private final ObjectMapper objectMapper;
private final AtomicLong events = new AtomicLong();
private final AtomicLong failures = new AtomicLong();
@Value("${stats.projection-async.enabled:true}") private boolean enabled;
@Value("${stats.projection-async.outbox-table:stats_projection_outbox}") private String outboxTable;
public StatsProjectionOutboxWriter(JdbcTemplate jdbc, ObjectMapper objectMapper) {
this.jdbc = jdbc;
this.objectMapper = objectMapper.copy();
this.objectMapper.deactivateDefaultTyping();
}
public boolean isEnabled() { return enabled; }
public int append(StatsProjectionOutboxEvent event) {
if (!enabled || event == null || event.isEmpty()) return 0;
if (!TransactionSynchronizationManager.isActualTransactionActive()) {
throw new IllegalStateException("Stats projection outbox must be written inside a transaction");
}
event.validate();
try {
int inserted = jdbc.update("INSERT INTO " + safeTable(outboxTable) +
" (event_id,event_schema,payload,status,attempt_count,next_attempt_at," +
"created_at,updated_at) VALUES (?,?,?,'PENDING',0,SYSDATE,SYSDATE,SYSDATE)",
event.getEventId(), event.getSchema(), objectMapper.writeValueAsString(event));
events.incrementAndGet();
return inserted;
} catch (JsonProcessingException serializationFailure) {
failures.incrementAndGet();
throw new IllegalStateException("Failed to serialize stats projection outbox event", serializationFailure);
} catch (RuntimeException databaseFailure) {
failures.incrementAndGet();
throw databaseFailure;
}
}
public Map<String, Object> status() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("enabled", enabled);
result.put("outbox_table", outboxTable);
result.put("events_written_total", events.get());
result.put("write_failures_total", failures.get());
return result;
}
static String safeTable(String table) {
if (table == null || !table.matches("[A-Za-z][A-Za-z0-9_]*")) {
throw new IllegalStateException("Invalid stats projection table: " + table);
}
return table;
}
}