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

API Platform多数据库连接:认证时指定连接的实现求助

基于API Platform实现多数据库动态切换(从认证Token获取连接ID)

核心思路

通过自定义认证器提取Token中的连接ID并绑定到认证用户;再通过动态EntityManager服务获取对应数据库连接,最后替换API Platform默认的数据提供者实现,实现全请求链路的数据库动态切换。


1. 自定义认证器提取连接ID

必须自定义认证器,在认证过程中解析Token并提取connection_id,将其绑定到用户对象中,方便后续请求获取。

// src/Security/TokenAuthenticator.php
namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Lcobucci\JWT\Configuration;

class TokenAuthenticator extends AbstractAuthenticator
{
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('Authorization') && str_starts_with($request->headers->get('Authorization'), 'Bearer ');
    }

    public function authenticate(Request $request): SelfValidatingPassport
    {
        // 提取并解析JWT Token
        $rawToken = str_replace('Bearer ', '', $request->headers->get('Authorization'));
        $jwt = Configuration::forUnsecuredSigner()->parser()->parse($rawToken);
        $payload = $jwt->claims()->all();

        // 从Payload中获取连接ID
        $connectionId = $payload['connection_id'];
        $username = $payload['username'];

        // 返回包含连接ID的自定义User对象
        return new SelfValidatingPassport(
            new UserBadge($username, function() use ($username, $connectionId) {
                return new class($username, $connectionId) implements UserInterface {
                    private string $username;
                    private string $connectionId;

                    public function __construct(string $username, string $connectionId)
                    {
                        $this->username = $username;
                        $this->connectionId = $connectionId;
                    }

                    public function getRoles(): array { return []; }
                    public function eraseCredentials(): void {}
                    public function getUserIdentifier(): string { return $this->username; }
                    public function getConnectionId(): string { return $this->connectionId; }
                };
            })
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?JsonResponse
    {
        return null; // 继续请求流程
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
    {
        return new JsonResponse(['error' => $exception->getMessage()], 401);
    }
}

在security.yaml中注册该认证器:

# config/packages/security.yaml
security:
    firewalls:
        main:
            stateless: true
            custom_authenticators:
                - App\Security\TokenAuthenticator

2. 动态获取EntityManager服务

创建服务,根据当前认证用户的connection_id从容器中获取对应配置的EntityManager:

// src/Doctrine/DynamicEntityManagerProvider.php
namespace App\Doctrine;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;

class DynamicEntityManagerProvider
{
    private ContainerInterface $container;
    private Security $security;

    public function __construct(ContainerInterface $container, Security $security)
    {
        $this->container = $container;
        $this->security = $security;
    }

    public function getEntityManager(): EntityManagerInterface
    {
        $token = $this->security->getToken();
        if (!$token || !($user = $token->getUser()) instanceof UserInterface) {
            throw new \RuntimeException('未认证用户无法访问数据库');
        }

        $connectionId = $user->getConnectionId();
        $serviceId = sprintf('doctrine.orm.%s_entity_manager', $connectionId);

        if (!$this->container->has($serviceId)) {
            throw new \RuntimeException("未配置连接ID为{$connectionId}的EntityManager");
        }

        return $this->container->get($serviceId);
    }
}

3. 替换API Platform默认DataProvider

API Platform默认使用配置的默认EntityManager,需要自定义Collection和Item DataProvider,替换为动态获取的EntityManager:

自定义Collection DataProvider

// src/DataProvider/DynamicDoctrineOrmCollectionDataProvider.php
namespace App\DataProvider;

use ApiPlatform\Core\Bridge\Doctrine\Orm\DataProvider\CollectionDataProvider;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use App\Doctrine\DynamicEntityManagerProvider;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;

class DynamicDoctrineOrmCollectionDataProvider extends CollectionDataProvider
{
    private DynamicEntityManagerProvider $emProvider;

    public function __construct(
        DynamicEntityManagerProvider $emProvider,
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
        array $collectionExtensions = []
    ) {
        parent::__construct($emProvider->getEntityManager(), $resourceMetadataFactory, $collectionExtensions);
        $this->emProvider = $emProvider;
    }

    protected function createQueryBuilder(string $resourceClass, string $operationName = null): \Doctrine\ORM\QueryBuilder
    {
        $em = $this->emProvider->getEntityManager();
        $repository = $em->getRepository($resourceClass);

        if (!method_exists($repository, 'createQueryBuilder')) {
            throw new ResourceClassNotFoundException("资源{$resourceClass}的Repository未实现createQueryBuilder方法");
        }

        return $repository->createQueryBuilder('o');
    }
}

自定义Item DataProvider

// src/DataProvider/DynamicDoctrineOrmItemDataProvider.php
namespace App\DataProvider;

use ApiPlatform\Core\Bridge\Doctrine\Orm\DataProvider\ItemDataProvider;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use App\Doctrine\DynamicEntityManagerProvider;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;

class DynamicDoctrineOrmItemDataProvider extends ItemDataProvider
{
    private DynamicEntityManagerProvider $emProvider;

    public function __construct(
        DynamicEntityManagerProvider $emProvider,
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
        array $itemExtensions = []
    ) {
        parent::__construct($emProvider->getEntityManager(), $resourceMetadataFactory, $itemExtensions);
        $this->emProvider = $emProvider;
    }

    protected function createQueryBuilder(string $resourceClass, string $operationName = null): \Doctrine\ORM\QueryBuilder
    {
        $em = $this->emProvider->getEntityManager();
        $repository = $em->getRepository($resourceClass);

        if (!method_exists($repository, 'createQueryBuilder')) {
            throw new ResourceClassNotFoundException("资源{$resourceClass}的Repository未实现createQueryBuilder方法");
        }

        return $repository->createQueryBuilder('o');
    }
}

服务配置替换默认实现

在services.yaml中覆盖API Platform的默认DataProvider:

# config/services.yaml
services:
    # 替换Collection DataProvider
    ApiPlatform\Core\Bridge\Doctrine\Orm\DataProvider\CollectionDataProvider:
        class: App\DataProvider\DynamicDoctrineOrmCollectionDataProvider
        arguments:
            $emProvider: '@App\Doctrine\DynamicEntityManagerProvider'
            $resourceMetadataFactory: '@api_platform.metadata.resource_metadata_factory'
            $collectionExtensions: !tagged_iterator api_platform.doctrine.orm.query_extension.collection

    # 替换Item DataProvider
    ApiPlatform\Core\Bridge\Doctrine\Orm\DataProvider\ItemDataProvider:
        class: App\DataProvider\DynamicDoctrineOrmItemDataProvider
        arguments:
            $emProvider: '@App\Doctrine\DynamicEntityManagerProvider'
            $resourceMetadataFactory: '@api_platform.metadata.resource_metadata_factory'
            $itemExtensions: !tagged_iterator api_platform.doctrine.orm.query_extension.item

4. 验证Doctrine多连接配置

确保doctrine.yaml中已正确配置多个SQL Server连接和对应的EntityManager:

# config/packages/doctrine.yaml
doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                url: '%env(resolve:DATABASE_URL)%'
                driver: pdo_sqlsrv
            client_xyz:
                url: '%env(resolve:DATABASE_CLIENT_XYZ_URL)%'
                driver: pdo_sqlsrv
            # 其他连接...
    orm:
        default_entity_manager: default
        entity_managers:
            default:
                connection: default
                mappings:
                    App:
                        is_bundle: false
                        dir: '%kernel.project_dir%/src/Entity'
                        prefix: 'App\Entity'
                        alias: App
            client_xyz:
                connection: client_xyz
                mappings:
                    App:
                        is_bundle: false
                        dir: '%kernel.project_dir%/src/Entity'
                        prefix: 'App\Entity'
                        alias: App
            # 对应每个连接的EntityManager...

内容的提问来源于stack exchange,提问作者Ivan Mtnz Vigil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 23:49:52