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
相关产品推荐
相关产品推荐

