2020-09-29 15:07:43 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
declare(strict_types=1);
|
2020-09-29 15:07:43 +08:00
|
|
|
|
|
|
|
|
|
|
namespace ZM\Store;
|
|
|
|
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
|
|
use Swoole\Table;
|
2021-02-15 15:15:26 +08:00
|
|
|
|
use ZM\Annotation\Swoole\OnSave;
|
2020-10-03 23:00:18 +08:00
|
|
|
|
use ZM\Console\Console;
|
2021-02-15 15:15:26 +08:00
|
|
|
|
use ZM\Event\EventDispatcher;
|
2021-09-01 14:14:00 +08:00
|
|
|
|
use ZM\Exception\LightCacheException;
|
2021-01-20 16:11:04 +08:00
|
|
|
|
use ZM\Exception\ZMException;
|
2021-03-29 15:34:24 +08:00
|
|
|
|
use ZM\Framework;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
use ZM\Utils\Manager\WorkerManager;
|
2020-09-29 15:07:43 +08:00
|
|
|
|
|
|
|
|
|
|
class LightCache
|
|
|
|
|
|
{
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static $last_error = '';
|
|
|
|
|
|
|
|
|
|
|
|
/** @var null|Table */
|
|
|
|
|
|
private static $kv_table;
|
2020-09-29 15:07:43 +08:00
|
|
|
|
|
|
|
|
|
|
private static $config = [];
|
|
|
|
|
|
|
2021-01-20 16:11:04 +08:00
|
|
|
|
/**
|
2022-04-02 23:37:22 +08:00
|
|
|
|
* @param array $config 配置
|
2021-01-20 16:11:04 +08:00
|
|
|
|
* @throws Exception
|
2022-04-02 23:37:22 +08:00
|
|
|
|
* @return bool|mixed 返回失败(false)或创建SwooleTable成功结果
|
2021-01-20 16:11:04 +08:00
|
|
|
|
*/
|
2022-04-02 23:37:22 +08:00
|
|
|
|
public static function init(array $config)
|
2022-03-15 18:05:33 +08:00
|
|
|
|
{
|
2020-09-29 15:07:43 +08:00
|
|
|
|
self::$config = $config;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
self::$kv_table = new Table($config['size'], $config['hash_conflict_proportion']);
|
|
|
|
|
|
self::$kv_table->column('value', Table::TYPE_STRING, $config['max_strlen']);
|
|
|
|
|
|
self::$kv_table->column('expire', Table::TYPE_INT);
|
|
|
|
|
|
self::$kv_table->column('data_type', Table::TYPE_STRING, 8);
|
2020-10-03 23:00:18 +08:00
|
|
|
|
$result = self::$kv_table->create();
|
2021-03-27 16:30:15 +08:00
|
|
|
|
// 加载内容
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if ($result === true && isset($config['persistence_path'])) {
|
|
|
|
|
|
if (file_exists($config['persistence_path'])) {
|
|
|
|
|
|
$r = json_decode(file_get_contents($config['persistence_path']), true);
|
|
|
|
|
|
if ($r === null) {
|
|
|
|
|
|
$r = [];
|
|
|
|
|
|
}
|
2020-11-03 21:02:24 +08:00
|
|
|
|
foreach ($r as $k => $v) {
|
2021-03-27 16:30:15 +08:00
|
|
|
|
$write = self::set($k, $v);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
Console::verbose('Writing LightCache: ' . $k);
|
2020-11-03 21:02:24 +08:00
|
|
|
|
if ($write === false) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
self::$last_error = zm_internal_errcode('E00051') . '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
|
2020-11-03 21:02:24 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2020-10-03 23:00:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($result === false) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
self::$last_error = zm_internal_errcode('E00050') . '系统内存不足,申请失败';
|
2021-03-29 15:34:24 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$obj = Framework::loadFrameworkState();
|
2022-03-15 18:05:33 +08:00
|
|
|
|
foreach (($obj['expiring_light_cache'] ?? []) as $k => $v) {
|
|
|
|
|
|
$value = $v['value'];
|
2021-03-29 15:34:24 +08:00
|
|
|
|
if (is_array($value)) {
|
|
|
|
|
|
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (strlen($value) >= self::$config['max_strlen']) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
$data_type = 'json';
|
2021-03-29 15:34:24 +08:00
|
|
|
|
} elseif (is_string($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = '';
|
2021-03-29 15:34:24 +08:00
|
|
|
|
} elseif (is_int($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = 'int';
|
2021-03-29 15:34:24 +08:00
|
|
|
|
} elseif (is_bool($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = 'bool';
|
2021-03-29 15:34:24 +08:00
|
|
|
|
$value = json_encode($value);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2023-06-12 18:33:06 +08:00
|
|
|
|
$result = self::$kv_table->set((string) $k, [
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'value' => $value,
|
|
|
|
|
|
'expire' => $v['expire'],
|
|
|
|
|
|
'data_type' => $data_type,
|
2021-03-29 15:34:24 +08:00
|
|
|
|
]);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if ($result === false) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2021-03-29 15:34:24 +08:00
|
|
|
|
}
|
2020-10-03 23:00:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
return $result;
|
2020-09-29 15:07:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2021-01-20 16:11:04 +08:00
|
|
|
|
* @throws ZMException
|
2022-03-15 18:05:33 +08:00
|
|
|
|
* @return null|mixed
|
2020-09-29 15:07:43 +08:00
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function get(string $key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (self::$kv_table === null) {
|
|
|
|
|
|
throw new LightCacheException('E00048', 'not initialized LightCache');
|
|
|
|
|
|
}
|
2020-09-29 15:07:43 +08:00
|
|
|
|
self::checkExpire($key);
|
|
|
|
|
|
$r = self::$kv_table->get($key);
|
|
|
|
|
|
return $r === false ? null : self::parseGet($r);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2021-01-20 16:11:04 +08:00
|
|
|
|
* @throws ZMException
|
2022-03-15 18:05:33 +08:00
|
|
|
|
* @return null|mixed
|
2020-09-29 15:07:43 +08:00
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function getExpire(string $key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (self::$kv_table === null) {
|
|
|
|
|
|
throw new LightCacheException('E00048', 'not initialized LightCache');
|
|
|
|
|
|
}
|
2020-09-29 15:07:43 +08:00
|
|
|
|
self::checkExpire($key);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$r = self::$kv_table->get($key, 'expire');
|
2020-09-29 15:07:43 +08:00
|
|
|
|
return $r === false ? null : $r - time();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-29 15:34:24 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @throws ZMException
|
2022-03-15 18:05:33 +08:00
|
|
|
|
* @return null|mixed
|
2021-03-29 15:34:24 +08:00
|
|
|
|
* @since 2.4.3
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function getExpireTS(string $key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (self::$kv_table === null) {
|
|
|
|
|
|
throw new LightCacheException('E00048', 'not initialized LightCache');
|
|
|
|
|
|
}
|
2021-03-29 15:34:24 +08:00
|
|
|
|
self::checkExpire($key);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$r = self::$kv_table->get($key, 'expire');
|
2021-03-29 15:34:24 +08:00
|
|
|
|
return $r === false ? null : $r;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-29 15:07:43 +08:00
|
|
|
|
/**
|
2022-03-15 18:05:33 +08:00
|
|
|
|
* @param array|int|string $value
|
2021-01-20 16:11:04 +08:00
|
|
|
|
* @throws ZMException
|
2022-03-15 18:05:33 +08:00
|
|
|
|
* @return bool
|
2020-09-29 15:07:43 +08:00
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function set(string $key, $value, int $expire = -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (self::$kv_table === null) {
|
|
|
|
|
|
throw new LightCacheException('E00048', 'not initialized LightCache');
|
|
|
|
|
|
}
|
2020-10-03 23:00:18 +08:00
|
|
|
|
if (is_array($value)) {
|
2020-09-29 15:07:43 +08:00
|
|
|
|
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (strlen($value) >= self::$config['max_strlen']) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
$data_type = 'json';
|
2020-09-29 15:07:43 +08:00
|
|
|
|
} elseif (is_string($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = '';
|
2020-10-03 23:00:18 +08:00
|
|
|
|
} elseif (is_int($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = 'int';
|
2020-11-03 21:02:24 +08:00
|
|
|
|
} elseif (is_bool($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = 'bool';
|
2020-11-03 21:02:24 +08:00
|
|
|
|
$value = json_encode($value);
|
2020-12-14 01:24:34 +08:00
|
|
|
|
} else {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
throw new LightCacheException('E00049', 'Only can set string, array and int');
|
2020-09-29 15:07:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
return self::$kv_table->set($key, [
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'value' => $value,
|
|
|
|
|
|
'expire' => $expire >= 0 ? $expire + time() : $expire,
|
|
|
|
|
|
'data_type' => $data_type,
|
2020-11-03 21:02:24 +08:00
|
|
|
|
]);
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2022-04-02 23:37:22 +08:00
|
|
|
|
* @param mixed $value
|
2021-01-20 16:11:04 +08:00
|
|
|
|
* @throws ZMException
|
2022-03-15 18:05:33 +08:00
|
|
|
|
* @return bool
|
2020-11-03 21:02:24 +08:00
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function update(string $key, $value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (self::$kv_table === null) {
|
|
|
|
|
|
throw new LightCacheException('E00048', 'not initialized LightCache.');
|
|
|
|
|
|
}
|
2020-11-03 21:02:24 +08:00
|
|
|
|
if (is_array($value)) {
|
|
|
|
|
|
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (strlen($value) >= self::$config['max_strlen']) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
$data_type = 'json';
|
2020-11-03 21:02:24 +08:00
|
|
|
|
} elseif (is_string($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = '';
|
2020-11-03 21:02:24 +08:00
|
|
|
|
} elseif (is_int($value)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$data_type = 'int';
|
2023-03-03 21:58:40 +08:00
|
|
|
|
} elseif (is_bool($value)) {
|
|
|
|
|
|
$data_type = 'bool';
|
|
|
|
|
|
$value = json_encode($value);
|
2023-06-12 18:33:06 +08:00
|
|
|
|
} else {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
throw new LightCacheException('E00048', 'Only can set string, array and int');
|
2020-11-03 21:02:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
try {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (self::$kv_table->get($key) === false) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2020-11-03 21:02:24 +08:00
|
|
|
|
return self::$kv_table->set($key, [
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'value' => $value,
|
|
|
|
|
|
'data_type' => $data_type,
|
2020-09-29 15:07:43 +08:00
|
|
|
|
]);
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function getMemoryUsage()
|
|
|
|
|
|
{
|
2020-09-29 15:07:43 +08:00
|
|
|
|
return self::$kv_table->getMemorySize();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @throws Exception
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function isset(string $key): bool
|
|
|
|
|
|
{
|
2020-09-29 15:07:43 +08:00
|
|
|
|
return self::get($key) !== null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function unset(string $key)
|
|
|
|
|
|
{
|
2020-09-29 15:07:43 +08:00
|
|
|
|
return self::$kv_table->del($key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function getAll(): array
|
|
|
|
|
|
{
|
2020-09-29 15:07:43 +08:00
|
|
|
|
$r = [];
|
|
|
|
|
|
$del = [];
|
|
|
|
|
|
foreach (self::$kv_table as $k => $v) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if ($v['expire'] <= time() && $v['expire'] >= 0) {
|
2020-10-03 23:00:18 +08:00
|
|
|
|
$del[] = $k;
|
2020-09-29 15:07:43 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$r[$k] = self::parseGet($v);
|
|
|
|
|
|
}
|
2020-10-03 23:00:18 +08:00
|
|
|
|
foreach ($del as $v) {
|
2020-09-29 15:07:43 +08:00
|
|
|
|
self::unset($v);
|
|
|
|
|
|
}
|
|
|
|
|
|
return $r;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function addPersistence($key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (file_exists(self::$config['persistence_path'])) {
|
|
|
|
|
|
$r = json_decode(file_get_contents(self::$config['persistence_path']), true);
|
|
|
|
|
|
if ($r === null) {
|
|
|
|
|
|
$r = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isset($r[$key])) {
|
|
|
|
|
|
$r[$key] = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
file_put_contents(self::$config['persistence_path'], json_encode($r, 64 | 128 | 256));
|
2021-03-27 16:30:15 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2022-03-15 18:05:33 +08:00
|
|
|
|
return false;
|
2021-03-27 16:30:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function removePersistence($key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (file_exists(self::$config['persistence_path'])) {
|
|
|
|
|
|
$r = json_decode(file_get_contents(self::$config['persistence_path']), true);
|
|
|
|
|
|
if ($r === null) {
|
|
|
|
|
|
$r = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isset($r[$key])) {
|
|
|
|
|
|
unset($r[$key]);
|
|
|
|
|
|
}
|
|
|
|
|
|
file_put_contents(self::$config['persistence_path'], json_encode($r, 64 | 128 | 256));
|
2021-03-27 16:30:15 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2022-03-15 18:05:33 +08:00
|
|
|
|
return false;
|
2021-03-27 16:30:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-18 14:56:35 +08:00
|
|
|
|
/**
|
2021-03-24 23:34:46 +08:00
|
|
|
|
* 这个只能在唯一一个工作进程中执行
|
2021-03-18 14:56:35 +08:00
|
|
|
|
* @throws Exception
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public static function savePersistence()
|
|
|
|
|
|
{
|
2021-03-29 15:34:24 +08:00
|
|
|
|
if (server()->worker_id !== MAIN_WORKER) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
WorkerManager::sendActionToWorker(MAIN_WORKER, 'save_persistence', []);
|
2021-03-29 15:34:24 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-03-24 23:34:46 +08:00
|
|
|
|
$dispatcher = new EventDispatcher(OnSave::class);
|
|
|
|
|
|
$dispatcher->dispatchEvents();
|
2021-02-15 15:15:26 +08:00
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (self::$kv_table === null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-03-27 16:30:15 +08:00
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (!empty(self::$config['persistence_path'])) {
|
|
|
|
|
|
if (file_exists(self::$config['persistence_path'])) {
|
|
|
|
|
|
$r = json_decode(file_get_contents(self::$config['persistence_path']), true);
|
2021-03-29 15:48:47 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$r = [];
|
|
|
|
|
|
}
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if ($r === null) {
|
|
|
|
|
|
$r = [];
|
|
|
|
|
|
}
|
2021-03-27 16:30:15 +08:00
|
|
|
|
foreach ($r as $k => $v) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
Console::verbose('Saving ' . $k);
|
2021-03-27 16:30:15 +08:00
|
|
|
|
$r[$k] = self::get($k);
|
2020-10-03 23:00:18 +08:00
|
|
|
|
}
|
2022-03-15 18:05:33 +08:00
|
|
|
|
file_put_contents(self::$config['persistence_path'], json_encode($r, 64 | 128 | 256));
|
2020-12-14 01:24:34 +08:00
|
|
|
|
}
|
2021-03-29 15:34:24 +08:00
|
|
|
|
|
|
|
|
|
|
$obj = Framework::loadFrameworkState();
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$obj['expiring_light_cache'] = [];
|
2021-03-29 15:34:24 +08:00
|
|
|
|
$del = [];
|
|
|
|
|
|
foreach (self::$kv_table as $k => $v) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if ($v['expire'] <= time() && $v['expire'] >= 0) {
|
2021-03-29 15:34:24 +08:00
|
|
|
|
$del[] = $k;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
} elseif ($v['expire'] > time()) {
|
|
|
|
|
|
$obj['expiring_light_cache'][$k] = [
|
|
|
|
|
|
'expire' => $v['expire'],
|
|
|
|
|
|
'value' => self::parseGet($v),
|
2021-03-29 15:34:24 +08:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ($del as $v) {
|
|
|
|
|
|
self::unset($v);
|
|
|
|
|
|
}
|
|
|
|
|
|
Framework::saveFrameworkState($obj);
|
2022-03-15 18:05:33 +08:00
|
|
|
|
Console::verbose('Saved.');
|
2020-10-03 23:00:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private static function checkExpire($key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (($expire = self::$kv_table->get($key, 'expire')) >= 0) {
|
2020-09-29 15:07:43 +08:00
|
|
|
|
if ($expire <= time()) {
|
|
|
|
|
|
self::$kv_table->del($key);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private static function parseGet($r)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch ($r['data_type']) {
|
|
|
|
|
|
case 'json':
|
|
|
|
|
|
case 'int':
|
|
|
|
|
|
case 'bool':
|
|
|
|
|
|
return json_decode($r['value'], true);
|
|
|
|
|
|
case '':
|
2020-09-29 15:07:43 +08:00
|
|
|
|
default:
|
2022-03-15 18:05:33 +08:00
|
|
|
|
return $r['value'];
|
2020-09-29 15:07:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|