mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-18 05:04:51 +08:00
commit
cd52bfbfa5
@ -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;
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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('框架正以源码模式启动');
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
137
src/ZM/Store/KV/Redis/KVRedis.php
Normal file
137
src/ZM/Store/KV/Redis/KVRedis.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Store\KV\Redis;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use ZM\Store\KV\KVInterface;
|
||||
|
||||
class KVRedis implements CacheInterface, KVInterface
|
||||
{
|
||||
private string $pool_name;
|
||||
|
||||
public function __construct(private string $name = '')
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
11
src/ZM/Store/KV/Redis/RedisException.php
Normal file
11
src/ZM/Store/KV/Redis/RedisException.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Store\KV\Redis;
|
||||
|
||||
use ZM\Exception\ZMException;
|
||||
|
||||
class RedisException extends ZMException
|
||||
{
|
||||
}
|
||||
93
src/ZM/Store/KV/Redis/RedisPool.php
Normal file
93
src/ZM/Store/KV/Redis/RedisPool.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Store\KV\Redis;
|
||||
|
||||
use OneBot\Driver\Driver;
|
||||
use OneBot\Driver\Interfaces\PoolInterface;
|
||||
use OneBot\Driver\Swoole\ObjectPool as SwooleObjectPool;
|
||||
use OneBot\Driver\Swoole\SwooleDriver;
|
||||
use OneBot\Driver\Workerman\ObjectPool as WorkermanObjectPool;
|
||||
use OneBot\Driver\Workerman\WorkermanDriver;
|
||||
|
||||
class RedisPool
|
||||
{
|
||||
/**
|
||||
* @var array<string, SwooleObjectPool|WorkermanObjectPool> 连接池列表
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
29
src/ZM/Store/KV/Redis/ZMRedis.php
Normal file
29
src/ZM/Store/KV/Redis/ZMRedis.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Store\KV\Redis;
|
||||
|
||||
class ZMRedis extends \Redis
|
||||
{
|
||||
/**
|
||||
* @throws \RedisException
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user