This commit is contained in:
2023-10-05 15:33:18 +08:00
parent 0c2134a361
commit 2fb2111526
+273 -277
View File
@@ -18,286 +18,282 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class BackupCommand extends Command class BackupCommand extends Command
{ {
public string $command = 'db:backup';
public string $description = 'php kiri.php db:backup --database users --table u_user --data 1 /Users/admin/snowflake-bi/test.sql';
private LocalService $service;
public array $percentStatus = [];
/**
*
*/
protected function configure()
{
$this->service = \Kiri::getDi()->get(LocalService::class);
$this->setName('db:backup')
->addOption('data', 'd', InputArgument::OPTIONAL)
->addArgument('path', InputArgument::REQUIRED, "save to path", null)
->addOption('table', 't', InputArgument::OPTIONAL)
->addOption('database', 'db', InputArgument::OPTIONAL)
->setDescription('php kiri.php db:backup --database users --table u_user --data 1 /Users/admin/snowflake-bi/test.sql');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
* @throws Exception
*/
public function execute(InputInterface $input, OutputInterface $output): int
{
try {
/** @var Connection $data */
$data = $this->service->get($input->getOption('database'));
$table = $input->getOption('table');
if ($table !== null) {
$table = explode(',', $table);
} else {
$tmp = $data->createCommand('show tables from `' . $data->database . '`')->all();
$table = [];
foreach ($tmp as $value) {
$table[] = current($value);
}
}
$path = $input->getArgument('path'); public string $command = 'db:backup';
if (!str_starts_with($path, '/')) {
$path = APP_PATH . $path;
}
$isData = $input->getOption('data');
if (!is_dir($path)) {
mkdir($path);
}
$waite = new Coroutine\WaitGroup();
foreach ($table as $value) {
$tableInfo = $data->createCommand('show create table `' . $data->database . '`.`' . $value . '`')->one();
$tmp = rtrim($path, '/') . '/' . current($tableInfo) . '.sql';
if (!file_exists($tmp)) {
touch($tmp);
}
file_put_contents($tmp, '');
$tableCreator = next($tableInfo);
if (preg_match('/AUTO_INCREMENT=\d+\s/', $tableCreator)) { public string $description = 'php kiri.php db:backup --database users --table u_user --data 1 /Users/admin/snowflake-bi/test.sql';
$tableCreator = preg_replace('/AUTO_INCREMENT=\d+\s/', 'AUTO_INCREMENT=1 ', $tableCreator);
}
private LocalService $service;
file_put_contents($tmp, $tableCreator . ';' . PHP_EOL . PHP_EOL, FILE_APPEND);
public array $percentStatus = [];
if ($isData == 1) {
$waite->add(1);
Coroutine::create(function () use ($waite, $input, $value, $tmp) { /**
defer(function () use ($waite) { *
$waite->done(); */
}); protected function configure()
$this->writeData( $input->getOption('database'), $value, $tmp); {
}); $this->service = \Kiri::getDi()->get(LocalService::class);
} $this->setName('db:backup')
} ->addOption('data', 'd', InputArgument::OPTIONAL)
->addArgument('path', InputArgument::REQUIRED, "save to path", null)
$waite->wait(); ->addOption('table', 't', InputArgument::OPTIONAL)
->addOption('database', 'db', InputArgument::OPTIONAL)
$output->write('dump data success'); ->setDescription('php kiri.php db:backup --database users --table u_user --data 1 /Users/admin/snowflake-bi/test.sql');
} catch (\Throwable $throwable) { }
$output->writeln(throwable($throwable));
} finally {
return 1; /**
} * @param InputInterface $input
} * @param OutputInterface $output
* @return int
* @throws Exception
public bool $isEnd = false; */
public function execute(InputInterface $input, OutputInterface $output): int
{
/** try {
* @param string $dbname /** @var Connection $data */
* @param string $tableName $data = $this->service->get($input->getOption('database'));
* @param string $path
* @return void $table = $input->getOption('table');
* @throws Exception if ($table !== null) {
*/ $table = explode(',', $table);
public function writeData(string $dbname, string $tableName, string $path): void } else {
{ $tmp = $data->createCommand('show tables from `' . $data->database . '`')->all();
$offset = 0; $table = [];
$size = 1000; foreach ($tmp as $value) {
$table[] = current($value);
$channel = new Channel(200); }
for ($i = 0; $i < 200; $i++) { }
Coroutine::create(function () use ($channel, $path, $dbname, $tableName) {
while ($channel->errCode != SWOOLE_CHANNEL_CLOSED) { $path = $input->getArgument('path');
if (!str_starts_with($path, '/')) {
$path = APP_PATH . $path;
}
$isData = $input->getOption('data');
if (!is_dir($path)) {
mkdir($path);
}
$waite = new Coroutine\WaitGroup();
foreach ($table as $value) {
$tableInfo = $data->createCommand('show create table `' . $data->database . '`.`' . $value . '`')->one();
$tmp = rtrim($path, '/') . '/' . current($tableInfo) . '.sql';
if (!file_exists($tmp)) {
touch($tmp);
}
file_put_contents($tmp, '');
$tableCreator = next($tableInfo);
if (preg_match('/AUTO_INCREMENT=\d+\s/', $tableCreator)) {
$tableCreator = preg_replace('/AUTO_INCREMENT=\d+\s/', 'AUTO_INCREMENT=1 ', $tableCreator);
}
file_put_contents($tmp, $tableCreator . ';' . PHP_EOL . PHP_EOL, FILE_APPEND);
if ($isData == 1) {
$waite->add(1);
Coroutine::create(function () use ($waite, $input, $value, $tmp) {
defer(function () use ($waite) {
$waite->done();
});
$this->writeData($input->getOption('database'), $value, $tmp);
});
}
}
$waite->wait();
$output->write('dump data success');
} catch (\Throwable $throwable) {
$output->writeln(throwable($throwable));
} finally {
return 1;
}
}
public bool $isEnd = false;
/**
* @param string $dbname
* @param string $tableName
* @param string $path
* @return void
* @throws Exception
*/
public function writeData(string $dbname, string $tableName, string $path): void
{
$offset = 0;
$size = 1000;
$channel = new Channel(200);
for ($i = 0; $i < 200; $i++) {
Coroutine::create(function () use ($channel, $path, $dbname, $tableName) {
while ($channel->errCode != SWOOLE_CHANNEL_CLOSED) {
if (($data = $channel->pop()) === false) { if (($data = $channel->pop()) === false) {
break; break;
} }
if (($value = json_decode($data, true)) === null) { if (($value = json_decode($data, true)) === null) {
continue; continue;
} }
$value = $this->toSql($dbname, $tableName, $value); $value = $this->toSql($dbname, $tableName, $value);
file_put_contents($path, $value . PHP_EOL, FILE_APPEND); file_put_contents($path, $value . PHP_EOL, FILE_APPEND);
} }
}); });
} }
$id = Coroutine::create(function () use ($channel) { $id = Coroutine::create(function () use ($channel) {
$data = Coroutine::waitSignal(SIGTERM | SIGINT); $data = Coroutine::waitSignal(SIGTERM | SIGINT);
if ($data) { if ($data) {
$this->isEnd = true; $this->isEnd = true;
} }
}); });
/** @var Connection $database */ /** @var Connection $database */
$database = \Kiri::service()->get($dbname); $database = \Kiri::service()->get($dbname);
$total = $database->createCommand("SELECT COUNT(*) as total FROM " . $tableName)->one()['total']; $total = $database->createCommand("SELECT COUNT(*) as total FROM " . $tableName)->one()['total'];
$startTime = time(); $startTime = time();
$wait = new Coroutine\WaitGroup(); $wait = new Coroutine\WaitGroup();
while ($this->isEnd === false && $offset < $total) { while ($this->isEnd === false && $offset < $total) {
$wait->add(1); $wait->add(1);
Coroutine::create(function () use ($wait, $offset, $size, $total, $dbname, $tableName, $channel) { Coroutine::create(function () use ($wait, $offset, $size, $total, $dbname, $tableName, $channel) {
defer(function () use ($wait) { defer(function () use ($wait) {
$wait->done(); $wait->done();
}); });
/** @var Connection $database */ /** @var Connection $database */
$database = \Kiri::service()->get($dbname); $database = \Kiri::service()->get($dbname);
$data = $database->createCommand("SELECT * FROM $tableName LIMIT $offset,$size")->all(); $data = $database->createCommand("SELECT * FROM $tableName LIMIT $offset,$size")->all();
if (is_bool($data) || count($data) < 1) { if (is_bool($data) || count($data) < 1) {
return; return;
} }
$channel->push(json_encode($data)); $channel->push(json_encode($data));
$this->percentStatus[$dbname . $tableName] = intval(round(($offset + $size) / $total, 2) * 100); $this->percentStatus[$dbname . $tableName] = intval(round(($offset + $size) / $total, 2) * 100);
$this->outputProgress(true); $this->outputProgress(true);
}); });
$offset += $size; $offset += $size;
} }
$wait->wait(); $wait->wait();
echo 'use time ' . (time() - $startTime) . 's' . PHP_EOL; echo 'use time ' . (time() - $startTime) . 's' . PHP_EOL;
$channel->close(); $channel->close();
Coroutine::cancel($id); Coroutine::cancel($id);
} }
/** /**
* @param string $dbname * @param string $dbname
* @param string $value * @param string $value
* @param array $data * @param array $data
* @return string * @return string
*/ */
public function toSql(string $dbname, string $value, array $data): string public function toSql(string $dbname, string $value, array $data): string
{ {
$strings = ['INSERT INTO ' . $dbname . '.' . $value]; $strings = ['INSERT INTO ' . $dbname . '.' . $value];
foreach ($data as $datum) { $strings[] = '(' . implode(',', array_keys($data[0])) . ') VALUES';
if (count($strings) == 1) { foreach ($data as $datum) {
$keys = array_keys($datum); $keys = array_values($datum);
$strings[] = '(' . implode(',', $keys) . ') VALUES'; $encode = [];
} else { foreach ($keys as $val) {
$keys = array_values($datum); if (is_string($val)) {
$encode = []; $encode[] = '\'' . addcslashes($val, '\'') . '\'';
foreach ($keys as $val) { } else {
if (is_string($val)) { $encode[] = $val === '' ? '\'\'' : $val;
$encode[] = '\'' . $val . '\''; }
} else { }
$encode[] = $val === '' ? '\'\'' : $val; $strings[] = '(' . implode(',', $encode) . '),';
} }
} return rtrim(implode('', $strings), ',') . ';';
$strings[] = '(' . implode(',', $encode) . '),'; }
}
}
return rtrim(implode('', $strings), ',') . ';'; /**
} * @param $key
* @param $percent
* @return string
/** * 组合成进度条
* @param $key */
* @param $percent public function buildLine($key, $percent): string
* @return string {
* 组合成进度条 $repeatTimes = 100;
*/ if ($percent > 100) {
public function buildLine($key, $percent): string $percent = 100;
{ }
$repeatTimes = 100; if ($percent > 0) {
if ($percent > 100) { $hasColor = str_repeat('■', $percent);
$percent = 100; } else {
} $hasColor = '';
if ($percent > 0) { }
$hasColor = str_repeat('■', $percent);
} else { if ($repeatTimes - $percent > 0) {
$hasColor = ''; $noColor = str_repeat(' ', $repeatTimes - $percent);
} } else {
$noColor = '';
if ($repeatTimes - $percent > 0) { }
$noColor = str_repeat(' ', $repeatTimes - $percent);
} else { $buffer = "[{$hasColor}{$noColor}]";
$noColor = ''; if ($percent !== 100) {
} $percentString = sprintf("[ %s %-6s]", $key, $percent . '%');
} else {
$buffer = "[{$hasColor}{$noColor}]"; $percentString = sprintf("[ %s %-5s]", $key, 'OK');;
if ($percent !== 100) { }
$percentString = sprintf("[ %s %-6s]", $key, $percent . '%');
} else { return $percentString . $buffer . "\r";
$percentString = sprintf("[ %s %-5s]", $key, 'OK');; }
}
/**
return $percentString . $buffer . "\r"; * @param bool $clear
} * @return void
* 输出进度条
/** */
* @param bool $clear public function outputProgress(bool $clear = false): void
* @return void {
* 输出进度条 if ($clear) {
*/ $number = count($this->percentStatus);
public function outputProgress(bool $clear = false): void for ($i = 0; $i < $number; $i++) {
{ system("tput cuu1");
if ($clear) { system("tput el");
$number = count($this->percentStatus); }
for ($i = 0; $i < $number; $i++) { }
system("tput cuu1"); foreach ($this->percentStatus as $key => $value) {
system("tput el"); echo $this->buildLine($key, $value) . "\n";
} }
} }
foreach ($this->percentStatus as $key => $value) {
echo $this->buildLine($key, $value) . "\n"; /**
} * @param $k
} * @param $value
* @return void
/** * 更新进度条值
* @param $k */
* @param $value public function updateProgressValue($k, $value): void
* @return void {
* 更新进度条值 $this->percentStatus[$k] = $value;
*/ if ($this->percentStatus[$k] >= 1000) {
public function updateProgressValue($k, $value): void $this->percentStatus[$k] = 1000;
{ $this->outputProgress(true);
$this->percentStatus[$k] = $value; return;
if ($this->percentStatus[$k] >= 1000) { }
$this->percentStatus[$k] = 1000;
$this->outputProgress(true); $this->outputProgress(true);
return; usleep(50000);
} }
$this->outputProgress(true);
usleep(50000);
}
} }