From 26e172680d953a736efa97e2b553814dea185a3d Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Jan 2023 00:48:31 +0800 Subject: [PATCH 1/4] add sql validation --- src/ZM/Store/Database/DBPool.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ZM/Store/Database/DBPool.php b/src/ZM/Store/Database/DBPool.php index 6137d359..2da79b7f 100644 --- a/src/ZM/Store/Database/DBPool.php +++ b/src/ZM/Store/Database/DBPool.php @@ -63,6 +63,20 @@ class DBPool case SwooleDriver::class: self::$pools[$name] = new SwooleObjectPool($size, \PDO::class, $connect_str, ...$args); } + switch ($config['type']) { + case 'sqlite': + /** @var \PDO $pool */ + $pool = self::$pools[$name]->get(); + $a = $pool->query('select sqlite_version();')->fetchAll()[0][0] ?? ''; + if (str_starts_with($a, '3')) { + logger()->debug('sqlite ' . $name . ' connected'); + } + self::$pools[$name]->put($pool); + break; + case 'mysql': + // TODO: 编写验证 MySQL 连接有效性的功能 + break; + } } /** From d45d34430d79de96deaf145689e9022733caadee Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Jan 2023 00:49:06 +0800 Subject: [PATCH 2/4] refactor console table output things --- src/ZM/Framework.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index c39ef11f..b683d521 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -317,17 +317,23 @@ class Framework foreach (config('global.servers') as $k => $v) { $properties['listen_' . $k] = $v['type'] . '://' . $v['host'] . ':' . $v['port']; } - // 打印 MySQL 连接信息 - if ((config('global.mysql_config.host') ?? '') !== '') { - $conf = config('global', 'mysql_config'); - $properties['mysql'] = $conf['dbname'] . '@' . $conf['host'] . ':' . $conf['port']; + // 打印 database 连接信息 + foreach (config('global.database') as $name => $db) { + if (!$db['enable']) { + continue; + } + $properties['db[' . $name . ']'] = match ($db['type']) { + 'sqlite' => $db['type'] . '://' . $db['dbname'], + 'mysql' => $db['type'] . '://' . $db['host'] . ':' . $db['port'] . '/' . $db['dbname'], + default => '未知数据库类型', + }; } - // 打印 Redis 连接信息 - if ((config('global', 'redis_config')['host'] ?? '') !== '') { - $conf = config('global', 'redis_config'); - $properties['redis_pool'] = $conf['host'] . ':' . $conf['port']; + // 打印 redis 连接信息 + foreach (config('global.redis') as $name => $redis) { + if ($redis['enable']) { + $properties['redis[' . $name . ']'] = $redis['host'] . ':' . $redis['port']; + } } - if (LOAD_MODE === 0) { logger()->info('框架正以源码模式启动'); } From e5b3509ebf14de0ccddcddf93520c9b6a74d5f68 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Jan 2023 00:49:44 +0800 Subject: [PATCH 3/4] update config for redis --- config/global.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/config/global.php b/config/global.php index 63e7268c..8da695f8 100644 --- a/config/global.php +++ b/config/global.php @@ -89,7 +89,7 @@ $config['database'] = [ 'dbname' => 'a.db', 'pool_size' => 10, ], - 'zm' => [ + 'default' => [ 'enable' => false, 'type' => 'mysql', 'host' => '127.0.0.1', // 填写数据库服务器地址后才会创建数据库连接 @@ -102,11 +102,24 @@ $config['database'] = [ ], ]; +/* Redis 连接配置,框架将自动生成连接池,支持多个连接池 */ +$config['redis'] = [ + 'default' => [ + 'enable' => false, + 'host' => '127.0.0.1', + 'port' => 6379, + 'index' => 0, + 'auth' => '', + 'pool_size' => 10, + ], +]; + /* KV 数据库的配置 */ $config['kv'] = [ 'use' => \LightCache::class, // 默认在单进程模式下使用 LightCache,多进程需要使用 ZMRedis 'light_cache_dir' => $config['data_dir'] . '/lc', // 默认的 LightCache 保存持久化数据的位置 'light_cache_autosave_time' => 600, // LightCache 自动保存时间(秒) + 'redis_config' => 'default', ]; return $config; From c78b7fa2b0b384452ec0540ae30c04601474545a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Jan 2023 00:50:31 +0800 Subject: [PATCH 4/4] add redis support --- src/Globals/global_class_alias.php | 1 + src/ZM/Event/Listener/WorkerEventListener.php | 11 ++ src/ZM/Store/KV/Redis/KVRedis.php | 137 ++++++++++++++++++ src/ZM/Store/KV/Redis/RedisException.php | 11 ++ src/ZM/Store/KV/Redis/RedisPool.php | 93 ++++++++++++ src/ZM/Store/KV/Redis/ZMRedis.php | 29 ++++ 6 files changed, 282 insertions(+) create mode 100644 src/ZM/Store/KV/Redis/KVRedis.php create mode 100644 src/ZM/Store/KV/Redis/RedisException.php create mode 100644 src/ZM/Store/KV/Redis/RedisPool.php create mode 100644 src/ZM/Store/KV/Redis/ZMRedis.php diff --git a/src/Globals/global_class_alias.php b/src/Globals/global_class_alias.php index 749e28ce..b300a620 100644 --- a/src/Globals/global_class_alias.php +++ b/src/Globals/global_class_alias.php @@ -16,6 +16,7 @@ class_alias(\ZM\Plugin\ZMPlugin::class, 'ZMPlugin'); class_alias(\OneBot\V12\Object\OneBotEvent::class, 'OneBotEvent'); class_alias(\ZM\Context\BotContext::class, 'BotContext'); class_alias(\ZM\Store\KV\LightCache::class, 'LightCache'); +class_alias(\ZM\Store\KV\Redis\KVRedis::class, 'KVRedis'); // 下面是 OneBot 相关类的全局别称 class_alias(\OneBot\Driver\Event\WebSocket\WebSocketOpenEvent::class, 'WebSocketOpenEvent'); diff --git a/src/ZM/Event/Listener/WorkerEventListener.php b/src/ZM/Event/Listener/WorkerEventListener.php index ae4cf423..540ed1b7 100644 --- a/src/ZM/Event/Listener/WorkerEventListener.php +++ b/src/ZM/Event/Listener/WorkerEventListener.php @@ -21,6 +21,7 @@ use ZM\Store\Database\DBException; use ZM\Store\Database\DBPool; use ZM\Store\FileSystem; use ZM\Store\KV\LightCache; +use ZM\Store\KV\Redis\RedisPool; use ZM\Utils\ZMUtil; class WorkerEventListener @@ -219,6 +220,9 @@ class WorkerEventListener foreach (DBPool::getAllPools() as $name => $pool) { DBPool::destroyPool($name); } + foreach (RedisPool::getAllPools() as $name => $pool) { + RedisPool::destroyPool($name); + } // 读取 MySQL 配置文件 $conf = config('global.database'); @@ -228,5 +232,12 @@ class WorkerEventListener DBPool::create($name, $conn_conf); } } + + $redis_conf = config('global.redis'); + foreach ($redis_conf as $name => $conn_conf) { + if (($conn_conf['enable'] ?? true) !== false) { + RedisPool::create($name, $conn_conf); + } + } } } diff --git a/src/ZM/Store/KV/Redis/KVRedis.php b/src/ZM/Store/KV/Redis/KVRedis.php new file mode 100644 index 00000000..f1a2906f --- /dev/null +++ b/src/ZM/Store/KV/Redis/KVRedis.php @@ -0,0 +1,137 @@ +pool_name = config('global.kv.redis_config', 'default'); + } + + public static function open(string $name = ''): CacheInterface + { + return new KVRedis($name); + } + + /** + * {@inheritDoc} + */ + public function get(string $key, mixed $default = null): mixed + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = $redis->get($this->name . ':' . $key); + if ($ret === false) { + $ret = $default; + } else { + $ret = unserialize($ret); + } + RedisPool::pool($this->pool_name)->put($redis); + return $ret; + } + + /** + * {@inheritDoc} + */ + public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): bool + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = $redis->set($this->name . ':' . $key, serialize($value), $ttl); + RedisPool::pool($this->pool_name)->put($redis); + return (bool) $ret; + } + + /** + * {@inheritDoc} + */ + public function delete(string $key): bool + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = $redis->del($this->name . ':' . $key); + RedisPool::pool($this->pool_name)->put($redis); + return (bool) $ret; + } + + /** + * {@inheritDoc} + */ + public function clear(): bool + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = $redis->del($this->name . ':*'); + RedisPool::pool($this->pool_name)->put($redis); + return (bool) $ret; + } + + /** + * {@inheritDoc} + */ + public function getMultiple(iterable $keys, mixed $default = null): iterable + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + foreach ($keys as $key) { + $value = $redis->get($this->name . ':' . $key); + if ($value === false) { + $value = $default; + } else { + $value = unserialize($value); + } + yield $key => $value; + } + RedisPool::pool($this->pool_name)->put($redis); + } + + /** + * {@inheritDoc} + */ + public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): bool + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = true; + foreach ($values as $key => $value) { + $ret = $ret && $redis->set($this->name . ':' . $key, serialize($value), $ttl); + } + RedisPool::pool($this->pool_name)->put($redis); + return $ret; + } + + /** + * {@inheritDoc} + */ + public function deleteMultiple(iterable $keys): bool + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = true; + foreach ($keys as $key) { + $ret = $ret && $redis->del($this->name . ':' . $key); + } + RedisPool::pool($this->pool_name)->put($redis); + return $ret; + } + + /** + * {@inheritDoc} + */ + public function has(string $key): bool + { + /** @var ZMRedis $redis */ + $redis = RedisPool::pool($this->pool_name)->get(); + $ret = $redis->exists($this->name . ':' . $key); + RedisPool::pool($this->pool_name)->put($redis); + return (bool) $ret; + } +} diff --git a/src/ZM/Store/KV/Redis/RedisException.php b/src/ZM/Store/KV/Redis/RedisException.php new file mode 100644 index 00000000..e2e32b25 --- /dev/null +++ b/src/ZM/Store/KV/Redis/RedisException.php @@ -0,0 +1,11 @@ + 连接池列表 + */ + public static array $pools = []; + + /** + * @throws RedisException + */ + public static function create(string $name, array $config): void + { + $size = $config['pool_size'] ?? 10; + self::checkRedisExtension(); + switch (Driver::getActiveDriverClass()) { + case WorkermanDriver::class: + self::$pools[$name] = new WorkermanObjectPool($size, ZMRedis::class, $config); + break; + case SwooleDriver::class: + self::$pools[$name] = new SwooleObjectPool($size, ZMRedis::class, $config); + break; + } + /** @var ZMRedis $r */ + $r = self::$pools[$name]->get(); + try { + /* @phpstan-ignore-next-line */ + $result = $r->ping('123'); + if (str_contains($result, '123')) { + self::$pools[$name]->put($r); + logger()->debug('Redis pool ' . $name . ' created'); + } + } catch (\RedisException $e) { + self::$pools[$name] = null; + logger()->error(zm_internal_errcode('E00047') . 'Redis init failed! ' . $e->getMessage()); + } + } + + /** + * 获取一个数据库连接池 + * + * @param string $name 连接池名称 + */ + public static function pool(string $name): PoolInterface + { + if (!isset(self::$pools[$name]) && count(self::$pools) !== 1) { + throw new \RuntimeException("Pool {$name} not found"); + } + return self::$pools[$name] ?? self::$pools[array_key_first(self::$pools)]; + } + + /** + * @throws RedisException + */ + public static function checkRedisExtension(): void + { + if (!extension_loaded('redis')) { + throw new RedisException(zm_internal_errcode('E00029') . '未安装 redis 扩展'); + } + } + + /** + * 销毁数据库连接池 + * + * @param string $name 数据库连接池名称 + */ + public static function destroyPool(string $name) + { + unset(self::$pools[$name]); + } + + /** + * 获取所有数据库连接池 + * + * @return PoolInterface[] + */ + public static function getAllPools(): array + { + return self::$pools; + } +} diff --git a/src/ZM/Store/KV/Redis/ZMRedis.php b/src/ZM/Store/KV/Redis/ZMRedis.php new file mode 100644 index 00000000..6b39bad7 --- /dev/null +++ b/src/ZM/Store/KV/Redis/ZMRedis.php @@ -0,0 +1,29 @@ +connect($config['host'], $config['port'], $config['timeout'] ?? 0); + if (!empty($config['auth'])) { + $this->auth($config['auth']); + } + $this->select($config['index']); + } + + /** + * @throws \RedisException + */ + public function __destruct() + { + $this->close(); + } +}