日志记录提交

This commit is contained in:
2026-08-05 14:20:04 +08:00
parent 264458920b
commit 06f7f9d2f2
83 changed files with 3256 additions and 27 deletions
@@ -0,0 +1,73 @@
package com.baiyue.audit.repository;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
/** 审计 RocketMQ 死信及补偿状态仓储。 */
@Repository
public class AuditDeadLetterRepository {
@Resource
private JdbcTemplate jdbc;
public void save(String eventId, String payload) {
jdbc.update("""
INSERT INTO sys_audit_dead_letter(event_id,payload_text,status,retry_count,next_retry_at,created_at)
VALUES (?,?,'PENDING',0,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3))
ON DUPLICATE KEY UPDATE payload_text=VALUES(payload_text)
""", eventId, payload);
}
public List<Long> pendingIds(int limit) {
return jdbc.queryForList("""
SELECT id FROM sys_audit_dead_letter
WHERE status='PENDING' AND next_retry_at<=CURRENT_TIMESTAMP(3)
ORDER BY next_retry_at,id LIMIT ?
""", Long.class, limit);
}
public boolean claim(long id, String owner, int leaseSeconds) {
return jdbc.update("""
UPDATE sys_audit_dead_letter SET status='PROCESSING',lease_owner=?,
lease_until=DATE_ADD(CURRENT_TIMESTAMP(3),INTERVAL ? SECOND),retry_count=retry_count+1
WHERE id=? AND status='PENDING' AND next_retry_at<=CURRENT_TIMESTAMP(3)
""", owner, leaseSeconds, id) == 1;
}
public DeadRow find(long id) {
List<DeadRow> rows = jdbc.query(
"SELECT id,event_id,payload_text,retry_count FROM sys_audit_dead_letter WHERE id=?",
(rs, rowNum) -> new DeadRow(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getInt(4)), id);
return rows.isEmpty() ? null : rows.get(0);
}
public void processed(long id, String owner) {
jdbc.update("""
UPDATE sys_audit_dead_letter SET status='PROCESSED',processed_at=CURRENT_TIMESTAMP(3),
lease_owner=NULL,lease_until=NULL,last_error=NULL
WHERE id=? AND status='PROCESSING' AND lease_owner=?
""", id, owner);
}
public void failed(long id, String owner, int maxAttempts, String error) {
jdbc.update("""
UPDATE sys_audit_dead_letter
SET status=IF(retry_count>=?,'DEAD','PENDING'),
next_retry_at=DATE_ADD(CURRENT_TIMESTAMP(3),INTERVAL 60 SECOND),
lease_owner=NULL,lease_until=NULL,last_error=?
WHERE id=? AND status='PROCESSING' AND lease_owner=?
""", maxAttempts, error, id, owner);
}
public int releaseExpired() {
return jdbc.update("""
UPDATE sys_audit_dead_letter SET status='PENDING',next_retry_at=CURRENT_TIMESTAMP(3),
lease_owner=NULL,lease_until=NULL
WHERE status='PROCESSING' AND lease_until<=CURRENT_TIMESTAMP(3) LIMIT 100
""");
}
public record DeadRow(long id, String eventId, String payload, int retryCount) { }
}
@@ -5,7 +5,13 @@ import jakarta.annotation.Resource;
import com.baiyue.audit.dto.AuditIngestRequest;
import com.baiyue.audit.entity.AuditLogEntity;
import com.baiyue.audit.mapper.AuditLogMapper;
import com.baiyue.audit.dto.AuditLogQuery;
import com.baiyue.common.core.api.PageResult;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Repository;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.util.StringUtils;
/**
* 审计事件持久化仓储,负责将内部审计请求映射并追加写入审计日志表。
@@ -15,8 +21,9 @@ public class AuditEventRepository {
@Resource
private AuditLogMapper mapper;
public void save(AuditIngestRequest event) {
public boolean save(AuditIngestRequest event, String eventId) {
AuditLogEntity entity = new AuditLogEntity();
entity.setEventId(eventId);
entity.setEventType(event.eventType());
entity.setResourceType(event.resourceType());
entity.setResourceId(event.resourceId());
@@ -40,7 +47,40 @@ public class AuditEventRepository {
entity.setAfterData(jsonOrNull(event.afterData()));
entity.setOccurredAt(event.occurredAt());
entity.setServiceName(event.serviceName());
mapper.insert(entity);
try {
mapper.insert(entity);
return true;
} catch (DuplicateKeyException ignored) {
return false;
}
}
public PageResult<AuditLogEntity> page(AuditLogQuery query, long page, long size) {
LambdaQueryWrapper<AuditLogEntity> wrapper = new LambdaQueryWrapper<AuditLogEntity>()
.eq(query.accountId() != null, AuditLogEntity::getTenantId, query.accountId())
.eq(query.operatorUserId() != null, AuditLogEntity::getUserId, query.operatorUserId())
.eq(StringUtils.hasText(query.eventType()), AuditLogEntity::getEventType, query.eventType())
.eq(StringUtils.hasText(query.resourceType()), AuditLogEntity::getResourceType, query.resourceType())
.eq(StringUtils.hasText(query.resourceId()), AuditLogEntity::getResourceId, query.resourceId())
.eq(query.success() != null, AuditLogEntity::getSuccess, query.success())
.ge(query.occurredFrom() != null, AuditLogEntity::getOccurredAt, query.occurredFrom())
.le(query.occurredTo() != null, AuditLogEntity::getOccurredAt, query.occurredTo())
.orderByDesc(AuditLogEntity::getOccurredAt)
.orderByDesc(AuditLogEntity::getId);
if (StringUtils.hasText(query.keyword())) {
String keyword = query.keyword().trim();
wrapper.and(condition -> condition
.like(AuditLogEntity::getDescription, keyword)
.or().like(AuditLogEntity::getUsername, keyword)
.or().like(AuditLogEntity::getResourceId, keyword)
.or().like(AuditLogEntity::getRequestId, keyword)
.or().like(AuditLogEntity::getTraceId, keyword));
}
return PageResult.from(mapper.selectPage(new Page<>(page, size), wrapper));
}
public AuditLogEntity findById(Long id) {
return mapper.selectById(id);
}
private static String jsonOrNull(String value) {