Hyperf交流群
裡每天都會有各種各樣的問題,今天有小夥伴問了一個問題,這裡提供一個思路給大家。
有些 PHPer 會有這樣的疑問
- 某些路由必須登入了才能訪問,有些路由卻不需要登入態。
- 某些路由在使用者登入的情況下,會在原有資料的基礎上增加一部分特殊資料。
中介軟體配合協程單例
第一個問題其實很好解決,而大多數同學也一直在這麼做,這裡再重新整理一下。
首先我們建立一箇中介軟體 UserMiddleware
,並允許它處理所有的路由。然後我們從 Headers
中獲取到 X-Token
,當 X-Token
不存在時,我們再判斷當前的環境是否是開發環境(這裡方便自己除錯),如果 X-Token
存在,則根據 X-Token
獲取對應的資料。這裡使用 JWT
來做。
<?php
declare(strict_types=1);
namespace App\Middleware;
use App\Constants\Constants;
use App\Service\Instance\JwtInstance;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class UserMiddleware implements MiddlewareInterface
{
/**
* @var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$token = $request->getHeaderLine(Constants::X_TOKEN);
if (! empty($token)) {
JwtInstance::instance()->decode($token);
} elseif (env('APP_DEBUG', false) === true) {
JwtInstance::instance()->id = 1;
}
return $handler->handle($request);
}
}
接下來我們實現一個簡單的 JwtInstance
,元件使用 "firebase/php-jwt": "^5.0"
。
<?php
declare(strict_types=1);
namespace App\Service\Instance;
use App\Constants\ErrorCode;
use App\Exception\BusinessException;
use App\Model\User;
use App\Service\Dao\UserDao;
use Firebase\JWT\JWT;
use Hyperf\Utils\Traits\StaticInstance;
class JwtInstance
{
use StaticInstance;
const KEY = 'NoteBook';
/**
* @var int
*/
public $id;
/**
* @var User
*/
public $user;
public function encode(User $user)
{
$this->id = $user->id;
$this->user = $user;
return JWT::encode(['id' => $user->id], self::KEY);
}
public function decode(string $token): self
{
try {
$decoded = (array) JWT::decode($token, self::KEY, ['HS256']);
} catch (\Throwable $exception) {
return $this;
}
if ($id = $decoded['id'] ?? null) {
$this->id = $id;
$this->user = di()->get(UserDao::class)->first($id);
}
return $this;
}
public function build(): self
{
if (empty($this->id)) {
throw new BusinessException(ErrorCode::TOKEN_INVALID);
}
return $this;
}
/**
* @return int
*/
public function getId(): ?int
{
return $this->id;
}
/**
* @return User
*/
public function getUser(): ?User
{
if ($this->user === null && $this->id) {
$this->user = di()->get(UserDao::class)->first($this->id);
}
return $this->user;
}
}
常規的 decode
,encode
就不再贅述了,著重講一下 build
和 getId
方法,其實很好了解,當我們有路由必須要登入時,我們就在控制器中透過 build
獲取 JwtInstance
。
比如我們實現一個 save
方法,每當使用者儲存資訊時,都透過以下程式碼獲取 $userId
,然後再進行儲存。這樣就有效的實現了 第一個問題
。
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Request\NoteSearchRequest;
use App\Request\SaveNoteRequest;
use App\Service\Instance\JwtInstance;
use App\Service\NoteService;
use Hyperf\Di\Annotation\Inject;
class NoteController extends Controller
{
/**
* @Inject
* @var NoteService
*/
protected $service;
public function save(SaveNoteRequest $request, int $id)
{
$text = $request->input('text');
$userId = JwtInstance::instance()->build()->getId();
$result = $this->service->save($id, $userId, $text);
return $this->response->success($result);
}
}
接下來我們檢視第二個問題,以下我們實現一個列表方法。然後透過 getId
獲取當前使用者ID,如果使用者有登陸態,則在列表中返回使用者的 user_id
。
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Request\NoteSearchRequest;
use App\Request\SaveNoteRequest;
use App\Service\Instance\JwtInstance;
use App\Service\NoteService;
use Hyperf\Di\Annotation\Inject;
class NoteController extends Controller
{
/**
* @Inject
* @var NoteService
*/
protected $service;
public function index(NoteSearchRequest $request)
{
$offset = (int) $request->input('offset');
$limit = (int) $request->input('limit');
$result = $this->service->search($userId, $offset, $limit);
$userId = JwtInstance::instance()->getId();
if ($userId) {
$result['user_id'] = $userId;
}
return $this->response->success($result);
}
}
注意事項
現在我發現很多同學都喜歡使用 JWT
來做 Token
,但特殊情況下,如果 Token
被別人竊取,使用者也知道了這件事,但就算他選擇登出,也沒有任何效果。因為 Token
還是能被 decode
出有效的資訊。所以,這種情況還是推薦在服務端存一下對應的 Token
,當授權判斷時,先驗證一下當前 Token
是否存在。
寫在最後
Hyperf 是基於 Swoole 4.4+
實現的高效能、高靈活性的 PHP 協程框架,內建協程伺服器及大量常用的元件,效能較傳統基於 PHP-FPM
的框架有質的提升,提供超高效能的同時,也保持著極其靈活的可擴充套件性,標準元件均基於 PSR 標準 實現,基於強大的依賴注入設計,保證了絕大部分元件或類都是 可替換
與 可複用
的。
框架元件庫除了常見的協程版的 MySQL 客戶端
、Redis 客戶端
,還為您準備了協程版的 Eloquent ORM
、WebSocket 服務端及客戶端
、JSON RPC 服務端及客戶端
、GRPC 服務端及客戶端
、Zipkin/Jaeger (OpenTracing) 客戶端
、Guzzle HTTP 客戶端
、Elasticsearch 客戶端
、Consul 客戶端
、ETCD 客戶端
、AMQP 元件
、Apollo 配置中心
、阿里雲 ACM 應用配置管理
、ETCD 配置中心
、基於令牌桶演算法的限流器
、通用連線池
、熔斷器
、Swagger 文件生成
、Swoole Tracker
、Blade 和 Smarty 檢視引擎
、Snowflake 全域性ID生成器
等元件,省去了自己實現對應協程版本的麻煩。
Hyperf 還提供了 基於 PSR-11 的依賴注入容器
、註解
、AOP 面向切面程式設計
、基於 PSR-15 的中介軟體
、自定義程式
、基於 PSR-14 的事件管理器
、Redis/RabbitMQ 訊息佇列
、自動模型快取
、基於 PSR-16 的快取
、Crontab 秒級定時任務
、Translation 國際化
、Validation 驗證器
等非常便捷的功能,滿足豐富的技術場景和業務場景,開箱即用。
本作品採用《CC 協議》,轉載必須註明作者和本文連結