thinkphp6:根据ip和时间用redis做限流(thinkphp v6.0.12LTS)

一,创建路由 / 配置中间件/配置redis

创建controller:
liuhongdi@lhdpc:/data/php/imgtouch$ php think make:controller Serve
Controller:app\controller\Serve created successfully.
创建中间件:
liuhongdi@lhdpc:/data/php/imgtouch$ php think make:middleware ServeLimit
Middleware:app\middleware\ServeLimit created successfully.
查看创建的middleware
liuhongdi@lhdpc:/data/php/imgtouch$ ls app/middleware
CheckJwt.php  ServeLimit.php
查看创建的controller
liuhongdi@lhdpc:/data/php/imgtouch$ ls app/controller/
Auth.php  Home.php  Image.php  Index.php  Serve.php
配置路由和中间件
route/app.php:
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\facade\Route;
 
Route::group('home', function () {
    Route::get('home', 'home/home');
    Route::post('postdata', 'home/postdata');
});
 
Route::group('serve', function () {
    Route::get('image', 'serve/image');
})->middleware(\app\middleware\ServeLimit::class);
配置redis:
.env
[REDIS0]
TYPE = redis
HOST = 127.0.0.1
PORT = 6379
PASSWORD =

说明:刘宏缔的架构森林是一个专注架构的博客,

网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/06/03/thinkphp6-gen-ju-ip-he-shi-jian-yong-redis-zuo-xian-liu/

         对应的源码可以访问这里获取: https://github.com/liuhongdi/
         或: https://gitee.com/liuhongdi

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,php代码:

1,ServeLimit.php
<?php
declare (strict_types = 1);

namespace app\middleware;

use app\result\Result;
use log\BusinessLog;
class ServeLimit
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return \think\response\Json
     */
    public function handle($request, \Closure $next){

            //得到当前IP
            $ip = $request->ip();
            //判断是否可访问,1:可访问 0:不可访问
            $isCan = $this->isCanAccess($ip);
            if ($isCan == "1"){
                return $next($request);
            } else {
                return Result::ErrorCode(1,"访问超出次数限制");
            }
    }
    //判断是否允许访问
    //参数:ip地址
    //返回:1:可访问 0:不可访问
    private function isCanAccess($ip) {
          $day = date("Ymd");
          $key = $day."_".$ip;
          //访问redis,用lua脚本判断是否可访问
        $lua = <<<SCRIPT
local key = KEYS[1];
local limit = tonumber(KEYS[2])
local duration = tonumber(KEYS[3])
--redis.log(redis.LOG_NOTICE,' duration: '..duration)
local current = redis.call('GET', key)
if current == false then
   --redis.log(redis.LOG_NOTICE,key..' is nil ')
   redis.call('SET', key,1)
   redis.call('EXPIRE',key,duration)
   --redis.log(redis.LOG_NOTICE,' set expire end')
   return '1'
else
   --redis.log(redis.LOG_NOTICE,key..' value: '..current)
   local num_current = tonumber(current)
   if num_current+1 > limit then
       return '0'
   else
       redis.call('INCRBY',key,1)
       return '1'
   end
end
SCRIPT;

        $redis = new \Redis();
        $redisHost = env('redis0.host', '127.0.0.1');
        $redisIp = env('redis0.port', '6379');

        $redis->connect($redisHost,(int)$redisIp);
        $redis->select(0);
        $duration = 3600 * 24;   //时长,以秒为单位
        $number = 3;      //允许访问的次数
        $isSucc = $redis->eval($lua, [$key,$number,$duration], 3);
        //记录
        $log = new BusinessLog('test');
        $log->log("isSucc:".$isSucc.":");
        return $isSucc;
    }
}
2,Serve.php
<?php
declare (strict_types = 1);

namespace app\controller;

use app\result\Result;
use think\Request;

class Serve
{

    //测试一个get方式的访问
    public function image() {
        return Result::Success(["msg"=>'this is serve image']);
    }
}
3,BusinessLog.php
<?php
namespace log;

class BusinessLog{

    var $baseDir;
    var $subDir;
    var $fileName;
    //构造
    function __construct($type){
        if ($type == "test") {
            $this->baseDir = "/data/businesslog/testlog";
            $this->subDir = date("Ym");
            $this->fileName = "test_".date("Ymd") . ".txt";
        }
    }
    //记录
    function log($content){
        if (is_array($content)) {
            $contentStr = var_export($content, true);
        } else {
            $contentStr = $content;
        }
        $fileDir = $this->baseDir."/".$this->subDir;
        if (!is_dir($fileDir)) {
            mkdir($fileDir, 0777, true);
        }

        $filePath = $fileDir . "/" . $this->fileName;
        $ip = request()->ip();
        $logContent = date("Y-m-d H:i:s") . "--" . $ip . "--" . $contentStr . "\r\n";
        error_log($logContent, 3, $filePath);
    }
}
4,Result.php
<?php

namespace app\result;

use think\response\Json;

class Result {
    //success,返回数据
    static public function Success($data):Json {
        $rs = [
            'code'=>0,
            'msg'=>"success",
            'data'=>$data,
        ];
        return json($rs);
    }
    //error需要code/msg参数
    static public function ErrorCode($code,$msg):Json {
        $rs = [
            'code'=>$code,
            'msg'=>$msg,
            'data'=>"",
        ];
        return json($rs);
    }
    //error,传入定义的数组常量
    static public function Error($arr):Json {
        $rs = [
            'code'=>$arr['code'],
            'msg'=>$arr['msg'],
            'data'=>"",
        ];
        return json($rs);
    }
}

三,测试效果

1,浏览器访问:页面返回的内容:
2,redis中查看效果:
liuhongdi@lhdpc:/data/phplog/202210$ redis-cli
127.0.0.1:6379> get 20221026_127.0.0.1
"3"
127.0.0.1:6379> ttl 20221026_127.0.0.1
(integer) 85885
3,通过并发测试效果:
ab10并发
liuhongdi@lhdpc:/data/php/admapi/app$ ab -c 10 -n 10 http://127.0.0.1:8002/serve/image
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking 127.0.0.1 (be patient).....done
 
 
Server Software:        nginx/1.18.0
Server Hostname:        127.0.0.1
Server Port:            8002
 
Document Path:          /serve/image
Document Length:        63 bytes
 
Concurrency Level:      10
Time taken for tests:   0.022 seconds
Complete requests:      10
Failed requests:        7
   (Connect: 0, Receive: 0, Length: 7, Exceptions: 0)
Total transferred:      2340 bytes
HTML transferred:       560 bytes
Requests per second:    447.85 [#/sec] (mean)
Time per request:       22.329 [ms] (mean)
Time per request:       2.233 [ms] (mean, across all concurrent requests)
Transfer rate:          102.34 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:     3    9   4.3     10      15
Waiting:        3    9   4.4     10      15
Total:          4    9   4.2     10      15
 
Percentage of the requests served within a certain time (ms)
  50%     10
  66%     11
  75%     14
  80%     15
  90%     15
  95%     15
  98%     15
  99%     15
100%     15 (longest request)

查看日志:

liuhongdi@lhdpc:/data/php/admapi/app$ more /data/businesslog/testlog/202210/test_20221026.txt
2022-10-26 18:13:20--127.0.0.1--isSucc:1:
2022-10-26 18:13:20--127.0.0.1--isSucc:1:
2022-10-26 18:13:20--127.0.0.1--isSucc:1:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:
2022-10-26 18:13:20--127.0.0.1--isSucc:0:

四,查看thinkphp的版本:

liuhongdi@lhdpc:/data/php/imgtouch$ php think version
v6.0.12LTS 

 

posted @ 2022-10-26 18:24  刘宏缔的架构森林  阅读(518)  评论(0编辑  收藏  举报