mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-08 19:12:48 +08:00
增加测试机制;完善DebugMysql日志逻辑;更新debug_log表字段格式;发布新版本
This commit is contained in:
9
app/common/command/Test.php
Normal file
9
app/common/command/Test.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\command;
|
||||||
|
|
||||||
|
use base\common\command\TestBase;
|
||||||
|
|
||||||
|
class Test extends TestBase
|
||||||
|
{
|
||||||
|
}
|
||||||
9
app/common/interface/test/CommandTestInterface.php
Normal file
9
app/common/interface/test/CommandTestInterface.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\interface\test;
|
||||||
|
|
||||||
|
use base\common\interface\test\CommandTestInterfaceBase;
|
||||||
|
|
||||||
|
interface CommandTestInterface extends CommandTestInterfaceBase
|
||||||
|
{
|
||||||
|
}
|
||||||
9
app/common/service/TestService.php
Normal file
9
app/common/service/TestService.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\service;
|
||||||
|
|
||||||
|
use base\common\service\TestServiceBase;
|
||||||
|
|
||||||
|
class TestService extends TestServiceBase
|
||||||
|
{
|
||||||
|
}
|
||||||
9
app/common/service/test/LogTestService.php
Normal file
9
app/common/service/test/LogTestService.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\service\test;
|
||||||
|
|
||||||
|
use base\common\service\test\LogTesServicetBase;
|
||||||
|
|
||||||
|
class LogTestService extends LogTesServicetBase
|
||||||
|
{
|
||||||
|
}
|
||||||
38
database/migrations/20240103073052_fix_debug_log_content.php
Normal file
38
database/migrations/20240103073052_fix_debug_log_content.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Phinx\Db\Adapter\AdapterInterface;
|
||||||
|
use Phinx\Db\Adapter\MysqlAdapter;
|
||||||
|
use think\migration\Migrator;
|
||||||
|
|
||||||
|
class FixDebugLogContent extends Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$table = $this->table('debug_log');
|
||||||
|
|
||||||
|
$table->changeColumn('content', AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_LONG]);
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
58
extend/base/common/command/TestBase.php
Normal file
58
extend/base/common/command/TestBase.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace base\common\command;
|
||||||
|
|
||||||
|
use app\common\interface\test\CommandTestInterface;
|
||||||
|
use app\common\service\test\LogTestService;
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Option;
|
||||||
|
use think\console\Output;
|
||||||
|
|
||||||
|
class TestBase extends Command
|
||||||
|
{
|
||||||
|
protected $program = [
|
||||||
|
LogTestService::NAME => LogTestService::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
// 指令配置
|
||||||
|
$this->setName('test')
|
||||||
|
->addArgument('program', Option::VALUE_REQUIRED, '测试项目')
|
||||||
|
->setDescription('the admin:update command');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(Input $input, Output $output)
|
||||||
|
{
|
||||||
|
$program = $input->getArgument('program');
|
||||||
|
|
||||||
|
if (empty($program)) {
|
||||||
|
$output->writeln('请输入测试项目');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->program[$program])) {
|
||||||
|
$output->writeln('测试项目不存在');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = $this->program[$program];
|
||||||
|
|
||||||
|
$run = $class::RUN;
|
||||||
|
|
||||||
|
$instance = new $class();
|
||||||
|
|
||||||
|
$output->writeln('测试项目名称:' . $instance->getName());
|
||||||
|
$output->writeln('测试项目描述:' . $instance->getDesc());
|
||||||
|
|
||||||
|
if ($instance instanceof CommandTestInterface) {
|
||||||
|
$instance->setInput($input);
|
||||||
|
$instance->setOutput($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance->$run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,14 @@ use think\console\Output;
|
|||||||
|
|
||||||
class VersionBase extends Command
|
class VersionBase extends Command
|
||||||
{
|
{
|
||||||
public const VERSION = 'v2.0.85';
|
public const VERSION = 'v2.0.86';
|
||||||
|
|
||||||
public const LAYUI_VERSION = '2.8.17';
|
public const LAYUI_VERSION = '2.8.17';
|
||||||
|
|
||||||
public const COMMENT = [
|
public const COMMENT = [
|
||||||
'优化搜索查询构造逻辑',
|
'增加测试机制',
|
||||||
|
'完善DebugMysql日志逻辑',
|
||||||
|
'更新debug_log表字段格式',
|
||||||
'发布新版本',
|
'发布新版本',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace base\common\interface\test;
|
||||||
|
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\Output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试类接口.
|
||||||
|
*
|
||||||
|
* 实现了该接口的类,会在执行命令时,自动执行该接口的方法,比如传入参数,输出结果等。
|
||||||
|
* 要注意的是,并不代表该接口只能在命令行中使用,也可以在其他地方使用,在其他地方使用时,传入不同“实现”的output和input。例如在控制器中使用,传入的output会输出到Response。
|
||||||
|
*/
|
||||||
|
interface CommandTestInterfaceBase
|
||||||
|
{
|
||||||
|
public function setOutput(Output $output);
|
||||||
|
|
||||||
|
public function setInput(Input $input);
|
||||||
|
}
|
||||||
32
extend/base/common/service/TestServiceBase.php
Normal file
32
extend/base/common/service/TestServiceBase.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace base\common\service;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class TestServiceBase
|
||||||
|
{
|
||||||
|
public const NAME = null;
|
||||||
|
|
||||||
|
public const DESC = null;
|
||||||
|
|
||||||
|
public const RUN = 'run';
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
if ($this::NAME === null) {
|
||||||
|
throw new Exception('name is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this::NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDesc()
|
||||||
|
{
|
||||||
|
if ($this::DESC === null) {
|
||||||
|
throw new Exception('desc is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this::DESC;
|
||||||
|
}
|
||||||
|
}
|
||||||
315
extend/base/common/service/test/LogTesServicetBase.php
Normal file
315
extend/base/common/service/test/LogTesServicetBase.php
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace base\common\service\test;
|
||||||
|
|
||||||
|
use app\common\interface\test\CommandTestInterface;
|
||||||
|
use app\common\service\TestService;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\Output;
|
||||||
|
use think\console\Table;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
|
class LogTesServicetBase extends TestService implements CommandTestInterface
|
||||||
|
{
|
||||||
|
public const NAME = 'log';
|
||||||
|
|
||||||
|
public const DESC = '测试mysqllog驱动的兼容性、性能、边界情况';
|
||||||
|
|
||||||
|
public const RUN = 'run';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Output
|
||||||
|
*/
|
||||||
|
protected $output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Input
|
||||||
|
*/
|
||||||
|
protected $input;
|
||||||
|
|
||||||
|
protected $summary = [];
|
||||||
|
|
||||||
|
protected $configContent = '';
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->getLogInfo();
|
||||||
|
$this->output->writeln(str_repeat('=', 50));
|
||||||
|
$this->testLogContent();
|
||||||
|
$this->output->writeln(str_repeat('=', 50));
|
||||||
|
$this->testIoTimes();
|
||||||
|
$this->output->writeln(str_repeat('=', 50));
|
||||||
|
$this->testIoSize();
|
||||||
|
$this->output->writeln(str_repeat('=', 50));
|
||||||
|
$this->testNetworkChange();
|
||||||
|
$this->output->writeln(str_repeat('=', 50));
|
||||||
|
|
||||||
|
$this->output->writeln($this->configContent);
|
||||||
|
|
||||||
|
$this->output->writeln('测试结果汇总');
|
||||||
|
|
||||||
|
|
||||||
|
$output_table = new Table();
|
||||||
|
|
||||||
|
$output_table->setHeader(['测试项', '测试描述', '测试结果']);
|
||||||
|
|
||||||
|
$output_table->setRows($this->summary);
|
||||||
|
|
||||||
|
$table_content = $output_table->render();
|
||||||
|
|
||||||
|
$this->output->writeln($table_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLogInfo()
|
||||||
|
{
|
||||||
|
$this->output->writeln('当前日志配置项');
|
||||||
|
|
||||||
|
$config = Log::getConfig();
|
||||||
|
|
||||||
|
$table = array_to_table($config);
|
||||||
|
|
||||||
|
$output_table = new Table();
|
||||||
|
|
||||||
|
$output_table->setHeader(['配置项', '配置值']);
|
||||||
|
|
||||||
|
$output_table->setRows($table);
|
||||||
|
|
||||||
|
$table_content = $output_table->render();
|
||||||
|
|
||||||
|
$this->configContent = $table_content;
|
||||||
|
|
||||||
|
$this->output->writeln($table_content);
|
||||||
|
|
||||||
|
$this->output->writeln('当前日志驱动:' . $config['default']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIoTimes()
|
||||||
|
{
|
||||||
|
$this->output->writeln('测试写入100条记录,统计写入时间');
|
||||||
|
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
$total_times = 100;
|
||||||
|
for ($i = 0; $i < $total_times; $i++) {
|
||||||
|
$log_content = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
Log::record($log_content, 'info');
|
||||||
|
|
||||||
|
$this->output->writeln("({$i}/{$total_times})写入日志:{$log_content}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$end_time = microtime(true);
|
||||||
|
|
||||||
|
$time = $end_time - $start_time;
|
||||||
|
|
||||||
|
$this->output->writeln('测试完成');
|
||||||
|
|
||||||
|
$this->output->writeln("总写入时间:{$time}秒");
|
||||||
|
|
||||||
|
$this->summary[] = [
|
||||||
|
'title' => '写入次数测试',
|
||||||
|
'desc' => '测试写入100条记录,统计写入时间',
|
||||||
|
'result' => "总写入时间:{$time}秒",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIoSize()
|
||||||
|
{
|
||||||
|
$this->output->writeln('测试大内容写入,统计写入时间和写入大小');
|
||||||
|
|
||||||
|
$this->output->writeln('程序会写入100条记录,间隔1秒,日志内容为100KB的字符串');
|
||||||
|
|
||||||
|
$num = 0;
|
||||||
|
$total = 100;
|
||||||
|
|
||||||
|
$log_content = str_repeat('test', 100 * 1000);
|
||||||
|
|
||||||
|
$total_size = 0;
|
||||||
|
$total_time = 0;
|
||||||
|
|
||||||
|
while ($num < $total) {
|
||||||
|
$num++;
|
||||||
|
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
Log::record($log_content, 'info');
|
||||||
|
|
||||||
|
$end_time = microtime(true);
|
||||||
|
|
||||||
|
$time = $end_time - $start_time;
|
||||||
|
|
||||||
|
$total_time += $time;
|
||||||
|
|
||||||
|
$size = strlen($log_content);
|
||||||
|
|
||||||
|
$total_size += $size;
|
||||||
|
|
||||||
|
$size = format_bytes($size);
|
||||||
|
$this->output->writeln("({$num}/{$total})写入大小:{$size},写入时间:{$time}秒");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln('测试完成');
|
||||||
|
|
||||||
|
$total_size = format_bytes($total_size);
|
||||||
|
$this->output->writeln("总写入大小:{$total_size},总写入时间:{$total_time}秒");
|
||||||
|
|
||||||
|
$this->summary[] = [
|
||||||
|
'title' => '写入大小测试',
|
||||||
|
'desc' => '测试大内容写入,统计写入时间和写入大小',
|
||||||
|
'result' => "总写入大小:{$total_size},总写入时间:{$total_time}秒",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNetworkChange()
|
||||||
|
{
|
||||||
|
$this->output->writeln('测试网络切换');
|
||||||
|
|
||||||
|
$this->output->writeln('程序会写入30条记录,间隔1秒,日志内容为当前时间,测试期间可以反复断开网络连接,查看是否丢失或报错');
|
||||||
|
|
||||||
|
$num = 0;
|
||||||
|
$total = 30;
|
||||||
|
|
||||||
|
while ($num < $total) {
|
||||||
|
$num++;
|
||||||
|
|
||||||
|
$log_content = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
Log::record($log_content, 'info');
|
||||||
|
|
||||||
|
$this->output->writeln("({$num}/{$total})写入日志:{$log_content}");
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln('测试完成');
|
||||||
|
|
||||||
|
$this->summary[] = [
|
||||||
|
'title' => '网络切换测试',
|
||||||
|
'desc' => '测试网络切换',
|
||||||
|
'result' => '请查看是否有丢失',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLogContent()
|
||||||
|
{
|
||||||
|
$this->output->writeln('测试日志内容兼容性');
|
||||||
|
|
||||||
|
$test_content_item = [];
|
||||||
|
|
||||||
|
// 生成测试内容,包括简单英文字符串、中文字符串、大文本、数字、大数字、负数、负数大数字、数组、对象、资源、布尔值、null、空字符串、空数组、空对象、空资源、空布尔值
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '简单字符串',
|
||||||
|
'content' => 'test',
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '中文字符串',
|
||||||
|
'content' => '测试',
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '大文本',
|
||||||
|
'content' => str_repeat('test测试', 1000),
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '数字',
|
||||||
|
'content' => 1,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '大数字',
|
||||||
|
'content' => 100000 * 100000,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '负数',
|
||||||
|
'content' => -1,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '负数大数字',
|
||||||
|
'content' => -100000 * 100000,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '数组',
|
||||||
|
'content' => [
|
||||||
|
'test' => 'test',
|
||||||
|
'测试' => '测试',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '对象',
|
||||||
|
'content' => new \stdClass(),
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '资源',
|
||||||
|
'content' => fopen(__FILE__, 'r'),
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '布尔值',
|
||||||
|
'content' => true,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => 'null',
|
||||||
|
'content' => null,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '空字符串',
|
||||||
|
'content' => '',
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '空数组',
|
||||||
|
'content' => [],
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '空对象',
|
||||||
|
'content' => new \stdClass(),
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '空资源',
|
||||||
|
'content' => fopen('php://memory', 'r'),
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '空布尔值',
|
||||||
|
'content' => false,
|
||||||
|
];
|
||||||
|
$test_content_item[] = [
|
||||||
|
'name' => '异常',
|
||||||
|
'content' => new \Exception('test'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$fail_count = 0;
|
||||||
|
|
||||||
|
foreach ($test_content_item as $key => $value) {
|
||||||
|
try {
|
||||||
|
$total_count = count($test_content_item);
|
||||||
|
$num = $key + 1;
|
||||||
|
|
||||||
|
Log::record($value['content'], 'info');
|
||||||
|
$this->output->writeln("({$num}/{$total_count})" . '测试内容:' . $value['name'] . ',测试结果:成功');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
$this->output->writeln('测试内容失败:' . $value['name'] . ',测试结果:' . $th->getMessage());
|
||||||
|
|
||||||
|
if ($this->output->isDebug()) {
|
||||||
|
$this->output->error($th);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fail_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln('测试完成');
|
||||||
|
|
||||||
|
$this->summary[] = [
|
||||||
|
'title' => '日志内容兼容性测试',
|
||||||
|
'desc' => '测试日志内容兼容性',
|
||||||
|
'result' => "测试完成,失败{$fail_count}个",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOutput(Output $output)
|
||||||
|
{
|
||||||
|
$this->output = $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInput(Input $input)
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -167,9 +167,9 @@ if (!function_exists('auth')) {
|
|||||||
* auth权限验证
|
* auth权限验证
|
||||||
* @param $node
|
* @param $node
|
||||||
* @return bool
|
* @return bool
|
||||||
* @throws \think\db\exception\DataNotFoundException
|
* @throws think\db\exception\DataNotFoundException
|
||||||
* @throws \think\db\exception\DbException
|
* @throws think\db\exception\DbException
|
||||||
* @throws \think\db\exception\ModelNotFoundException
|
* @throws think\db\exception\ModelNotFoundException
|
||||||
*/
|
*/
|
||||||
function auth($node = null)
|
function auth($node = null)
|
||||||
{
|
{
|
||||||
@@ -321,3 +321,31 @@ function app_file_path($file_path)
|
|||||||
|
|
||||||
return $app_file_path;
|
return $app_file_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function array_to_table($array_tree, $prefix_key = '')
|
||||||
|
{
|
||||||
|
$table = [];
|
||||||
|
|
||||||
|
foreach ($array_tree as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$table = array_merge($table, array_to_table($value, $key . '.'));
|
||||||
|
} else {
|
||||||
|
$table[] = [
|
||||||
|
'key' => $prefix_key . $key,
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_bytes($size, $delimiter = '')
|
||||||
|
{
|
||||||
|
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||||
|
for ($i = 0; $size >= 1024 && $i < 5; $i++) {
|
||||||
|
$size /= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($size, 2) . $delimiter . $units[$i];
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace think;
|
namespace think;
|
||||||
|
|
||||||
|
use app\common\command\Test;
|
||||||
use app\common\event\AdminLoginSuccess\LogEvent;
|
use app\common\event\AdminLoginSuccess\LogEvent;
|
||||||
use app\common\event\AdminLoginType\DemoEvent;
|
use app\common\event\AdminLoginType\DemoEvent;
|
||||||
use app\common\provider\ExceptionHandle;
|
use app\common\provider\ExceptionHandle;
|
||||||
@@ -10,9 +11,9 @@ use app\common\provider\View;
|
|||||||
use think\app\Service as AppService;
|
use think\app\Service as AppService;
|
||||||
use think\captcha\CaptchaService;
|
use think\captcha\CaptchaService;
|
||||||
use think\facade\App;
|
use think\facade\App;
|
||||||
use think\migration\Service;
|
use think\migration\Service as MigrateService;
|
||||||
|
|
||||||
class UlthonAdminService extends \think\Service
|
class UlthonAdminService extends Service
|
||||||
{
|
{
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
@@ -40,10 +41,14 @@ class UlthonAdminService extends \think\Service
|
|||||||
$this->app->register(AppService::class);
|
$this->app->register(AppService::class);
|
||||||
|
|
||||||
// 注册数据库迁移服务
|
// 注册数据库迁移服务
|
||||||
$this->app->register(Service::class);
|
$this->app->register(MigrateService::class);
|
||||||
|
|
||||||
|
// 绑定命令行
|
||||||
|
$this->commands([
|
||||||
|
Test::class,
|
||||||
|
]);
|
||||||
|
|
||||||
// 绑定标识容器
|
// 绑定标识容器
|
||||||
|
|
||||||
$provider_default = [
|
$provider_default = [
|
||||||
'think\Request' => Request::class,
|
'think\Request' => Request::class,
|
||||||
'think\exception\Handle' => ExceptionHandle::class,
|
'think\exception\Handle' => ExceptionHandle::class,
|
||||||
@@ -68,7 +73,7 @@ class UlthonAdminService extends \think\Service
|
|||||||
// 多语言加载
|
// 多语言加载
|
||||||
// \think\middleware\LoadLangPack::class,
|
// \think\middleware\LoadLangPack::class,
|
||||||
// Session初始化
|
// Session初始化
|
||||||
100 => \think\middleware\SessionInit::class,
|
100 => middleware\SessionInit::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->app->middleware->import($middleware);
|
$this->app->middleware->import($middleware);
|
||||||
|
|||||||
@@ -2,77 +2,90 @@
|
|||||||
|
|
||||||
namespace think\log\driver;
|
namespace think\log\driver;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
use think\contract\LogHandlerInterface;
|
use think\contract\LogHandlerInterface;
|
||||||
use think\facade\App;
|
use think\facade\App;
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class DebugMysql implements LogHandlerInterface
|
class DebugMysql implements LogHandlerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $enableLog = true;
|
protected $enableLog = true;
|
||||||
|
|
||||||
protected $config = [];
|
protected $config = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PDO
|
||||||
|
*/
|
||||||
protected $pdo = null;
|
protected $pdo = null;
|
||||||
|
|
||||||
|
protected $file = null;
|
||||||
|
|
||||||
protected $fileRescource = null;
|
protected $fileRescource = null;
|
||||||
|
|
||||||
protected $tableName = '';
|
protected $tableName = '';
|
||||||
|
|
||||||
|
protected $reConnectTimes = 0;
|
||||||
|
|
||||||
|
protected $fileLogTimes = 0;
|
||||||
|
|
||||||
|
public $devMode = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器断线标识字符.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $breakMatchStr = [
|
||||||
|
'server has gone away',
|
||||||
|
'no connection to the server',
|
||||||
|
'Lost connection',
|
||||||
|
'is dead or not enabled',
|
||||||
|
'Error while sending',
|
||||||
|
'decryption failed or bad record mac',
|
||||||
|
'server closed the connection unexpectedly',
|
||||||
|
'SSL connection has been closed unexpectedly',
|
||||||
|
'Error writing data to the connection',
|
||||||
|
'Resource deadlock avoided',
|
||||||
|
'failed with errno',
|
||||||
|
'child connection forced to terminate due to client_idle_limit',
|
||||||
|
'query_wait_timeout',
|
||||||
|
'reset by peer',
|
||||||
|
'Physical connection is not usable',
|
||||||
|
'TCP Provider: Error code 0x68',
|
||||||
|
'ORA-03114',
|
||||||
|
'Packets out of order. Expected',
|
||||||
|
'Adaptive Server connection failed',
|
||||||
|
'Communication link failure',
|
||||||
|
'connection is no longer usable',
|
||||||
|
'Login timeout expired',
|
||||||
|
'SQLSTATE[HY000] [2002] Connection refused',
|
||||||
|
'running with the --read-only option so it cannot execute this statement',
|
||||||
|
'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
|
||||||
|
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
|
||||||
|
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
|
||||||
|
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
|
||||||
|
'SQLSTATE[HY000] [2002] Connection timed out',
|
||||||
|
'SSL: Connection timed out',
|
||||||
|
'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
|
||||||
|
];
|
||||||
|
|
||||||
public function __construct(App $app, $config = [])
|
public function __construct(App $app, $config = [])
|
||||||
{
|
{
|
||||||
if (is_array($config)) {
|
if (is_array($config)) {
|
||||||
$this->config = array_merge($this->config, $config);
|
$this->config = array_merge($this->config, $config);
|
||||||
}
|
}
|
||||||
|
|
||||||
$dsn = $this->parseDsn($config);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$this->initConnect();
|
||||||
$pdo = $this->createPdo($dsn, $config['username'], $config['password'], $config['params']);
|
|
||||||
|
|
||||||
$this->pdo = $pdo;
|
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
$this->pdo = null;
|
$this->pdo = null;
|
||||||
|
$this->initFile();
|
||||||
$log_path = App::getRuntimePath() . 'log/' . date('ymd') . '.csv';
|
|
||||||
|
|
||||||
$dirname = dirname($log_path);
|
|
||||||
|
|
||||||
if (!is_dir($dirname)) {
|
|
||||||
mkdir($log_path, 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$first_line = false;
|
|
||||||
if (!file_exists($log_path)) {
|
|
||||||
$first_line = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->fileRescource = fopen($log_path, 'a');
|
|
||||||
|
|
||||||
if ($first_line) {
|
|
||||||
$fields = [
|
|
||||||
'level',
|
|
||||||
'content',
|
|
||||||
'create_time',
|
|
||||||
'create_time_title',
|
|
||||||
'uid',
|
|
||||||
'app_name',
|
|
||||||
'controller_name',
|
|
||||||
'action_name',
|
|
||||||
];
|
|
||||||
fputcsv($this->fileRescource, $fields);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->tableName = $config['prefix'] . 'debug_log';
|
$this->tableName = $config['prefix'] . 'debug_log';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(array $log): bool
|
public function save(array $log): bool
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
$app_name = app('http')->getName() ?: '';
|
$app_name = app('http')->getName() ?: '';
|
||||||
|
|
||||||
$controller_name = '';
|
$controller_name = '';
|
||||||
@@ -81,7 +94,6 @@ class DebugMysql implements LogHandlerInterface
|
|||||||
if (App::runningInConsole()) {
|
if (App::runningInConsole()) {
|
||||||
$app_name = 'cli';
|
$app_name = 'cli';
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$controller_name = request()->controller();
|
$controller_name = request()->controller();
|
||||||
$action_name = request()->action();
|
$action_name = request()->action();
|
||||||
}
|
}
|
||||||
@@ -100,7 +112,10 @@ class DebugMysql implements LogHandlerInterface
|
|||||||
|
|
||||||
foreach ($log as $log_level => $log_list) {
|
foreach ($log as $log_level => $log_list) {
|
||||||
foreach ($log_list as $key => $log_item) {
|
foreach ($log_list as $key => $log_item) {
|
||||||
|
if (!is_string($log_item)) {
|
||||||
|
$log_item = print_r($log_item, true);
|
||||||
|
}
|
||||||
|
|
||||||
$log_data = [
|
$log_data = [
|
||||||
'level' => $log_level,
|
'level' => $log_level,
|
||||||
'content' => $log_item,
|
'content' => $log_item,
|
||||||
@@ -109,30 +124,17 @@ class DebugMysql implements LogHandlerInterface
|
|||||||
'uid' => $log_key,
|
'uid' => $log_key,
|
||||||
'app_name' => $app_name,
|
'app_name' => $app_name,
|
||||||
'controller_name' => $controller_name,
|
'controller_name' => $controller_name,
|
||||||
'action_name' => $action_name
|
'action_name' => $action_name,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!is_null($this->pdo)) {
|
try {
|
||||||
|
if (!is_null($this->pdo)) {
|
||||||
$prepare_name = [];
|
$this->saveByConnect($log_data);
|
||||||
foreach ($log_data as $key => $value) {
|
} else {
|
||||||
$prepare_name[] = ':' . $key;
|
$this->saveByFile($log_data);
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
$data_keys = array_keys($log_data);
|
$this->saveByFile($log_data);
|
||||||
|
|
||||||
$data_keys_in_sql = join(',', $data_keys);
|
|
||||||
|
|
||||||
$prepare_name_in_sql = join(',', $prepare_name);
|
|
||||||
|
|
||||||
$sql = "INSERT INTO {$this->tableName} ($data_keys_in_sql) VALUES ($prepare_name_in_sql);";
|
|
||||||
|
|
||||||
$stmt = $this->pdo->prepare($sql);
|
|
||||||
|
|
||||||
$stmt->execute($log_data);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
fputcsv($this->fileRescource, $log_data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,9 +142,156 @@ class DebugMysql implements LogHandlerInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function saveByConnect($log_data)
|
||||||
|
{
|
||||||
|
if (is_null($this->pdo)) {
|
||||||
|
$this->saveByFile($log_data);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->devLog('save by connect');
|
||||||
|
$prepare_name = [];
|
||||||
|
foreach ($log_data as $key => $value) {
|
||||||
|
$prepare_name[] = ':' . $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data_keys = array_keys($log_data);
|
||||||
|
|
||||||
|
$data_keys_in_sql = implode(',', $data_keys);
|
||||||
|
|
||||||
|
$prepare_name_in_sql = implode(',', $prepare_name);
|
||||||
|
|
||||||
|
$sql = "INSERT INTO {$this->tableName} ($data_keys_in_sql) VALUES ($prepare_name_in_sql);";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->execute($log_data);
|
||||||
|
} catch (\Exception $th) {
|
||||||
|
if ($this->isBreak($th)) {
|
||||||
|
if ($this->reConnectTimes > 3) {
|
||||||
|
$this->initFile();
|
||||||
|
throw $th;
|
||||||
|
}
|
||||||
|
$this->initConnect();
|
||||||
|
$this->reConnectTimes++;
|
||||||
|
$this->devLog('reconnect ' . $this->reConnectTimes);
|
||||||
|
$this->saveByConnect($log_data);
|
||||||
|
} else {
|
||||||
|
dump($th);
|
||||||
|
$this->saveByFile($log_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function saveByFile($log_data)
|
||||||
|
{
|
||||||
|
$this->devLog('save by file');
|
||||||
|
|
||||||
|
// 如果文件日志超过100条,尝试重新通过数据库连接
|
||||||
|
if ($this->fileLogTimes > 10) {
|
||||||
|
$this->fileLogTimes = 0;
|
||||||
|
$this->initConnect();
|
||||||
|
$this->saveByConnect($log_data);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fputcsv($this->fileRescource, $log_data);
|
||||||
|
$this->fileLogTimes++;
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
$this->initFile();
|
||||||
|
$this->fileLogTimes++;
|
||||||
|
$this->saveByFile($log_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initConnect()
|
||||||
|
{
|
||||||
|
$this->devLog('init connect');
|
||||||
|
|
||||||
|
if (!is_null($this->pdo)) {
|
||||||
|
$this->pdo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reConnectTimes = 0;
|
||||||
|
|
||||||
|
$config = $this->config;
|
||||||
|
|
||||||
|
$dsn = $this->parseDsn($config);
|
||||||
|
try {
|
||||||
|
$pdo = $this->createPdo($dsn, $config['username'], $config['password'], $config['params']);
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
$this->pdo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initFile()
|
||||||
|
{
|
||||||
|
$this->devLog('init file');
|
||||||
|
|
||||||
|
if (!is_null($this->fileRescource)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$log_path = App::getRuntimePath() . 'log/' . date('ymd') . '.csv';
|
||||||
|
|
||||||
|
$dirname = dirname($log_path);
|
||||||
|
|
||||||
|
if (!is_dir($dirname)) {
|
||||||
|
mkdir($log_path, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$first_line = false;
|
||||||
|
if (!file_exists($log_path)) {
|
||||||
|
$first_line = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fileRescource = fopen($log_path, 'a');
|
||||||
|
|
||||||
|
if ($first_line) {
|
||||||
|
$fields = [
|
||||||
|
'level',
|
||||||
|
'content',
|
||||||
|
'create_time',
|
||||||
|
'create_time_title',
|
||||||
|
'uid',
|
||||||
|
'app_name',
|
||||||
|
'controller_name',
|
||||||
|
'action_name',
|
||||||
|
];
|
||||||
|
fputcsv($this->fileRescource, $fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析pdo连接的dsn信息
|
* 是否断线
|
||||||
* @access protected
|
*
|
||||||
|
* @param \PDOException|\Exception $e 异常对象
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isBreak($e): bool
|
||||||
|
{
|
||||||
|
$error = $e->getMessage();
|
||||||
|
|
||||||
|
foreach ($this->breakMatchStr as $msg) {
|
||||||
|
if (false !== stripos($error, $msg)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析pdo连接的dsn信息.
|
||||||
* @param array $config 连接信息
|
* @param array $config 连接信息
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -177,4 +326,11 @@ class DebugMysql implements LogHandlerInterface
|
|||||||
fclose($this->fileRescource);
|
fclose($this->fileRescource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function devLog($content)
|
||||||
|
{
|
||||||
|
if ($this->devMode) {
|
||||||
|
dump($content);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class Service extends \think\Service
|
|||||||
|
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->app->bind(FakerGenerator::class, function () {
|
$this->app->bind(FakerGenerator::class, function () {
|
||||||
return FakerFactory::create($this->app->config->get('app.faker_locale', 'zh_CN'));
|
return FakerFactory::create($this->app->config->get('app.faker_locale', 'zh_CN'));
|
||||||
});
|
});
|
||||||
@@ -47,5 +48,6 @@ class Service extends \think\Service
|
|||||||
SeedRun::class,
|
SeedRun::class,
|
||||||
FactoryCreate::class,
|
FactoryCreate::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user