debug
This commit is contained in:
@@ -776,18 +776,24 @@ namespace {
|
|||||||
return $strZh;
|
return $strZh;
|
||||||
}
|
}
|
||||||
|
|
||||||
$strPinYinToolsPath = root_path() . '/extend/tools/pinyin-tool';
|
$strText = trim($strZh);
|
||||||
|
|
||||||
$strPinYin = shell_exec(sprintf("%s %s", $strPinYinToolsPath, escapeshellcmd($strZh)));
|
if (class_exists(\Transliterator::class)) {
|
||||||
|
$Transliterator = \Transliterator::create('Han-Latin; Latin-ASCII; Lower()');
|
||||||
$strPinYin = trim((string) $strPinYin);
|
if ($Transliterator !== null) {
|
||||||
|
$strText = (string) $Transliterator->transliterate($strText);
|
||||||
if (!is_string($strPinYin) || trim($strPinYin) === '') {
|
}
|
||||||
return 'k_' . substr(sha1($strZh), 0, 12);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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
|
function convertToWebP(string $strInputFile, string $strOutputFile, int $intQuality = 80): bool
|
||||||
{
|
{
|
||||||
$strCwebpPath = root_path() . '/extend/tools/cwebp';
|
if (!is_file($strInputFile) || !function_exists('imagewebp')) {
|
||||||
|
|
||||||
$strCMD = escapeshellcmd("$strCwebpPath -q $intQuality " . escapeshellarg($strInputFile) . " -o " . escapeshellarg($strOutputFile));
|
|
||||||
|
|
||||||
exec($strCMD . ' 2>&1', $strOutput, $intReturnCode);
|
|
||||||
|
|
||||||
if ($intReturnCode === 0) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -516,7 +516,10 @@ class SiteContext
|
|||||||
|
|
||||||
public function getSiteMapByCode(string $strCode): Response
|
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);
|
$intPage = $this->Request->route('page', 1);
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class SiteMapLogic
|
|||||||
$this->arrNovelStatus = StaticConfig::$arrNovelStatus;
|
$this->arrNovelStatus = StaticConfig::$arrNovelStatus;
|
||||||
|
|
||||||
$this->strDate = date('Y-m-d');
|
$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();
|
$this->NovelModel = NovelModel::getInstance();
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class VideoSiteMapLogic
|
|||||||
$this->arrVideoYear = StaticConfig::$arrVideoYear;
|
$this->arrVideoYear = StaticConfig::$arrVideoYear;
|
||||||
|
|
||||||
$this->strDate = date('Y-m-d');
|
$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();
|
$this->VideoModel = VideoModel::getInstance();
|
||||||
|
|
||||||
|
|||||||
102
code/extend/db/DatabaseManage.php
Normal file
102
code/extend/db/DatabaseManage.php
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace db;
|
||||||
|
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Argument;
|
||||||
|
use think\console\Output;
|
||||||
|
|
||||||
|
class DatabaseManage extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
code/extend/db/MongoMigrate.php
Normal file
88
code/extend/db/MongoMigrate.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace db;
|
||||||
|
|
||||||
|
use MongoDB\Client;
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Argument;
|
||||||
|
use think\console\Output;
|
||||||
|
|
||||||
|
class MongoMigrate extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->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'] ?? ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
47
code/extend/db/NovelManage.php
Normal file
47
code/extend/db/NovelManage.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace db;
|
||||||
|
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Argument;
|
||||||
|
use think\console\Output;
|
||||||
|
use think\facade\Cache;
|
||||||
|
|
||||||
|
class NovelManage extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,17 @@ class Init
|
|||||||
{
|
{
|
||||||
private const REPLACED_DDL_MAP = [
|
private const REPLACED_DDL_MAP = [
|
||||||
'1.ddl' => \copyright\Auth::class,
|
'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,
|
'10.ddl' => \db\mongo\MongoBase::class,
|
||||||
|
'11.ddl' => \db\MongoMigrate::class,
|
||||||
|
'12.ddl' => \db\NovelManage::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function run()
|
public static function run()
|
||||||
|
|||||||
36
code/extend/microserver/ProcessSanitizer.php
Normal file
36
code/extend/microserver/ProcessSanitizer.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
use think\facade\Cache;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
class ProcessSanitizer
|
||||||
|
{
|
||||||
|
public static function reloadRedis(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$redis = Cache::store('redis')->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
58
code/extend/microserver/QueueManage.php
Normal file
58
code/extend/microserver/QueueManage.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
use think\facade\Cache;
|
||||||
|
|
||||||
|
class QueueManage
|
||||||
|
{
|
||||||
|
private static array $instances = [];
|
||||||
|
|
||||||
|
private int $index;
|
||||||
|
|
||||||
|
private string $queueName;
|
||||||
|
|
||||||
|
public static function getInstance(int $intIndex = 1): self
|
||||||
|
{
|
||||||
|
if (!isset(self::$instances[$intIndex])) {
|
||||||
|
self::$instances[$intIndex] = new self($intIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instances[$intIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(int $intIndex = 1)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
code/extend/microserver/Rout.php
Normal file
16
code/extend/microserver/Rout.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
class Rout
|
||||||
|
{
|
||||||
|
public static function httpDispense(...$args): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tcpDispense(...$args): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
29
code/extend/microserver/ScheduledTasks.php
Normal file
29
code/extend/microserver/ScheduledTasks.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
class ScheduledTasks
|
||||||
|
{
|
||||||
|
public static function hander(array $arrTask): void
|
||||||
|
{
|
||||||
|
$arrCallback = $arrTask['callback'] ?? null;
|
||||||
|
if (!is_array($arrCallback) || count($arrCallback) < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
call_user_func($arrCallback, $arrTask['param'] ?? []);
|
||||||
|
} catch (\Throwable $Throwable) {
|
||||||
|
ServerManage::appendLog(
|
||||||
|
sprintf(
|
||||||
|
'Scheduled task failed: %s in %s:%d',
|
||||||
|
$Throwable->getMessage(),
|
||||||
|
$Throwable->getFile(),
|
||||||
|
$Throwable->getLine()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
code/extend/microserver/ServerConsole.php
Normal file
47
code/extend/microserver/ServerConsole.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Argument;
|
||||||
|
use think\console\input\Option;
|
||||||
|
use think\console\Output;
|
||||||
|
|
||||||
|
class ServerConsole extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->setName('task')
|
||||||
|
->setDescription('run task service')
|
||||||
|
->setHelp('php think task <action> [-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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
59
code/extend/microserver/ServerCore.php
Normal file
59
code/extend/microserver/ServerCore.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
class ServerCore
|
||||||
|
{
|
||||||
|
private static ?self $obj = null;
|
||||||
|
|
||||||
|
private array $arrConfig = [];
|
||||||
|
|
||||||
|
public static function instance(array $arrConfig = []): self
|
||||||
|
{
|
||||||
|
if (self::$obj === null) {
|
||||||
|
self::$obj = new self($arrConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(array $arrConfig = [])
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
348
code/extend/microserver/ServerManage.php
Normal file
348
code/extend/microserver/ServerManage.php
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace microserver;
|
||||||
|
|
||||||
|
use Swoole\Process;
|
||||||
|
|
||||||
|
class ServerManage
|
||||||
|
{
|
||||||
|
private array $arrDynamicConfig = [];
|
||||||
|
|
||||||
|
private array $processPool = [];
|
||||||
|
|
||||||
|
private array $scheduledTasks = [];
|
||||||
|
|
||||||
|
private bool $daemonize = false;
|
||||||
|
|
||||||
|
private bool $running = true;
|
||||||
|
|
||||||
|
private array $childDefinitions = [];
|
||||||
|
|
||||||
|
private array $childPids = [];
|
||||||
|
|
||||||
|
public function __init(array $arrDynamicConfig = []): self
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user