hyperf框架采坑

javascript/jquery

浏览数:33

2020-6-26

AD:资源代下载服务

目标

上手,打通通用功能使用障碍,swoole相关错误调试。

1、课程学习

《黄朝晖:Hyperf从入门到精通系列

php知识点:

__invoke(): 类被函数式调用时触发执行

避免cli编程的内存泄漏

普通函数请求和响应对象:

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

实际实例化的对象:

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个闭包函数的协程!!

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.php]

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:listener 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…

2、应用点

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

移步官网 组件列表

小结

hyperf功能全面,文档齐全、降低了上手门槛。

代码上传:
https://github.com/cffycls/default/tree/master/hyperf
https://github.com/cffycls/cluster/tree/master/html/rpc-server
https://github.com/cffycls/cluster/tree/master/html/rpc-client

作者:沧浪水