hrperf框架的入门
前言
我跳槽了,去了一家电商。
人家都是docker开发,用的框架是hyperf,然后入职第一天,搭了一天环境。
这就是学习的动力,开始吧!加油打工人!!!
数据库配置 (读写分离)
- Coroutine\MySQL
协程MySQL客户端,但是人家不推荐使用了,现在推荐使用的是Swoole\Runtime::enableCoroutine+PDO或Mysqli方式,即一键协程化原生PHP的MySQL客户端 - hyperf/db-connection组件
基于hyperf/pool实现数据库连接池并对模型进行了新的抽象,以它作为桥梁,hyperf才能把数据库组件及事件组件接进来
composer require hyperf/db-connection
其实composer.json已经有
“hyper/db-connection”:“XXX”,
所以可以忽略这条
database配置在哪?
在config/autoluad/databases.php
如何配置读写分离?
普通
return [
'dafault'=>[
'driver'=>env('DB_DRIVER','mysql'),
'host'=>env('DB_HOST','localhost'),
'database'=>env("DB_DATABASE",'hyperf'),
'port'=>env("DB_PORT",'3306'),
....
]
]
读写分离
主要就三个参数 read write sticky
return [
'dafault'=>[
'driver'=>env('DB_DRIVER','mysql'),
'read'=>[
'host'=>['192.168.1.1'],
],
'write'=>[
'host'=>['192.168.1.2'],
],
'sticky'=>true,
'database'=>env("DB_DATABASE",'hyperf'),
'port'=>env("DB_PORT",'3306'),
....
]
]
.env文件里面放一些 数据库的配置
DB_DRIVER =mysql
DB_HOST =127.0.0.1
DB_PORT=3306
DB_DATABASE=hyperf
DB_USERNAME=root
创建表
php bin/hyperf.php gen:model 表名
增加
获取请求的值和响应 hyperf提供了
Hyperf\HttpServer\Contract\RequestInterface和Hyperf\HttpServer\Contract\ResponseInterface
public function save(RequestInterface $request)
{
$user = $request->query('username');
$res = Db::table('user')->insert(
['username'=>$username]
);
return [
'res'=>$res
];
}
删除
public function del(RequestInterface $request)
{
$id=(int)$request->query('id');
$res = Db::table('user')->where('id','=',$id)->delete();
return [
'res'=>$res
];
}
查询
ps:alt + ctrl 点鼠标 能把缺少的包引入
Query查询类
use Hyperf\DbConnection\Db
查单条
public function getOne()
{
$user = Db::select("select * from `user` where id=?",[1]);//返回array
//或者
User::query()->where('id',1)->get()->toarray();
return $user;
}
查多条
public function getList()
{
$users = Db::select("select * from `users` where id>=? and id<=?",[0,100])
return $users;
}
路由
注解路由
顾名思义就是注解里面有,路由的信息,代码说一下
/**
* @return array
* @GetMapping(path="index3",methods="get,post")
*/
public function index3()
{
return '123';
}
会生成一条域名/index/index3
ps:有时候,有些代码是灰的,用ctrl 鼠标点一下,建议把什么包引入,点完就发现有个包引入了use Hyperf\HttpServer\Annotation\RequestMapping;
注解路由首先是控制器的名 然后是方法名
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
/**
* @Controller()
*/
class UserController
{
// Hyperf 会自动为此方法生成一个 /user/index 的路由,允许通过 GET 或 POST 方式请求
/**
* @RequestMapping(path="index", methods="get,post")
*/
public function index(RequestInterface $request)
{
// 从请求中获得 id 参数
$id = $request->input('id', 1);
return (string)$id;
}
}
route文件的路由
get post head首先了解一下子
config/routes.php文件配置
//意思是只能通过get获取 域名/index 访问到app/controller/IndexController 的index方法 这是方法一
Router::get('/index','App\Controller\IndexController::index');
//还可以用@
Router::get('/index','App\Controller\IndexController@index');
//还可以用数组方式
Router::get('/index',[App\Controller\IndexController::class,'index']);
//注册任意HTTP METHOD路由
Router::addRoute(['GET','POST','HEAD'],'/','App\Controller\IndexController@index')
//闭包定义路由
Router::get('/hello',function (){
return 'hello';
});
//还有一种方式偷懒方法 路由组
Router::addGroup('/user/',function(){
Router::get('index',[App\Controller\UsersController::class,'index']);
Router::post('info',[App\Controller\UsersController::class,'info']);
});
//注册与方法名一致的HTTP METHOD的路由
Router::get($uri,$callback);
Router::post($uri,$callback);
Router::put($uri,$callback);
Router::patch($uri,$callback);
Router::delete($uri,$callback);
Router::head($uri,$callback);
//注解路由 注意三个要点
//1.不要在routes.php文件中增加相关配置
//2.use Hyper\HttpServer\Annotation\AutoController;
//3.添加以下的注释
/**
* @AutoController()
*/
//访问 是通过 域名+/控制器名/+方法名访问
//如果不想用控制器名的话用下面的方法
/**
* @AutoController(prefix="user")
*/
//此时访问 是通过 域名+/user/+方法名 访问
//如果是增加了一层user目录 ,注解是下面这样写的,那怎么访问呢
/**
* @AutoController()
*/
//此时访问是 域名+/user/+/控制器名/+方法名
//ps:plugins 安装php annotations的扩展 就有自动引入的功能了 方便极了
//还有种注解形式配置路由,@Controller 需要搭配 @RequestMapping,@GetMapping,@PostMapping,@PutMapping 等注释一起使用
/**
* @Controller(prefix="index")
*/
class IndexController extends AbstractController
{
/**
* @RequestMapping(path="index", methods={"get","post"})
*/
public function index()
{
return 1;
}
}
//地址访问 域名+/index/+index
//有个小技巧 如果遇到一种情况,不想用注解里面的index,可以将
@RequestMapping(path="/over",methods={"get","post"})
//地址访问 域名+/over
//@GetMapping(path="/over") get访问 域名+/over
在Controller目录下新建IndexController控制器
<?php
namespace App\Controller;
class IndexController extends AbstractController
{
public function index()
{
return 'hi';
}
}
UsersController.php
<?php
namespace App\Controller;
class UsersController extends AbstractController
{
public function index()
{
return 'index';
}
public function info()
{
return 'info';
}
}
hyperf的依赖注入
hyperf里面用到了ioC(控制反转) DI(依赖注入)
容器将依赖注入对象,对象实例化通过容器自动得到外部依赖
由hyperf/di组件提供功能支持
注入方式
通过构造方法注入
namespace App\Service
class userService
{
public function getInfoById(int $id)
{
return (new Info())->fill($id);
}
}
如果其他的类想要用引入这个类的话,应该怎么办呢?
namespace App\Controller;
use App\Service\UserService;
class IndexController
{
/**
* @var UserService
*/
private $userService;
//通过构造函数的参数上声明参数类型完成自动注入
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function index()
{
//此时可以直接使用了
return $this->userService->getInfoById(1);
}
}
通过@inject注解注入
namespace App\Controller;
use App\Service\UserService;
use Hyperf\Di\Annotation\Inject; //这个要引入
class IndexController
{
/**
* 通过'@Inject' 注释注入由 '@var' 注解声明的属性类型对象
*
* @Inject
* @var UserService
*/
private $userService;
public function index()
{
//直接使用
return $this->userService->getInfoById(1);
}
}
注入类型
简单对象注入
抽象对象注入
namespace App\Service;
interface UserServiceInterface
{
public function getInfoById(int $id);
}
class UserService implements UserServiceInterface
{
public function getInfoById(int $id)
{
return (new Info()->fill($id));
}
}
还没完,要在对应的位置进行接口类与实现类的关系绑定
use App\Service\UserServiceInterface;
use App\Service\UserService;
//在config/dependencies.php内完成关系配置
return [
'dependencies'=>[
UserServiceInterface::class=>UserService::class
],
];
controller 怎么使用?
namespace App\Controller;
use App\Service\UserServiceInterface;
use Hyperf\Di\Annotation\Inject;
class IndexController
{
/**
* @Inject
* @var UserServiceInterface
*/
private $userService;
public function index()
{
return $this->userService->getInfoById(1);
}
}
工厂对象注入
namespace App\Service;
class UserService implements UserServiceInterface
{
/**
* @var bool
*/
private $enableCache;
public function __construct(bool $enableCache)
{
//接收值并储存于类属性中
$this->enableCache = $enableCache;
}
public function getInfoById(int $id)
{
return (new Info()->fill($id));
}
}
新建一个工厂类
namespace App\Service;
use Hyperf\Contract\ConfigInterface;
use Psr\Container\ContainerInterface;
class UserServiceFactory
{
//实现一个 __invoke()方法来完成对象的生产
//方法参数会自动注入一个当前的容器实例
public function __invoke(ContainerInterface $container)
{
$config = $container->get(ConfigInterface::class)
};
//我们假设对应的配置的key 为cache.enable
$enableCache = $config->get('cache.enable',false);
//make(string $name,array $paramters=[])方法
//等同于 new,使用make()方法时为了允许AOP的介入 而直接new会导致AOP无法正常介入流程
return make(UserService::class,compact('enableCache'));
}
定义一个工厂类,于__invoke()方法内实现对象的创建并返回
make()函数创建短声明周期对象
ps: compact 函数是取 enableCache的变量的值 就是。
array('enableCache'=>$enableCache);
//compact 就是取的$enableCache;
这还没完,别忘了在配置里面加
use App\Service\UserServiceInterface;
use App\Service\UserServiceFactory;
//config/dependencies.php
return [
'dependencies'=>[
UserServiceInterface::class=>UserServiceFactory::class
],
];
还可以注入容器本身
直接注入 Psr\Container\ContainerInterface
通过Hyperf\Utils\ApplicationContext::getContainer()获得
注意DI容器创建出来的对象是个单例,是个长生命周期的对象,通过$container->make()方法或者make()函数创建短生命周期对象
自定义注解
namespace App\Annotation;
use Hyper\Di\Annotation\AnnotationInterface;
/**
* @Annotation
* @Target({"CLASS","METHOD"})
*/
class Foo implements AnnotationInterface
{
public function collectClass(string $className):void
{
}
public function collectMethod(string $className, $string $target):void
{
}
public function collectProperty(string $className, $string $target):void
{
}
}
//其实不用这么写 因为AbstractAnnotation 已经帮我们做了,用下面的写法
namespace App\Annotation;
use Hyperf\Di\Annotation\AbstractAnnotation;
/**
* @Annotation
* @Target({"CLASS","METHOD"})
*/
class Foo extends AbstracAnnotation
{
/**
* 假如想接收一下属性
* @var string
*/
public $bar;
/**
* @var string
*/
public $bab;
public function __construct($value = null)
{
var_dump($value);
parent::__construct($value);
//如果bab是一个可选参数用下面的写法,控制器文件注解那里@Foo(bar="123", bab="321") 换成 @Foo(123)
$this->bindMainProperty('bar',$value);
}
public function collectClass(string $className):void
{
parent::collectClass($className);
}
}
怎么用?
namespace App\Controller;
use App\Annotation\Foo;
use Hyperf\HttpServer\Annotation\AutoController;
/**
* @AutoController()
* @Foo(bar="123", bab="321")
*/
class IndexController
{
public function index()
{
var_dump(AnnotationCollector::getClassByAnnotation(Foo::class));
return 1;
}
}
//打印结果是个数组
AOP是什么
- 面向切面编程,通过预编译方式或者运行期动态代理实现程序功能的统一维护的一种技术
- AOP是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果
每个方法的前后就是切面。
Hyperf的AOP是基于DI实现的
必须使用的是Hyperf的DI组件,即hyperd/di
必须通过DI创建的对象才能使AOP生效,直接new不行
必须当代理类缓存文件不存在时才会重新生成代理类
AOP的应用场景
-
参数校验,日志,无侵入埋点,安全控制,性能统计,事务处理,异常处理,缓存,无侵入监控,资源池,连接池管理等
例子 -
假设有一个IndexController类
-
通过AOP对该类切入
-
创建一个Aspect类,并在$classes属性定义该类的index方法
class IndexController
{
public function index(RequestInterface $request)
{
return 'hello';
}
}
namespace App\Aspect;
class FooAspect extends AbstractAspect
{
//要切入的类,可以多个,也可以通过::标识到具体的某个方法,通过*可以模糊匹配
public $classes = [
SomeClass::class,
'App\IndexController::index',
];
}
Aspect 实际通过DI得到的类为下图的一个代理类,储存于runtime/container/proxy
是一个继承原类的子类,原类方法会被重写
class IndexController_sdhafkjshdladjla8374 extends IndexController
{
use \Hyper\Di\Aop\ProxyTrait;
public function index(RequesInterface $request)
{
$__function__ = __FUNCTION__;
$__method__ = __METHOD__;
return self::__proxyCall(IndexController::class,__FUNCTION__,self::getParamsMap(IndexController::class,__FUNCTION__,func_get_args(),function(RequestInterface $request) use($__function__,$__method__){
});
}
}
基于AOP的功能
- @Cacheable(通过这个注解定义在任意类的方法上,可以对,对应方法的结果缓存) @CachePut(更新缓存) @CacheEvict(移除缓存)
- @Task 在任意类的任意方法上定义Task注解 ,表明在调用方法时,将任务分配到task work里面执行,意思是假如有同步阻塞的代码,要在协程里面运行,就能用task
- @RateLimit 限流的注解
- @CircuitBreaker 熔断器
- 调用链追踪的无侵入埋点
完整例子
对目标indexController.php前后添加业务逻辑
namespace App\Controller;
use Hyper\HttpServer\Annotation\AutoController;
/**
* @AutoController()
*/
class IndexController
{
public function index()
{
return 2;
}
}
1.首先在app下面新建一个文件夹,Aspect
2.新建一个方法indexAspect.php
namespace App\Aspect;
use Hyperf\Di\Aop\AbstractAspect; //这里
use Hyperf\Di\Aop\ProcessdingJoinPoint; //这里
/**
* @Aspect() //这里
*/
class IndexAspect extends AbstractAspect
{
//定义要切入的类 这里
public $classes = [
IndexController::class.'::'.'index',
];
public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
//to do 目标类之前 可以添加点逻辑
var_dump('before');
$rusult = $proceedingJoinPoint::process();
//to do 在目标类之后,可以添加逻辑
var_dump('after');
return 'before'.$result.'after';
}
}
注意:此时如果将indexController.php 中返回值改成3,发现返回值还是before2after
是因为有代理类缓存, 将app/runtime/container 删除
命令:rm -rf runtime runtime/container && php bin/hyperf.php start
例子2
namespace App\Aspect;
use Hyperf\Di\Aop\AbstractAspect; //这里
use Hyperf\Di\Aop\ProcessdingJoinPoint; //这里
/**
* @Aspect() //这里
*/
class IndexAspect extends AbstractAspect
{
//定义要切入的类 这里
public $annotations = [
Foo::class,
];
public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
$result = $proceedingJoinPoint->process();
$bar = 0;
$foo = $proceedingJoinPoint->getAnnotationMetadata()->class[Foo::class];//这里可以换成根据不同的类获取
$bar = $foo->bar;
return $result+$bar;
}
}
namespace App\Controller;
use App\Annotation\Foo;
use Hyperf\HttpServer\Annotation\AutoController;
/**
* @AutoContraller()
* @Foo(bar="5")
*/
class IndexController
{
public function index()
{
return 3;
}
}
如何接收参数,保证数据不会混淆
namespace App\Controller;
use Hyper\HttpServer\Annotation\AutoController;
use Hyper\HttpServer\Contract\RequestInterface;
/**
* @AutoController()
*/
class CoController
{
private $foo = 1;
public function get()
{
return $this->foo;
}
public function index(Requestinterface $request)
{
$foo = $request->input('foo');
$this->foo = $foo;
return $this->foo;
}
}
//这样取有个不好的地方,就是有一个地方改了,所有的地方的foo获取的值都变了
那怎么处理?
namespace App\Controller;
use Hyper\HttpServer\Annotation\AutoController;
use Hyper\HttpServer\Contract\RequestInterface;
use Hyper\Utils\Context;
/**
* @AutoController()
*/
class CoController
{
private $foo = 1;
public function get()
{
return Context::get('foo','null');
}
public function index(Requestinterface $request)
{
$foo = $request->input('foo');
Context::set('foo',$foo);
return Context::get('foo');
}
}
如果不想这么写。还可以通过魔术方法__get __set获取
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\Context;
/**
* @AutoController()
* @property int $foo //用于消除foo的下划线
*/
class CoController
{
public function get()
{
return $this->foo;
}
public function index(Requestinterface $request)
{
$foo = $request->input('foo');
$this->foo = $foo;
return $this->foo;
}
public function __get($name)
{
return Context::get(__CLASS__.":".$name);
}
public function __set($name)
{
return Context::set(__CLASS.':'.$name,$value);
}
}
为了不影响接收到的参数混淆,因为他是单例,所以要把值放到上下文。
Hyperf里的协程三种方式
1.根据hyper\Utils\Coroutine
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
/**
* @AutoController()
*/
class CoController
{
public function test()
{
Coroutine::create(function(){
sleep(1);
var_dump(1);
});
}
}
2.swoole提供的全局
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
/**
* @AutoController()
*/
class CoController
{
public function test()
{
go(function(){
sleep(1);
var_dump(1);
});
}
}
3.还有一种方式
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
/**
* @AutoController()
*/
class CoController
{
/**
* @Inject()
* @var \Hyperf\Guzzle\ClientFactory
*/
private $clientFactory;
public function sleep(Requestinterface $request)
{
$seconds = $request->query('seconds',1);
sleep($seconds);
return $seconds;
}
public function test()
{
//如果不使用协程
//$client = $this->clientFactory->create();
//$client->get('127.0.0.1:9501/co/sleep?seconds=2');
//$client->$this->clientFactory->create();
//$client->get('127.0.0.1:9501/co/sleep?seconds=2');
//return 1;
//此时耗时4s 输出1
//使用协程
$channel = new Channel();
var_dump(1);
co(function()use($channel){
//sleep(1);
$client = $this->clientFactory->create();
var_dump(2);
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
var_dump(3);
$channel->push(123);
});
var_dump(4);
co(function()use($channel){
//sleep(1);
$client = $this->clientFactory->create();
var_dump(5);
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
var_dump(6);
$channel->push(321);
});
var_dump(7);
//拿到协程里面push的值
$result[] = $channel->pop();
var_dump(8);
$result[] = $channel->pop();
return $result;
}
}
//1 2 4 5 7 3 8 6
下面用WaitGroup实现
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
/**
* @AutoController()
*/
class CoController
{
/**
* @Inject()
* @var \Hyperf\Guzzle\ClientFactory
*/
private $clientFactory;
public function sleep(Requestinterface $request)
{
$seconds = $request->query('seconds',1);
sleep($seconds);
return $seconds;
}
public function test()
{
//使用协程
$wg = new WaitGroup();//new 一个实例
$result = [];
$wg->add(2);//几个协程就add几个 调用add方法创建协程
co(function()use($wg, &$result){
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
$result[] = 123;
$wg->done();//调用done方法,意思是协程执行完毕了,给add的计数器减1
});
co(function()use($wg, &$result){
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
$result[] = 321;
$wg->done();
});
$wg->wait();//等待 add的计数器变为0
return $result;
}
}
这种写法还是不简洁,Hyperf提供了一种写法
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
/**
* @AutoController()
*/
class CoController
{
/**
* @Inject()
* @var \Hyperf\Guzzle\ClientFactory
*/
private $clientFactory;
public function sleep(Requestinterface $request)
{
$seconds = $request->query('seconds',1);
sleep($seconds);
return $seconds;
}
public function test()
{
$parallel = new Parallel();
$parallel->add(funcion(){
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
return 123;
},'key1');
$parallel->add(funcion(){
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
return 321;
},'key2');
$result = $parallel -> wait();//wait会返回一个结果
return $result;
}
}
//返回结果
//{'key1':123,'key2':321}
//如果不想要键,把key1 key2删掉
如果还是嫌麻烦 ,还有一个parallel全局函数的方法
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
/**
* @AutoController()
*/
class CoController
{
/**
* @Inject()
* @var \Hyperf\Guzzle\ClientFactory
*/
private $clientFactory;
public function sleep(Requestinterface $request)
{
$seconds = $request->query('seconds',1);
sleep($seconds);
return $seconds;
}
public function test()
{
$result = parallel([
funcion(){
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
return 123;
},
funcion(){
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/co/sleep?seconds=2');
return 321;
}
]);
return $result;
}
}
hyperf 配置文件的三种获取
第一种通过依赖注入
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Di\Annotation\Inject;
/**
* @AutoController()
*/
class ConfigController
{
/**
* @Inject()
* @var \Hyperf\Contract\ConfigInterface
*/
private $config;
public function inject()
{
return $this->config->get('foo.bar',123);//第一个参数这个就是配置文件的key,第二个参数是默认值,如果没有配置的话,就是123,有的话就是321
}
}
config/config.php
return[
'foo'=>[
'bar' => 321;
],
];
也可以不改配置文件,在config/autoload 文件夹 增加一个foo.php文件
return [
'bar'=>1234,
];
第二种 通过value 取的
namespace App\Controller;
use Hyperf\Config\Annotation\Value;
use Hyper\HttpServer\Annotation\AutoController;
/**
* @AutoController()
*/
class ConfigController
{
/**
* @Value("foo.bar")
*/
private $bar;
public function value()
{
return $this->bar;
}
}
第三种形式
namespace App\Controller;
use Hyperf\Config\Annotation\Value;
use Hyper\HttpServer\Annotation\AutoController;
/**
* @AutoController()
*/
class ConfigController
{
public function config()
{
return config('foo.bar',123);
}
}
hyperf中间件
中间件主要适用于编织从请求到响应的整个流程,通过对多个中间件的组织,使数据的流动按照我们预定的方式进行,中间件的本质是一个洋葱模型。
就是请求来了到1到2到3然后到2到1 到响应
命令 php bin/hyperf.php gen:middleware FooMiddleware
php bin/hyperf.php gen:middleware BarMiddleware
php bin/hyperf.php gen:middleware BazMiddleware
然后。Middleware就会产生三个文件 BarMiddleware.php ,BazMiddleware.php,FooMiddleware.php
在indexController.php 演示一遍
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;
use Hyperf\Middleware\FooMiddleware;
use Hyperf\Middleware\BarMiddleware;
use Hyperf\Middleware\BazMiddleware;
/**
* @AutoController()
* @Middlewares(
* @Middlewares(BarMiddleware::class),
* @Middlewares(BazMiddleware::class)
* )
*/
class IndexController
{
//如果想要只是index方法 多使用一个FooMiddleware一个中间件 那加下面的注解
/**
* @middleware(FooMiddleware::class)
*/
public function index()
{
return 'index';
}
}
//如果FooMiddleware是全局的中间件,那就在config/autoload/middlewares.php 里增加
use App\Middleware\FooMiddleware;
return [
'http'=>[
FooMiddleware::class,
],
];
//这里的http 要和 config/autoload/server.php 里面的匹配
//执行顺序 先全局 后类注解 之后是方法上的注解
//如果用中间件用注解来表示的话,那此时路由也必须用注解路由 @AutoController
值在中间件能传递,但是在controller是取不到值的,这是因为controller的值是通过上下文获取到,怎么改写呢?
FooMiddleware.php
class FooMiddleware
{
public function process(ServerRequestInterface $request,RequestHandlerInterface $handler)
{
$request =Context::override(ServerRequestInterface::class,function()use($request){
return $request->withAttribute('foo',1);
});
$response = $handler->handle($request);
return $response->withBody(new SwooleStream($body.'foo'));
}
}
hyperf异常处理(工作必用)
在hyperf里,业务代码都运行在Worker进程上,这就意味着一旦任意一个请求的业务存在没有捕获处理的异常的话,都会导致对应的worker进程被中断退出。
我们可以通过对各个server定义不同的异常处理器,一旦业务流程存在没有捕获的异常,都会被传递到已注册的异常处理器去处理
创建一个TestController.php
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
class TestController
{
//协程,每秒输出一个1,不停止的话,是不会中断的
public function co()
{
co(function(){
while(true){
echo 1;
sleep(1);
}
});
return 1;
}
public function exception()
{
throw new \RuntimeException('test');
}
}
创建一个异常类 Exception/FooException.php
namespace App\Exception;
class FooException extends
创建handler.php Exception/Handler/FooExceptionHandler.php
namespace App\Exception\Handler;
use App\Exception\FooException;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class FooExceptionHandler extends ExceptionHandler
{
public function handle(Throwable $throwable, ResponseInterface $response)
{
$this->stopPropagation();//意味异常到这里就停止了
return $reponse->withStatus(500)->withBody(new SwooleStream('this is the FooExceptionHandler'));
}
public function isValid(Throwable $throwable):bool
{
return $throwable instanceof FooException;
}
}
然后要配置
在config/autoload/exception.php
use App\Exception\Handler\FooExceptionHandler;
return[
'handler'=>[
'http'=>[
FooExceptionHandler::class,
App\Exception\Handler\AppExceptionHandler::class,
],
],
];
事件机制
举个例子
创建app/service/UserService.php
namespace App\Service;
use App\Event\UserRegistered;
use Hyperf\Di\Annotation\Inject;
class UserService
{
/**
* @Inject()
* @var \Psr\EventDispatcher\EventDispatcherInterface
*/
private $eventDispatcher;
public function register()
{
//注册成功前
$beforeUserRegister = new BeforeUserRegister();
$this->eventDispatcher->dispatch($beforeUserRegister);
//注册用户 如果是true就去注册
//写一个监听器 php bin/hyperf.php gen:listener ValidateRegisterListener 在app/Listener生成ValidateRegisterListener.php
if($beforeUserRegister->shouldRegister)
{
$userId=rand(0,9999);
}else{
//不注册
return false;
}
//注册成功后
if($userId){
$this->eventDispatcher->dispatch(new UserRegistered($userId));
//然后把发短信发邮件,放到监听器里
//php bin/hyperf.php gen:listener SendSmsListener
//然后app/Listener文件就有了 SendSmsListener.php
//$this->sendSms();
//$this->sendEmail();
//同步用户信息到其他系统
//将用户注册的信息收集到用户行为分析系统
}
return $userId;
}
public function sendSms()
{
//发送短信
return true;
}
public function sendEmail()
{
return true;
}
}
随着逻辑的越来越复杂,我们需要将功能进行解耦
创建 App/Event/UserRegistered.php
namespace App\Event;
class UserRegistered
{
/**
* @var int
*/
public $userId;
public function __construct(int $userId)
{
$this->userId = $userId;
}
}
打开SendSmsListener.php
declare(strict_type=1);
namespace App\Listener;
use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Psr\Container\ContainerInterface;
use Hyperf\Event\Contract\ListenerInterface;
/**
* @Listener(priority=10) 括号里面加权重值 格式为priority = 权重值
*/
class SendEmailListenr implements ListenerInterface
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function listen():array
{
return [
UserRegistered::class,
];
}
/**
* @param UserRegistered $event
*/
public function process(object $event)
{
echo '发送短信给'.$event->userId.PHP_EOL;
}
}
创建一个EventController.php
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
/**
* @AutoController()
*/
class EventController
{
/**
* @Inject()
* @var \App\Service\UserService
*/
private $userService;
public function test()
{
return $this->userService->register();
}
}
//这是用户注册之后
如果想要在用户之前,附加业务逻辑
创建app/Event/BeforeUserRegister.php
就是来风控
namespace App\Event;
class BeforeUserRegister
{
/**
* @var bool
*/
public $shouldRegister = true;
}
打开ValidateRegisterListener.php
declare(strict_type=1);
namespace App\Listener;
use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Psr\Container\ContainerInterface;
use Hyperf\Event\Contract\ListenerInterface;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function listen():array
{
return [
BeforeUserRegister::class,
];
}
/**
* @param BeforeUserRegister $event
*/
public function process(object $event)
{
$event->shouldRegister = (bool)rand(0,1);
}
当然也可以监听多个事件
declare(strict_type=1);
namespace App\Listener;
use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Psr\Container\ContainerInterface;
use Hyperf\Event\Contract\ListenerInterface;
/**
* @Listener(priority=10) 括号里面加权重值 格式为priority = 权重值
*/
class SendEmailListenr implements ListenerInterface
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function listen():array
{
return [
BeforeUserRegister::class,
UserRegistered::class,
];
}
/**
* @param UserRegistered $event
*/
public function process(object $event)
{
if($event instanceof BeforeUserRegister){
echo get_class($event).$event->shouldRegister;
}elseif($event instanceof UserRegistered){
echo get_class($event).$event->userId;
}
}
}
请求和响应整理
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Hyperf\HttpServer\Contract\RequestInterface;(保证数据不会在协程中来回调用)
use Hyperf\HttpServer\Request;
use Hyperf\HttpMessage\Base\Request;
use Hyperf\HttpMessage\Server\Request;
use Swoole\Http\Request;
use Swoole\Http2\Request;
use Psr\Http\Message\ResponseInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\HttpServer\Response;
use Hyperf\HttpMessage\Base\Response;
use Hyperf\HttpMessage\Server\Response;
use Swoole\Http\Response;
use Swoole\Http2\Response;
ps:phpstrom中自动开启hyperf
1.edit Configurations
2.PHP Script
3.添加入下图
表单验证
创建一个表单验证类
在Request文件夹下创建TestRequest.php
namespace App\Request\Test;
use Hyperf\Validation\Request\FormRequest;
class TestRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'id' => 'required|int',//id必须的 并且是zheng'xing
'reason' => 'string',
];
}
}
控制器里TestController.php
/**
* 测试
* @RequestMapping(methods="get,post")
* @param TestRequest $request
* @return array
*/
public function test(TestRequest $request)
{
try {
$params = $request->validated();
return ;
} catch (\Throwable $e) {
return ;
}
}
hyperf用mq实现 导出功能
实现思路,点击导出,将导出任务标识,是否跨系统,当前导出状态,查询条件,mq配置信息标识落表。
添加配置,设置exchage_name,queue_name,route_key,成功后回调的地址。
在当前系统或者别的系统创建消费者,来消费,将excel上传到
云空间,成功后ack掉,并将表里的状态改成完成,且将云空间的地址落表
/**
* @Consumer(exchange="test_export", routingKey="test_export", queue="test_export", name="TestExportConsumer", nums=1)
*/
class TestExportConsumer extends ConsumerMessage
{
use Trace; //hyperf composer require hyperf/tracer
//用来追踪的
//导出扩展 为了方便
public const EXPORT_TYPE_INVOICE = 1;//
//导出状态
public const COMPLETED_STATUS = 3; //导出完成
public const EXPORT_ERROR = 5; //导出错误
/**
* @Inject
* @var ClientFactory
*/
private $clientFactory;
/**
* 消费
* @param $exportData
* @throws \GuzzleHttp\Exception\GuzzleException
* @return string
*/
public function consume($exportData): string
{
$this->putTraceId();//
if (! $exportData) {
//记录日志
return Result::ACK;
}
//区分导出编码,用source 标示来源
$localExportCode = $exportData['taskCode'] . '_' . $exportData['source'];
//回调使用的请求对象
$client = $this->clientFactory->create();
//这里拼接参数 回调的数据
$options['query'] = ['导出状态' => $exportData['status'], 'url' => ''];
try {
//拼接数据,上传云空间 返回地址
$url = (self::getExportService(self::EXPORT_TYPE_INVOICE))->export($exportData['queryParams'],$localExportCode);
$options['query']['url'] = $url;
$options['query']['task_status'] = self::COMPLETED_STATUS;
//记录日志
return Result::ACK;
} catch (\Throwable $e) {
//记录日志抛出异常
return Result::NACK;
} finally {
//回调通知 获取mq中的回调地址(这个地址是从mq获取到的)
$response = $client->get($exportData['回调地址'], $options);
$responseContents = $response->getBody()
->getContents();
$responseContents = json_decode($responseContents, true);
//记录日志
}
}
/**
* 获取导出对象 1.导出
* @param int $exportType
* @return null|TestServicesExport|mixed
*/
private static function getExportService(int $exportType)
{
switch ($exportType) {
case self::EXPORT_TYPE_INVOICE:
return self::getTestServicesExportService();
default:
throw new \RuntimeException('未知的导出类型');
}
}
/**
* 导出
* @return null|TestServicesExport|mixed
*/
private static function getTestServicesExportService()
{
$testServicesExport = Context::get('testServicesExport');
if (! $testServicesExport instanceof TestServicesExport) {
$testServicesExport = new TestServicesExport();
Context::set('testServicesExport', $testServicesExport);
}
return $testServicesExport;
}
}
实现一个队列
假如需要在执行完某个操作后,发送短信
创建一个生产者
在app/Amqp/Producer/ 目录创建一个文件
Sms.php
<?php
declare(strict_types=1);
namespace App\Amqp\Producer;
use Hyperf\Amqp\Annotation\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
use Hyperf\Amqp\Message\Type;
use Sht\Helper\Log;
/**
* 某个功能成功后 发送提示短信队列.
* @Producer(exchange="ms.send", routingKey="sms")
*/
class Sms extends ProducerMessage
{
// 类型
protected $type = 'dirct';
/**
* 生产.
* @param $data
*/
public function __construct($data)
{
// 记录推送日志
// 推送消息
$this->payload = $data;
}
}
在service
app/modules/services/ 创建服务文件
SmsService.php
<?php
declare(strict_types=1);
namespace App\Modules\Services;
use Hyperf\Amqp\Producer;
use Hyperf\DbConnection\Db;
use Sht\Helper\Log;
use Sht\Helper\Redis;
class AccountStatementService
{
private static function sendSms($mobile, $text)
{
if (empty($mobile) || empty($text)) {
return;
}
$data = [
'mobiles' => (string) $mobile,
'text' => $text,
];
// 执行推送
di(Producer::class)->produce(new Sms($data));
}
}
未完待续~~