package vip.mate.audit.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import vip.mate.audit.model.AuditEventEntity; import vip.mate.audit.repository.AuditEventMapper; import vip.mate.auth.model.UserEntity; import vip.mate.auth.service.AuthService; import java.time.LocalDateTime; /** * 操作审计服务 *
* 异步记录用户对资源的 CRUD 操作,不阻塞业务请求。 * * @author MateClaw Team */ @Slf4j @Service @RequiredArgsConstructor public class AuditEventService { private final AuditEventMapper auditEventMapper; private final AuthService authService; /** * 异步记录审计事件。 *
* 在调用线程(请求线程)中捕获完整上下文,然后交给异步线程写库。 * 这样避免了 SecurityContext/RequestContext 在异步线程中丢失的问题。 */ public void record(String action, String resourceType, String resourceId, String resourceName, String detailJson) { record(action, resourceType, resourceId, resourceName, detailJson, null); } /** * 异步记录审计事件(显式指定 workspace ID)。 *
* 当调用方已知 workspace ID 时,优先使用此方法以避免依赖 request header 解析。
*/
public void record(String action, String resourceType, String resourceId,
String resourceName, String detailJson, Long workspaceId) {
// 在请求线程中构建事件(可以访问 SecurityContext 和 RequestContext)
AuditEventEntity event = buildEvent(action, resourceType, resourceId, resourceName, detailJson);
if (event != null) {
// 显式传入的 workspaceId 优先于 header 解析结果
if (workspaceId != null) {
event.setWorkspaceId(workspaceId);
}
insertAsync(event);
}
}
@Async
void insertAsync(AuditEventEntity event) {
try {
auditEventMapper.insert(event);
} catch (Exception e) {
log.warn("Failed to insert audit event: {}/{}", event.getAction(), event.getResourceType(), e);
}
}
/**
* 同步记录(用于必须确保落库的场景,如登录/登出)
*/
public void recordSync(String action, String resourceType, String resourceId,
String resourceName, String detailJson) {
AuditEventEntity event = buildEvent(action, resourceType, resourceId, resourceName, detailJson);
if (event != null) {
auditEventMapper.insert(event);
}
}
/**
* 分页查询审计事件
*/
public IPage