From 2176dabbb712a5af2ddfc166013f8205cf2a9c6f Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Mon, 25 Mar 2024 23:56:41 +0800
Subject: [PATCH 1/7] Splitting the crontab component
---
 composer.json                                 |  6 +-
 phpunit.xml                                   |  1 +
 src/Crontab/README.md                         |  3 +
 src/Crontab/composer.json                     | 35 +++++++
 src/Crontab/src/ConfigProvider.php            | 27 ++++++
 src/Crontab/src/Contract/RegisterContract.php | 18 ++++
 src/Crontab/src/Crontab.php                   | 94 +++++++++++++++++++
 src/Crontab/src/CrontabContainer.php          | 20 ++++
 .../CrontabProcessStarredListener.php         | 57 +++++++++++
 src/Crontab/src/Schedule.php                  | 39 ++++++++
 10 files changed, 298 insertions(+), 2 deletions(-)
 create mode 100644 src/Crontab/README.md
 create mode 100644 src/Crontab/composer.json
 create mode 100644 src/Crontab/src/ConfigProvider.php
 create mode 100644 src/Crontab/src/Contract/RegisterContract.php
 create mode 100644 src/Crontab/src/Crontab.php
 create mode 100644 src/Crontab/src/CrontabContainer.php
 create mode 100644 src/Crontab/src/Listener/CrontabProcessStarredListener.php
 create mode 100644 src/Crontab/src/Schedule.php
diff --git a/composer.json b/composer.json
index 1fc3cca4..1ef1670b 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,8 @@
       "Mine\\Generator\\": "src/mine-generator/src",
       "Xmo\\AppStore\\": "src/app-store/src",
       "Mine\\NextCoreX\\": "src/next-core-x/src",
-      "Mine\\HttpServer\\": "src/HttpServer/src"
+      "Mine\\HttpServer\\": "src/HttpServer/src",
+      "Mine\\Crontab\\": "src/Crontab/src"
     },
     "files": [
       "src/mine-helpers/src/functions.php"
@@ -32,7 +33,8 @@
       "Xmo\\AppStore\\Tests\\": "src/app-store/tests",
       "Xmo\\MineCore\\Tests\\": "src/mine-core/tests",
       "Mine\\NextCoreX\\Tests\\": "src/next-core-x/tests",
-      "Mine\\HttpServer\\Tests\\": "src/HttpServer/tests"
+      "Mine\\HttpServer\\Tests\\": "src/HttpServer/tests",
+      "Mine\\Crontab\\Tests\\": "src/Crontab/tests"
     }
   },
   "authors": [
diff --git a/phpunit.xml b/phpunit.xml
index 4d219de5..1b992cd6 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -3,6 +3,7 @@
   
   
     
+      ./src/Crontab/tests
       ./src/HttpServer/tests
       ./src/app-store/tests
       ./src/mine-core/tests
diff --git a/src/Crontab/README.md b/src/Crontab/README.md
new file mode 100644
index 00000000..e8f99fbe
--- /dev/null
+++ b/src/Crontab/README.md
@@ -0,0 +1,3 @@
+# Hyperf Crontab 加强
+
+在原有的基础上增加了定时任务从数据库读取的配置
\ No newline at end of file
diff --git a/src/Crontab/composer.json b/src/Crontab/composer.json
new file mode 100644
index 00000000..4328897d
--- /dev/null
+++ b/src/Crontab/composer.json
@@ -0,0 +1,35 @@
+{
+  "name": "mineadmin/crontab",
+  "description": "加强 Hyperf Crontab",
+  "type": "library",
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "x.mo",
+      "role": "Developer"
+    },
+    {
+      "name": "zds",
+      "role": "Developer"
+    }
+  ],
+  "require": {
+    "hyperf/crontab": "^3.1",
+    "Hyperf/database": "^3.1"
+  },
+  "autoload": {
+    "psr-4": {
+      "Mine\\Crontab\\": "src/"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Mine\\Crontab\\Tests\\": "tests/"
+    }
+  },
+  "extra": {
+    "hyperf": {
+      "config": "\\Mine\\Crontab\\ConfigProvider"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/Crontab/src/ConfigProvider.php b/src/Crontab/src/ConfigProvider.php
new file mode 100644
index 00000000..85a9e153
--- /dev/null
+++ b/src/Crontab/src/ConfigProvider.php
@@ -0,0 +1,27 @@
+ [
+                CrontabProcessStarredListener::class,
+            ],
+        ];
+    }
+}
diff --git a/src/Crontab/src/Contract/RegisterContract.php b/src/Crontab/src/Contract/RegisterContract.php
new file mode 100644
index 00000000..d619d6d9
--- /dev/null
+++ b/src/Crontab/src/Contract/RegisterContract.php
@@ -0,0 +1,18 @@
+getBuilder()->value('name');
+    }
+
+    public function isEnable(): bool
+    {
+        return (bool) $this->getBuilder()->value(self::ENABLE_COLUMN);
+    }
+
+    public function getType(): string
+    {
+        $type = $this->getBuilder()->value(self::TYPE_COLUMN);
+        return match ($type) {
+            'url', 'class' => 'callback',
+            default => $type
+        };
+    }
+
+    public function getMemo(): ?string
+    {
+        return (string) $this->getBuilder()->value(self::MEMO_COLUMN);
+    }
+
+    public function getBuilder(): Builder
+    {
+        return Db::table(self::TABLE)->where(self::TABLE_KEY, $this->cronId);
+    }
+
+    /**
+     * @throws \JsonException
+     */
+    public function getCallback(): mixed
+    {
+        $type = $this->getBuilder()->value(self::TYPE_COLUMN);
+        $value = $this->getBuilder()->value(self::VALUE_COLUMN);
+        switch ($type) {
+            case 'eval':
+                return $value;
+            case 'url':
+                return function () use ($value) {
+                    [$method,$url,$data] = explode(',', $value);
+                    $clientFactory = ApplicationContext::getContainer()->get(ClientFactory::class);
+                    $client = $clientFactory->create();
+                    return $client->request($method, $url, compact('data'))->getBody()->getContents();
+                };
+            case 'class':
+                return function () use ($value) {
+                    return [$value, 'execute'];
+                };
+            case 'command':
+                return json_decode($value, true, 512, JSON_THROW_ON_ERROR);
+        }
+        return $value;
+    }
+}
diff --git a/src/Crontab/src/CrontabContainer.php b/src/Crontab/src/CrontabContainer.php
new file mode 100644
index 00000000..2161775e
--- /dev/null
+++ b/src/Crontab/src/CrontabContainer.php
@@ -0,0 +1,20 @@
+registerCrontab();
+                sleep(30);
+            }
+        });
+    }
+
+    public function registerCrontab(): void
+    {
+        $crontabList = $this->schedule->getCrontab();
+        foreach ($crontabList as $crontab) {
+            if (CrontabContainer::has($crontab->getName())) {
+                continue;
+            }
+            $this->crontabManager->register($crontab);
+            CrontabContainer::set($crontab->getName(), 1);
+        }
+    }
+}
diff --git a/src/Crontab/src/Schedule.php b/src/Crontab/src/Schedule.php
new file mode 100644
index 00000000..001a92ed
--- /dev/null
+++ b/src/Crontab/src/Schedule.php
@@ -0,0 +1,39 @@
+where('status', 1)->get();
+        if ($crontabList->count() === 0) {
+            return [];
+        }
+        foreach ($crontabList as $crontab) {
+            $list[] = new \Mine\Crontab\Crontab($crontab->id);
+        }
+        return $list;
+    }
+}
From 0ebc394bf99f95806e482cd1d8b77eacc7f699f8 Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Tue, 26 Mar 2024 12:13:41 +0800
Subject: [PATCH 2/7] Update
---
 .../2024_03_26_020159_create_crontab.php      | 49 +++++++++++++++
 ...3_26_020825_create_crontab_execute_log.php | 39 ++++++++++++
 .../src/Command/CrontabMigrateCommand.php     | 49 +++++++++++++++
 src/Crontab/src/Contract/RegisterContract.php | 18 ------
 src/Crontab/src/Crontab.php                   | 24 +++----
 src/Crontab/src/CrontabUrl.php                | 33 ++++++++++
 .../CrontabProcessStarredListener.php         |  7 ++-
 .../Command/CrontabMigrateCommandTest.php     | 63 +++++++++++++++++++
 .../tests/Cases/ConfigProviderTest.php        | 28 +++++++++
 .../tests/Cases/CrontabContainerTest.php      | 34 ++++++++++
 src/Crontab/tests/Cases/CrontabUrlTest.php    | 41 ++++++++++++
 .../CrontabProcessStarredListenerTest.php     | 58 +++++++++++++++++
 12 files changed, 412 insertions(+), 31 deletions(-)
 create mode 100644 src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php
 create mode 100644 src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php
 create mode 100644 src/Crontab/src/Command/CrontabMigrateCommand.php
 delete mode 100644 src/Crontab/src/Contract/RegisterContract.php
 create mode 100644 src/Crontab/src/CrontabUrl.php
 create mode 100644 src/Crontab/tests/Cases/Command/CrontabMigrateCommandTest.php
 create mode 100644 src/Crontab/tests/Cases/ConfigProviderTest.php
 create mode 100644 src/Crontab/tests/Cases/CrontabContainerTest.php
 create mode 100644 src/Crontab/tests/Cases/CrontabUrlTest.php
 create mode 100644 src/Crontab/tests/Cases/Listener/CrontabProcessStarredListenerTest.php
diff --git a/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php b/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php
new file mode 100644
index 00000000..9f3ad8e8
--- /dev/null
+++ b/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php
@@ -0,0 +1,49 @@
+bigIncrements('id');
+            /*
+             * name string 30
+             * status tinyint 1 default 0
+             * memo string 60 default null
+             * type string 10 not null
+             * value string longtext not null
+             */
+            $table->string('name', 30);
+            $table->tinyInteger('status')->default(0);
+            $table->string('memo', 60)->default(null);
+            $table->string('type', 10);
+            $table->string('rule', 10);
+            $table->text('value');
+            $table->datetimes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('crontab');
+    }
+}
diff --git a/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php b/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php
new file mode 100644
index 00000000..703d1f36
--- /dev/null
+++ b/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php
@@ -0,0 +1,39 @@
+bigIncrements('id');
+            $table->bigInteger('crontab_id');
+            $table->tinyInteger('status')->default(0);
+            $table->string('output')->default(0);
+            $table->datetimes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('crontab_execute_log');
+    }
+}
diff --git a/src/Crontab/src/Command/CrontabMigrateCommand.php b/src/Crontab/src/Command/CrontabMigrateCommand.php
new file mode 100644
index 00000000..245f53fb
--- /dev/null
+++ b/src/Crontab/src/Command/CrontabMigrateCommand.php
@@ -0,0 +1,49 @@
+input->getOption('connection');
+        if (empty($connection)) {
+            $connection = 'default';
+        }
+        $migrator = $this->migrator;
+        $migrator->setConnection($connection);
+        $this->migrator
+            ->setOutput(new NullOutput())
+            ->run(dirname(__DIR__, 2) . '/Database/Migrations');
+    }
+
+    protected function getOptions(): array
+    {
+        return [
+            new InputOption('connection', 'connection', InputOption::VALUE_OPTIONAL, 'connection name'),
+        ];
+    }
+}
diff --git a/src/Crontab/src/Contract/RegisterContract.php b/src/Crontab/src/Contract/RegisterContract.php
deleted file mode 100644
index d619d6d9..00000000
--- a/src/Crontab/src/Contract/RegisterContract.php
+++ /dev/null
@@ -1,18 +0,0 @@
-get(ClientFactory::class);
-                    $client = $clientFactory->create();
-                    return $client->request($method, $url, compact('data'))->getBody()->getContents();
-                };
+                return [
+                    CrontabUrl::class,
+                    'execute',
+                    explode($value, ','),
+                ];
             case 'class':
-                return function () use ($value) {
-                    return [$value, 'execute'];
-                };
+                return [$value, 'execute'];
             case 'command':
                 return json_decode($value, true, 512, JSON_THROW_ON_ERROR);
         }
         return $value;
     }
+
+    public function getRule(): ?string
+    {
+        return $this->getBuilder()->value(self::RULE_COLUMN);
+    }
 }
diff --git a/src/Crontab/src/CrontabUrl.php b/src/Crontab/src/CrontabUrl.php
new file mode 100644
index 00000000..1a79ac09
--- /dev/null
+++ b/src/Crontab/src/CrontabUrl.php
@@ -0,0 +1,33 @@
+clientFactory->create();
+    }
+
+    public function execute(string $url)
+    {
+        return $this->getClient()->get($url);
+    }
+}
diff --git a/src/Crontab/src/Listener/CrontabProcessStarredListener.php b/src/Crontab/src/Listener/CrontabProcessStarredListener.php
index 20c80a4e..5c16d37a 100644
--- a/src/Crontab/src/Listener/CrontabProcessStarredListener.php
+++ b/src/Crontab/src/Listener/CrontabProcessStarredListener.php
@@ -16,11 +16,14 @@
 use Hyperf\Crontab\Event\CrontabDispatcherStarted;
 use Hyperf\Engine\Coroutine;
 use Hyperf\Event\Contract\ListenerInterface;
+use Hyperf\Process\ProcessManager;
 use Mine\Crontab\CrontabContainer;
 use Mine\Crontab\Schedule;
 
 class CrontabProcessStarredListener implements ListenerInterface
 {
+    public static int $sleep = 30;
+
     public function __construct(
         private readonly CrontabManager $crontabManager,
         private readonly Schedule $schedule
@@ -36,9 +39,9 @@ public function listen(): array
     public function process(object $event): void
     {
         Coroutine::create(function () {
-            while (true) {
+            while (ProcessManager::isRunning()) {
                 $this->registerCrontab();
-                sleep(30);
+                sleep(self::$sleep);
             }
         });
     }
diff --git a/src/Crontab/tests/Cases/Command/CrontabMigrateCommandTest.php b/src/Crontab/tests/Cases/Command/CrontabMigrateCommandTest.php
new file mode 100644
index 00000000..513dabc0
--- /dev/null
+++ b/src/Crontab/tests/Cases/Command/CrontabMigrateCommandTest.php
@@ -0,0 +1,63 @@
+newInstance(\Mockery::mock(Migrator::class));
+        $this->assertTrue(true);
+    }
+
+    public function testInvoke(): void
+    {
+        $reflectionClass = new \ReflectionClass(CrontabMigrateCommand::class);
+        $migrator = \Mockery::mock(Migrator::class);
+        $migrator->allows('setOutput')->andReturnUsing(function ($output) use ($migrator) {
+            $this->assertInstanceOf(NullOutput::class, $output);
+            return $migrator;
+        });
+        $migrator->allows('run')->andReturnUsing(function ($path) {
+            $this->assertIsString($path);
+            $this->assertSame($path, dirname(__DIR__, 3) . '/Database/Migrations');
+            return [];
+        });
+        $migrator->allows('setConnection')
+            ->andReturnUsing(function ($connection) {
+                $this->assertSame($connection, 'default');
+            }, function ($connection) {
+                $this->assertSame($connection, 'test');
+            });
+        /**
+         * @var CrontabMigrateCommand $instance
+         */
+        $instance = $reflectionClass->newInstance($migrator);
+        $inputMock = \Mockery::mock(InputInterface::class);
+        $inputMock->allows('getOption')->andReturn(null, 'test');
+        $instance->setInput($inputMock);
+        $instance();
+        $instance();
+    }
+}
diff --git a/src/Crontab/tests/Cases/ConfigProviderTest.php b/src/Crontab/tests/Cases/ConfigProviderTest.php
new file mode 100644
index 00000000..3868c188
--- /dev/null
+++ b/src/Crontab/tests/Cases/ConfigProviderTest.php
@@ -0,0 +1,28 @@
+assertIsArray((new ConfigProvider())());
+    }
+}
diff --git a/src/Crontab/tests/Cases/CrontabContainerTest.php b/src/Crontab/tests/Cases/CrontabContainerTest.php
new file mode 100644
index 00000000..5b011c8f
--- /dev/null
+++ b/src/Crontab/tests/Cases/CrontabContainerTest.php
@@ -0,0 +1,34 @@
+assertSame(CrontabContainer::get('id'), 'xxx');
+        $this->assertTrue(CrontabContainer::has('id'));
+        $this->assertFalse(CrontabContainer::has('test'));
+    }
+}
diff --git a/src/Crontab/tests/Cases/CrontabUrlTest.php b/src/Crontab/tests/Cases/CrontabUrlTest.php
new file mode 100644
index 00000000..c33535ee
--- /dev/null
+++ b/src/Crontab/tests/Cases/CrontabUrlTest.php
@@ -0,0 +1,41 @@
+allows('get')->andReturnUsing(function ($url) {
+            $this->assertSame($url, 'http://mineadmin.com');
+            return \Mockery::mock(ResponseInterface::class);
+        });
+        $clientFactory->allows('create')->andReturn($client);
+        ApplicationContext::getContainer()->set(ClientFactory::class, $clientFactory);
+        $crontabUrl = ApplicationContext::getContainer()->get(CrontabUrl::class);
+        $crontabUrl->execute('http://mineadmin.com');
+    }
+}
diff --git a/src/Crontab/tests/Cases/Listener/CrontabProcessStarredListenerTest.php b/src/Crontab/tests/Cases/Listener/CrontabProcessStarredListenerTest.php
new file mode 100644
index 00000000..efc9d3ab
--- /dev/null
+++ b/src/Crontab/tests/Cases/Listener/CrontabProcessStarredListenerTest.php
@@ -0,0 +1,58 @@
+getMethod('listen');
+        $result = $method->invoke($instance);
+        $this->assertSame($result, [
+            CrontabDispatcherStarted::class,
+        ]);
+    }
+
+    public function testProcess(): void
+    {
+        $reflectionClass = new \ReflectionClass(CrontabProcessStarredListener::class);
+        $instance = \Mockery::mock(CrontabProcessStarredListener::class);
+        CrontabProcessStarredListener::$sleep = 1;
+        $instance->allows('registerCrontab');
+        $method = $reflectionClass->getMethod('process');
+        ProcessManager::setRunning(false);
+        $method->invoke($instance, \Mockery::mock(CrontabDispatcherStarted::class));
+        ProcessManager::setRunning(true);
+        Coroutine::create(function () {
+            sleep(2);
+            ProcessManager::setRunning(false);
+        });
+        $method->invoke($instance, \Mockery::mock(CrontabDispatcherStarted::class));
+        $this->assertTrue(true);
+    }
+}
From 7c5261738b2639e08adb2369a2481de03b3ed7e1 Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Tue, 26 Mar 2024 14:31:07 +0800
Subject: [PATCH 3/7] Update
---
 .../2024_03_26_020159_create_crontab.php      |   2 +
 src/Crontab/src/Crontab.php                   |  30 +++-
 src/Crontab/src/Schedule.php                  |   4 +-
 src/Crontab/tests/Cases/CrontabTest.php       | 156 ++++++++++++++++++
 src/Crontab/tests/Cases/ScheduleTest.php      |  84 ++++++++++
 5 files changed, 270 insertions(+), 6 deletions(-)
 create mode 100644 src/Crontab/tests/Cases/CrontabTest.php
 create mode 100644 src/Crontab/tests/Cases/ScheduleTest.php
diff --git a/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php b/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php
index 9f3ad8e8..621a6e70 100644
--- a/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php
+++ b/src/Crontab/Database/Migrations/2024_03_26_020159_create_crontab.php
@@ -31,6 +31,8 @@ public function up(): void
              */
             $table->string('name', 30);
             $table->tinyInteger('status')->default(0);
+            $table->tinyInteger('is_on_one_server')->default(0);
+            $table->tinyInteger('is_singleton')->default(0);
             $table->string('memo', 60)->default(null);
             $table->string('type', 10);
             $table->string('rule', 10);
diff --git a/src/Crontab/src/Crontab.php b/src/Crontab/src/Crontab.php
index 22737e41..2ca110c0 100644
--- a/src/Crontab/src/Crontab.php
+++ b/src/Crontab/src/Crontab.php
@@ -32,13 +32,21 @@ class Crontab extends Base
 
     public const RULE_COLUMN = 'rule';
 
+    public const NAME_COLUMN = 'name';
+
+    public const IS_ON_ONE_SERVER_COLUMN = 'is_on_one_server';
+
+    public const IS_SINGLETON = 'is_singleton';
+
+    public static string $connectionName = 'default';
+
     public function __construct(
         private readonly int $cronId,
     ) {}
 
     public function getName(): ?string
     {
-        return $this->getBuilder()->value('name');
+        return $this->getBuilder()->value(self::NAME_COLUMN);
     }
 
     public function isEnable(): bool
@@ -62,7 +70,7 @@ public function getMemo(): ?string
 
     public function getBuilder(): Builder
     {
-        return Db::table(self::TABLE)->where(self::TABLE_KEY, $this->cronId);
+        return Db::connection(self::$connectionName)->table(self::TABLE)->where(self::TABLE_KEY, $this->cronId);
     }
 
     /**
@@ -79,11 +87,12 @@ public function getCallback(): mixed
                 return [
                     CrontabUrl::class,
                     'execute',
-                    explode($value, ','),
+                    explode(',', $value),
                 ];
             case 'class':
                 return [$value, 'execute'];
             case 'command':
+            case 'callback':
                 return json_decode($value, true, 512, JSON_THROW_ON_ERROR);
         }
         return $value;
@@ -93,4 +102,19 @@ public function getRule(): ?string
     {
         return $this->getBuilder()->value(self::RULE_COLUMN);
     }
+
+    public function getCronId(): int
+    {
+        return $this->cronId;
+    }
+
+    public function isOnOneServer(): bool
+    {
+        return (bool) $this->getBuilder()->value(self::IS_ON_ONE_SERVER_COLUMN);
+    }
+
+    public function isSingleton(): bool
+    {
+        return (bool) $this->getBuilder()->value(self::IS_SINGLETON);
+    }
 }
diff --git a/src/Crontab/src/Schedule.php b/src/Crontab/src/Schedule.php
index 001a92ed..41459e37 100644
--- a/src/Crontab/src/Schedule.php
+++ b/src/Crontab/src/Schedule.php
@@ -15,12 +15,10 @@
 use Hyperf\Crontab\Crontab;
 use Hyperf\DbConnection\Db;
 
-final class Schedule
+class Schedule
 {
     public const CRONTAB_TABLE = 'crontab';
 
-    public function __construct() {}
-
     /**
      * @return Crontab[]
      */
diff --git a/src/Crontab/tests/Cases/CrontabTest.php b/src/Crontab/tests/Cases/CrontabTest.php
new file mode 100644
index 00000000..00a46360
--- /dev/null
+++ b/src/Crontab/tests/Cases/CrontabTest.php
@@ -0,0 +1,156 @@
+set(ConfigInterface::class, new Config([]));
+        $connectionResolverInterface = \Mockery::mock(ConnectionResolverInterface::class);
+        $connectionInterface = \Mockery::mock(ConnectionInterface::class);
+        $connectionResolverInterface
+            ->allows('connection')
+            ->andReturn($connectionInterface);
+        $builder = \Mockery::mock(Builder::class);
+        $builder->allows('where')->with(Crontab::TABLE_KEY, 1)->andReturn($builder);
+        $builder->allows('value')->with(Crontab::ENABLE_COLUMN)->andReturn(1, 0);
+        $builder->allows('value')->with(Crontab::IS_SINGLETON)->andReturn(1, 0);
+        $builder->allows('value')->with(Crontab::IS_ON_ONE_SERVER_COLUMN)->andReturn(1, 0);
+        $builder->allows('value')->with(Crontab::NAME_COLUMN)->andReturn('xxx');
+        $builder->allows('value')->with(Crontab::MEMO_COLUMN)->andReturn('xxx');
+        $builder->allows('value')
+            ->with(Crontab::RULE_COLUMN)
+            ->andReturn('* * * * *', '0 0 * * *');
+        $builder->allows('value')
+            ->with(Crontab::TYPE_COLUMN)
+            ->andReturn(
+                'xxx',
+                'callback',
+                'url',
+                'class',
+                'eval',
+                'command',
+                'xxx',
+                'callback',
+                'url',
+                'class',
+                'eval',
+                'command'
+            );
+        $builder->allows('value')
+            ->with(Crontab::VALUE_COLUMN)
+            ->andReturn(
+                'xxx',
+                '["xxx","xxx"]',
+                'http://baidu.com',
+                'AppTest',
+                'echo 1;',
+                '["xxx","xxx"]'
+            );
+        $connectionInterface->allows('table')->andReturnUsing(function ($table) use ($builder) {
+            $this->assertSame(Crontab::TABLE, $table);
+            return $builder;
+        });
+        ApplicationContext::getContainer()->set(ConnectionResolverInterface::class, $connectionResolverInterface);
+    }
+
+    public function testConstruct(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertSame($crontab->getCronId(), 1);
+    }
+
+    public function testGetBuilder(): void
+    {
+        $crontab = new Crontab(1);
+        $crontab->getBuilder();
+        $this->assertTrue(true);
+    }
+
+    public function testGetName(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertSame($crontab->getName(), 'xxx');
+    }
+
+    public function testGetMemo(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertSame($crontab->getMemo(), 'xxx');
+    }
+
+    public function testIsEnable(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertTrue($crontab->isEnable());
+        $this->assertFalse($crontab->isEnable());
+    }
+
+    public function testGetType(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertSame('xxx', $crontab->getType());
+        $this->assertSame('callback', $crontab->getType());
+        $this->assertSame('callback', $crontab->getType());
+        $this->assertSame('callback', $crontab->getType());
+        $this->assertSame('eval', $crontab->getType());
+        $this->assertSame('command', $crontab->getType());
+    }
+
+    public function testGetCallback(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertSame($crontab->getCallback(), 'xxx');
+        $this->assertSame($crontab->getCallback(), ['xxx', 'xxx']);
+        $this->assertSame($crontab->getCallback(), [CrontabUrl::class, 'execute', ['http://baidu.com']]);
+        $this->assertSame($crontab->getCallback(), ['AppTest', 'execute']);
+        $this->assertSame($crontab->getCallback(), 'echo 1;');
+        $this->assertSame($crontab->getCallback(), ['xxx', 'xxx']);
+    }
+
+    public function testGetRule(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertSame($crontab->getRule(), '* * * * *');
+        $this->assertSame($crontab->getRule(), '0 0 * * *');
+    }
+
+    public function testIsSingleton(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertTrue($crontab->isSingleton());
+        $this->assertFalse($crontab->isSingleton());
+    }
+
+    public function testIsOnOneServer(): void
+    {
+        $crontab = new Crontab(1);
+        $this->assertTrue($crontab->isOnOneServer());
+        $this->assertFalse($crontab->isOnOneServer());
+    }
+}
diff --git a/src/Crontab/tests/Cases/ScheduleTest.php b/src/Crontab/tests/Cases/ScheduleTest.php
new file mode 100644
index 00000000..5e520e39
--- /dev/null
+++ b/src/Crontab/tests/Cases/ScheduleTest.php
@@ -0,0 +1,84 @@
+set(ConfigInterface::class, $config);
+    }
+
+    public function testGetCrontab(): void
+    {
+        $connectionResolverInterface = \Mockery::mock(ConnectionResolverInterface::class);
+        $connectionInterface = \Mockery::mock(ConnectionInterface::class);
+        $connectionResolverInterface
+            ->allows('connection')
+            ->andReturn($connectionInterface);
+        $connectionInterface->allows('table')->andReturnUsing(function ($table) {
+            $this->assertSame($table, Schedule::CRONTAB_TABLE);
+            $builder = \Mockery::mock(Builder::class);
+            $stdclass = new \stdClass();
+            $stdclass->id = 1;
+            $builder->allows('get')
+                ->andReturn(new Collection([$stdclass]));
+            $builder->allows('where')->andReturnUsing(function ($column, $val) use ($builder) {
+                $this->assertSame($column, 'status');
+                $this->assertSame($val, 1);
+                return $builder;
+            });
+            return $builder;
+        }, function ($table) {
+            $this->assertSame($table, Schedule::CRONTAB_TABLE);
+            $builder = \Mockery::mock(Builder::class);
+            $builder->allows('get')
+                ->andReturn(new Collection([]));
+            $builder->allows('where')->andReturnUsing(function ($column, $val) use ($builder) {
+                $this->assertSame($column, 'status');
+                $this->assertSame($val, 1);
+                return $builder;
+            });
+            return $builder;
+        });
+        ApplicationContext::getContainer()->set(ConnectionResolverInterface::class, $connectionResolverInterface);
+
+        $schedule = new \ReflectionClass(Schedule::class);
+        $method = $schedule->getMethod('getCrontab');
+        $instance = \Mockery::mock(Schedule::class);
+        $result = $method->invoke($instance);
+        $this->assertIsArray($result);
+        $this->assertCount(1, $result);
+        $result = $method->invoke($instance);
+        $this->assertIsArray($result);
+        $this->assertCount(0, $result);
+    }
+}
From 650c14f3117d0cb7d3735d1a0fe905e937da6eff Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Tue, 26 Mar 2024 16:05:51 +0800
Subject: [PATCH 4/7] Update
---
 ...3_26_020825_create_crontab_execute_log.php |   4 +-
 .../src/Aspect/CrontabExecutorAspect.php      |  53 +++++++++
 src/Crontab/src/ConfigProvider.php            |   8 ++
 src/Crontab/src/Crontab.php                   |   4 +-
 src/Crontab/src/CrontabContainer.php          |   2 +
 .../Aspect/CrontabExecutorAspectTest.php      | 102 ++++++++++++++++++
 6 files changed, 169 insertions(+), 4 deletions(-)
 create mode 100644 src/Crontab/src/Aspect/CrontabExecutorAspect.php
 create mode 100644 src/Crontab/tests/Cases/Aspect/CrontabExecutorAspectTest.php
diff --git a/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php b/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php
index 703d1f36..7cea3e40 100644
--- a/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php
+++ b/src/Crontab/Database/Migrations/2024_03_26_020825_create_crontab_execute_log.php
@@ -23,8 +23,10 @@ public function up(): void
         Schema::create('crontab_execute_log', function (Blueprint $table) {
             $table->bigIncrements('id');
             $table->bigInteger('crontab_id');
+            $table->string('name', 100);
             $table->tinyInteger('status')->default(0);
-            $table->string('output')->default(0);
+            $table->string('target', 200);
+            $table->text('exception_info');
             $table->datetimes();
         });
     }
diff --git a/src/Crontab/src/Aspect/CrontabExecutorAspect.php b/src/Crontab/src/Aspect/CrontabExecutorAspect.php
new file mode 100644
index 00000000..10462188
--- /dev/null
+++ b/src/Crontab/src/Aspect/CrontabExecutorAspect.php
@@ -0,0 +1,53 @@
+getArguments();
+        if ($crontab instanceof \Mine\Crontab\Crontab) {
+            $callback = $crontab->getCallback();
+            if (is_array($callback)) {
+                $callback = json_encode($callback, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
+            }
+            Db::connection(CrontabContainer::$connectionName)
+                ->table('crontab_execute_log')
+                ->insert([
+                    'crontab_id' => $crontab->getCronId(),
+                    'name' => $crontab->getName(),
+                    'target' => $callback,
+                    'status' => $isSuccess ? 1 : 0,
+                    'exception_info' => $throwable === null ? '' : $throwable->getMessage(),
+                ]);
+        }
+        return $proceedingJoinPoint->process();
+    }
+}
diff --git a/src/Crontab/src/ConfigProvider.php b/src/Crontab/src/ConfigProvider.php
index 85a9e153..9933cc96 100644
--- a/src/Crontab/src/ConfigProvider.php
+++ b/src/Crontab/src/ConfigProvider.php
@@ -12,6 +12,8 @@
 
 namespace Mine\Crontab;
 
+use Mine\Crontab\Aspect\CrontabExecutorAspect;
+use Mine\Crontab\Command\CrontabMigrateCommand;
 use Mine\Crontab\Listener\CrontabProcessStarredListener;
 
 class ConfigProvider
@@ -22,6 +24,12 @@ public function __invoke(): array
             'listener' => [
                 CrontabProcessStarredListener::class,
             ],
+            'aspects' => [
+                CrontabExecutorAspect::class,
+            ],
+            'commands' => [
+                CrontabMigrateCommand::class,
+            ],
         ];
     }
 }
diff --git a/src/Crontab/src/Crontab.php b/src/Crontab/src/Crontab.php
index 2ca110c0..2450edf2 100644
--- a/src/Crontab/src/Crontab.php
+++ b/src/Crontab/src/Crontab.php
@@ -38,8 +38,6 @@ class Crontab extends Base
 
     public const IS_SINGLETON = 'is_singleton';
 
-    public static string $connectionName = 'default';
-
     public function __construct(
         private readonly int $cronId,
     ) {}
@@ -70,7 +68,7 @@ public function getMemo(): ?string
 
     public function getBuilder(): Builder
     {
-        return Db::connection(self::$connectionName)->table(self::TABLE)->where(self::TABLE_KEY, $this->cronId);
+        return Db::connection(CrontabContainer::$connectionName)->table(self::TABLE)->where(self::TABLE_KEY, $this->cronId);
     }
 
     /**
diff --git a/src/Crontab/src/CrontabContainer.php b/src/Crontab/src/CrontabContainer.php
index 2161775e..50835cd6 100644
--- a/src/Crontab/src/CrontabContainer.php
+++ b/src/Crontab/src/CrontabContainer.php
@@ -17,4 +17,6 @@
 final class CrontabContainer
 {
     use Container;
+
+    public static string $connectionName = 'default';
 }
diff --git a/src/Crontab/tests/Cases/Aspect/CrontabExecutorAspectTest.php b/src/Crontab/tests/Cases/Aspect/CrontabExecutorAspectTest.php
new file mode 100644
index 00000000..68d8136b
--- /dev/null
+++ b/src/Crontab/tests/Cases/Aspect/CrontabExecutorAspectTest.php
@@ -0,0 +1,102 @@
+set(ConfigInterface::class, new Config([]));
+        $connectionResolverInterface = \Mockery::mock(ConnectionResolverInterface::class);
+        $connectionInterface = \Mockery::mock(ConnectionInterface::class);
+        $connectionResolverInterface
+            ->allows('connection')
+            ->andReturn($connectionInterface);
+        $builder = \Mockery::mock(Builder::class);
+        $connectionInterface->allows('table')->andReturn($builder);
+        $builder->allows('where')->with(Crontab::TABLE_KEY, 1)->andReturn($builder);
+        $builder->allows('value')->with(Crontab::ENABLE_COLUMN)->andReturn(1, 0);
+        $builder->allows('value')->with(Crontab::IS_SINGLETON)->andReturn(1, 0);
+        $builder->allows('value')->with(Crontab::IS_ON_ONE_SERVER_COLUMN)->andReturn(1, 0);
+        $builder->allows('value')->with(Crontab::NAME_COLUMN)->andReturn('xxx');
+        $builder->allows('value')->with(Crontab::MEMO_COLUMN)->andReturn('xxx');
+        $builder->allows('value')
+            ->with(Crontab::RULE_COLUMN)
+            ->andReturn('* * * * *', '0 0 * * *');
+        $builder->allows('value')
+            ->with(Crontab::TYPE_COLUMN)
+            ->andReturn(
+                'xxx',
+                'callback',
+                'url',
+                'class',
+                'eval',
+                'command',
+                'xxx',
+                'callback',
+                'url',
+                'class',
+                'eval',
+                'command'
+            );
+        $builder->allows('value')
+            ->with(Crontab::VALUE_COLUMN)
+            ->andReturn(
+                'xxx',
+                '["xxx","xxx"]',
+                'http://baidu.com',
+                'AppTest',
+                'echo 1;',
+                '["xxx","xxx"]'
+            );
+        $builder->allows('insert')->andReturnUsing(function ($data) {
+            return true;
+        });
+        ApplicationContext::getContainer()->set(ConnectionResolverInterface::class, $connectionResolverInterface);
+    }
+
+    public function testProcess(): void
+    {
+        $aspect = new CrontabExecutorAspect();
+        $this->assertSame($aspect->classes, [
+            Executor::class . '::logResult',
+        ]);
+        $proceedingJoinPoint = \Mockery::mock(ProceedingJoinPoint::class);
+        $proceedingJoinPoint->allows('process');
+        $proceedingJoinPoint->allows('getArguments')->andReturn([
+            new Crontab(1),
+            true,
+            new \Exception('test'),
+        ]);
+        $aspect->process($proceedingJoinPoint);
+        $aspect->process($proceedingJoinPoint);
+        $aspect->process($proceedingJoinPoint);
+        $aspect->process($proceedingJoinPoint);
+    }
+}
From e96d4050916df09d9546f578b8512b86306a664a Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Tue, 26 Mar 2024 16:16:36 +0800
Subject: [PATCH 5/7] Update
---
 src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php | 5 ++++-
 src/HttpServer/tests/Cases/ResultTest.php                    | 3 +++
 2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php b/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php
index e74baa10..acb83364 100644
--- a/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php
+++ b/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php
@@ -17,6 +17,8 @@
 use Hyperf\Contract\ConfigInterface;
 use Hyperf\Contract\StdoutLoggerInterface;
 use Hyperf\Contract\TranslatorInterface;
+use Hyperf\Di\Container;
+use Hyperf\Di\Definition\DefinitionSourceFactory;
 use Mine\HttpServer\Contract\Log\RequestIdGeneratorInterface;
 use Mine\HttpServer\Log\RequestIdGenerator;
 use Mine\HttpServer\Middleware\I18nMiddleware;
@@ -34,6 +36,7 @@ class I18nMiddlewareTest extends TestCase
 {
     protected function setUp(): void
     {
+        ApplicationContext::setContainer(new Container((new DefinitionSourceFactory(true))()));
         $config = new Config([
             StdoutLoggerInterface::class => [
                 LogLevel::DEBUG,
@@ -58,9 +61,9 @@ protected function setUp(): void
 
     public function testProcess(): void
     {
+        $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class);
         $instance = ApplicationContext::getContainer()->get(I18nMiddleware::class);
         $request = \Mockery::mock(ServerRequestInterface::class);
-        $translator = ApplicationContext::getContainer()->get(TranslatorInterface::class);
         $request->allows('hasHeader')
             ->andReturn(false, true, true, true, true);
         $request->allows('getHeaderLine')
diff --git a/src/HttpServer/tests/Cases/ResultTest.php b/src/HttpServer/tests/Cases/ResultTest.php
index 733eb399..759a83c0 100644
--- a/src/HttpServer/tests/Cases/ResultTest.php
+++ b/src/HttpServer/tests/Cases/ResultTest.php
@@ -16,6 +16,8 @@
 use Hyperf\Context\ApplicationContext;
 use Hyperf\Contract\ConfigInterface;
 use Hyperf\Contract\StdoutLoggerInterface;
+use Hyperf\Di\Container;
+use Hyperf\Di\Definition\DefinitionSourceFactory;
 use Mine\HttpServer\Constant\HttpResultCode;
 use Mine\HttpServer\Contract\Log\RequestIdGeneratorInterface;
 use Mine\HttpServer\Log\RequestIdGenerator;
@@ -32,6 +34,7 @@ class ResultTest extends TestCase
 {
     protected function setUp(): void
     {
+        ApplicationContext::setContainer(new Container((new DefinitionSourceFactory(true))()));
         $config = new Config([
             StdoutLoggerInterface::class => [
                 LogLevel::DEBUG,
From 4d13ffd7ffb2661fd0ca1dd15798524e6a984e35 Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Tue, 26 Mar 2024 16:22:53 +0800
Subject: [PATCH 6/7] Update
---
 .../tests/Cases/Middleware/I18nMiddlewareTest.php           | 6 +++++-
 src/HttpServer/tests/Cases/ResultTest.php                   | 6 +++++-
 2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php b/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php
index acb83364..6343931f 100644
--- a/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php
+++ b/src/HttpServer/tests/Cases/Middleware/I18nMiddlewareTest.php
@@ -19,6 +19,7 @@
 use Hyperf\Contract\TranslatorInterface;
 use Hyperf\Di\Container;
 use Hyperf\Di\Definition\DefinitionSourceFactory;
+use Hyperf\Di\Exception\Exception;
 use Mine\HttpServer\Contract\Log\RequestIdGeneratorInterface;
 use Mine\HttpServer\Log\RequestIdGenerator;
 use Mine\HttpServer\Middleware\I18nMiddleware;
@@ -34,9 +35,12 @@
  */
 class I18nMiddlewareTest extends TestCase
 {
+    /**
+     * @throws Exception
+     */
     protected function setUp(): void
     {
-        ApplicationContext::setContainer(new Container((new DefinitionSourceFactory(true))()));
+        ApplicationContext::setContainer(new Container((new DefinitionSourceFactory())()));
         $config = new Config([
             StdoutLoggerInterface::class => [
                 LogLevel::DEBUG,
diff --git a/src/HttpServer/tests/Cases/ResultTest.php b/src/HttpServer/tests/Cases/ResultTest.php
index 759a83c0..18919a00 100644
--- a/src/HttpServer/tests/Cases/ResultTest.php
+++ b/src/HttpServer/tests/Cases/ResultTest.php
@@ -18,6 +18,7 @@
 use Hyperf\Contract\StdoutLoggerInterface;
 use Hyperf\Di\Container;
 use Hyperf\Di\Definition\DefinitionSourceFactory;
+use Hyperf\Di\Exception\Exception;
 use Mine\HttpServer\Constant\HttpResultCode;
 use Mine\HttpServer\Contract\Log\RequestIdGeneratorInterface;
 use Mine\HttpServer\Log\RequestIdGenerator;
@@ -32,9 +33,12 @@
  */
 class ResultTest extends TestCase
 {
+    /**
+     * @throws Exception
+     */
     protected function setUp(): void
     {
-        ApplicationContext::setContainer(new Container((new DefinitionSourceFactory(true))()));
+        ApplicationContext::setContainer(new Container((new DefinitionSourceFactory())()));
         $config = new Config([
             StdoutLoggerInterface::class => [
                 LogLevel::DEBUG,
From adffdeaf617aa28d37bef7dee1e0a98ccc541af0 Mon Sep 17 00:00:00 2001
From: Zds <49744633+zds-s@users.noreply.github.com>
Date: Tue, 26 Mar 2024 16:25:08 +0800
Subject: [PATCH 7/7] Update
---
 CHANGELOG-2.0.md       | 3 ++-
 CHANGELOG-2.0.zh_CN.md | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG-2.0.md b/CHANGELOG-2.0.md
index 8c2fd6cc..6ad25acb 100644
--- a/CHANGELOG-2.0.md
+++ b/CHANGELOG-2.0.md
@@ -4,4 +4,5 @@
 
 ## Added
 
-- [#53](https://github.com/mineadmin/components/pull/53) Splitting components http-server
\ No newline at end of file
+- [#53](https://github.com/mineadmin/components/pull/53) Splitting components http-server
+- [#55](https://github.com/mineadmin/components/pull/55) Splitting the crontab component
\ No newline at end of file
diff --git a/CHANGELOG-2.0.zh_CN.md b/CHANGELOG-2.0.zh_CN.md
index 38ff172e..257f0d9c 100644
--- a/CHANGELOG-2.0.zh_CN.md
+++ b/CHANGELOG-2.0.zh_CN.md
@@ -4,4 +4,5 @@
 
 ## Added
 
-- [#53](https://github.com/mineadmin/components/pull/53) 拆分组件 http-server
\ No newline at end of file
+- [#53](https://github.com/mineadmin/components/pull/53) 拆分组件 http-server
+- [#55](https://github.com/mineadmin/components/pull/55) 拆分优化组件 crontab
\ No newline at end of file