You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus中/q/health端点Web过滤器未触发,如何实现租户专属健康检查?

在Quarkus中为/q前缀系统端点添加租户上下文过滤器的解决方案

Quarkus的/q前缀端点(如健康检查)属于系统扩展提供的独立路由,默认不会触发应用内的JAX-RS ContainerRequestFilter。以下是几种可行的解决方案,帮你在这类端点中获取租户上下文:

方法1:使用Vert.x RouteFilter拦截所有请求

Vert.x层面的过滤器可以覆盖Quarkus处理的所有HTTP请求,包括/q前缀的系统端点,是最通用的方案。

  • 编写RouteFilter Bean,提前解析租户上下文:
import io.quarkus.vertx.web.RouteFilter;
import io.vertx.ext.web.RoutingContext;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class TenantRouteFilter {

    @RouteFilter(100) // 数值越小,执行优先级越高
    public void injectTenantContext(RoutingContext ctx) {
        // 示例:从请求头获取租户ID,可根据实际逻辑调整(如参数、Cookie)
        String tenantId = ctx.request().getHeader("X-Tenant-ID");
        if (tenantId != null) {
            // 将租户ID存入Vertx上下文,供后续健康检查使用
            ctx.put("tenantId", tenantId);
            // 或存入ThreadLocal,供其他组件共享
            TenantContextHolder.setTenantId(tenantId);
        }
        // 继续执行后续请求逻辑
        ctx.next();
    }
}
  • 在自定义健康检查类中获取租户上下文:
import io.quarkus.smallrye.health.HealthCheck;
import io.smallrye.health.api.HealthCheckResponse;
import io.vertx.ext.web.RoutingContext;
import jakarta.inject.Inject;

public class TenantHealthCheck implements HealthCheck {

    @Inject
    RoutingContext routingContext;

    @Override
    public HealthCheckResponse call() {
        String tenantId = routingContext.get("tenantId");
        if (tenantId == null) {
            return HealthCheckResponse.named("tenant-health")
                    .down()
                    .withData("reason", "租户ID未提供")
                    .build();
        }
        // 执行租户专属健康检查(如验证租户数据库连接、资源状态)
        boolean isHealthy = checkTenantResources(tenantId);
        return HealthCheckResponse.named("tenant-health")
                .status(isHealthy)
                .withData("tenant-id", tenantId)
                .build();
    }

    private boolean checkTenantResources(String tenantId) {
        // 自定义检查逻辑
        return true;
    }
}

方法2:使用Servlet Filter覆盖全路径

Servlet过滤器能拦截所有HTTP请求,适合熟悉Servlet生态的场景。

  • 实现Servlet Filter并指定拦截所有路径:
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class TenantServletFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest) request;
        String tenantId = httpReq.getHeader("X-Tenant-ID");
        if (tenantId != null) {
            // 将租户ID存入请求属性
            request.setAttribute("tenantId", tenantId);
            // 或存入ThreadLocal,注意后续清理避免内存泄漏
            TenantContextHolder.setTenantId(tenantId);
        }
        try {
            chain.doFilter(request, response);
        } finally {
            // 清理ThreadLocal(异步场景必须做)
            TenantContextHolder.clear();
        }
    }
}
  • 在健康检查类中通过HttpServletRequest获取租户ID:
import io.quarkus.smallrye.health.HealthCheck;
import io.smallrye.health.api.HealthCheckResponse;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;

public class TenantServletHealthCheck implements HealthCheck {

    @Inject
    HttpServletRequest request;

    @Override
    public HealthCheckResponse call() {
        String tenantId = (String) request.getAttribute("tenantId");
        if (tenantId == null) {
            return HealthCheckResponse.named("servlet-tenant-health")
                    .down()
                    .withData("reason", "租户上下文缺失")
                    .build();
        }
        // 执行租户特定检查
        boolean healthy = verifyTenantServices(tenantId);
        return HealthCheckResponse.named("servlet-tenant-health")
                .status(healthy)
                .withData("tenant", tenantId)
                .build();
    }

    private boolean verifyTenantServices(String tenantId) {
        // 自定义检查逻辑
        return true;
    }
}

方法3:直接在健康检查类中解析租户信息

如果不需要全局过滤器,可直接在健康检查逻辑中获取请求参数/头,省去过滤器步骤:

import io.quarkus.smallrye.health.HealthCheck;
import io.smallrye.health.api.HealthCheckResponse;
import io.vertx.ext.web.RoutingContext;
import jakarta.inject.Inject;

public class DirectTenantHealthCheck implements HealthCheck {

    @Inject
    RoutingContext routingContext;

    @Override
    public HealthCheckResponse call() {
        // 直接从请求头提取租户ID
        String tenantId = routingContext.request().getHeader("X-Tenant-ID");
        if (tenantId == null) {
            return HealthCheckResponse.named("direct-tenant-health").down().build();
        }
        // 租户专属健康检查逻辑
        return HealthCheckResponse.named("direct-tenant-health")
                .up()
                .withData("tenant-id", tenantId)
                .build();
    }
}

注意事项

  • 使用ThreadLocal存储租户上下文时,务必在请求结束后清理,避免内存泄漏(异步场景尤其重要)。
  • RouteFilter的执行顺序可通过注解参数调整,确保租户上下文解析在其他业务逻辑之前完成。
  • JAX-RS过滤器默认不会覆盖/q路径,因此Vert.x或Servlet过滤器是更可靠的选择。

内容的提问来源于stack exchange,提问作者Herr Derb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:07:50