otezvikentiy / json-rpc-api
Symfony Json RPC API 扩展包
1.7
2024-06-03 21:40 UTC
Requires
- php: >=8.1
- doctrine/annotations: ^2.0
- symfony/console: >=6.3
- symfony/framework-bundle: >=6.3
- symfony/serializer: >=6.3
- symfony/validator: >=6.3
- symfony/yaml: >=6.3
Requires (Dev)
- phpunit/phpunit: ^10.3
- symfony/property-access: >=6.3
README
该扩展包允许您快速方便地部署基于 Symfony 6 框架的 JSON RPC API 应用程序。
特性
- 简单的 API 版本管理
- 简单的扩展包安装
- 兼容属性
- 兼容 POST、GET、PUT、PATCH、DELETE 请求
- 完全兼容 https://www.jsonrpc.org/specification
- 开箱即用的 Swagger OpenAPI
github: https://github.com/OtezVikentiy/symfony-jsonrpc-api-bundle
说明: https://otezvikentiy.tech/articles/symfony-json-rpc-api-bundle-prostoe-api-so-vsem-neobhodimym
扩展包安装
- 将扩展包作为依赖项。
$ composer require otezvikentiy/json-rpc-api
- 在您的应用程序 Kernel 中启用它。(如果使用 flex 则不必要)
<?php // config/bundles.php return [ //... OV\JsonRPCAPIBundle\OVJsonRPCAPIBundle::class => ['all' => true], ];
- 创建/更新以下配置文件
# config/routes/ov_json_rpc_api.yaml ov_json_rpc_api: resource: '@OVJsonRPCAPIBundle/config/routes/routes.yaml'
# config/services.yaml services: App\RPC\V1\: resource: '../src/RPC/V1/{*Method.php}' tags: - { name: ov.rpc.method, namespace: App\RPC\V1\, version: 1 }
# config/packages/ov_json_rpc_api.yaml ov_json_rpc_api: swagger: api_v1: api_version: '1' base_path: '%env(string:OV_JSON_RPC_API_BASE_URL)%' auth_token_name: 'X-AUTH-TOKEN' auth_token_test_value: '%env(string:OV_JSON_RPC_API_AUTH_TOKEN)%' #set blank for prod environment info: title: 'Some awesome api title here' description: 'Some description about your api here would be appreciated if you like' terms_of_service_url: 'https://terms_of_service_url.test/url' contact: name: 'John Doe' url: 'https://john-doe.test' email: 'john.doe@john-doe.test' license: 'MIT license' licenseUrl: 'https://john-doe.test/mit-license'
# .env ###> otezvikentiy/json-rpc-api ### OV_JSON_RPC_API_SWAGGER_PATH=public/openapi/ OV_JSON_RPC_API_BASE_URL=https:// OV_JSON_RPC_API_AUTH_TOKEN=2f1f6aee7d994528fde6e47a493cc097 ###< otezvikentiy/json-rpc-api ###
测试驱动
创建目录和文件
在安装过程中,我们在 services 中定义了 src/RPC/V1/{*Method.php}
目录,并使用标签标记了其中所有以 *Method.php
结尾的类 - 这些将是我们的 API 端点。
└── src
└── RPC
└── V1
└── getProducts
├── GetProductsRequest.php
└── GetProductsResponse.php
└── GetProductsMethod.php
创建以下类
<?php namespace App\RPC\V1\getProducts; class GetProductsRequest { private int $id; private string $title; public function __construct(int $id) { $this->id = $id; } public function getId(): int { return $this->id; } public function setId(int $id): void { $this->id = $id; } public function getTitle(): string { return $this->title; } public function setTitle(string $title): void { $this->title = $title; } }
<?php namespace App\RPC\V1\getProducts; class GetProductsResponse { private bool $success; private string $title; public function __construct(string $title, bool $success = true) { $this->success = $success; $this->title = $title; } public function getTitle(): string { return $this->title; } public function setTitle(string $title): void { $this->title = $title; } public function isSuccess(): bool { return $this->success; } public function setSuccess(bool $success): void { $this->success = $success; } }
<?php namespace App\RPC\V1; use OV\JsonRPCAPIBundle\Core\Annotation\JsonRPCAPI; use App\RPC\V1\getProducts\GetProductsRequest; use App\RPC\V1\getProducts\GetProductsResponse; #[JsonRPCAPI(methodName: 'getProducts', type: 'POST')] class GetProductsMethod { /** * @param GetProductsRequest $request // !!!ATTENTION!!! Do not rename this param - just change type, but not the name of variable * @return GetProductsResponse */ public function call(GetProductsRequest $request): GetProductsResponse { return new GetProductsResponse($request->getTitle().'OLOLOLOLO'); } }
现在您可以通过以下方式执行 curl 请求
curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc": "2.0","method": "getProducts","params": {"title": "AZAZAZA"},"id": 1}' https:///api/v1
回答将是这样的
{"jsonrpc":"2.0","result":{"title":"AZAZAZAOLOLOLOLO","success":true},"id":"1"}
总共,为了为您的 RPC API 创建新的端点,您只需要添加 3 个类 - 这是方法本身以及请求和响应的文件夹。
Swagger
如果您希望生成 openapi swagger yaml 文件,则运行此命令
bin/console ov:swagger:generate
它将生成一个 swagger 文件(例如 public/openapi/api_v1.yaml),您可以在 Swagger 实例中使用它
安全
您还可以添加这样的令牌授权
- 创建 src/Entity/ApiToken.php
<?php namespace App\Entity; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class ApiToken { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private int $id; #[ORM\Column(type: 'string', length: 500, nullable: false)] private string $token; #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)] private DateTimeInterface $expiresAt; #[ORM\ManyToOne(inversedBy: 'apiTokens')] #[ORM\JoinColumn(nullable: false)] private User $user; public function getId(): int { return $this->id; } public function setId(int $id): ApiToken { $this->id = $id; return $this; } public function getToken(): string { return $this->token; } public function setToken(string $token): ApiToken { $this->token = $token; return $this; } public function getExpiresAt(): DateTimeInterface { return $this->expiresAt; } public function setExpiresAt(DateTimeInterface $expiresAt): ApiToken { $this->expiresAt = $expiresAt; return $this; } public function getUser(): User { return $this->user; } public function setUser(User $user): ApiToken { $this->user = $user; return $this; } public function isValid(): bool { return (new DateTime())->getTimestamp() > $this->expiresAt->getTimestamp(); } }
- 创建 src/Security/ApiKeyAuthenticator.php
<?php namespace App\Security; use App\Entity\ApiToken; use App\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; class ApiKeyAuthenticator extends AbstractAuthenticator { public function __construct( private readonly EntityManagerInterface $em ){ } public function supports(Request $request): bool { return str_contains($request->getRequestUri(), '/api/v'); } public function authenticate(Request $request): Passport { $apiToken = $request->headers->get('X-AUTH-TOKEN'); if (null === $apiToken) { throw new CustomUserMessageAuthenticationException('No API token provided'); } $apiTokenEntity = $this->em->getRepository(ApiToken::class)->findOneBy(['token' => $apiToken]); if (is_null($apiTokenEntity)) { throw new CustomUserMessageAuthenticationException('No API token provided'); } return new SelfValidatingPassport(new UserBadge( $apiTokenEntity->getUser()->getId(), function () use ($apiTokenEntity) { return $this->em->getRepository(User::class)->find($apiTokenEntity->getUser()->getId()); } )); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { $data = [ 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) ]; return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); } }
- 在安全部分添加新的防火墙 security.firewalls.api...
security: # https://symfony.com.cn/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com.cn/doc/current/security.html#loading-the-user-the-user-provider providers: # used to reload user from session & other features (e.g. switch_user) app_user_provider: entity: class: App\Entity\User property: email firewalls: api: pattern: ^/api provider: app_user_provider custom_authenticators: - App\Security\ApiKeyAuthenticator
-
运行迁移以创建表并添加用户令牌 - 就这样!这是在 Symfony 中创建令牌认证的标准方法: https://symfony.com.cn/doc/current/security/custom_authenticator.html
-
现在您可以在请求头中添加 X-AUTH-TOKEN 并以此方式授权请求