Lumen5.X使用频率限制组件笔记

编写中间件,是根据vendor/illuminate/routing/Middleware/ThrottleRequests.php改写

备注:需要先配置cache

<?php

namespace App\Http\Middleware;

use Closure;
use Carbon\Carbon;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

class ThrottleMiddleware
{
    /**
     * The rate limiter instance.
     *
     * @var \Illuminate\Cache\RateLimiter
     */
    protected $limiter;

    /**
     * Create a new request throttler.
     *
     * @param  \Illuminate\Cache\RateLimiter  $limiter
     * @return void
     */
    public function __construct(RateLimiter $limiter)
    {
        $this->limiter = $limiter;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  int  $maxAttempts
     * @param  float|int  $decayMinutes
     * @return mixed
     */
    public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
    {
        $key = $this->resolveRequestSignature($request);

        if ($this->limiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
            return $this->buildResponse($key, $maxAttempts);
        }

        $this->limiter->hit($key, $decayMinutes);

        $response = $next($request);

        return $this->addHeaders(
            $response, $maxAttempts,
            $this->calculateRemainingAttempts($key, $maxAttempts)
        );
    }
    
    /**
     * Resolve request signature.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    protected function resolveRequestSignature($request)
    {
        return sha1(
            $request->method() .
            '|' . $request->server('SERVER_NAME') .
            '|' . $request->path() .
            '|' . $request->ip()
        );
    }
//    protected function resolveRequestSignature($request)
//    {
//        return $request->fingerprint();
//    }

    /**
     * Create a 'too many attempts' response.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function buildResponse($key, $maxAttempts)
    {
        $response = new Response('请求超出设定频率', 429);
        $retryAfter = $this->limiter->availableIn($key);

        return $this->addHeaders(
            $response, $maxAttempts,
            $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
            $retryAfter
        );
    }

    /**
     * Add the limit header information to the given response.
     *
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @param  int  $maxAttempts
     * @param  int  $remainingAttempts
     * @param  int|null  $retryAfter
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
    {
        $headers = [
            'X-RateLimit-Limit' => $maxAttempts,
            'X-RateLimit-Remaining' => $remainingAttempts,
        ];

        if (! is_null($retryAfter)) {
            $headers['Retry-After'] = $retryAfter;
            $headers['X-RateLimit-Reset'] = Carbon::now()->getTimestamp() + $retryAfter;
        }

        $response->headers->add($headers);

        return $response;
    }

    /**
     * Calculate the number of remaining attempts.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @param  int|null  $retryAfter
     * @return int
     */
    protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
    {
        if (is_null($retryAfter)) {
            return $this->limiter->retriesLeft($key, $maxAttempts);
        }

        return 0;
    }
}
//使用笔记
'middleware' => 'throttle:2,0.5'

Laravel队列使用案例

1、配置数据库和Redis
	//编辑config/database.php
	'mysql' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'port' => 3306,
            'database' => 'demo_laravel',
            'username' => 'demo',
            'password' => '123456',
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix' => '',
            'strict' => false,
            'engine' => null,
        ],
	'queue' => [
            'host' => '192.168.56.101',
            'password' => null,
            'port' => 6379,
            'database' => 0,
        ],
	//config/queue.php
	'default' => 'redis',
	'connections' => [
        'redis' => [
            'driver' => 'redis',
            'connection' => 'queue',
            'queue' => 'default',
            'expire' => 3600,//队列有效期
        ],

    ],
	'failed' => [
        'database' => 'mysql',
        'table' => 'failed_jobs',
    ],
2、添加failed-table
执行:php artisan queue:failed-table
执行:php artisan migrate:install
执行:php artisan migrate
3、生产者
将数据丢入队列
执行:php artisan make:console Demo/Test
<?php

namespace App\Console\Commands\Demo;

use App\Jobs\Demo\HandleTest;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;

class Test extends Command
{
    use DispatchesJobs;
    /**
     * 将数据丢入队列
     */
    protected $signature = 'demo:test {--num=}';
    protected $description = '将数据丢入队列';
    public function __construct()
    {
        parent::__construct();
    }
    public function handle()
    {
        $num = $this->option('num');
        for($i = 0; $i <= $num; $i++) {
            $queueName = 'demo_1';//这里可以按照业务取模之后启动多个队列
            $job = (new HandleTest($i))->onConnection('redis')->onQueue($queueName);
            $this->dispatch($job);
        }
    }
}


4、消费者
将消费队列内容
php artisan make:job Demo/HandleTest
app/Jobs/Demo/HandleTest.php
<?php

namespace App\Jobs\Demo;

use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class HandleTest extends Job implements ShouldQueue
{
    use InteractsWithQueue, SerializesModels;
    //初始化数据
    public function __construct($data)
    {
        $this->data = $data;
    }
    public function handle()
    {
        echo $this->data . PHP_EOL;
    }
}
5、启动生产者
php artisan demo:test --num=2
//这样可以看到redis的queues:demo_1
在redis执行:LRANGE queues:demo_1 0 -1
可以看到3个元素
6、启动消费队列(可以使用supervisor管理消费队列)
php artisan queue:work redis --queue=demo_1 --daemon --tries=10
7、队列失败
如果队列执行失败则会将任务存入failed_jobs表
重试JOB可以执行 php artisan queue:retry	1

Laravle Queue命令

php artisan queue:work --help
Usage:
  queue:work [options] [--] [<connection>]

Arguments:
  connection             队列连接redis、database等

Options:
      --queue[=QUEUE]    队列任务
      --daemon           后台执行
      --delay[=DELAY]    任务执行失败之后延迟多久重试
      --force            Force the worker to run even in maintenance mode
      --memory[=MEMORY]  The memory limit in megabytes [default: 128]
      --sleep[=SLEEP]    队列无可用任务休息时间间隔,默认3s
      --tries[=TRIES]    失败任务最多重试次数 [default: 0]
  -h, --help             输出帮助信息
  -q, --quiet            不输出信息
  -V, --version          Display this application version
      --ansi             Force ANSI output
      --no-ansi          Disable ANSI output
  -n, --no-interaction   Do not ask any interactive question
      --env[=ENV]        The environment the command should run under.
  -v|vv|vvv, --verbose   Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
使用案例
php artisan queue:work redis --queue=recommend --daemon --tries=1
备注:
queue:work 默认只执行一次队列请求, 当请求执行完成后就终止;
queue:listen 监听队列请求, 只要运行着, 就能一直接受请求, 除非手动终止;
queue:work --daemon 同 listen 一样, 只要运行着, 就能一直接受请求, 不一样的地方是在这个运行模式下, 当新的请求到来的时候, 不重新加载整个框架, 而是直接 fire 动作.
能看出来, queue:work --daemon 是最高级的, 一般推荐使用这个来处理队列监听.

注意: 使用 queue:work --daemon , 当更新代码的时候, 需要停止, 然后重新启动, 这样才能把修改的代码应用上.
因此开发环境建议用queue:listen


php artisan queue:listen --help
Usage:
  queue:listen [options] [--] [<connection>]

Arguments:
  connection               The name of connection

Options:
      --queue[=QUEUE]      The queue to listen on
      --delay[=DELAY]      Amount of time to delay failed jobs [default: 0]
      --memory[=MEMORY]    The memory limit in megabytes [default: 128]
      --timeout[=TIMEOUT]  Seconds a job may run before timing out [default: 60]
      --sleep[=SLEEP]      Seconds to wait before checking queue for jobs [default: 3]
      --tries[=TRIES]      Number of times to attempt a job before logging it failed [default: 0]
  -h, --help               Display this help message
  -q, --quiet              Do not output any message
  -V, --version            Display this application version
      --ansi               Force ANSI output
      --no-ansi            Disable ANSI output
  -n, --no-interaction     Do not ask any interactive question
      --env[=ENV]          The environment the command should run under.
  -v|vv|vvv, --verbose     Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Laravel 权限控制基础之Gate 和Policy

policy和Gate
php artisan make:policy PostPolicy –model=Post //特定model
//AuthServiceProvider.php在定义权限
//Gate::define(‘view-post’, ‘ClassName@methodOne’);
Gate::define(‘show-info’, function ($test, $post) {
return $test->owns($post);
});
//user.php
public function owns($post)
{
return $this->id == $post->user_id;
}
//控制器
use Gate;
auth()->loginUsingId(1);
$test = TestModel::findOrFail(1);
//Gate::allows();//判断是否允许
if(Gate::denies(‘show-info’,$test)) {
echo ‘禁止访问’;
} else {
echo ‘允许访问’;
}
//可以用policy替换Gate
//AuthServiceProvider.php
protected $policies = [
‘app\Models\TestModel’ => ‘app\Policies\PostPolicy’,
];
$user = auth()->loginUsingId(1);
$test = TestModel::findOrFail(1);
echo $user->id;
echo $test->user_id;
if ($user->cannot(‘show-info’, $test)) {
echo ‘禁止访问’;
} else {
echo ‘允许访问’;
}
对于Gate来定义和policy替换, 在一个项目中通常两种方式都使用,如果访问控制的逻辑非常简单,且只需用到一个方法,那么直接写成Gate的闭包即可,如果比较复杂,且需要对模型进行多个方法的权限控制,那么就写成policy

Laravel :notification使用笔记

php artisan make:notification PayFinish
php artisan notifications:table 创建通知表
//TestModel
namespace app\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
class TestModel extends Model
{
use Notifiable;
protected $table = ‘test’;
}
//PayFinish
public function via($notifiable)
{
return [‘database’];
}
//toDatabase和toArray格式化消息内容
public function toArray($notifiable)
{
return [
‘test_id’ => $this->id,
‘name’ => $this->data->name,
];
}
//使用
$user = TestModel::find(1);
//$user->notify(new PayFinish($user));
foreach ($user->notifications as $notification) {
print_r($notification->data);
}
foreach ($user->unreadNotifications as $notification) {
print_r($notification->data);
//$notification->markAsRead();//标记已读
}
//Notification::send($user, new PayFinish());

Laravel Migrate修改表和创建表

php artisan make:migration create_table_test –table=test_a 修改表

Schema::table(‘test’, function (Blueprint $table) {
$table->dropColumn([‘data’]);
$table->renameColumn(‘tttt’,’test’);
$table->addColumn(‘string’,’t_id’, [‘length’ => 200]);
});

php artisan make:migration create_table_test –create=test_a 创建表
Schema::create(‘users’, function (Blueprint $table) {
$table->increments(‘id’);
$table->string(‘name’);
$table->string(’email’, 150)->unique();
$table->string(‘password’);
$table->rememberToken();
$table->timestamps();
});

 

需要添加:

{"require": {"doctrine/dbal": "v2.5.5"}}

Laravel middleware中间件

Laravel Middleware 中间件

php artisan make:middleware TestMiddleware

public function handle($request, Closure $next)
{
if($request->input(‘id’) > 1) {
die( ‘hello world’ );
}
return $next($request);
}
注册中间件
app/Http/Kernel.php$middleware添加

控制器当中
$this->middleware(‘Test’);
路由器
Route::get(‘/’,’Test\\TestController@index’, [‘middleware’ => ‘Test’]);

Laravle Job 案例

make:job 创建一个新JOb类

protected $msg;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($msg)
{
$this->msg = $msg;
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
echo date(‘Y-m-d H:i:s’).”=>”.$this->msg .”\n”;
}
控制器使用案例
for($i = 0; $i < 10; $i ++) {
$job = new TestJob(‘hello ‘. $i);
$this->dispatch($job);
}

Laravel Command命令行

make:command php artisan make:command Test/Test生成新命令
在app/Console/Kernel.php文件当中注册命令
protected $commands = [
//
TestCommand::class,
];
编辑命令
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = ‘test:test {name} {date?} {default=default} {–op1=}’;
//{name} 必须参数 {date?} 非必须 {default=default}默认参数 {–op1}设置项
/**
* The console command description.
*
* @var string
*/
protected $description = ‘命令描述’;

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$argument = $this->argument();
print_r($argument);
echo $this->option(‘op1’);
$this->info(‘提示信息’);
}
执行命令
php artisan test:test name1 date1 default –op1=option1
//程序调用
Artisan::call(“test:test”, array(‘name’ => ‘test’, ‘date’ => ‘date1’, ‘default’ => ‘def’,’–op1′ => ‘option’));

Laravel Event和Listener

make:event TestEvent 创建一个新事件类
php artisan make:listener TestListener –event TestEvent 创建一个listener类
注册事件TestEvent
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($str)
{
$this->data = $str;
}

/**
* Get the channels the event should broadcast on.
*
* @return Channel|array
*/
public function broadcastOn()
{
return [];
}
监听时间TestListener
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Handle the event.
*
* @param TestEvent $event
* @return void
*/
public function handle(TestEvent $event)
{
file_put_contents(‘/data/tmp/test.log’, $event->data, FILE_APPEND);
}
注册应用关联事件EventServiceProvider.php
protected $listen = [

‘app\Events\TestEvent’ => [
‘app\Listeners\TestListener’,
],
];
触发事件
$str = ‘test….’;
Event::fire(new TestEvent($str));
event(new TestEvent($str));

php artisan event:generate 批量生成event和listen 编辑EventServiceProvider.php

备注:事件可以广播,通过websocket服务,依赖Redis的pub和sub模式