Files
ai-security-xdr/haobang-security-dm/syslog-consumer-rule/src/main/java/com/common/service/StatsProjectionOutboxWriter.java
T
王绍宏 0e9e6656db feat(syslog): 优化大日志通道并增加异常连续消费兜底
- 128 KiB 及以上日志默认进入大日志 Topic
- InfluxDB 原文及达梦大字段超过 256 KiB 时截断保存
- 截断内容追加 HB_XDR_TRUNCATED 标记
- InfluxDB 写入失败时继续执行规则解析和达梦写入
- 未知或系统性异常优先写入普通/大日志 DLT
- DLT 写入失败时记录特殊告警并提交 offset,避免阻塞后续消息
- DLT 失败等待时间默认限制为 1 秒
- 增加连续性放行、DLT 成功及失败监控指标
- 更新 Docker Compose、v3 升级文档及相关测试
2026-09-12 19:22:52 +08:00

71 lines
3.0 KiB
Java

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;
}
}