hyperf框架採坑

目標

上手,打統統用功能使用障礙,swoole相關錯誤調試。php

一、課程學習

《黃朝暉:Hyperf從入門到精通系列html

php知識點:

__invoke(): 類被函數式調用時觸發執行node

避免cli編程的內存泄漏

普通函數請求和響應對象:git

public function index(RequestInterface $request, ResponseInterface $response){}

實際實例化的對象:github

HyperfHttpServerRequest
-- Context::get(ServerRequestInterface::class)

這些對象存於協程上下文資源,即時釋放。注意公共對象資源的修改!數據庫

a.依賴注入

簡單注入

@AutoController 的路由信息注入
@inject 的簡單對象注入 [ == 經過構造方法注入]

高級注入

# 抽象對象注入
因爲 Hyperf\HttpServer\ConfigProvider.dependencies [
    RequestInterface::class => Request::class
    ResponseInterface::class => Response::class
]
在控制器接收方法裏,直接使用抽象對象即依賴自實例對象

b.切片編程

AOP(Aspect Oriented Programming):面向切片編程編程

註解

App\Annotation\FooAnnotation.php
/**
 * @Annotation
 * @Target({"CLASS","METHOD","PROPERTY"})
 */
 class FooAnnotation extends AbstractAnnotation{}
 -- 添加到註解樹 @Annotation
    -把類路徑、方法、屬性添加到註解樹
註解是添做使用類(當前類)的一部分

切片

App\Aspect\FooSpect.php
/**
 * @Aspect
 */
class FooSpect extends AbstractAspect{
    //定義切入類
    public $classes = [];
    /** 註解引入、重寫切片「環繞」處理方法
     * @param ProceedingJoinPoint $proceedingJoinPoint
     * @return mixed|void
     */
    public function process(ProceedingJoinPoint $proceedingJoinPoint){}
}
-- 添加切片類、方法 [註解方式] 到切面樹
    -重寫定義切入類 $classes 
    -ProceedingJoinPoint $proceedingJoinPoint

控制器

App\Controller\FooController.php
/**
 * @AutoController()
 * @FooAnnotation(bar="123", calc=11)
 */
class FooController{}
--修改註解屬性 @FooAnnotation(bar="123", calc=11)

curl -v http://127.0.0.1:9501/foo/index 或 test

c.協程

震驚:parallel():2個閉包函數的協程!!json

namespace App\Controller;

use Hyperf\Di\Annotation\Inject;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
class CoroutineController
{
    /**
     * @Inject()
     * @var ClientFactory
     */
    private $clientFactory;

    public function sleep(RequestInterface $request)
    {
        $sec = $request->query('second',1);
        sleep($sec);
        return $sec;
    }

    /** Parallel 特性
     * 便捷版 WaitGroup
     * @return array
     */
    public function testCo33()
    {
        $time = (float) time() + floatval(microtime());

        $result = parallel([
            function () {
                $client = $this->clientFactory->create();
                $client->get('127.0.0.1:9501/Coroutine/sleep?second=1');
                return '123: '. \Hyperf\Utils\Coroutine::id();
            },
            function () {
                $client = $this->clientFactory->create();
                $client->get('127.0.0.1:9501/Coroutine/sleep?second=2');
                return '321: '. \Hyperf\Utils\Coroutine::id();
            }
        ]);

        return [__FUNCTION__.' ok: '. round((float) time() + floatval(microtime()) - $time,4), $result];
    }

    /** Concurrent 協程運行控制
     * 高級控制版 WaitGroup
     * @return array
     */
    public function testCo9()
    {
        $time = (float) time() + floatval(microtime());

        $concurrent = new \Hyperf\Utils\Coroutine\Concurrent(5);
        $result = [];

        for ($i = 0; $i < 15; ++$i) {
            $concurrent->create(function () use ($i, &$result) {
                $client = $this->clientFactory->create();
                $client->get('127.0.0.1:9501/Coroutine/sleep?second='. ($i%5+1));
                $result[] = [$i. ': '. \Hyperf\Utils\Coroutine::id()];
                return 1;
            });
        }
        //因爲並行機制 句柄移交的緣由, 這裏result結果輸出數是 15-5=12 個

        return [__FUNCTION__.' ok: '. round((float) time() + floatval(microtime()) - $time,4), $result];
    }
}

本文實例:CoroutineController.phpswoole

d.中間件

與切片同理,閉包

/* 添加中間件多個、一個
 * @Middlewares({
 *     @Middleware(Test1MIddleware::class),
 *     @Middleware(Test2Middleware::class)
 * })
 */
class MidController{}

修改傳值注意:保存到 Context會話對象

Hyperf\Utils\Context::set(ServerRequestInterface::class, $request->withAttribute()...)

App\Controller\MidController.php
    /** curl -v http://127.0.0.1:9501/mid/index
     * b. 方法中間件
     * @Middleware(FooMiddleware::class)
     */
    public function index(RequestInterface $request){}
    
App\Middleware\FooMiddleware.php
class FooMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // $request 和 $response 爲修改後的對象
        $request = Context::set(ServerRequestInterface::class, $request->withAttribute('foo', 'test2 set into value a...'));

        $response = $handler->handle($request);
        $body = $response->getBody()->getContents();
        echo __CLASS__ .__LINE__.PHP_EOL;
        return $response->withBody(new SwooleStream($body. PHP_EOL. ' in func see Foo deal.'));
    }
}

執行順序是:先入、先執行、後出。

e.RPC調用

創建項目

composer create-project hyperf/hyperf-skeleton rpc-server
cp rpc-server rpc-client -r

業務處理

[文件夾 rpc-server]

建立服務
App\Rpc\CalculatorService.php:
use Hyperf\RpcServer\Annotation\RpcService;
/**
 * @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="rpc", publishTo="consul")
 */
class CalculatorService implements CalculatorServiceInterface
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }
    public function minus(int $a, int $b): int
    {
        return $a - $b;
    }
}

@RpcService 引用RpcService類
這裏protocol="jsonrpc-http/tcp", server配置在config/autoload/server.php的servers中,publishTo="consul"註冊到consul。
在編輯器中右鍵,Refactor導出接口CalculatorServiceInterface.php[本例到相同目錄]。

配置主機端口

參考:定義-json-rpc-server

server.php:
"servers"=> #添加或替換
        [
            'name' => 'rpc', //與CalculatorService.php:server="rpc"相同
            'type' => Server::SERVER_HTTP,
            'host' => '0.0.0.0',
            'port' => 9600,
            'sock_type' => SWOOLE_SOCK_TCP,
            'callbacks' => [
                SwooleEvent::ON_REQUEST => [\Hyperf\JsonRpc\HttpServer::class, 'onRequest'],
            ],
        ],
添加接口實例化依賴

添加依賴後,接口的對象自動實例化

config/autoload/dependencies.php:
    /**
     * 接口的實體化依賴
     */
    App\Rpc\CalculatorServiceInterface::class => App\Rpc\CalculatorService::class

業務處理

[文件夾 rpc-client]
接口文件一樣放到 appRpc 下

class CalcController extends AbstractController
{
    /**
     * @Inject()
     * @var CalculatorServiceInterface
     */
    private $calcService;
    public function add(){ return $this->calcService->add(12, 56); }
    public function minus(){ return $this->calcService->minus(23, 78); }
}

添加 config/autoload/services.php 參考官方添加:

'consumers'=>[
    'name' => 'CalculatorService',
    'service' => \App\Rpc\CalculatorServiceInterface::class,
    'registry' => [
        'protocol' => 'consul',
        'address' => 'http://172.10.1.11:8500', //服務羣端主節點
    ],
    'nodes' => [
        ['host' => '172.10.1.22', 'port' => 8500], //備用:客戶羣羣端節點
    ],
]

啓動驗證

php rpc-server/bin/hyperf.php start [9600,發佈到8500]
php rpc-client/bin/hyperf.php start [9501,註冊到8500]

curl -v http://127.0.0.1:9501/calc/add //和minus

consul啓動腳本

f.事件監聽

命令行生成模板文件:

php bin/hyperf.php list
php bin/hyperf.php gen:SendSmsListener
定義服務
/**
 * @Inject()
 * @var EventDispatcherInterface
 */
private $eventDispatcher; //引入事件監聽分發類

    //用戶註冊以前
    $beforeRegister = new BeforeRegister();
    $this->eventDispatcher->dispatch($beforeRegister);
    if($beforeRegister->shouldRegister){
        //註冊用戶
        $userId = rand(1,99999);
    }
    //註冊成功後
    if($userId){
        $this->eventDispatcher->dispatch(new UserRegistered($userId));
    }
添加監聽服務

Listener:

/** 權重默認是1
 * @Listener(priority=2)
 */
class SendSmsListener implements ListenerInterface
{
    public function listen(): array
    {
        return [
            UserRegistered::class
        ];
    }
    /**
     * @param UserRegistered $event
     */
    public function process(object $event)
    {
        echo '發送短信給'. $event->userId .PHP_EOL;
    }
}

class VaildRegisterListener implements ListenerInterface
{
    public function listen(): array
    {
        return [
            BeforeRegister::class
        ];
    }
    /**
     * @param BeforeRegister $event
     */
    public function process(object $event)
    {
        $event->shouldRegister = (bool) rand(0,2);
        echo '註冊身份驗證'. ($event->shouldRegister ? '經過' : '失敗') .PHP_EOL;
    }
}

Event:

class UserRegistered
{
    public $userId;
    public function __construct(int $userId) { $this->userId = $userId; }
}

class BeforeRegister
{
    public $shouldRegister = false;
}

Controller:

class ListenController
{
    /**
     * @Inject()
     * @var UserService
     */
    public $userService;
    public function test()
    {
        return $this->userService->register();
    }
}

實例代碼上傳:
https://github.com/cffycls/cl...

二、應用點

a.文件上傳:GuzzleClient客戶端

文件上傳、接收,[這裏文件類型判斷須要啓用fileinfo擴展]:

客戶端
$body = [
            'multipart' =>
                [
                    [
                        'name' => 'data',
                        'contents' => '{"field_1":"Test","field_2":"Test","field_3":"Test"}',
                        'headers' =>
                            [
                                'Content-Type' => 'application/json',
                            ],
                    ],
                    [
                        'name' => 'file',
                        'filename' => 'README.md',
                        'Mime-Type' => 'application/text',
                        'contents' => file_get_contents('./README.md'),
                    ]
                ]
        ];
$res = (new GuzzleHttp\Client())->request('POST', 'http://127.0.0.1:9501/guzzle_client/write', $body);

服務端:
$files = $this->request->getUploadedFiles();
//var_dump($files);
foreach ($files as $f => $fileObj){
    //2者等效,同一對象
    $file = $this->request->file($f);
    var_dump($file->getMimeType());
    $fileInfo = $file->toArray();
    var_dump($fileInfo);
    echo '如下是接收的文件內容: '. PHP_EOL;
    var_dump( file_get_contents($fileInfo['tmp_file']) );
    if(file_exists('/tmp/README.md.tmp')){
        echo '文件已存在: '. PHP_EOL;
    }else{
        $file->moveTo('/tmp/README.md.tmp'); //保存文件
        // 經過 isMoved(): bool 方法判斷方法是否已移動
        if ($file->isMoved()) {
            echo $fileInfo['name'] .'文件已上傳 '. PHP_EOL;
            unlink('/tmp/README.md.tmp');
            return $fileInfo;
        }
    }
}

b.組件列表

路由,事件,日誌,命令,數據庫,依賴注入容器,服務,客戶端,消息隊列,配置中心,RPC,服務治理,定時任務,ID 生成器,文檔生成,Graphql,熱更新/熱重載,Swoole,開發調試,權限認證,第三方 SDK

移步官網 組件列表

相關文章
相關標籤/搜索