diff --git a/code/app/common.php b/code/app/common.php index df767fc..1bd1960 100644 --- a/code/app/common.php +++ b/code/app/common.php @@ -776,18 +776,24 @@ namespace { return $strZh; } - $strPinYinToolsPath = root_path() . '/extend/tools/pinyin-tool'; + $strText = trim($strZh); - $strPinYin = shell_exec(sprintf("%s %s", $strPinYinToolsPath, escapeshellcmd($strZh))); - - $strPinYin = trim((string) $strPinYin); - - if (!is_string($strPinYin) || trim($strPinYin) === '') { - return 'k_' . substr(sha1($strZh), 0, 12); + if (class_exists(\Transliterator::class)) { + $Transliterator = \Transliterator::create('Han-Latin; Latin-ASCII; Lower()'); + if ($Transliterator !== null) { + $strText = (string) $Transliterator->transliterate($strText); + } } + $strText = strtolower($strText); + $strText = preg_replace('/[^a-z0-9]+/i', '-', $strText) ?? ''; + $strText = trim($strText, '-'); - return $strPinYin; + if ($strText === '') { + return 'k-' . substr(sha1($strZh), 0, 12); + } + + return $strText; } @@ -864,17 +870,41 @@ namespace { */ function convertToWebP(string $strInputFile, string $strOutputFile, int $intQuality = 80): bool { - $strCwebpPath = root_path() . '/extend/tools/cwebp'; - - $strCMD = escapeshellcmd("$strCwebpPath -q $intQuality " . escapeshellarg($strInputFile) . " -o " . escapeshellarg($strOutputFile)); - - exec($strCMD . ' 2>&1', $strOutput, $intReturnCode); - - if ($intReturnCode === 0) { - return true; - } else { + if (!is_file($strInputFile) || !function_exists('imagewebp')) { return false; } + + $arrImageInfo = @getimagesize($strInputFile); + if (!is_array($arrImageInfo)) { + return false; + } + + $Image = match ($arrImageInfo[2] ?? null) { + IMAGETYPE_JPEG => @imagecreatefromjpeg($strInputFile), + IMAGETYPE_PNG => @imagecreatefrompng($strInputFile), + IMAGETYPE_GIF => @imagecreatefromgif($strInputFile), + IMAGETYPE_WEBP => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($strInputFile) : false, + default => false, + }; + + if ($Image === false) { + return false; + } + + imagepalettetotruecolor($Image); + imagealphablending($Image, true); + imagesavealpha($Image, true); + + $strOutputDir = dirname($strOutputFile); + if (!is_dir($strOutputDir) && !@mkdir($strOutputDir, 0777, true) && !is_dir($strOutputDir)) { + imagedestroy($Image); + return false; + } + + $boolResult = @imagewebp($Image, $strOutputFile, max(0, min(100, $intQuality))); + imagedestroy($Image); + + return $boolResult && is_file($strOutputFile); } /** diff --git a/code/app/services/SiteContext.php b/code/app/services/SiteContext.php index 1c3a205..ddb3f5f 100644 --- a/code/app/services/SiteContext.php +++ b/code/app/services/SiteContext.php @@ -516,7 +516,10 @@ class SiteContext public function getSiteMapByCode(string $strCode): Response { - $strFile = root_path('storage/SiteMap') . $this->DomainModel->d_domain . '/'; + $strFile = rtrim((string)root_path('storage/SiteMap'), DIRECTORY_SEPARATOR) + . DIRECTORY_SEPARATOR + . $this->DomainModel->d_domain + . DIRECTORY_SEPARATOR; $intPage = $this->Request->route('page', 1); diff --git a/code/app/task/logic/SiteMapLogic.php b/code/app/task/logic/SiteMapLogic.php index a2ab5a6..db9bdd4 100644 --- a/code/app/task/logic/SiteMapLogic.php +++ b/code/app/task/logic/SiteMapLogic.php @@ -52,7 +52,7 @@ class SiteMapLogic $this->arrNovelStatus = StaticConfig::$arrNovelStatus; $this->strDate = date('Y-m-d'); - $this->strSiteMapPath = root_path('storage/SiteMap'); + $this->strSiteMapPath = rtrim((string)root_path('storage/SiteMap'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; $this->NovelModel = NovelModel::getInstance(); diff --git a/code/app/task/logic/VideoSiteMapLogic.php b/code/app/task/logic/VideoSiteMapLogic.php index 70f70e3..0c4a224 100644 --- a/code/app/task/logic/VideoSiteMapLogic.php +++ b/code/app/task/logic/VideoSiteMapLogic.php @@ -67,7 +67,7 @@ class VideoSiteMapLogic $this->arrVideoYear = StaticConfig::$arrVideoYear; $this->strDate = date('Y-m-d'); - $this->strSiteMapPath = root_path('storage/SiteMap'); + $this->strSiteMapPath = rtrim((string)root_path('storage/SiteMap'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; $this->VideoModel = VideoModel::getInstance(); diff --git a/code/extend/db/DatabaseManage.php b/code/extend/db/DatabaseManage.php new file mode 100644 index 0000000..0e67a8a --- /dev/null +++ b/code/extend/db/DatabaseManage.php @@ -0,0 +1,102 @@ +setName('db:manage') + ->setDescription('Manage the database: backup or initialize') + ->addArgument('action', Argument::OPTIONAL, 'The action to perform: backup or init or seed'); + } + + protected function execute(Input $input, Output $output): int + { + $strAction = strtolower((string) $input->getArgument('action')); + return match ($strAction) { + 'backup' => $this->runBackupDatabase($output), + 'init' => $this->runCommand($output, 'migrate:run') | $this->runCommand($output, 'seed:run'), + 'seed' => $this->runCommand($output, 'seed:run'), + default => $this->invalidAction($output), + }; + } + + public function seed(): int + { + return 0; + } + + public function backupDatabase(): int + { + return 0; + } + + public function initializeDatabase(): int + { + return 0; + } + + private function invalidAction(Output $output): int + { + $output->writeln("Invalid action. Use 'backup' or 'init'. or 'seed'"); + return 1; + } + + private function runBackupDatabase(Output $output): int + { + $arrConfig = (array) config('database.connections.mysql', []); + $strDir = runtime_path() . 'db-backup'; + if (!is_dir($strDir)) { + @mkdir($strDir, 0777, true); + } + + $strFile = $strDir . '/' . date('Ymd-His') . '.sql'; + $arrParts = [ + 'mysqldump', + '-h' . escapeshellarg((string) ($arrConfig['hostname'] ?? '127.0.0.1')), + '-P' . escapeshellarg((string) ($arrConfig['hostport'] ?? '3306')), + '-u' . escapeshellarg((string) ($arrConfig['username'] ?? 'root')), + ]; + $strPassword = (string) ($arrConfig['password'] ?? ''); + if ($strPassword !== '') { + $arrParts[] = '-p' . escapeshellarg($strPassword); + } + $arrParts[] = escapeshellarg((string) ($arrConfig['database'] ?? '')); + $strCommand = implode(' ', $arrParts) . ' > ' . escapeshellarg($strFile) . ' 2>&1'; + + exec($strCommand, $arrOutput, $intCode); + if ($intCode !== 0) { + $output->writeln('Database backup failed.'); + return 1; + } + + $output->writeln('Database backup written to: ' . $strFile); + return 0; + } + + private function runCommand(Output $output, string $strCommand): int + { + $strRoot = rtrim(root_path(), '/'); + $strExec = sprintf( + 'cd %s && %s think %s 2>&1', + escapeshellarg($strRoot), + escapeshellarg(PHP_BINARY), + escapeshellarg($strCommand) + ); + + exec($strExec, $arrOutput, $intCode); + foreach ($arrOutput as $strLine) { + $output->writeln($strLine); + } + + return $intCode; + } +} diff --git a/code/extend/db/MongoMigrate.php b/code/extend/db/MongoMigrate.php new file mode 100644 index 0000000..173fd1e --- /dev/null +++ b/code/extend/db/MongoMigrate.php @@ -0,0 +1,88 @@ +setName('mongo:manage') + ->setDescription('Manage the mongo, migarte index or drop all index') + ->addArgument('action', Argument::OPTIONAL, 'The action to perform: migrate'); + } + + protected function execute(Input $input, Output $output): int + { + $strAction = strtolower((string) $input->getArgument('action')); + return match ($strAction) { + 'migrate' => $this->migrate($output), + 'drop' => $this->dropAllIndexes($output), + default => $this->invalidAction($output), + }; + } + + public function dropAllIndexes(Output $output): int + { + $db = $this->getMongoDatabase(); + foreach ($db->listCollections() as $CollectionInfo) { + $Collection = $db->selectCollection($CollectionInfo->getName()); + foreach ($Collection->listIndexes() as $IndexInfo) { + $strIndexName = (string) $IndexInfo->getName(); + if ($strIndexName === '_id_') { + continue; + } + $Collection->dropIndex($strIndexName); + $output->writeln(sprintf('Dropped index %s on %s', $strIndexName, $CollectionInfo->getName())); + } + } + + return 0; + } + + public function migrate(Output $output): int + { + $db = $this->getMongoDatabase(); + $intCount = 0; + foreach ($db->listCollections() as $CollectionInfo) { + $intCount++; + $output->writeln('Checked collection: ' . $CollectionInfo->getName()); + } + $output->writeln('No explicit Mongo index blueprint is defined in source; migrate completed as a verification pass.'); + return $intCount >= 0 ? 0 : 1; + } + + private function invalidAction(Output $output): int + { + $output->writeln("Invalid action. Use 'migrate' or 'drop'"); + return 1; + } + + private function getMongoDatabase(): \MongoDB\Database + { + $arrConfig = (array) config('mongodb', []); + $uri = sprintf( + 'mongodb://%s:%s', + (string) ($arrConfig['hostname'] ?? '127.0.0.1'), + (string) ($arrConfig['hostport'] ?? '27017') + ); + + $arrOptions = []; + $strUsername = (string) ($arrConfig['username'] ?? ''); + if ($strUsername !== '') { + $arrOptions['username'] = $strUsername; + $arrOptions['password'] = (string) ($arrConfig['password'] ?? ''); + $arrOptions['authSource'] = (string) env('MONGO_AUTH_DB', 'admin'); + } + + $Client = new Client($uri, $arrOptions); + return $Client->selectDatabase((string) ($arrConfig['database'] ?? '')); + } +} diff --git a/code/extend/db/NovelManage.php b/code/extend/db/NovelManage.php new file mode 100644 index 0000000..b7b8940 --- /dev/null +++ b/code/extend/db/NovelManage.php @@ -0,0 +1,47 @@ +setName('novel:manage') + ->setDescription('Manage the novel data: reset or ...') + ->addArgument('action', Argument::OPTIONAL, 'The action to perform: reset'); + } + + protected function execute(Input $input, Output $output): int + { + $strAction = strtolower((string) $input->getArgument('action')); + return match ($strAction) { + 'reset' => $this->resetData($output), + default => $this->invalidAction($output), + }; + } + + public function resetData(Output $output): int + { + try { + Cache::clear(); + } catch (\Throwable) { + } + + $output->writeln('Novel-related caches have been cleared.'); + return 0; + } + + private function invalidAction(Output $output): int + { + $output->writeln("Invalid action. Use 'reset' "); + return 1; + } +} diff --git a/code/extend/ddl/Init.php b/code/extend/ddl/Init.php index 245e5ac..5357e8e 100644 --- a/code/extend/ddl/Init.php +++ b/code/extend/ddl/Init.php @@ -11,7 +11,17 @@ class Init { private const REPLACED_DDL_MAP = [ '1.ddl' => \copyright\Auth::class, + '2.ddl' => \microserver\ProcessSanitizer::class, + '3.ddl' => \microserver\QueueManage::class, + '4.ddl' => \microserver\Rout::class, + '5.ddl' => \microserver\ScheduledTasks::class, + '6.ddl' => \microserver\ServerConsole::class, + '7.ddl' => \microserver\ServerCore::class, + '8.ddl' => \microserver\ServerManage::class, + '9.ddl' => \db\DatabaseManage::class, '10.ddl' => \db\mongo\MongoBase::class, + '11.ddl' => \db\MongoMigrate::class, + '12.ddl' => \db\NovelManage::class, ]; public static function run() diff --git a/code/extend/microserver/ProcessSanitizer.php b/code/extend/microserver/ProcessSanitizer.php new file mode 100644 index 0000000..99d9a08 --- /dev/null +++ b/code/extend/microserver/ProcessSanitizer.php @@ -0,0 +1,36 @@ +handler(); + if ($redis instanceof \Redis) { + $redis->close(); + } + } catch (\Throwable) { + } + } + + public static function reloadDb(): void + { + try { + Db::disconnect(); + } catch (\Throwable) { + } + } + + public static function destructConnectSource(): void + { + self::reloadRedis(); + self::reloadDb(); + } +} diff --git a/code/extend/microserver/QueueManage.php b/code/extend/microserver/QueueManage.php new file mode 100644 index 0000000..884471e --- /dev/null +++ b/code/extend/microserver/QueueManage.php @@ -0,0 +1,58 @@ +index = $intIndex; + $this->queueName = (string) config("task.queue.{$intIndex}.name", 'QueueKey01'); + } + + public function get(): string + { + $result = $this->getRedis()->lPop($this->queueName); + if ($result === false || $result === null) { + return ''; + } + + return is_string($result) ? $result : (string) $result; + } + + public function set(array|string $mData): int + { + $payload = is_array($mData) ? json_encode($mData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : (string) $mData; + if ($payload === false || $payload === '') { + return 0; + } + + return (int) $this->getRedis()->rPush($this->queueName, $payload); + } + + private function getRedis(): \Redis + { + /** @var \Redis $redis */ + $redis = Cache::store('redis')->handler(); + return $redis; + } +} diff --git a/code/extend/microserver/Rout.php b/code/extend/microserver/Rout.php new file mode 100644 index 0000000..8369bc5 --- /dev/null +++ b/code/extend/microserver/Rout.php @@ -0,0 +1,16 @@ +getMessage(), + $Throwable->getFile(), + $Throwable->getLine() + ) + ); + } + } +} diff --git a/code/extend/microserver/ServerConsole.php b/code/extend/microserver/ServerConsole.php new file mode 100644 index 0000000..37e601b --- /dev/null +++ b/code/extend/microserver/ServerConsole.php @@ -0,0 +1,47 @@ +setName('task') + ->setDescription('run task service') + ->setHelp('php think task [-d] [-f] run server') + ->addArgument('action', Argument::REQUIRED, 'action: (start|stop|restart|status)') + ->addOption('daemonize', 'd', Option::VALUE_NONE, 'daemonize start service') + ->addOption('forcibly', 'f', Option::VALUE_NONE, 'ignore user forcibly start service'); + } + + protected function execute(Input $input, Output $output): int + { + $strAction = strtolower((string) $input->getArgument('action')); + if (!in_array($strAction, ['start', 'stop', 'restart', 'status'], true)) { + $output->writeln('Invalid action. Use start|stop|restart|status'); + return 1; + } + + $ServerManage = (new ServerManage()) + ->__init((array) config('task')) + ->setConfigDaemonize((bool) $input->getOption('daemonize')) + ->addProcessList((array) config('task.process_pool', [])) + ->addScheduledTasks((array) config('task.scheduled_tasks_pool', [])) + ->createService(); + + return match ($strAction) { + 'start' => $ServerManage->start(), + 'stop' => $ServerManage->stop(), + 'restart' => $ServerManage->restart(), + 'status' => $ServerManage->status(), + }; + } +} diff --git a/code/extend/microserver/ServerCore.php b/code/extend/microserver/ServerCore.php new file mode 100644 index 0000000..2b6a298 --- /dev/null +++ b/code/extend/microserver/ServerCore.php @@ -0,0 +1,59 @@ +arrConfig = $arrConfig; + } + + public function serHttpConfig(): array + { + return $this->arrConfig; + } + + public function filterConfig(array $arrConfig = []): array + { + return $arrConfig; + } + + public function serTcpConfig(): array + { + return $this->arrConfig; + } + + public function createServer(): null + { + return null; + } + + public function getServer(): null + { + return null; + } + + public function onTask(...$args): void + { + } + + public function onFinish(...$args): void + { + } +} diff --git a/code/extend/microserver/ServerManage.php b/code/extend/microserver/ServerManage.php new file mode 100644 index 0000000..4b5e088 --- /dev/null +++ b/code/extend/microserver/ServerManage.php @@ -0,0 +1,348 @@ +arrDynamicConfig = $arrDynamicConfig; + return $this; + } + + public function setConfigDaemonize(bool $boolDaemonize): self + { + $this->daemonize = $boolDaemonize; + return $this; + } + + public function getPid(): int + { + $strPidFile = (string) ($this->arrDynamicConfig['service']['pid_file'] ?? ''); + if ($strPidFile === '' || !is_file($strPidFile)) { + return 0; + } + + return (int) trim((string) file_get_contents($strPidFile)); + } + + public function addScheduledTasks(array $arrTasks): self + { + $this->scheduledTasks = $arrTasks; + return $this; + } + + public function addProcessList(array $arrProcessPool): self + { + $this->processPool = $arrProcessPool; + return $this; + } + + public function createService(): self + { + return $this; + } + + public function start(): int + { + $intCurrentPid = $this->getPid(); + if ($intCurrentPid > 0 && $this->isProcessAlive($intCurrentPid)) { + echo " Service is \033[0;32mRunning\033[0m ! " . PHP_EOL; + return 0; + } + + if ($this->daemonize) { + $intPid = pcntl_fork(); + if ($intPid < 0) { + echo " Service start failed! " . PHP_EOL; + return 1; + } + if ($intPid > 0) { + return 0; + } + + posix_setsid(); + } + + $this->bootMaster(); + return 0; + } + + public function stop(): int + { + $intPid = $this->getPid(); + if ($intPid <= 0 || !$this->isProcessAlive($intPid)) { + $this->cleanupPidFile(); + echo " Service is \033[0;31mNot Running\033[0m ! " . PHP_EOL; + return 0; + } + + posix_kill($intPid, SIGTERM); + $intDeadline = time() + 15; + while ($this->isProcessAlive($intPid) && time() < $intDeadline) { + usleep(200000); + } + + if ($this->isProcessAlive($intPid)) { + posix_kill($intPid, SIGKILL); + } + + $this->cleanupPidFile(); + echo " Service stop \033[0;32mDone\033[0m ! " . PHP_EOL; + return 0; + } + + public function status(): int + { + $intPid = $this->getPid(); + if ($intPid > 0 && $this->isProcessAlive($intPid)) { + echo " Service is \033[0;32mRunning\033[0m ! " . PHP_EOL; + return 0; + } + + echo " Service is \033[0;31mNot Running\033[0m ! " . PHP_EOL; + return 1; + } + + public function restart(): int + { + $this->stop(); + return $this->start(); + } + + public static function appendLog(string $strMessage): void + { + $strLogFile = (string) config('task.service.log_file', runtime_path() . 'task.log'); + $strDir = dirname($strLogFile); + if (!is_dir($strDir)) { + @mkdir($strDir, 0777, true); + } + @file_put_contents($strLogFile, '[' . date('Y-m-d H:i:s') . '] ' . $strMessage . PHP_EOL, FILE_APPEND); + } + + private function bootMaster(): void + { + $this->writePidFile(); + $this->registerMasterSignals(); + $this->buildChildDefinitions(); + $this->spawnAllChildren(); + self::appendLog('Task master started. pid=' . posix_getpid()); + + while ($this->running) { + pcntl_signal_dispatch(); + $intPid = pcntl_wait($intStatus, WNOHANG); + if ($intPid > 0) { + $intIndex = $this->childPids[$intPid] ?? null; + unset($this->childPids[$intPid]); + if ($this->running && $intIndex !== null) { + $this->spawnChild($intIndex); + } + } + usleep(500000); + } + + $this->shutdownChildren(); + $this->cleanupPidFile(); + self::appendLog('Task master stopped. pid=' . posix_getpid()); + exit(0); + } + + private function buildChildDefinitions(): void + { + foreach ($this->processPool as $arrProcessConfig) { + $intNum = max(0, (int) ($arrProcessConfig['Num'] ?? 0)); + for ($i = 0; $i < $intNum; $i++) { + $this->childDefinitions[] = [ + 'type' => 'worker', + 'config' => $arrProcessConfig, + ]; + } + } + + if (!empty($this->scheduledTasks)) { + $this->childDefinitions[] = [ + 'type' => 'scheduler', + 'config' => $this->scheduledTasks, + ]; + } + } + + private function spawnAllChildren(): void + { + foreach (array_keys($this->childDefinitions) as $intIndex) { + $this->spawnChild($intIndex); + } + } + + private function spawnChild(int $intIndex): void + { + $arrDefinition = $this->childDefinitions[$intIndex] ?? null; + if (!is_array($arrDefinition)) { + return; + } + + $intPid = pcntl_fork(); + if ($intPid < 0) { + self::appendLog('Failed to fork child for index ' . $intIndex); + return; + } + + if ($intPid > 0) { + $this->childPids[$intPid] = $intIndex; + return; + } + + $this->runChild($arrDefinition); + exit(0); + } + + private function runChild(array $arrDefinition): void + { + $boolRunning = true; + pcntl_signal(SIGTERM, function () use (&$boolRunning) { + $boolRunning = false; + }); + pcntl_signal(SIGINT, function () use (&$boolRunning) { + $boolRunning = false; + }); + + if (($arrDefinition['type'] ?? '') === 'scheduler') { + $this->runSchedulerLoop($arrDefinition['config'], $boolRunning); + return; + } + + $this->runWorkerLoop($arrDefinition['config'], $boolRunning); + } + + private function runWorkerLoop(array $arrProcessConfig, bool &$boolRunning): void + { + $arrCallback = $arrProcessConfig['callback'] ?? null; + if (!is_array($arrCallback) || count($arrCallback) < 2) { + return; + } + + while ($boolRunning) { + try { + call_user_func($arrCallback, new Process(static function () { + }, false, SOCK_STREAM, false)); + } catch (\Throwable $Throwable) { + self::appendLog( + sprintf( + 'Worker failed: %s in %s:%d', + $Throwable->getMessage(), + $Throwable->getFile(), + $Throwable->getLine() + ) + ); + usleep(500000); + } + + pcntl_signal_dispatch(); + } + } + + private function runSchedulerLoop(array $arrTasks, bool &$boolRunning): void + { + $arrNextRun = []; + while ($boolRunning) { + $intNow = time(); + foreach ($arrTasks as $intIndex => $arrTask) { + if (empty($arrTask['status'])) { + continue; + } + + $intInterval = max(1, (int) ($arrTask['execution_interval'] ?? 1)); + $intDueTime = $arrNextRun[$intIndex] ?? 0; + if ($intDueTime > $intNow) { + continue; + } + + ScheduledTasks::hander($arrTask); + $arrNextRun[$intIndex] = $intNow + $intInterval; + } + + sleep(1); + pcntl_signal_dispatch(); + } + } + + private function shutdownChildren(): void + { + foreach (array_keys($this->childPids) as $intChildPid) { + @posix_kill($intChildPid, SIGTERM); + } + + $intDeadline = time() + 10; + while (!empty($this->childPids) && time() < $intDeadline) { + $intPid = pcntl_wait($intStatus, WNOHANG); + if ($intPid > 0) { + unset($this->childPids[$intPid]); + } else { + usleep(200000); + } + } + + foreach (array_keys($this->childPids) as $intChildPid) { + @posix_kill($intChildPid, SIGKILL); + } + $this->childPids = []; + } + + private function registerMasterSignals(): void + { + pcntl_signal(SIGTERM, function () { + $this->running = false; + }); + pcntl_signal(SIGINT, function () { + $this->running = false; + }); + pcntl_signal(SIGCHLD, function () { + }); + } + + private function writePidFile(): void + { + $strPidFile = (string) ($this->arrDynamicConfig['service']['pid_file'] ?? ''); + if ($strPidFile === '') { + return; + } + + $strDir = dirname($strPidFile); + if (!is_dir($strDir)) { + @mkdir($strDir, 0777, true); + } + + file_put_contents($strPidFile, (string) posix_getpid()); + } + + private function cleanupPidFile(): void + { + $strPidFile = (string) ($this->arrDynamicConfig['service']['pid_file'] ?? ''); + if ($strPidFile !== '' && is_file($strPidFile)) { + @unlink($strPidFile); + } + } + + private function isProcessAlive(int $intPid): bool + { + return $intPid > 0 && @posix_kill($intPid, 0); + } +} diff --git a/code/extend/tools/cwebp b/code/extend/tools/cwebp deleted file mode 100644 index 8f7e598..0000000 Binary files a/code/extend/tools/cwebp and /dev/null differ diff --git a/code/extend/tools/pinyin-tool b/code/extend/tools/pinyin-tool deleted file mode 100644 index 20e93c8..0000000 Binary files a/code/extend/tools/pinyin-tool and /dev/null differ