This commit is contained in:
lq 2024-05-22 15:50:08 +08:00
parent df89d1d416
commit 8938ce9cbe
12 changed files with 424 additions and 4 deletions

View File

@ -1 +1 @@
42
1940

View File

@ -5,4 +5,6 @@ define("ROOT_PATH", dirname(dirname(__DIR__)));
$GLOBAL_CONFIGS = [
'db' => require_once(__DIR__ . "/db.php"),
'dbpool' => require_once(__DIR__ . '/dbpool.php'),
'redis' => require_once(__DIR__ . '/redis.php'),
'redispool' => require_once(__DIR__ . '/redispool.php'),
];

10
app/config/redis.php Normal file
View File

@ -0,0 +1,10 @@
<?php
return [
'default' => [
'host' => '127.0.0.1',
'port' => '6379',
'auth' => '',
'db' => 6
],
];

8
app/config/redispool.php Normal file
View File

@ -0,0 +1,8 @@
<?php
return [
"default" => [
"min" => 20,
"max" => 30,
"idleTime" => 40
]
];

View File

@ -3,11 +3,14 @@ namespace App\controller;
use App\model\User;
use Core\annotations\Bean;
use Core\annotations\Value;
use Core\annotations\Redis;
use Core\annotations\Lock;
use Core\annotations\DB;
use Core\annotations\RequestMapping;
use Core\http\Request;
use Core\http\Response;
use Core\init\MyDB;
use Core\init\RedisHelper;
use DI\Attribute\Inject;
/**
@ -20,6 +23,7 @@ class UserController
* @var MyDB
*/
private $db;
/**
* @Value ()
*/
@ -55,10 +59,20 @@ class UserController
/**
* @RequestMapping(value="/user/json")
* @Redis(key="name")
*/
public function json()
{
$user = $this->user::first();
$user = $this->db->table('user')->get();
return ['code' => 1, 'msg' => 'success', 'data' => $user];
}
/**
* @Lock(prefix="lock",key="#0")
* @RequestMapping(value="/user/lock/{uid:\d+}")
*/
public function lock( Request $request, $uid, Response $response)
{
return $this->db->table('user')->get();
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Core\annotationhandlers;
use Core\annotations\Lock;
use Core\annotations\Redis;
use Core\BeanFactory;
use Core\init\DecoratorCollector;
use Core\init\RedisHelper;
function getLock($self, $params)
{//生成锁脚本
$script = <<<LUA
local key = KEYS[1]
local expire = ARGV[1]
if redis.call('setnx',key,1)==1 then
return redis.call('expire',key,expire)
end
return 0
LUA;
return RedisHelper::eval($script, [$self->prefix . getKey($self->key, $params), $self->expire], 1);
}
function delLock($self, $params)
{//释放锁脚本
$script = <<<LUA
local key = KEYS[1]
return redis.call('del',key)
LUA;
return RedisHelper::eval($script, [$self->prefix . getKey($self->key, $params)], 1);
}
function lock($self, $params)
{//争抢锁
$retry = $self->retry;
while ($retry-- > 0) {
$get_lock = getLock($self, $params);
if ($get_lock) {
return true;
break;
}
usleep(1000 * 200);
}
return false;
}
function run($self, $params, $func)
{//执行抢锁
try {
if (lock($self, $params)) {
$result = call_user_func($func, ...$params);//执行业务逻辑
delLock($self, $params);
return $result;
}
return false;
} catch (\Exception $exception) {
delLock($self, $params);
return 'false';
}
}
return [
Lock::class => function (\ReflectionMethod $method, $instance, $self) {
$d_collector = BeanFactory::getBean(DecoratorCollector::class);
$key = get_class($instance) . "::" . $method->getName();
$d_collector->dSet[$key] = function ($func) use ($self) { //收集装饰器 放入 装饰器收集类
return function ($params) use ($func, $self) {
/** @var $self Lock */
if ($self->key != '') {
$result = run($self, $params, $func);
if (!$result) {
return '服务器繁忙';
}
return $result;
}
return call_user_func($func, ...$params);//执行业务逻辑
};
};
return $instance;
}
];

View File

@ -0,0 +1,126 @@
<?php
namespace Core\annotationhandlers;
use Core\annotations\Redis;
use Core\BeanFactory;
use Core\init\DecoratorCollector;
use Core\init\RedisHelper;
function getKey($key, $param)
{
$pattern = "/^#(\w+)/i";
if (preg_match($pattern, $key, $matches)) {
if (isset($param[$matches[1]])) {
if (is_string($param[$matches[1]]) || is_int($param[$matches[1]])) {
return $param[$matches[1]];
}
}
}
return $key;
}
function RedisByString(Redis $self, array $params, $func)
{//处理string类型的数据
$_key = $self->prefix . getKey($self->key, $params); //缓存key
$getFromRedis = RedisHelper::get($_key);
if ($getFromRedis) { //缓存如果有,直接返回
return $getFromRedis;
} else { //缓存没有,则直接执行原控制器方法,并返回
$getData = call_user_func($func, ...$params);
if ($self->expire > 0) {//过期时间
RedisHelper::setex($_key, $self->expire, json_encode($getData));
} else {
RedisHelper::set($_key, json_encode($getData));
}
return $getData;
}
}
function RedisByHash(Redis $self, array $params, $func)
{//处理hash类型的数据
$_key = $self->prefix . getKey($self->key, $params); //缓存key
$getFromRedis = RedisHelper::hgetall($_key);
if ($getFromRedis) { //缓存如果有,直接返回
if ($self->incr != '') {
RedisHelper::hIncrBy($_key, $self->incr, 1);
}
return $getFromRedis;
} else { //缓存没有,则直接执行原控制器方法,并返回
$getData = call_user_func($func, ...$params);
if (is_array($getData) || is_object($getData)) {
if (is_object($getData)) {//如果是对象,转换成数组
$getData = json_decode(json_encode($getData), 1);
}
$keys = implode("", array_keys($getData));
if (preg_match("/^\d+$/", $keys)) {
foreach ($getData as $k => $data) {
RedisHelper::hmset($self->prefix . getKey($self->key, $data) . $k, $data);
}
} else {
RedisHelper::hmset($_key, $getData);
}
}
return $getData;
}
}
function RedisByScrtedSet(Redis $self, array $params, $func)
{//处理有序集合类型的数据
if ($self->coroutine) {
$chan = call_user_func($func, ...$params);
$getData = [];
for ($i = 0; $i < $chan->capacity; $i++) {
$re = $chan->pop(5);
$getData = array_merge($getData, $re);
}
} else {
$getData = call_user_func($func, ...$params);
}
if (is_array($getData) || is_object($getData)) {
if (is_object($getData)) {//如果是对象,转换成数组
$getData = json_decode(json_encode($getData), 1);
}
foreach ($getData as $data) {
RedisHelper::zAdd($self->prefix, $data[$self->score], $self->member . $data[$self->key]);
}
}
return $getData;
}
function RedisByLua($self, $params, $func)
{//lua脚本
return RedisHelper::eval($self->script);
}
return [
Redis::class => function (\ReflectionMethod $method, $instance, $self) {
$d_collector = BeanFactory::getBean(DecoratorCollector::class);
$key = get_class($instance) . "::" . $method->getName();
$d_collector->dSet[$key] = function ($func) use ($self) { //收集装饰器 放入 装饰器收集类
return function ($params) use ($func, $self) {
/** @var $self Redis */
if ($self->script != '') {
return RedisByLua($self, $params, $func);
}
if ($self->key != "") { //处理缓存
switch ($self->type) {
case "string":
return RedisByString($self, $params, $func);
case "hash":
return RedisByHash($self, $params, $func);
case "sortedset":
return RedisByScrtedSet($self, $params, $func);
default:
return call_user_func($func, ...$params);
}
}
return call_user_func($func, ...$params);
};
};
return $instance;
}
];

17
core/annotations/Lock.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace Core\annotations;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* @Annotation
* @Target({"METHOD"})
*/
class Lock
{
public $prefix = '';
public $key = '';
public $retry = 3;//重试次数
public $expire = 30;//重试次数
}

View File

@ -0,0 +1,23 @@
<?php
namespace Core\annotations;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* @Annotation
* @Target({"METHOD"})
*/
class Redis
{
public $source = 'default';
public $key = '';
public $type = 'string';
public $prefix = '';
public $expire = -1;
public $incr = '';
public $member = '';
public $score = '';//有序集合分数
public $coroutine = false;//使用协程
public $script = '';//lua脚本
}

36
core/init/RedisHelper.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace Core\init;
use Core\BeanFactory;
use Core\lib\PHPRedisPool;
use phpDocumentor\Reflection\Types\Array_;
use Core\annotations\Bean;
/**
* @Bean()
* Class RedisHelper
* @method static string get(string $key)
* @method static bool set(string $key,string $value)
* @method static bool setex(string $key,int $ttl,string $value)
* @method static array hgetall(string $key)
* @method static bool hmset(string $key,array $keyandvalues)
* @method static bool zAdd(string $key, int $score, string $member)
* @method static mixed eval($script, $args=array(),$numberKeys=0)
*/
class RedisHelper{
public static function __callStatic($name, $arguments)
{
/** @var $pool PHPRedisPool */
$pool=BeanFactory::getBean(PHPRedisPool::class);
$redis_obj=$pool->getConnection();
try{
if(!$redis_obj) {
return false;
}
return $redis_obj->redis->$name(...$arguments);
}catch (\Exception $exception){
return $exception->getMessage();
}finally{
if($redis_obj)
$pool->close($redis_obj);
}
}
}

27
core/lib/PHPRedisPool.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace Core\lib;
/**
* Class PHPRedisPool
*/
class PHPRedisPool extends RedisPool{
public function __construct(int $min = 5, int $max = 10)
{
global $GLOBAL_CONFIGS;
$poolconfig=$GLOBAL_CONFIGS["redispool"]["default"];
parent::__construct($poolconfig['min'], $poolconfig['max'],$poolconfig['idleTime']);
}
protected function newRedis()
{
global $GLOBAL_CONFIGS;
$default=$GLOBAL_CONFIGS["redis"]["default"];
$redis=new \Redis();
$redis->connect($default["host"],$default["port"]);
if($default["auth"]!=""){
$redis->auth($default["auth"]);
}
$redis->select($default['db'] ?? 0);
return $redis;
}
}

75
core/lib/RedisPool.php Normal file
View File

@ -0,0 +1,75 @@
<?php
namespace Core\lib;
use Core\annotations\Bean;
/**
* @Bean()
*/
abstract class RedisPool
{
private $min;
private $max;
private $conns;
private $count;//当前所有连接数
private $idleTime = 10;//连接空闲时间秒
abstract protected function newRedis();
function __construct($min = 5, $max = 10, $idleTime = 10)
{
$this->min = $min;
$this->max = $max;
$this->idleTime = $idleTime;
$this->conns = new \Swoole\Coroutine\Channel($this->max);
//构造方法直接初始化Redis连接
for ($i = 0; $i < $this->min; $i++) {
$this->addRedisToPool();//统一调用
}
}
public function getCount()
{
return $this->count;
}
public function getConnection()
{//取出
if ($this->conns->isEmpty()) {
if ($this->count < $this->max) {//连接池没满
$this->addRedisToPool();
$getObject = $this->conns->pop();
} else {
$getObject = $this->conns->pop(5);
}
} else {
$getObject = $this->conns->pop();
}
if ($getObject)
$getObject->usedtime = time();
return $getObject;
}
public function close($conn)
{//放回连接
if ($conn) {
$this->conns->push($conn);
}
}
public function addRedisToPool()
{ //把对象加入池
try {
$this->count++;
$db = $this->newRedis();
if (!$db) throw new \Exception("redis创建错误");
$dbObject = new \stdClass();
$dbObject->usedTime = time();
$dbObject->redis = $db;
$this->conns->push($dbObject);
} catch (\Exception $ex) {
$this->count--;
}
}
}