feat(command): 新增数据库调试命令行工具集

This commit is contained in:
augushong
2026-01-26 23:10:10 +08:00
parent 148f8b7a6f
commit e8f58ef322
14 changed files with 935 additions and 1 deletions

View File

@@ -0,0 +1,79 @@
<?php
namespace base\common\command\tools\db;
use base\common\service\ToolsDbServiceBase;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
class ToolsDbCountBase extends Command
{
protected function configure()
{
$this->setName('tools:db:count')
->setDescription('统计表记录数')
->addArgument('table', null, '表名')
->addOption('where', null, Option::VALUE_OPTIONAL, 'WHERE 条件')
->addOption('connection', null, Option::VALUE_OPTIONAL, '指定数据库连接配置')
->addOption('help', 'h', Option::VALUE_NONE, '显示帮助信息');
}
protected function execute($input, $output)
{
if ($input->getOption('help')) {
$service = new ToolsDbServiceBase();
$service->showHelp('tools:db:count', $output);
return;
}
$service = new ToolsDbServiceBase();
if (!$service->checkDebugMode($output)) {
return;
}
$connection = $service->getDbConnection($input);
$service->setConnection($connection);
$tableName = $input->getArgument('table');
if (empty($tableName)) {
$output->error('请提供表名');
return;
}
$fullTableName = $service->getFullTableName($tableName);
$where = $input->getOption('where');
try {
$startTime = microtime(true);
$query = Db::connect($connection)->table($fullTableName);
if ($where) {
$query->whereRaw($where);
}
$count = $query->count();
$endTime = microtime(true);
$executionTime = round(($endTime - $startTime) * 1000, 2);
$output->writeln('');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('<info>表名:' . $fullTableName . '</info>');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('');
$output->writeln('<info>记录数:</info>' . $count);
$output->writeln('<info>执行时间:</info>' . $executionTime . 'ms');
$output->writeln('');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('');
} catch (\Exception $e) {
$output->error('统计失败:' . $e->getMessage());
return;
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace base\common\command\tools\db;
use base\common\service\ToolsDbServiceBase;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
class ToolsDbDescBase extends Command
{
protected function configure()
{
$this->setName('tools:db:desc')
->setDescription('显示表结构信息')
->addArgument('table', null, '表名')
->addOption('show-index', null, Option::VALUE_NONE, '显示索引信息')
->addOption('connection', null, Option::VALUE_OPTIONAL, '指定数据库连接配置')
->addOption('help', 'h', Option::VALUE_NONE, '显示帮助信息');
}
protected function execute($input, $output)
{
if ($input->getOption('help')) {
$service = new ToolsDbServiceBase();
$service->showHelp('tools:db:desc', $output);
return;
}
$service = new ToolsDbServiceBase();
if (!$service->checkDebugMode($output)) {
return;
}
$connection = $service->getDbConnection($input);
$service->setConnection($connection);
$tableName = $input->getArgument('table');
if (empty($tableName)) {
$output->error('请提供表名');
return;
}
$fullTableName = $service->getFullTableName($tableName);
$showIndex = $input->getOption('show-index');
try {
$output->writeln('');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('<info>表结构:' . $fullTableName . '</info>');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('');
$columns = Db::connect($connection)->query("SHOW FULL COLUMNS FROM `$fullTableName`");
if (empty($columns)) {
$output->writeln('表不存在或无字段');
$output->writeln('');
return;
}
$tableData = [];
foreach ($columns as $column) {
$tableData[] = [
'字段名' => $column['Field'],
'类型' => $column['Type'],
'是否为空' => $column['Null'] === 'YES' ? '是' : '否',
'键' => $column['Key'],
'默认值' => $column['Default'] ?? 'NULL',
'额外' => $column['Extra'] ?? '',
'注释' => $column['Comment'] ?? '',
];
}
$service->formatTableOutput($tableData, $output);
if ($showIndex) {
$output->writeln('');
$output->writeln('<comment>' . str_repeat('-', 60) . '</comment>');
$output->writeln('<info>索引信息</info>');
$output->writeln('<comment>' . str_repeat('-', 60) . '</comment>');
$output->writeln('');
$indexes = Db::connect($connection)->query("SHOW INDEX FROM `$fullTableName`");
if (!empty($indexes)) {
$indexData = [];
foreach ($indexes as $index) {
$indexData[] = [
'索引名' => $index['Key_name'],
'列名' => $index['Column_name'],
'唯一' => $index['Non_unique'] == 0 ? '是' : '否',
'索引类型' => $index['Index_type'],
'顺序' => $index['Seq_in_index'],
];
}
$service->formatTableOutput($indexData, $output);
} else {
$output->writeln('无索引');
}
}
$output->writeln('');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('');
} catch (\Exception $e) {
$output->error('获取表结构失败:' . $e->getMessage());
return;
}
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace base\common\command\tools\db;
use base\common\service\ToolsDbServiceBase;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
class ToolsDbExecuteBase extends Command
{
protected function configure()
{
$this->setName('tools:db:execute')
->setDescription('执行 SQL 非查询语句INSERT/UPDATE/DELETE')
->addArgument('sql', null, 'SQL 执行语句')
->addOption('force', null, Option::VALUE_NONE, '跳过确认直接执行')
->addOption('transaction', null, Option::VALUE_NONE, '在事务中执行(失败自动回滚)')
->addOption('connection', null, Option::VALUE_OPTIONAL, '指定数据库连接配置')
->addOption('help', 'h', Option::VALUE_NONE, '显示帮助信息');
}
protected function execute($input, $output)
{
if ($input->getOption('help')) {
$service = new ToolsDbServiceBase();
$service->showHelp('tools:db:execute', $output);
return;
}
$service = new ToolsDbServiceBase();
if (!$service->checkDebugMode($output)) {
return;
}
$connection = $service->getDbConnection($input);
$service->setConnection($connection);
$sql = $input->getArgument('sql');
if (empty($sql)) {
$output->error('请提供 SQL 执行语句');
return;
}
$force = $input->getOption('force');
$useTransaction = $input->getOption('transaction');
if (preg_match('/^\s*SELECT\s+/i', $sql)) {
$output->error('不支持 SELECT 语句,请使用 tools:db:query 执行查询');
return;
}
$output->writeln('');
$output->writeln('<comment>准备执行的 SQL</comment>');
$output->writeln($sql);
$output->writeln('');
if (!$force) {
$confirm = $output->confirm($input, '<question>确定要执行此 SQL 语句吗?</question> ');
if (!$confirm) {
$output->writeln('<comment>操作已取消</comment>');
return;
}
}
try {
$startTime = microtime(true);
if ($useTransaction) {
Db::connect($connection)->startTrans();
try {
$affectedRows = Db::connect($connection)->execute($sql);
Db::connect($connection)->commit();
} catch (\Exception $e) {
Db::connect($connection)->rollback();
throw $e;
}
} else {
$affectedRows = Db::connect($connection)->execute($sql);
}
$endTime = microtime(true);
$executionTime = round(($endTime - $startTime) * 1000, 2);
$output->writeln('');
$output->writeln('<info>执行成功</info>');
$output->writeln('影响行数:' . $affectedRows);
$output->writeln('执行时间:' . $executionTime . 'ms');
$output->writeln('');
} catch (\Exception $e) {
$output->writeln('');
$output->error('执行失败:' . $e->getMessage());
if ($useTransaction) {
$output->writeln('<comment>事务已回滚</comment>');
}
$output->writeln('');
return;
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace base\common\command\tools\db;
use base\common\service\ToolsDbServiceBase;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
class ToolsDbInfoBase extends Command
{
protected function configure()
{
$this->setName('tools:db:info')
->setDescription('显示数据库连接信息和表列表')
->addOption('with-count', null, Option::VALUE_NONE, '显示每个表的记录数')
->addOption('connection', null, Option::VALUE_OPTIONAL, '指定数据库连接配置')
->addOption('help', 'h', Option::VALUE_NONE, '显示帮助信息');
}
protected function execute($input, $output)
{
if ($input->getOption('help')) {
$service = new ToolsDbServiceBase();
$service->showHelp('tools:db:info', $output);
return;
}
$service = new ToolsDbServiceBase();
if (!$service->checkDebugMode($output)) {
return;
}
$connection = $service->getDbConnection($input);
$service->setConnection($connection);
$withCount = $input->getOption('with-count');
try {
$config = Config::get('database.connections.' . $connection);
if (!$config) {
$output->error('数据库连接配置不存在:' . $connection);
return;
}
$output->writeln('');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('<info>数据库连接信息</info>');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('');
$output->writeln('<info>连接名称:</info>' . $connection);
$output->writeln('<info>数据库类型:</info>' . ($config['type'] ?? 'unknown'));
$output->writeln('<info>主机地址:</info>' . ($config['hostname'] ?? 'unknown'));
$output->writeln('<info>数据库名:</info>' . ($config['database'] ?? 'unknown'));
$output->writeln('<info>端口:</info>' . ($config['hostport'] ?? 'unknown'));
$output->writeln('<info>字符集:</info>' . ($config['charset'] ?? 'unknown'));
$output->writeln('<info>表前缀:</info>' . ($config['prefix'] ?? ''));
$output->writeln('');
$output->writeln('<comment>' . str_repeat('-', 60) . '</comment>');
$output->writeln('<info>表列表</info>');
$output->writeln('<comment>' . str_repeat('-', 60) . '</comment>');
$output->writeln('');
$tables = Db::connect($connection)->getTables();
if (empty($tables)) {
$output->writeln('无表');
$output->writeln('');
return;
}
$prefix = $config['prefix'] ?? '';
foreach ($tables as $table) {
$shortName = $table;
if ($prefix && str_starts_with($table, $prefix)) {
$shortName = substr($table, strlen($prefix));
}
if ($withCount) {
$count = Db::connect($connection)->table($table)->count();
$output->writeln('<info>' . str_pad($shortName, 40) . '</info>' . $count . ' 条记录');
} else {
$output->writeln('<info>' . $shortName . '</info>');
}
}
$output->writeln('');
$output->writeln('<comment>' . str_repeat('=', 60) . '</comment>');
$output->writeln('');
} catch (\Exception $e) {
$output->error('获取信息失败:' . $e->getMessage());
return;
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace base\common\command\tools\db;
use base\common\service\ToolsDbServiceBase;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
class ToolsDbQueryBase extends Command
{
protected function configure()
{
$this->setName('tools:db:query')
->setDescription('执行 SQL 查询语句SELECT并显示结果')
->addArgument('sql', null, 'SQL 查询语句')
->addOption('format', null, Option::VALUE_OPTIONAL, '输出格式可选值table默认、json', 'table')
->addOption('limit', null, Option::VALUE_OPTIONAL, '限制显示行数')
->addOption('connection', null, Option::VALUE_OPTIONAL, '指定数据库连接配置')
->addOption('help', 'h', Option::VALUE_NONE, '显示帮助信息');
}
protected function execute($input, $output)
{
if ($input->getOption('help')) {
$service = new ToolsDbServiceBase();
$service->showHelp('tools:db:query', $output);
return;
}
$service = new ToolsDbServiceBase();
if (!$service->checkDebugMode($output)) {
return;
}
$connection = $service->getDbConnection($input);
$service->setConnection($connection);
$sql = $input->getArgument('sql');
if (empty($sql)) {
$output->error('请提供 SQL 查询语句');
return;
}
$format = $input->getOption('format') ?: 'table';
$limit = $input->getOption('limit');
if (!preg_match('/^\s*SELECT\s+/i', $sql)) {
$output->error('仅支持 SELECT 语句,请使用 tools:db:execute 执行其他 SQL 语句');
return;
}
try {
$startTime = microtime(true);
$query = Db::connect($connection)->query($sql);
$endTime = microtime(true);
$executionTime = round(($endTime - $startTime) * 1000, 2);
if (!is_array($query)) {
$query = [];
}
if ($limit && is_numeric($limit)) {
$query = array_slice($query, 0, (int)$limit);
}
$output->writeln('');
if ($format === 'json') {
$service->formatJsonOutput($query, $output);
} else {
$service->formatTableOutput($query, $output);
}
$output->writeln('');
$output->writeln('<info>查询成功</info>');
$output->writeln('影响行数:' . count($query));
$output->writeln('执行时间:' . $executionTime . 'ms');
$output->writeln('');
} catch (\Exception $e) {
$output->error('查询失败:' . $e->getMessage());
return;
}
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace base\common\command\tools\db;
use base\common\service\ToolsDbServiceBase;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
class ToolsDbTableBase extends Command
{
protected function configure()
{
$this->setName('tools:db:table')
->setDescription('使用查询构建器操作表')
->addArgument('table', null, '表名')
->addOption('where', null, Option::VALUE_OPTIONAL, 'WHERE 条件')
->addOption('field', null, Option::VALUE_OPTIONAL, '查询字段,多个用逗号分隔')
->addOption('limit', null, Option::VALUE_OPTIONAL, '限制行数')
->addOption('order', null, Option::VALUE_OPTIONAL, '排序,如 "id DESC"')
->addOption('count', null, Option::VALUE_NONE, '只统计数量')
->addOption('connection', null, Option::VALUE_OPTIONAL, '指定数据库连接配置')
->addOption('help', 'h', Option::VALUE_NONE, '显示帮助信息');
}
protected function execute($input, $output)
{
if ($input->getOption('help')) {
$service = new ToolsDbServiceBase();
$service->showHelp('tools:db:table', $output);
return;
}
$service = new ToolsDbServiceBase();
if (!$service->checkDebugMode($output)) {
return;
}
$connection = $service->getDbConnection($input);
$service->setConnection($connection);
$tableName = $input->getArgument('table');
if (empty($tableName)) {
$output->error('请提供表名');
return;
}
$fullTableName = $service->getFullTableName($tableName);
$where = $input->getOption('where');
$field = $input->getOption('field');
$limit = $input->getOption('limit');
$order = $input->getOption('order');
$count = $input->getOption('count');
try {
$startTime = microtime(true);
$query = Db::connect($connection)->table($fullTableName);
if ($field) {
$query->field($field);
}
if ($where) {
$query->whereRaw($where);
}
if ($order) {
$query->orderRaw($order);
}
if ($count) {
$result = $query->count();
$output->writeln('');
$output->writeln('<info>表名:' . $fullTableName . '</info>');
$output->writeln('记录数:' . $result);
$output->writeln('');
} else {
if ($limit && is_numeric($limit)) {
$query->limit((int)$limit);
}
$result = $query->select()->toArray();
$endTime = microtime(true);
$executionTime = round(($endTime - $startTime) * 1000, 2);
$output->writeln('');
$output->writeln('<info>表名:' . $fullTableName . '</info>');
$service->formatTableOutput($result, $output);
$output->writeln('');
$output->writeln('影响行数:' . count($result));
$output->writeln('执行时间:' . $executionTime . 'ms');
$output->writeln('');
}
} catch (\Exception $e) {
$output->error('查询失败:' . $e->getMessage());
return;
}
}
}