日志记录提交

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,40 @@
package com.baiyue.common.audit;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClient;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withAccepted;
class HttpAuditSinkTest {
private static final String ENDPOINT = "http://audit-service/internal/audit/events";
private static final String TOKEN = "0123456789abcdef0123456789abcdef";
@Test
void sendsInternalServiceToken() {
RestClient.Builder builder = RestClient.builder();
MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build();
server.expect(once(), requestTo(ENDPOINT))
.andExpect(method(HttpMethod.POST))
.andExpect(header(HttpAuditSink.INTERNAL_SERVICE_TOKEN_HEADER, TOKEN))
.andRespond(withAccepted().contentType(MediaType.APPLICATION_JSON));
new HttpAuditSink(builder, ENDPOINT, TOKEN).publish(sampleEvent());
server.verify();
}
private AuditEvent sampleEvent() {
return new AuditEvent("audit-1", "ROLE_UPDATE", "ROLE", "1", "update role",
"request-1", "trace-1", 1L, 1L, 1L, "admin", "PLATFORM", "WEB",
"PUT", "/api/platform/v1/roles/1", "127.0.0.1", 10L, true,
null, null, null, null, Instant.parse("2026-08-05T00:00:00Z"), "iam");
}
}
@@ -0,0 +1,113 @@
package com.baiyue.common.audit;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.support.TransactionOperations;
import java.time.Instant;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(ReliableAuditDeliveryTest.Config.class)
class ReliableAuditDeliveryTest {
@MockBean
private AuditOutboxRepository repository;
@MockBean
private RocketMQTemplate rocketMQ;
@MockBean
private TransactionOperations transactions;
@Resource
private OutboxAuditSink sink;
@Resource
private AuditOutboxPublisher publisher;
@Test
void storesEventBeforeDelivery() {
AuditEvent event = event("audit-1");
org.mockito.Mockito.doAnswer(invocation -> {
java.util.function.Consumer<?> callback = invocation.getArgument(0);
((java.util.function.Consumer<Object>) callback).accept(null);
return null;
}).when(transactions).executeWithoutResult(any());
sink.publish(event);
verify(repository).append(eq(event), anyString());
}
@Test
void marksDeliveredMessageSent() {
when(repository.publishableIds(50)).thenReturn(List.of("audit-1"));
when(repository.claim("audit-1", "test-owner", 60)).thenReturn(true);
when(repository.find("audit-1"))
.thenReturn(new AuditOutboxRepository.OutboxRow("audit-1", "{}", 1));
publisher.publish();
verify(rocketMQ).syncSend(anyString(), any(Message.class), anyLong());
verify(repository).sent("audit-1", "test-owner");
}
@Test
void schedulesRetryAfterMqFailure() {
when(repository.publishableIds(50)).thenReturn(List.of("audit-2"));
when(repository.claim("audit-2", "test-owner", 60)).thenReturn(true);
when(repository.find("audit-2"))
.thenReturn(new AuditOutboxRepository.OutboxRow("audit-2", "{}", 1));
doThrow(new IllegalStateException("broker unavailable"))
.when(rocketMQ).syncSend(anyString(), any(Message.class), anyLong());
publisher.publish();
verify(repository).failed("audit-2", "test-owner", 5, 5, "broker unavailable");
}
private static AuditEvent event(String id) {
return new AuditEvent(id, "ROLE_UPDATE", "ROLE", "1", "修改角色",
"request-1", "trace-1", 1L, 1L, 1L, "admin", "PLATFORM", "WEB",
"PUT", "/roles/1", "127.0.0.1", 10, true, null, null,
null, null, Instant.parse("2026-08-05T00:00:00Z"), "iam-service");
}
@Configuration
static class Config {
@Bean
ObjectMapper objectMapper() { return new ObjectMapper().findAndRegisterModules(); }
@Bean
AuditProperties properties() {
AuditProperties value = new AuditProperties();
value.getOutbox().setBatchSize(50);
value.getOutbox().setLeaseSeconds(60);
value.getOutbox().setMaxAttempts(5);
value.getRocketmq().setTopic("test-baiyue-audit-event-v1");
return value;
}
@Bean
OutboxAuditSink sink(AuditOutboxRepository repository, ObjectMapper json,
TransactionOperations transactions) {
return new OutboxAuditSink(repository, json, transactions);
}
@Bean
AuditOutboxPublisher publisher(AuditOutboxRepository repository, RocketMQTemplate rocketMQ,
AuditProperties properties) {
return new AuditOutboxPublisher(repository, rocketMQ, properties, "test-owner");
}
}
}