├── LICENSE ├── README.md ├── composer.json ├── example └── think3.2.3.php └── src ├── Check.php ├── Command.php ├── Env.php ├── Error.php ├── Exception └── ErrorException.php ├── Helper.php ├── Lock.php ├── Process ├── Linux.php ├── Process.php └── Win.php ├── Queue.php ├── Table.php ├── Task.php ├── Wpc.php └── Wts.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GaoJiuFeng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 

EasyTask is an easy-to-use PHP resident memory scheduled task package

2 |

3 | 4 | 5 | 6 | 7 | 8 |

9 | 10 | ##

Project Introduction

11 |

       EasyTask is a PHP resident memory timer Composer package, Same effect as Workerman timer, Multiple timers are running in multiple processes at the same time ,you can use it to complete tasks that need to be repeated (such as automatic cancellation of order timeout, asynchronous push of SMS mail, queue / consumer / channel Subscribers, etc.), and even handle Crontab scheduled tasks (such as synchronizing DB data from 1 am to 3 am every day, generating monthly unified reports on the 1st of every month, restarting the nginx server at 10 pm every night, etc.); built-in task abnormal reporting function, You can customize the handling of abnormal errors (such as automatic SMS notification of abnormal errors); it also supports automatic restart of abnormal task exits to make your task run more stable, and the toolkit supports the operation of windows, linux, and mac environments. 12 |

13 | 14 | ##

Operating environment

15 | 16 | 20 | 21 | ##

Composer install

22 | 23 | ~~~ 24 | composer require easy-task/easy-task 25 | ~~~ 26 | 27 | ##
【One】. Quick Start-> Create Task
28 | 29 | ~~~ 30 | // init 31 | $task = new Task(); 32 | 33 | // set up resident memory 34 | $task->setDaemon(false); 35 | 36 | // set project name 37 | $task->setPrefix('EasyTask'); 38 | 39 | // set the logging runtime directory (log or cache directory) 40 | $task->setRunTimePath('./Application/Runtime/'); 41 | 42 | // add closure function type timed task (open 2 processes, execute once every 10 seconds) 43 | $task->addFunc(function () { 44 | $url = 'https://www.gaojiufeng.cn/?id=243'; 45 | @file_get_contents($url); 46 | }, 'request', 10, 2); 47 | 48 | // add class method type timing task (also supports static methods) (start 1 process, execute once every 20 seconds) 49 | $task->addClass(Sms::class, 'send', 'sendsms', 20, 1); 50 | 51 | // add instruction-type timing tasks (start a process and execute it every 10 seconds) 52 | $command = 'php /www/web/orderAutoCancel.php'; 53 | $task->addCommand($command,'orderCancel',10,1); 54 | 55 | // add a closure function task, do not need a timer, execute immediately (open 1 process) 56 | $task->addFunc(function () { 57 | while(true) 58 | { 59 | //todo 60 | } 61 | }, 'request', 0, 1); 62 | 63 | // start task 64 | $task->start(); 65 | ~~~ 66 | 67 | ##
【Two】. Quick Start-> Coherent Operation
68 | 69 | ~~~ 70 | $task = new Task(); 71 | 72 | // Set non-resident memory 73 | $task->setDaemon(false) 74 | 75 | // set project name 76 | ->setPrefix('ThinkTask') 77 | 78 | // set system time zone 79 | ->setTimeZone('Asia/Shanghai') 80 | 81 | // set the child process to hang up and restart automatically 82 | ->setAutoRecover(true) 83 | 84 | // set the PHP running path, which is usually required for the Window system. You need to set it manually when the system cannot be found. 85 | ->setPhpPath('C:/phpEnv/php/php-7.0/php.exe') 86 | 87 | /** 88 | * set the logging runtime directory (log or cache directory) 89 | */ 90 | ->setRunTimePath('./Application/Runtime/') 91 | 92 | /** 93 | * set to turn off standard output STD file recording 94 | */ 95 | ->setCloseStdOutLog(true); 96 | 97 | /** 98 | * Close EasyTask's exception registration 99 | * EasyTask will no longer listen to set_error_handler / set_exception_handler / register_shutdown_function events 100 | */ 101 | ->setCloseErrorRegister(true) 102 | 103 | /** 104 | * set to receive errors or exceptions during operation (Mode 1) 105 | * you can customize the handling of abnormal information, such as sending them to your emails, SMS, as an early warning 106 | * (Not recommended, unless your code is robust) 107 | */ 108 | ->setErrorRegisterNotify(function ($ex) { 109 | //Get error information | error line | error file 110 | $message = $ex->getMessage(); 111 | $file = $ex->getFile(); 112 | $line = $ex->getLine(); 113 | }) 114 | 115 | /** 116 | * set the Http address to receive errors or exceptions in operation (Method 2) 117 | * EasyTask will notify this URL and pass the following parameters: 118 | * errStr:errStr 119 | * errFile:errFile 120 | * errLine:errLine 121 | * your Url receives a POST request and can write code to send an email or SMS to notify you 122 | * (Recommended wording) 123 | */ 124 | ->setErrorRegisterNotify('https://www.gaojiufeng.cn/rev.php') 125 | 126 | // add task to execute closure function regularly 127 | ->addFunc(function () { 128 | echo 'Success3' . PHP_EOL; 129 | }, 'fucn', 20, 1) 130 | 131 | // add a method for task execution class 132 | ->addClass(Sms::class, 'send', 'sendsms1', 20, 1) 133 | 134 | // add tasks to execute commands regularly 135 | ->addCommand('php /www/wwwroot/learn/curl.php','cmd',6,1) 136 | 137 | // start task 138 | ->start(); 139 | ~~~ 140 | 141 | ##
【Three】. Quick Start-> Command Integration
142 | 143 | ~~~ 144 | // get command 145 | $force = empty($_SERVER['argv']['2']) ? '' : $_SERVER['argv']['2']; 146 | $command = empty($_SERVER['argv']['1']) ? '' : $_SERVER['argv']['1']; 147 | 148 | // configuration tasks 149 | $task = new Task(); 150 | $task->setRunTimePath('./Application/Runtime/'); 151 | $task->addFunc(function () { 152 | $url = 'https://www.gaojiufeng.cn/?id=271'; 153 | @file_get_contents($url); 154 | }, 'request', 10, 2);; 155 | 156 | // execute according to the order 157 | if ($command == 'start') 158 | { 159 | $task->start(); 160 | } 161 | elseif ($command == 'status') 162 | { 163 | $task->status(); 164 | } 165 | elseif ($command == 'stop') 166 | { 167 | $force = ($force == 'force'); //whether to force stop 168 | $task->stop($force); 169 | } 170 | else 171 | { 172 | exit('Command is not exist'); 173 | } 174 | 175 | Start task: php console.php start 176 | Query task: php console.php status 177 | Stop Task: php console.php stop 178 | Force close task: php console.php stop force 179 | ~~~ 180 | 181 | ##
【Four】. Quick Start-> Understanding output information
182 | 183 | ~~~ 184 | ┌─────┬──────────────┬─────────────────────┬───────┬────────┬──────┐ 185 | │ pid │ name │ started │ time │ status │ ppid │ 186 | ├─────┼──────────────┼─────────────────────┼───────┼────────┼──────┤ 187 | │ 32 │ Task_request │ 2020-01-10 15:55:44 │ 10 │ active │ 31 │ 188 | │ 33 │ Task_request │ 2020-01-10 15:55:44 │ 10 │ active │ 31 │ 189 | └─────┴──────────────┴─────────────────────┴───────┴────────┴──────┘ 190 | 参数: 191 | pid:task process id 192 | name:task alias 193 | started:task start time 194 | time:task execution time 195 | status:task status 196 | ppid:daemon id 197 | ~~~ 198 | 199 | ##
【Five】. Advanced understanding-> recommended reading
200 | 201 | ~~~ 202 | (1). It is recommended that you use the absolute path for development, which is the standard and the norm 203 | (2). It is forbidden to use exit / die syntax in the task, otherwise it will cause the entire process to exit 204 | (3). Please close anti-virus software when installing Wpc extension in Windows to avoid false alarms 205 | (4). Windows recommends to open shell_exec method, it will automatically try to help you solve the problem of CMD output Chinese garbled, please try to use CMD administrator mode 206 | (5). Windows command line does not support utf8 international standard encoding, you can switch git_bash to run, solve the garbled problem 207 | (6). Windows prompts Failed to create COM object `Wpc.Core ': invalid syntax, please follow the documentation to install the Wpc extension 208 | (7). Windows prompt com () has been disabled for security reasons, please delete disable_classes = com configuration item in php.ini 209 | (8). The log file is in the Log directory of the runtime directory, and the input and output abnormal files are marked in the Std directory of the runtime directory 210 | (9). Normally stop the task, the task will start to exit safely after the execution is successful, force the task to quit the task directly, and may quit when it is being executed 211 | (10). The development follows the synchronous start test, normal operation without any errors, and then the asynchronous operation. If there is a problem, check the log file or the standard input and output abnormal file, or feedback on the QQ group. 212 | ~~~ 213 | 214 | ##
【Six】. Advanced Understanding-> Framework Integration Tutorial
215 | 216 |   [-> thinkphp3.2.x](https://www.gaojiufeng.cn/?id=293). 217 | 218 |   [-> thinkPhp5.x.x](https://www.gaojiufeng.cn/?id=294). 219 | 220 |   [-> thinkPhp6.x.x](https://www.gaojiufeng.cn/?id=328). 221 | 222 |   [-> laravelPhp6.x.x](https://www.gaojiufeng.cn/?id=295). 223 | 224 | ##
【Seven】. Advanced understanding-> Recommended actions
225 | 226 | ~~~ 227 | (1). It is recommended to use PHP version 7.1 or above, which supports asynchronous signals and does not depend on ticks 228 | (2). It is recommended to install php_event to extend the millisecond timing support based on event polling 229 | ~~~ 230 | 231 | ##
【Eight】. Advanced understanding-> time parameters support crontab command
232 | 233 | ~~~ 234 | Since the 2.3.6 version to reduce maintenance work, Crontab support has been removed, please use PHP's own time functionDateTime class for processing. 235 | For example, it only needs to be executed at 20 o'clock every night, and it is not necessary to execute Return at 20 o'clock. 236 | $task->addFunc(function () { 237 | $hour = date('H'); 238 | if ($hour != 20) 239 | { 240 | return; 241 | } 242 | 243 | //Write your code 244 | },'request', 1, 1); 245 | ~~~ 246 | 247 | ##
【Nine】. Special thanks to
248 | ~~~ 249 | (1) ThinkPHP (the official extension page shows EasyTask), official address: http://www.thinkphp.cn/ 250 | (2) ThinkPHP (command line output component based on Tp_Table component), official address: http://www.thinkphp.cn/ 251 | (3) Jetbrains (provide genuine authorization code, support genuine), official address: https://www.jetbrains.com/phpstorm/ 252 | ~~~ 253 | ##
【Ten】. Bug feedback
254 | ~~~ 255 | Please feedback to QQ group 777241713, thanks to the users who continue to feedback, your feedback makes EasyTask more and more stable! 256 | ~~~ -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-task/easy-task", 3 | "description": "easy-task, simple timer, timing task", 4 | "keywords": [ 5 | "easy-task" 6 | ], 7 | "type": "library", 8 | "license": "Apache-2.0", 9 | "authors": [ 10 | { 11 | "name": "GaoJiuFeng", 12 | "email": "392223903@qq.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.4", 17 | "ext-json": "*", 18 | "ext-curl": "*", 19 | "ext-mbstring": "*" 20 | }, 21 | "suggest": { 22 | "ext-event": "For better performance. " 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "EasyTask\\": "src/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/think3.2.3.php: -------------------------------------------------------------------------------- 1 | argv = $_SERVER['argv']; 39 | $this->argc = $_SERVER['argc']; 40 | 41 | //保存命令并清空Cli_Input 42 | $this->action = isset($_SERVER['argv']['1']) ? $_SERVER['argv']['1'] : ''; 43 | $this->force = isset($_SERVER['argv']['2']) ? $_SERVER['argv']['2'] : ''; 44 | $_SERVER['argv'] = [] && $_SERVER['argc'] = 0; 45 | 46 | //抑制Tp错误 47 | if (!isset($_SERVER['REMOTE_ADDR'])) $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; 48 | if (!isset($_SERVER['REQUEST_URI'])) $_SERVER['REQUEST_URI'] = 'localhost'; 49 | } 50 | 51 | /** 52 | * 加载Think代码 53 | * @param Closure $think 54 | * @return ThinkSupport 55 | */ 56 | public function invokeThink($think) 57 | { 58 | ob_start(); 59 | $think(); 60 | ob_get_clean(); 61 | return $this; 62 | } 63 | 64 | /** 65 | * 加载你的代码 66 | * @param Closure $code 67 | */ 68 | public function invokeYourCode($code) 69 | { 70 | //恢复Cli_Input.(方便自己扩展) 71 | $_SERVER['argv'] = $this->argv; 72 | $_SERVER['argc'] = $this->argc; 73 | 74 | //执行 75 | $code($this->action, $this->force); 76 | } 77 | } 78 | 79 | /** 80 | * Code start 81 | */ 82 | (new ThinkSupport()) 83 | ->invokeThink(function () { 84 | //加载tp的代码 85 | require './index.php'; 86 | }) 87 | ->invokeYourCode(function ($action, $force) { 88 | // 加载Composer 89 | require './vendor/autoload.php'; 90 | 91 | // $action值有start|status|stop 92 | 93 | // 编写你的代码 94 | }); 95 | 96 | /** 97 | * How to run ? 98 | * Use cmd or powerShell: 99 | * php ./index.php start|status|stop 100 | */ -------------------------------------------------------------------------------- /src/Check.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'json', 18 | 'curl', 19 | 'com_dotnet', 20 | 'mbstring', 21 | ], 22 | //Linux 23 | '2' => [ 24 | 'json', 25 | 'curl', 26 | 'pcntl', 27 | 'posix', 28 | 'mbstring', 29 | ] 30 | ]; 31 | 32 | /** 33 | * 待检查函数列表 34 | * @var array 35 | */ 36 | private static $waitFunctions = [ 37 | //Win 38 | '1' => [ 39 | 'umask', 40 | 'sleep', 41 | 'usleep', 42 | 'ob_start', 43 | 'ob_end_clean', 44 | 'ob_get_contents', 45 | ], 46 | //Linux 47 | '2' => [ 48 | 'umask', 49 | 'chdir', 50 | 'sleep', 51 | 'usleep', 52 | 'ob_start', 53 | 'ob_end_clean', 54 | 'ob_get_contents', 55 | 'pcntl_fork', 56 | 'posix_setsid', 57 | 'posix_getpid', 58 | 'posix_getppid', 59 | 'pcntl_wait', 60 | 'posix_kill', 61 | 'pcntl_signal', 62 | 'pcntl_alarm', 63 | 'pcntl_waitpid', 64 | 'pcntl_signal_dispatch', 65 | ] 66 | ]; 67 | 68 | /** 69 | * 解析运行环境 70 | * @param int $currentOs 71 | */ 72 | public static function analysis($currentOs) 73 | { 74 | //检查扩展 75 | $waitExtends = static::$waitExtends[$currentOs]; 76 | foreach ($waitExtends as $extend) 77 | { 78 | if (!extension_loaded($extend)) 79 | { 80 | Helper::showSysError("php_{$extend}.(dll/so) is not load,please check php.ini file"); 81 | } 82 | } 83 | //检查函数 84 | $waitFunctions = static::$waitFunctions[$currentOs]; 85 | foreach ($waitFunctions as $func) 86 | { 87 | if (!function_exists($func)) 88 | { 89 | Helper::showSysError("function $func may be disabled,please check disable_functions in php.ini"); 90 | } 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | initMsgFile(); 24 | } 25 | 26 | /** 27 | * 初始化文件 28 | */ 29 | private function initMsgFile() 30 | { 31 | //创建文件 32 | $path = Helper::getCsgPath(); 33 | $file = $path . '%s.csg'; 34 | $this->msgFile = sprintf($file, md5(__FILE__)); 35 | if (!file_exists($this->msgFile)) 36 | { 37 | if (!file_put_contents($this->msgFile, '[]', LOCK_EX)) 38 | { 39 | Helper::showError('failed to create msgFile'); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * 获取数据 46 | * @return array 47 | * @throws 48 | */ 49 | public function get() 50 | { 51 | $content = @file_get_contents($this->msgFile); 52 | if (!$content) 53 | { 54 | return []; 55 | } 56 | $data = json_decode($content, true); 57 | return is_array($data) ? $data : []; 58 | } 59 | 60 | /** 61 | * 重置数据 62 | * @param array $data 63 | */ 64 | public function set($data) 65 | { 66 | file_put_contents($this->msgFile, json_encode($data), LOCK_EX); 67 | } 68 | 69 | /** 70 | * 投递数据 71 | * @param array $command 72 | */ 73 | public function push($command) 74 | { 75 | $data = $this->get(); 76 | array_push($data, $command); 77 | $this->set($data); 78 | } 79 | 80 | /** 81 | * 发送命令 82 | * @param array $command 83 | */ 84 | public function send($command) 85 | { 86 | $command['time'] = time(); 87 | $this->push($command); 88 | } 89 | 90 | /** 91 | * 接收命令 92 | * @param string $msgType 消息类型 93 | * @param mixed $command 收到的命令 94 | */ 95 | public function receive($msgType, &$command) 96 | { 97 | $data = $this->get(); 98 | if (empty($data)) { 99 | return; 100 | } 101 | foreach ($data as $key => $item) 102 | { 103 | if ($item['msgType'] == $msgType) 104 | { 105 | $command = $item; 106 | unset($data[$key]); 107 | break; 108 | } 109 | } 110 | $this->set($data); 111 | } 112 | 113 | /** 114 | * 根据命令执行对应操作 115 | * @param int $msgType 消息类型 116 | * @param Closure $func 执行函数 117 | * @param int $time 等待方时间戳 118 | */ 119 | public function waitCommandForExecute($msgType, $func, $time) 120 | { 121 | $command = ''; 122 | $this->receive($msgType, $command); 123 | if (!$command || (!empty($command['time']) && $command['time'] < $time)) 124 | { 125 | return; 126 | } 127 | $func($command); 128 | } 129 | } -------------------------------------------------------------------------------- /src/Env.php: -------------------------------------------------------------------------------- 1 | $exception->getMessage(), 103 | 'errFile' => $exception->getFile(), 104 | 'errLine' => $exception->getLine(), 105 | ]; 106 | $result = Helper::curl($notify, $request); 107 | if (!$result || $result != 'success') 108 | { 109 | Helper::showError("request http api $notify failed", false, 'warring', true); 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/Exception/ErrorException.php: -------------------------------------------------------------------------------- 1 | line = $errLine; 27 | $this->file = $errFile; 28 | $this->code = 0; 29 | $this->message = $errStr; 30 | $this->severity = $severity; 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/Helper.php: -------------------------------------------------------------------------------- 1 | $value) { 76 | if (file_exists($value)) { 77 | $argv[$key] = realpath($value); 78 | } 79 | } 80 | 81 | //返回 82 | if ($type == 1) { 83 | return join(' ', $argv); 84 | } 85 | return $argv; 86 | } 87 | 88 | /** 89 | * 设置PHP二进制文件 90 | * @param string $path 91 | */ 92 | public static function setPhpPath($path = '') 93 | { 94 | if (!$path) $path = self::getBinary();; 95 | Env::set('phpPath', $path); 96 | } 97 | 98 | /** 99 | * 获取进程二进制文件 100 | * @return string 101 | */ 102 | public static function getBinary() 103 | { 104 | return PHP_BINARY; 105 | } 106 | 107 | /** 108 | * 是否Win平台 109 | * @return bool 110 | */ 111 | public static function isWin() 112 | { 113 | return (DIRECTORY_SEPARATOR == '\\') ? true : false; 114 | } 115 | 116 | /** 117 | * 开启异步信号 118 | * @return bool 119 | */ 120 | public static function openAsyncSignal() 121 | { 122 | return pcntl_async_signals(true); 123 | } 124 | 125 | /** 126 | * 是否支持异步信号 127 | * @return bool 128 | */ 129 | public static function canUseAsyncSignal() 130 | { 131 | return (function_exists('pcntl_async_signals')); 132 | } 133 | 134 | /** 135 | * 是否支持event事件 136 | * @return bool 137 | */ 138 | public static function canUseEvent() 139 | { 140 | return (extension_loaded('event')); 141 | } 142 | 143 | /** 144 | * 是否可执行命令 145 | * @return bool 146 | */ 147 | public static function canUseExcCommand() 148 | { 149 | return function_exists('shell_exec'); 150 | } 151 | 152 | /** 153 | * 获取运行时目录 154 | * @return string 155 | */ 156 | public static function getRunTimePath() 157 | { 158 | $path = Env::get('runTimePath') ? Env::get('runTimePath') : sys_get_temp_dir(); 159 | if (!is_dir($path)) { 160 | static::showSysError('please set runTimePath'); 161 | } 162 | $path = $path . DIRECTORY_SEPARATOR . Env::get('prefix') . DIRECTORY_SEPARATOR; 163 | $path = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $path); 164 | return $path; 165 | } 166 | 167 | /** 168 | * 获取Win进程目录 169 | * @return string 170 | */ 171 | public static function getWinPath() 172 | { 173 | return Helper::getRunTimePath() . 'Win' . DIRECTORY_SEPARATOR; 174 | } 175 | 176 | /** 177 | * 获取日志目录 178 | * @return string 179 | */ 180 | public static function getLogPath() 181 | { 182 | return Helper::getRunTimePath() . 'Log' . DIRECTORY_SEPARATOR; 183 | } 184 | 185 | /** 186 | * 获取进程命令通信目录 187 | * @return string 188 | */ 189 | public static function getCsgPath() 190 | { 191 | return Helper::getRunTimePath() . 'Csg' . DIRECTORY_SEPARATOR; 192 | } 193 | 194 | /** 195 | * 获取进程队列目录 196 | * @return string 197 | */ 198 | public static function getQuePath() 199 | { 200 | return Helper::getRunTimePath() . 'Que' . DIRECTORY_SEPARATOR; 201 | } 202 | 203 | /** 204 | * 获取进程锁目录 205 | * @return string 206 | */ 207 | public static function getLokPath() 208 | { 209 | return Helper::getRunTimePath() . 'Lok' . DIRECTORY_SEPARATOR; 210 | } 211 | 212 | /** 213 | * 获取标准输入输出目录 214 | * @return string 215 | */ 216 | public static function getStdPath() 217 | { 218 | return Helper::getRunTimePath() . 'Std' . DIRECTORY_SEPARATOR; 219 | } 220 | 221 | /** 222 | * 初始化所有目录 223 | */ 224 | public static function initAllPath() 225 | { 226 | $paths = [ 227 | static::getRunTimePath(), 228 | static::getWinPath(), 229 | static::getLogPath(), 230 | static::getLokPath(), 231 | static::getQuePath(), 232 | static::getCsgPath(), 233 | static::getStdPath(), 234 | ]; 235 | foreach ($paths as $path) { 236 | if (!is_dir($path)) { 237 | mkdir($path, 0777, true); 238 | } 239 | } 240 | } 241 | 242 | /** 243 | * 保存标准输入|输出 244 | * @param string $char 输入|输出 245 | */ 246 | public static function saveStdChar($char) 247 | { 248 | $path = static::getStdPath(); 249 | $file = $path . date('Y_m_d') . '.std'; 250 | $char = static::convert_char($char); 251 | file_put_contents($file, $char, FILE_APPEND); 252 | } 253 | 254 | /** 255 | * 保存日志 256 | * @param string $message 257 | */ 258 | public static function writeLog($message) 259 | { 260 | //日志文件 261 | $path = Helper::getLogPath(); 262 | $file = $path . date('Y_m_d') . '.log'; 263 | 264 | //加锁保存 265 | $message = static::convert_char($message); 266 | file_put_contents($file, $message, FILE_APPEND | LOCK_EX); 267 | } 268 | 269 | /** 270 | * 保存类型日志 271 | * @param string $message 272 | * @param string $type 273 | * @param bool $isExit 274 | */ 275 | public static function writeTypeLog($message, $type = 'info', $isExit = false) 276 | { 277 | //格式化信息 278 | $text = Helper::formatMessage($message, $type); 279 | 280 | //记录日志 281 | static::writeLog($text); 282 | if ($isExit) exit(); 283 | } 284 | 285 | /** 286 | * 编码转换 287 | * @param string $char 288 | * @param string $coding 289 | * @return string 290 | */ 291 | public static function convert_char($char, $coding = 'UTF-8') 292 | { 293 | $encode_arr = ['UTF-8', 'ASCII', 'GBK', 'GB2312', 'BIG5', 'JIS', 'eucjp-win', 'sjis-win', 'EUC-JP']; 294 | $encoded = mb_detect_encoding($char, $encode_arr); 295 | if ($encoded) { 296 | $char = mb_convert_encoding($char, $coding, $encoded); 297 | } 298 | return $char; 299 | } 300 | 301 | /** 302 | * 格式化异常信息 303 | * @param ErrorException|Exception|Throwable $exception 304 | * @param string $type 305 | * @return string 306 | */ 307 | public static function formatException($exception, $type = 'exception') 308 | { 309 | //参数 310 | $pid = getmypid(); 311 | $date = date('Y/m/d H:i:s', time()); 312 | 313 | //组装 314 | return $date . " [$type] : errStr:" . $exception->getMessage() . ',errFile:' . $exception->getFile() . ',errLine:' . $exception->getLine() . " (pid:$pid)" . PHP_EOL; 315 | } 316 | 317 | /** 318 | * 格式化异常信息 319 | * @param string $message 320 | * @param string $type 321 | * @return string 322 | */ 323 | public static function formatMessage($message, $type = 'error') 324 | { 325 | //参数 326 | $pid = getmypid(); 327 | $date = date('Y/m/d H:i:s', time()); 328 | 329 | //组装 330 | return $date . " [$type] : " . $message . " (pid:$pid)" . PHP_EOL; 331 | } 332 | 333 | /** 334 | * 检查任务时间是否合法 335 | * @param mixed $time 336 | */ 337 | public static function checkTaskTime($time) 338 | { 339 | if (is_int($time)) { 340 | if ($time < 0) static::showSysError('time must be greater than or equal to 0'); 341 | } elseif (is_float($time)) { 342 | if (!static::canUseEvent()) static::showSysError('please install php_event.(dll/so) extend for using milliseconds'); 343 | } else { 344 | static::showSysError('time parameter is an unsupported type'); 345 | } 346 | } 347 | 348 | /** 349 | * 输出字符串 350 | * @param string $char 351 | * @param bool $exit 352 | */ 353 | public static function output($char, $exit = false) 354 | { 355 | echo $char; 356 | if ($exit) exit(); 357 | } 358 | 359 | /** 360 | * 输出信息 361 | * @param string $message 362 | * @param bool $isExit 363 | * @param string $type 364 | * @throws 365 | */ 366 | public static function showInfo($message, $isExit = false, $type = 'info') 367 | { 368 | //格式化信息 369 | $text = static::formatMessage($message, $type); 370 | 371 | //记录日志 372 | static::writeLog($text); 373 | 374 | //输出信息 375 | static::output($text, $isExit); 376 | } 377 | 378 | /** 379 | * 输出错误 380 | * @param string $errStr 381 | * @param bool $isExit 382 | * @param string $type 383 | * @param bool $log 384 | * @throws 385 | */ 386 | public static function showError($errStr, $isExit = true, $type = 'error', $log = true) 387 | { 388 | //格式化信息 389 | $text = static::formatMessage($errStr, $type); 390 | 391 | //记录日志 392 | if ($log) static::writeLog($text); 393 | 394 | //输出信息 395 | static::output($text, $isExit); 396 | } 397 | 398 | /** 399 | * 输出系统错误 400 | * @param string $errStr 401 | * @param bool $isExit 402 | * @param string $type 403 | * @throws 404 | */ 405 | public static function showSysError($errStr, $isExit = true, $type = 'warring') 406 | { 407 | //格式化信息 408 | $text = static::formatMessage($errStr, $type); 409 | 410 | //输出信息 411 | static::output($text, $isExit); 412 | } 413 | 414 | /** 415 | * 输出异常 416 | * @param mixed $exception 417 | * @param string $type 418 | * @param bool $isExit 419 | * @throws 420 | */ 421 | public static function showException($exception, $type = 'exception', $isExit = true) 422 | { 423 | //格式化信息 424 | $text = static::formatException($exception, $type); 425 | 426 | //记录日志 427 | Helper::writeLog($text); 428 | 429 | //输出信息 430 | static::output($text, $isExit); 431 | } 432 | 433 | /** 434 | * 控制台输出表格 435 | * @param array $data 436 | * @param boolean $exit 437 | */ 438 | public static function showTable($data, $exit = true) 439 | { 440 | //提取表头 441 | $header = array_keys($data['0']); 442 | 443 | //组装数据 444 | foreach ($data as $key => $row) { 445 | $data[$key] = array_values($row); 446 | } 447 | 448 | //输出表格 449 | $table = new Table(); 450 | $table->setHeader($header); 451 | $table->setStyle('box'); 452 | $table->setRows($data); 453 | $render = static::convert_char($table->render()); 454 | if ($exit) { 455 | exit($render); 456 | } 457 | echo($render); 458 | } 459 | 460 | /** 461 | * 通过Curl方式提交数据 462 | * 463 | * @param string $url 目标URL 464 | * @param null $data 提交的数据 465 | * @param bool $return_array 是否转成数组 466 | * @param null $header 请求头信息 如:array("Content-Type: application/json") 467 | * 468 | * @return array|mixed 469 | */ 470 | public static function curl($url, $data = null, $return_array = false, $header = null) 471 | { 472 | //初始化curl 473 | $curl = curl_init(); 474 | 475 | //设置超时 476 | curl_setopt($curl, CURLOPT_TIMEOUT, 30); 477 | curl_setopt($curl, CURLOPT_URL, $url); 478 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 479 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); 480 | if (is_array($header)) { 481 | curl_setopt($curl, CURLOPT_HTTPHEADER, $header); 482 | } 483 | if ($data) { 484 | curl_setopt($curl, CURLOPT_POST, true); 485 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 486 | } 487 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 488 | 489 | //运行curl,获取结果 490 | $result = @curl_exec($curl); 491 | 492 | //关闭句柄 493 | curl_close($curl); 494 | 495 | //转成数组 496 | if ($return_array) { 497 | return json_decode($result, true); 498 | } 499 | 500 | //返回结果 501 | return $result; 502 | } 503 | } -------------------------------------------------------------------------------- /src/Lock.php: -------------------------------------------------------------------------------- 1 | file = $path . md5($name); 27 | if (!file_exists($this->file)) 28 | { 29 | @file_put_contents($this->file, ''); 30 | } 31 | } 32 | 33 | /** 34 | * 加锁执行 35 | * @param Closure $func 36 | * @param bool $block 37 | * @return mixed 38 | */ 39 | public function execute($func, $block = true) 40 | { 41 | $fp = fopen($this->file, 'r'); 42 | $is_flock = $block ? flock($fp, LOCK_EX) : flock($fp, LOCK_EX | LOCK_NB); 43 | $call_back = null; 44 | if ($is_flock) 45 | { 46 | $call_back = $func(); 47 | flock($fp, LOCK_UN); 48 | } 49 | fclose($fp); 50 | return $call_back; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Process/Linux.php: -------------------------------------------------------------------------------- 1 | commander->send([ 41 | 'type' => 'start', 42 | 'msgType' => 2 43 | ]); 44 | 45 | //异步处理 46 | if (Env::get('daemon')) 47 | { 48 | Helper::setMask(); 49 | $this->fork( 50 | function () { 51 | $sid = posix_setsid(); 52 | if ($sid < 0) 53 | { 54 | Helper::showError('set child processForManager failed,please try again'); 55 | } 56 | $this->allocate(); 57 | }, 58 | function () { 59 | pcntl_wait($status, WNOHANG); 60 | $this->status(); 61 | } 62 | ); 63 | } 64 | 65 | //同步处理 66 | $this->allocate(); 67 | } 68 | 69 | /** 70 | * 分配进程处理任务 71 | */ 72 | protected function allocate() 73 | { 74 | foreach ($this->taskList as $item) 75 | { 76 | //提取参数 77 | $prefix = Env::get('prefix'); 78 | $item['data'] = date('Y-m-d H:i:s'); 79 | $item['alas'] = "{$prefix}_{$item['alas']}"; 80 | $used = $item['used']; 81 | 82 | //根据Worker数分配进程 83 | for ($i = 0; $i < $used; $i++) 84 | { 85 | $this->forkItemExec($item); 86 | } 87 | } 88 | 89 | //常驻守护 90 | $this->daemonWait(); 91 | } 92 | 93 | /** 94 | * 创建子进程 95 | * @param Closure $childInvoke 96 | * @param Closure $mainInvoke 97 | */ 98 | protected function fork($childInvoke, $mainInvoke) 99 | { 100 | $pid = pcntl_fork(); 101 | if ($pid == -1) 102 | { 103 | Helper::showError('fork child process failed,please try again'); 104 | } 105 | elseif ($pid) 106 | { 107 | $mainInvoke($pid); 108 | } 109 | else 110 | { 111 | $childInvoke(); 112 | } 113 | } 114 | 115 | /** 116 | * 创建任务执行的子进程 117 | * @param array $item 118 | */ 119 | protected function forkItemExec($item) 120 | { 121 | $this->fork( 122 | function () use ($item) { 123 | $this->invoker($item); 124 | }, 125 | function ($pid) use ($item) { 126 | //write_log 127 | $ppid = posix_getpid(); 128 | $this->processList[] = ['pid' => $pid, 'name' => $item['alas'], 'item' => $item, 'started' => $item['data'], 'time' => $item['time'], 'status' => 'active', 'ppid' => $ppid]; 129 | //set not block 130 | pcntl_wait($status, WNOHANG); 131 | } 132 | ); 133 | } 134 | 135 | /** 136 | * 执行器 137 | * @param array $item 138 | * @throws Throwable 139 | */ 140 | protected function invoker($item) 141 | { 142 | //输出信息 143 | $item['ppid'] = posix_getppid(); 144 | $text = "this worker {$item['alas']}"; 145 | Helper::writeTypeLog("$text is start"); 146 | 147 | //进程标题 148 | Helper::cli_set_process_title($item['alas']); 149 | 150 | //Kill信号 151 | pcntl_signal(SIGTERM, function () use ($text) { 152 | Helper::writeTypeLog("listened kill command, $text not to exit the program for safety"); 153 | }); 154 | 155 | //执行任务 156 | $this->executeInvoker($item); 157 | } 158 | 159 | /** 160 | * 通过闹钟信号执行 161 | * @param array $item 162 | */ 163 | protected function invokeByDefault($item) 164 | { 165 | //安装信号管理 166 | pcntl_signal(SIGALRM, function () use ($item) { 167 | pcntl_alarm($item['time']); 168 | $this->execute($item); 169 | }, false); 170 | 171 | //发送闹钟信号 172 | pcntl_alarm($item['time']); 173 | 174 | //挂起进程(同步调用信号,异步CPU休息) 175 | while (true) 176 | { 177 | //CPU休息 178 | Helper::sleep(1); 179 | 180 | //信号处理(同步/异步) 181 | if (!Env::get('canAsync')) pcntl_signal_dispatch(); 182 | } 183 | } 184 | 185 | /** 186 | * 检查常驻进程是否存活 187 | * @param array $item 188 | */ 189 | protected function checkDaemonForExit($item) 190 | { 191 | if (!posix_kill($item['ppid'], 0)) 192 | { 193 | Helper::writeTypeLog("listened exit command, this worker {$item['alas']} is exiting safely", 'info', true); 194 | } 195 | } 196 | 197 | /** 198 | * 守护进程常驻 199 | */ 200 | protected function daemonWait() 201 | { 202 | //设置进程标题 203 | Helper::cli_set_process_title(Env::get('prefix')); 204 | 205 | //输出信息 206 | $text = "this manager"; 207 | Helper::writeTypeLog("$text is start"); 208 | if (!Env::get('daemon')) 209 | { 210 | Helper::showTable($this->processStatus(), false); 211 | Helper::showInfo('start success,press ctrl+c to stop'); 212 | } 213 | 214 | //Kill信号 215 | pcntl_signal(SIGTERM, function () use ($text) { 216 | Helper::writeTypeLog("listened kill command $text is exiting safely", 'info', true); 217 | }); 218 | 219 | //挂起进程 220 | while (true) 221 | { 222 | //CPU休息 223 | Helper::sleep(1); 224 | 225 | //接收命令start/status/stop 226 | $this->commander->waitCommandForExecute(2, function ($command) use ($text) { 227 | $exitText = "listened exit command, $text is exiting safely"; 228 | $statusText = "listened status command, $text is reported"; 229 | $forceExitText = "listened exit command, $text is exiting unsafely"; 230 | if ($command['type'] == 'start') 231 | { 232 | if ($command['time'] > $this->startTime) 233 | { 234 | Helper::writeTypeLog($forceExitText); 235 | posix_kill(0, SIGKILL); 236 | } 237 | } 238 | if ($command['type'] == 'status') 239 | { 240 | $report = $this->processStatus(); 241 | $this->commander->send([ 242 | 'type' => 'status', 243 | 'msgType' => 1, 244 | 'status' => $report, 245 | ]); 246 | Helper::writeTypeLog($statusText); 247 | } 248 | if ($command['type'] == 'stop') 249 | { 250 | if ($command['force']) 251 | { 252 | Helper::writeTypeLog($forceExitText); 253 | posix_kill(0, SIGKILL); 254 | } 255 | else 256 | { 257 | Helper::writeTypeLog($exitText); 258 | exit(); 259 | } 260 | } 261 | 262 | }, $this->startTime); 263 | 264 | //信号调度 265 | if (!Env::get('canAsync')) pcntl_signal_dispatch(); 266 | 267 | //检查进程 268 | if (Env::get('canAutoRec')) $this->processStatus(); 269 | } 270 | } 271 | 272 | /** 273 | * 查看进程状态 274 | * @return array 275 | */ 276 | protected function processStatus() 277 | { 278 | $report = []; 279 | foreach ($this->processList as $key => $item) 280 | { 281 | //提取参数 282 | $pid = $item['pid']; 283 | 284 | //进程状态 285 | $rel = pcntl_waitpid($pid, $status, WNOHANG); 286 | if ($rel == -1 || $rel > 0) 287 | { 288 | //标记状态 289 | $item['status'] = 'stop'; 290 | 291 | //进程退出,重新fork 292 | if (Env::get('canAutoRec')) 293 | { 294 | $this->forkItemExec($item['item']); 295 | Helper::writeTypeLog("the worker {$item['name']}(pid:{$pid}) is stop,try to fork a new one"); 296 | unset($this->processList[$key]); 297 | } 298 | } 299 | 300 | //记录状态 301 | unset($item['item']); 302 | $report[] = $item; 303 | } 304 | 305 | return $report; 306 | } 307 | } -------------------------------------------------------------------------------- /src/Process/Process.php: -------------------------------------------------------------------------------- 1 | startTime = time(); 52 | $this->taskList = $taskList; 53 | $this->setTaskCount(); 54 | $this->commander = new Command(); 55 | } 56 | 57 | /** 58 | * 开始运行 59 | */ 60 | abstract public function start(); 61 | 62 | /** 63 | * 运行状态 64 | */ 65 | public function status() 66 | { 67 | //发送命令 68 | $this->commander->send([ 69 | 'type' => 'status', 70 | 'msgType' => 2 71 | ]); 72 | $this->masterWaitExit(); 73 | } 74 | 75 | /** 76 | * 停止运行 77 | * @param bool $force 是否强制 78 | */ 79 | public function stop($force = false) 80 | { 81 | //发送命令 82 | $force = $force ?: true; 83 | $this->commander->send([ 84 | 'type' => 'stop', 85 | 'force' => $force, 86 | 'msgType' => 2 87 | ]); 88 | } 89 | 90 | /** 91 | * 初始化任务数量 92 | */ 93 | protected function setTaskCount() 94 | { 95 | $count = 0; 96 | foreach ($this->taskList as $key => $item) { 97 | $count += (int)$item['used']; 98 | } 99 | $this->taskCount = $count; 100 | } 101 | 102 | /** 103 | * 检查是否可写标准输出日志 104 | * @return bool 105 | */ 106 | protected function canWriteStd() 107 | { 108 | return Env::get('daemon') && !Env::get('closeStdOutLog'); 109 | } 110 | 111 | /** 112 | * 执行任务代码 113 | * @param array $item 114 | * @throws 115 | */ 116 | protected function execute($item) 117 | { 118 | //根据任务类型执行 119 | $daemon = Env::get('daemon'); 120 | 121 | //Std_Start 122 | if ($this->canWriteStd()) ob_start(); 123 | try { 124 | $type = $item['type']; 125 | switch ($type) { 126 | case 1: 127 | $func = $item['func']; 128 | $func(); 129 | break; 130 | case 2: 131 | call_user_func([$item['class'], $item['func']]); 132 | break; 133 | case 3: 134 | $object = new $item['class'](); 135 | call_user_func([$object, $item['func']]); 136 | break; 137 | default: 138 | $result = shell_exec($item['command']); 139 | if ($result) { 140 | echo $result . PHP_EOL; 141 | Helper::output($result); 142 | } 143 | if ($result === false) { 144 | $errorResult = 'failed to execute ' . $item['alas'] . ' task' . PHP_EOL; 145 | Helper::output($errorResult); 146 | } 147 | } 148 | 149 | } catch (Exception $exception) { 150 | if (Helper::isWin()) { 151 | Helper::showException($exception, 'exception', !$daemon); 152 | } else { 153 | if (!$daemon) throw $exception; 154 | Helper::writeLog(Helper::formatException($exception)); 155 | } 156 | } catch (Throwable $exception) { 157 | if (Helper::isWin()) { 158 | Helper::showException($exception, 'exception', !$daemon); 159 | } else { 160 | if (!$daemon) throw $exception; 161 | Helper::writeLog(Helper::formatException($exception)); 162 | } 163 | } 164 | 165 | //Std_End 166 | if ($this->canWriteStd()) { 167 | $stdChar = ob_get_contents(); 168 | if ($stdChar) Helper::saveStdChar($stdChar); 169 | ob_end_clean(); 170 | } 171 | 172 | //检查常驻进程存活 173 | $this->checkDaemonForExit($item); 174 | } 175 | 176 | /** 177 | * 执行任务 178 | * @param array $item 179 | * @throws Throwable 180 | */ 181 | protected function executeInvoker($item) 182 | { 183 | if ($item['time'] === 0) { 184 | $this->invokerByDirect($item); 185 | } else { 186 | Env::get('canEvent') ? $this->invokeByEvent($item) : $this->invokeByDefault($item); 187 | } 188 | } 189 | 190 | /** 191 | * 通过Event事件执行 192 | * @param array $item 193 | */ 194 | protected function invokeByEvent($item) 195 | { 196 | //创建Event事件 197 | $eventConfig = new EventConfig(); 198 | $eventBase = new EventBase($eventConfig); 199 | $event = new Event($eventBase, -1, Event::TIMEOUT | Event::PERSIST, function () use ($item) { 200 | try { 201 | $this->execute($item); 202 | } catch (Throwable $exception) { 203 | $type = 'exception'; 204 | Error::report($type, $exception); 205 | $this->checkDaemonForExit($item); 206 | } 207 | }); 208 | 209 | //添加事件 210 | $event->add($item['time']); 211 | 212 | //事件循环 213 | $eventBase->loop(); 214 | } 215 | 216 | /** 217 | * 普通执行 218 | * @param array $item 219 | * @throws Throwable 220 | */ 221 | protected function invokerByDirect($item) 222 | { 223 | $this->execute($item); 224 | exit; 225 | } 226 | 227 | /** 228 | * 主进程等待结束退出 229 | */ 230 | protected function masterWaitExit() 231 | { 232 | $i = $this->taskCount + 30; 233 | while ($i--) { 234 | //接收汇报 235 | $this->commander->waitCommandForExecute(1, function ($report) { 236 | if ($report['type'] == 'status' && $report['status']) { 237 | Helper::showTable($report['status']); 238 | } 239 | }, $this->startTime); 240 | 241 | //CPU休息 242 | Helper::sleep(1); 243 | } 244 | Helper::showInfo('this cpu is too busy,please use status command try again'); 245 | exit; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Process/Win.php: -------------------------------------------------------------------------------- 1 | wts = new Wts(); 48 | parent::__construct($taskList); 49 | } 50 | 51 | /** 52 | * 开始运行 53 | */ 54 | public function start() 55 | { 56 | //构建基础 57 | $this->make(); 58 | 59 | //启动检查 60 | $this->checkForRun(); 61 | 62 | //进程分配 63 | $func = function ($name) { 64 | $this->executeByProcessName($name); 65 | }; 66 | if (!$this->wts->allocateProcess($func)) 67 | { 68 | Helper::showError('unexpected error, process has been allocated'); 69 | } 70 | } 71 | 72 | /** 73 | * 启动检查 74 | */ 75 | protected function checkForRun() 76 | { 77 | if (!Env::get('phpPath')) 78 | { 79 | Helper::showError('please use setPhpPath api to set phpPath'); 80 | } 81 | if (!$this->chkCanStart()) 82 | { 83 | Helper::showError('please close the running process first'); 84 | } 85 | } 86 | 87 | /** 88 | * 检查进程 89 | * @return bool 90 | */ 91 | protected function chkCanStart() 92 | { 93 | $workerList = $this->workerList; 94 | foreach ($workerList as $name => $item) 95 | { 96 | $status = $this->wts->getProcessStatus($name); 97 | if (!$status) 98 | { 99 | return true; 100 | } 101 | } 102 | return false; 103 | } 104 | 105 | /** 106 | * 跟进进程名称执行任务 107 | * @param string $name 108 | * @throws Exception|Throwable 109 | */ 110 | protected function executeByProcessName($name) 111 | { 112 | switch ($name) 113 | { 114 | case 'master': 115 | $this->master(); 116 | break; 117 | case 'manager': 118 | $this->manager(); 119 | break; 120 | default: 121 | $this->invoker($name); 122 | } 123 | } 124 | 125 | /** 126 | * 构建任务 127 | */ 128 | protected function make() 129 | { 130 | $list = []; 131 | if (!$this->wts->getProcessStatus('manager')) 132 | { 133 | $list = ['master', 'manager']; 134 | } 135 | foreach ($list as $name) 136 | { 137 | $this->wts->joinProcess($name); 138 | } 139 | foreach ($this->taskList as $key => $item) 140 | { 141 | //提取参数 142 | $alas = $item['alas']; 143 | $used = $item['used']; 144 | 145 | //根据Worker数构建 146 | for ($i = 0; $i < $used; $i++) 147 | { 148 | $name = $item['name'] = $alas . '___' . $i; 149 | $this->workerList[$name] = $item; 150 | $this->wts->joinProcess($name); 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * 主进程 157 | * @throws Exception 158 | */ 159 | protected function master() 160 | { 161 | //创建常驻进程 162 | $this->forkItemExec(); 163 | 164 | //查询状态 165 | $i = $this->taskCount + 15; 166 | while ($i--) 167 | { 168 | $status = $this->wts->getProcessStatus('manager'); 169 | if ($status) 170 | { 171 | $this->status(); 172 | break; 173 | } 174 | Helper::sleep(1); 175 | } 176 | } 177 | 178 | /** 179 | * 常驻进程 180 | */ 181 | protected function manager() 182 | { 183 | //分配子进程 184 | $this->allocate(); 185 | 186 | //后台常驻运行 187 | $this->daemonWait(); 188 | } 189 | 190 | /** 191 | * 分配子进程 192 | */ 193 | protected function allocate() 194 | { 195 | //清理进程信息 196 | $this->wts->cleanProcessInfo(); 197 | 198 | foreach ($this->taskList as $key => $item) 199 | { 200 | //提取参数 201 | $used = $item['used']; 202 | 203 | //根据Worker数创建子进程 204 | for ($i = 0; $i < $used; $i++) 205 | { 206 | $this->joinWpcContainer($this->forkItemExec()); 207 | } 208 | } 209 | } 210 | 211 | /** 212 | * 注册实体进程 213 | * @param Wpc $wpc 214 | */ 215 | protected function joinWpcContainer($wpc) 216 | { 217 | $this->wpcContainer[] = $wpc; 218 | foreach ($this->wpcContainer as $key => $wpc) 219 | { 220 | if ($wpc->hasExited()) 221 | { 222 | unset($this->wpcContainer[$key]); 223 | } 224 | } 225 | } 226 | 227 | /** 228 | * 创建任务执行子进程 229 | * @return Wpc 230 | */ 231 | protected function forkItemExec() 232 | { 233 | $wpc = null; 234 | try 235 | { 236 | //提取参数 237 | $argv = Helper::getCliInput(2); 238 | $file = array_shift($argv);; 239 | $char = join(' ', $argv); 240 | $work = dirname(array_shift($argv)); 241 | $style = Env::get('daemon') ? 1 : 0; 242 | 243 | //创建进程 244 | $wpc = new Wpc(); 245 | $wpc->setFile($file); 246 | $wpc->setArgument($char); 247 | $wpc->setStyle($style); 248 | $wpc->setWorkDir($work); 249 | $pid = $wpc->start(); 250 | if (!$pid) Helper::showError('create process failed,please try again', true); 251 | } 252 | catch (Exception $exception) 253 | { 254 | Helper::showError(Helper::convert_char($exception->getMessage()), true); 255 | } 256 | 257 | return $wpc; 258 | } 259 | 260 | /** 261 | * 执行器 262 | * @param string $name 任务名称 263 | * @throws Throwable 264 | */ 265 | protected function invoker($name) 266 | { 267 | //提取字典 268 | $taskDict = $this->workerList; 269 | if (!isset($taskDict[$name])) 270 | { 271 | Helper::showError("the task name $name is not exist" . json_encode($taskDict)); 272 | } 273 | 274 | //提取Task字典 275 | $item = $taskDict[$name]; 276 | 277 | //输出信息 278 | $pid = getmypid(); 279 | $title = Env::get('prefix') . '_' . $item['alas']; 280 | Helper::showInfo("this worker $title is start"); 281 | 282 | //设置进程标题 283 | Helper::cli_set_process_title($title); 284 | 285 | //保存进程信息 286 | $item['pid'] = $pid; 287 | $this->wts->saveProcessInfo([ 288 | 'pid' => $pid, 289 | 'name' => $item['name'], 290 | 'alas' => $item['alas'], 291 | 'started' => date('Y-m-d H:i:s', $this->startTime), 292 | 'time' => $item['time'] 293 | ]); 294 | 295 | //执行任务 296 | $this->executeInvoker($item); 297 | } 298 | 299 | /** 300 | * 通过默认定时执行 301 | * @param array $item 执行项目 302 | * @throws Throwable 303 | */ 304 | protected function invokeByDefault($item) 305 | { 306 | while (true) 307 | { 308 | //CPU休息 309 | Helper::sleep($item['time']); 310 | 311 | //执行任务 312 | $this->execute($item); 313 | } 314 | exit; 315 | } 316 | 317 | /** 318 | * 检查常驻进程是否存活 319 | * @param array $item 320 | */ 321 | protected function checkDaemonForExit($item) 322 | { 323 | //检查进程存活 324 | $status = $this->wts->getProcessStatus('manager'); 325 | if (!$status) 326 | { 327 | $text = Env::get('prefix') . '_' . $item['alas']; 328 | Helper::showInfo("listened exit command, this worker $text is exiting safely", true); 329 | } 330 | } 331 | 332 | /** 333 | * 后台常驻运行 334 | */ 335 | protected function daemonWait() 336 | { 337 | //进程标题 338 | Helper::cli_set_process_title(Env::get('prefix')); 339 | 340 | //输出信息 341 | $text = "this manager"; 342 | Helper::showInfo("$text is start");; 343 | 344 | //挂起进程 345 | while (true) 346 | { 347 | //CPU休息 348 | Helper::sleep(1); 349 | 350 | //接收命令status/stop 351 | $this->commander->waitCommandForExecute(2, function ($command) use ($text) { 352 | $commandType = $command['type']; 353 | switch ($commandType) 354 | { 355 | case 'status': 356 | $this->commander->send([ 357 | 'type' => 'status', 358 | 'msgType' => 1, 359 | 'status' => $this->getReport(), 360 | ]); 361 | Helper::showInfo("listened status command, $text is reported"); 362 | break; 363 | case 'stop': 364 | if ($command['force']) $this->stopWorkerByForce(); 365 | Helper::showInfo("listened exit command, $text is exiting safely", true); 366 | break; 367 | } 368 | }, $this->startTime); 369 | 370 | //检查进程 371 | if (Env::get('canAutoRec')) 372 | { 373 | $this->getReport(true); 374 | if ($this->autoRecEvent) 375 | { 376 | $this->autoRecEvent = false; 377 | } 378 | } 379 | } 380 | } 381 | 382 | /** 383 | * 获取报告 384 | * @param bool $output 385 | * @return array 386 | * @throws 387 | */ 388 | protected function getReport($output = false) 389 | { 390 | $report = $this->workerStatus($this->taskCount); 391 | foreach ($report as $key => $item) 392 | { 393 | if ($item['status'] == 'stop' && Env::get('canAutoRec')) 394 | { 395 | $this->joinWpcContainer($this->forkItemExec()); 396 | if ($output) 397 | { 398 | $this->autoRecEvent = true; 399 | Helper::showInfo("the worker {$item['name']}(pid:{$item['pid']}) is stop,try to fork a new one"); 400 | } 401 | } 402 | } 403 | 404 | return $report; 405 | } 406 | 407 | /** 408 | * 查看进程状态 409 | * @param int $count 410 | * @return array 411 | */ 412 | protected function workerStatus($count) 413 | { 414 | //构建报告 415 | $report = $infoData = []; 416 | $tryTotal = 10; 417 | while ($tryTotal--) 418 | { 419 | Helper::sleep(1); 420 | $infoData = $this->wts->getProcessInfo(); 421 | if ($count == count($infoData)) break; 422 | } 423 | 424 | //组装数据 425 | $pid = getmypid(); 426 | $prefix = Env::get('prefix'); 427 | foreach ($infoData as $name => $item) 428 | { 429 | $report[] = [ 430 | 'pid' => $item['pid'], 431 | 'name' => "{$prefix}_{$item['alas']}", 432 | 'started' => $item['started'], 433 | 'time' => $item['time'], 434 | 'status' => $this->wts->getProcessStatus($name) ? 'active' : 'stop', 435 | 'ppid' => $pid, 436 | ]; 437 | } 438 | 439 | return $report; 440 | } 441 | 442 | /** 443 | * 强制关闭所有进程 444 | */ 445 | protected function stopWorkerByForce() 446 | { 447 | foreach ($this->wpcContainer as $wpc) 448 | { 449 | try 450 | { 451 | $wpc->stop(2); 452 | } 453 | catch (Exception $exception) 454 | { 455 | Helper::showError(Helper::convert_char($exception->getMessage()), false); 456 | } 457 | } 458 | } 459 | } -------------------------------------------------------------------------------- /src/Queue.php: -------------------------------------------------------------------------------- 1 | lock = new Lock($name); 31 | 32 | //创建队列文件 33 | $path = Helper::getQuePath(); 34 | $file = $path . '%s.dat'; 35 | $this->queFile = sprintf($file, md5($name)); 36 | if (!file_exists($this->queFile)) 37 | { 38 | if (!file_put_contents($this->queFile, '[]', LOCK_EX)) 39 | { 40 | Helper::showError('crate queFile failed,please try again'); 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * 向队列投递数据 47 | * @param string $item 48 | */ 49 | public function push($item) 50 | { 51 | $this->lock->execute(function () use ($item) { 52 | //read 53 | $content = file_get_contents($this->queFile); 54 | $queue_data = $content ? json_decode($content, true) : []; 55 | $queue_data = is_array($queue_data) ? $queue_data : []; 56 | 57 | //write 58 | array_push($queue_data, $item); 59 | if (!file_put_contents($this->queFile, json_encode($queue_data))) 60 | { 61 | Helper::showError('failed to save data to queue file'); 62 | } 63 | }); 64 | } 65 | 66 | /** 67 | * 从队列弹出数据 68 | * @return string|null 69 | */ 70 | public function shift() 71 | { 72 | return $this->lock->execute(function () { 73 | //read 74 | $content = file_get_contents($this->queFile); 75 | $queue_data = $content ? json_decode($content, true) : []; 76 | $queue_data = is_array($queue_data) ? $queue_data : []; 77 | 78 | //shift+write 79 | $value = array_shift($queue_data); 80 | if (!file_put_contents($this->queFile, json_encode($queue_data))) 81 | { 82 | Helper::showError('failed to save data to queue file'); 83 | } 84 | return $value; 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Table.php: -------------------------------------------------------------------------------- 1 | [], 56 | 'default' => [ 57 | 'top' => ['+', '-', '+', '+'], 58 | 'cell' => ['|', ' ', '|', '|'], 59 | 'middle' => ['+', '-', '+', '+'], 60 | 'bottom' => ['+', '-', '+', '+'], 61 | 'cross-top' => ['+', '-', '-', '+'], 62 | 'cross-bottom' => ['+', '-', '-', '+'], 63 | ], 64 | 'markdown' => [ 65 | 'top' => [' ', ' ', ' ', ' '], 66 | 'cell' => ['|', ' ', '|', '|'], 67 | 'middle' => ['|', '-', '|', '|'], 68 | 'bottom' => [' ', ' ', ' ', ' '], 69 | 'cross-top' => ['|', ' ', ' ', '|'], 70 | 'cross-bottom' => ['|', ' ', ' ', '|'], 71 | ], 72 | 'borderless' => [ 73 | 'top' => ['=', '=', ' ', '='], 74 | 'cell' => [' ', ' ', ' ', ' '], 75 | 'middle' => ['=', '=', ' ', '='], 76 | 'bottom' => ['=', '=', ' ', '='], 77 | 'cross-top' => ['=', '=', ' ', '='], 78 | 'cross-bottom' => ['=', '=', ' ', '='], 79 | ], 80 | 'box' => [ 81 | 'top' => ['┌', '─', '┬', '┐'], 82 | 'cell' => ['│', ' ', '│', '│'], 83 | 'middle' => ['├', '─', '┼', '┤'], 84 | 'bottom' => ['└', '─', '┴', '┘'], 85 | 'cross-top' => ['├', '─', '┴', '┤'], 86 | 'cross-bottom' => ['├', '─', '┬', '┤'], 87 | ], 88 | 'box-double' => [ 89 | 'top' => ['╔', '═', '╤', '╗'], 90 | 'cell' => ['║', ' ', '│', '║'], 91 | 'middle' => ['╠', '─', '╪', '╣'], 92 | 'bottom' => ['╚', '═', '╧', '╝'], 93 | 'cross-top' => ['╠', '═', '╧', '╣'], 94 | 'cross-bottom' => ['╠', '═', '╤', '╣'], 95 | ], 96 | ]; 97 | 98 | /** 99 | * 设置表格头信息 以及对齐方式 100 | * @param array $header 要输出的Header信息 101 | * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER 102 | * @return void 103 | */ 104 | public function setHeader($header, $align = self::ALIGN_LEFT) 105 | { 106 | $this->header = $header; 107 | $this->headerAlign = $align; 108 | $this->checkColWidth($header); 109 | } 110 | 111 | /** 112 | * 设置输出表格数据 及对齐方式 113 | * @param array $rows 要输出的表格数据(二维数组) 114 | * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER 115 | * @return void 116 | */ 117 | public function setRows($rows, $align = self::ALIGN_LEFT) 118 | { 119 | $this->rows = $rows; 120 | $this->cellAlign = $align; 121 | 122 | foreach ($rows as $row) 123 | { 124 | $this->checkColWidth($row); 125 | } 126 | } 127 | 128 | /** 129 | * 检查列数据的显示宽度 130 | * @param mixed $row 行数据 131 | * @return void 132 | */ 133 | protected function checkColWidth($row) 134 | { 135 | if (is_array($row)) 136 | { 137 | foreach ($row as $key => $cell) 138 | { 139 | if (!isset($this->colWidth[$key]) || strlen($cell) > $this->colWidth[$key]) 140 | { 141 | $this->colWidth[$key] = strlen($cell); 142 | } 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * 增加一行表格数据 149 | * @param mixed $row 行数据 150 | * @param bool $first 是否在开头插入 151 | * @return void 152 | */ 153 | public function addRow($row, $first = false) 154 | { 155 | if ($first) 156 | { 157 | array_unshift($this->rows, $row); 158 | } 159 | else 160 | { 161 | $this->rows[] = $row; 162 | } 163 | 164 | $this->checkColWidth($row); 165 | } 166 | 167 | /** 168 | * 设置输出表格的样式 169 | * @param string $style 样式名 170 | * @return void 171 | */ 172 | public function setStyle($style) 173 | { 174 | $this->style = isset($this->format[$style]) ? $style : 'default'; 175 | } 176 | 177 | /** 178 | * 输出分隔行 179 | * @param string $pos 位置 180 | * @return string 181 | */ 182 | protected function renderSeparator($pos) 183 | { 184 | $style = $this->getStyle($pos); 185 | $array = []; 186 | 187 | foreach ($this->colWidth as $width) 188 | { 189 | $array[] = str_repeat($style[1], $width + 2); 190 | } 191 | 192 | return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL; 193 | } 194 | 195 | /** 196 | * 输出表格头部 197 | * @return string 198 | */ 199 | protected function renderHeader() 200 | { 201 | $style = $this->getStyle('cell'); 202 | $content = $this->renderSeparator('top'); 203 | 204 | foreach ($this->header as $key => $header) 205 | { 206 | $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign); 207 | } 208 | 209 | if (!empty($array)) 210 | { 211 | $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; 212 | 213 | if ($this->rows) 214 | { 215 | $content .= $this->renderSeparator('middle'); 216 | } 217 | } 218 | 219 | return $content; 220 | } 221 | 222 | /** 223 | * 获取风格 224 | * @param string $style 225 | * @return array 226 | */ 227 | protected function getStyle($style) 228 | { 229 | if ($this->format[$this->style]) 230 | { 231 | $style = $this->format[$this->style][$style]; 232 | } 233 | else 234 | { 235 | $style = [' ', ' ', ' ', ' ']; 236 | } 237 | 238 | return $style; 239 | } 240 | 241 | /** 242 | * 输出表格 243 | * @param array $dataList 表格数据 244 | * @return string 245 | */ 246 | public function render($dataList = []) 247 | { 248 | if ($dataList) 249 | { 250 | $this->setRows($dataList); 251 | } 252 | 253 | // 输出头部 254 | $content = $this->renderHeader(); 255 | $style = $this->getStyle('cell'); 256 | 257 | if ($this->rows) 258 | { 259 | foreach ($this->rows as $row) 260 | { 261 | if (is_string($row) && '-' === $row) 262 | { 263 | $content .= $this->renderSeparator('middle'); 264 | } 265 | elseif (is_scalar($row)) 266 | { 267 | $content .= $this->renderSeparator('cross-top'); 268 | $array = str_pad($row, 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) { 269 | return $a + $b; 270 | })); 271 | 272 | $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL; 273 | $content .= $this->renderSeparator('cross-bottom'); 274 | } 275 | else 276 | { 277 | $array = []; 278 | 279 | foreach ($row as $key => $val) 280 | { 281 | $array[] = ' ' . str_pad($val, $this->colWidth[$key], ' ', $this->cellAlign); 282 | } 283 | 284 | $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; 285 | 286 | } 287 | } 288 | } 289 | $content .= $this->renderSeparator('bottom'); 290 | return $content; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/Task.php: -------------------------------------------------------------------------------- 1 | initialise($currentOs); 33 | } 34 | 35 | /** 36 | * 进程初始化 37 | * @param int $currentOs 38 | */ 39 | private function initialise($currentOs) 40 | { 41 | //初始化基础配置 42 | Env::set('prefix', 'Task'); 43 | Env::set('canEvent', Helper::canUseEvent()); 44 | Env::set('currentOs', $currentOs); 45 | Env::set('canAsync', Helper::canUseAsyncSignal()); 46 | Env::set('closeErrorRegister', false); 47 | 48 | //初始化PHP_BIN|CODE_PAGE 49 | if ($currentOs == 1) { 50 | Helper::setPhpPath(); 51 | Helper::setCodePage(); 52 | } 53 | } 54 | 55 | /** 56 | * 设置是否守护进程 57 | * @param bool $daemon 58 | * @return $this 59 | */ 60 | public function setDaemon($daemon = false) 61 | { 62 | Env::set('daemon', $daemon); 63 | return $this; 64 | } 65 | 66 | /** 67 | * 设置任务前缀 68 | * @param string $prefix 69 | * @return $this 70 | */ 71 | public function setPrefix($prefix = 'Task') 72 | { 73 | if (Env::get('runTimePath')) { 74 | Helper::showSysError('should use setPrefix before setRunTimePath'); 75 | } 76 | Env::set('prefix', $prefix); 77 | return $this; 78 | } 79 | 80 | /** 81 | * 设置PHP执行路径(windows) 82 | * @param string $path 83 | * @return $this 84 | */ 85 | public function setPhpPath($path) 86 | { 87 | $file = realpath($path); 88 | if (!file_exists($file)) { 89 | Helper::showSysError("the path {$path} is not exists"); 90 | } 91 | Helper::setPhpPath($path); 92 | return $this; 93 | } 94 | 95 | /** 96 | * 设置时区 97 | * @param string $timeIdent 98 | * @return $this 99 | */ 100 | public function setTimeZone($timeIdent) 101 | { 102 | date_default_timezone_set($timeIdent); 103 | return $this; 104 | } 105 | 106 | /** 107 | * 设置运行时目录 108 | * @param string $path 109 | * @return $this 110 | */ 111 | public function setRunTimePath($path) 112 | { 113 | if (!is_dir($path)) { 114 | Helper::showSysError("the path {$path} is not exist"); 115 | } 116 | if (!is_writable($path)) { 117 | Helper::showSysError("the path {$path} is not writeable"); 118 | } 119 | Env::set('runTimePath', realpath($path)); 120 | return $this; 121 | } 122 | 123 | /** 124 | * 设置子进程自动恢复 125 | * @param bool $isRec 126 | * @return $this 127 | */ 128 | public function setAutoRecover($isRec = false) 129 | { 130 | Env::set('canAutoRec', $isRec); 131 | return $this; 132 | } 133 | 134 | /** 135 | * 设置关闭标准输出的日志 136 | * @param bool $close 137 | * @return $this 138 | */ 139 | public function setCloseStdOutLog($close = false) 140 | { 141 | Env::set('closeStdOutLog', $close); 142 | return $this; 143 | } 144 | 145 | /** 146 | * 设置关闭系统异常注册 147 | * @param bool $isReg 是否关闭 148 | * @return $this 149 | */ 150 | public function setCloseErrorRegister($isReg = false) 151 | { 152 | Env::set('closeErrorRegister', $isReg); 153 | return $this; 154 | } 155 | 156 | /** 157 | * 异常通知 158 | * @param string|Closure $notify 159 | * @return $this 160 | */ 161 | public function setErrorRegisterNotify($notify) 162 | { 163 | if (Env::get('closeErrorRegister')) { 164 | Helper::showSysError('you must set closeErrorRegister as false before use this api'); 165 | } 166 | if (!$notify instanceof Closure && !is_string($notify)) { 167 | Helper::showSysError('notify parameter can only be string or closure'); 168 | } 169 | Env::set('notifyHand', $notify); 170 | return $this; 171 | } 172 | 173 | /** 174 | * 新增匿名函数作为任务 175 | * @param Closure $func 匿名函数 176 | * @param string $alas 任务别名 177 | * @param mixed $time 定时器间隔 178 | * @param int $used 定时器占用进程数 179 | * @return $this 180 | * @throws 181 | */ 182 | public function addFunc($func, $alas, $time = 1, $used = 1) 183 | { 184 | $uniqueId = md5($alas); 185 | if (!($func instanceof Closure)) { 186 | Helper::showSysError('func must instanceof Closure'); 187 | } 188 | if (isset($this->taskList[$uniqueId])) { 189 | Helper::showSysError("task $alas already exists"); 190 | } 191 | Helper::checkTaskTime($time); 192 | $this->taskList[$uniqueId] = [ 193 | 'type' => 1, 194 | 'func' => $func, 195 | 'alas' => $alas, 196 | 'time' => $time, 197 | 'used' => $used 198 | ]; 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * 新增类作为任务 205 | * @param string $class 类名称 206 | * @param string $func 方法名称 207 | * @param string $alas 任务别名 208 | * @param mixed $time 定时器间隔 209 | * @param int $used 定时器占用进程数 210 | * @return $this 211 | * @throws 212 | */ 213 | public function addClass($class, $func, $alas, $time = 1, $used = 1) 214 | { 215 | $uniqueId = md5($alas); 216 | if (!class_exists($class)) { 217 | Helper::showSysError("class {$class} is not exist"); 218 | } 219 | if (isset($this->taskList[$uniqueId])) { 220 | Helper::showSysError("task $alas already exists"); 221 | } 222 | try { 223 | $reflect = new ReflectionClass($class); 224 | if (!$reflect->hasMethod($func)) { 225 | Helper::showSysError("class {$class}'s func {$func} is not exist"); 226 | } 227 | $method = new ReflectionMethod($class, $func); 228 | if (!$method->isPublic()) { 229 | Helper::showSysError("class {$class}'s func {$func} must public"); 230 | } 231 | Helper::checkTaskTime($time); 232 | $this->taskList[$uniqueId] = [ 233 | 'type' => $method->isStatic() ? 2 : 3, 234 | 'func' => $func, 235 | 'alas' => $alas, 236 | 'time' => $time, 237 | 'used' => $used, 238 | 'class' => $class 239 | ]; 240 | } catch (ReflectionException $exception) { 241 | Helper::showException($exception); 242 | } 243 | 244 | return $this; 245 | } 246 | 247 | /** 248 | * 新增指令作为任务 249 | * @param string $command 指令 250 | * @param string $alas 任务别名 251 | * @param mixed $time 定时器间隔 252 | * @param int $used 定时器占用进程数 253 | * @return $this 254 | */ 255 | public function addCommand($command, $alas, $time = 1, $used = 1) 256 | { 257 | $uniqueId = md5($alas); 258 | if (!Helper::canUseExcCommand()) { 259 | Helper::showSysError('please open the disabled function of shell_exec'); 260 | } 261 | if (isset($this->taskList[$uniqueId])) { 262 | Helper::showSysError("task $alas already exists"); 263 | } 264 | Helper::checkTaskTime($time); 265 | $this->taskList[$uniqueId] = [ 266 | 'type' => 4, 267 | 'alas' => $alas, 268 | 'time' => $time, 269 | 'used' => $used, 270 | 'command' => $command, 271 | ]; 272 | 273 | return $this; 274 | } 275 | 276 | /** 277 | * 获取进程管理实例 278 | * @return Win | Linux 279 | */ 280 | private function getProcess() 281 | { 282 | $taskList = $this->taskList; 283 | $currentOs = Env::get('currentOs'); 284 | if ($currentOs == 1) { 285 | return (new Win($taskList)); 286 | } else { 287 | return (new Linux($taskList)); 288 | } 289 | } 290 | 291 | /** 292 | * 开始运行 293 | * @throws 294 | */ 295 | public function start() 296 | { 297 | if (!$this->taskList) { 298 | Helper::showSysError('please add task to run'); 299 | } 300 | 301 | //异常注册 302 | if (!Env::get('closeErrorRegister')) { 303 | Error::register(); 304 | } 305 | 306 | //目录构建 307 | Helper::initAllPath(); 308 | 309 | //进程启动 310 | $process = $this->getProcess(); 311 | $process->start(); 312 | } 313 | 314 | /** 315 | * 运行状态 316 | * @throws 317 | */ 318 | public function status() 319 | { 320 | $process = $this->getProcess(); 321 | $process->status(); 322 | } 323 | 324 | /** 325 | * 停止运行 326 | * @param bool $force 是否强制 327 | * @throws 328 | */ 329 | public function stop($force = false) 330 | { 331 | $process = $this->getProcess(); 332 | $process->stop($force); 333 | } 334 | } -------------------------------------------------------------------------------- /src/Wpc.php: -------------------------------------------------------------------------------- 1 | instance = new Com('Wpc.Core'); 26 | return $this; 27 | } 28 | 29 | /** 30 | * 获取Com_Variant 31 | * @return Com 32 | */ 33 | public function getInstance() 34 | { 35 | return $this->instance; 36 | } 37 | 38 | /** 39 | * 设置进程文件 40 | * @param string $filename 41 | * @return $this 42 | * @throws Exception 43 | */ 44 | public function setFile($filename) 45 | { 46 | $filename = realpath($filename); 47 | if (!file_exists($filename)) 48 | { 49 | throw new Exception("the file:{$filename} is not exist"); 50 | } 51 | $this->instance->SetFile($filename); 52 | return $this; 53 | } 54 | 55 | /** 56 | * 设置进程域 57 | * @param string $domain 58 | * @return $this 59 | */ 60 | public function setDomain($domain) 61 | { 62 | $domain = (string)$domain; 63 | $this->instance->SetDomain($domain); 64 | return $this; 65 | } 66 | 67 | /** 68 | * 设置进程参数 69 | * @param string $argument 70 | * @return $this 71 | */ 72 | public function setArgument($argument) 73 | { 74 | $argument = (string)$argument; 75 | $this->instance->SetArgument($argument); 76 | return $this; 77 | } 78 | 79 | /** 80 | * 设置进程是否带窗口 81 | * @param bool $set 82 | * @return $this 83 | */ 84 | public function setNoWindow($set) 85 | { 86 | $set = (bool)$set; 87 | $this->instance->SetNoWindow($set); 88 | return $this; 89 | } 90 | 91 | /** 92 | * 设置启动进程的用户 93 | * @param string $username 94 | * @return $this 95 | */ 96 | public function setUsername($username) 97 | { 98 | $username = (string)$username; 99 | $this->instance->SetUsername($username); 100 | return $this; 101 | } 102 | 103 | /** 104 | * 设置启动进程的密码 105 | * @param string $password 106 | * @return $this 107 | */ 108 | public function setPassword($password) 109 | { 110 | $password = (string)$password; 111 | $this->instance->SetPassword($password); 112 | return $this; 113 | } 114 | 115 | /** 116 | * 设置进程风格 117 | * @param int $style (0.正常 1.隐藏 2.最小化 3.最大化) 118 | * @return $this 119 | */ 120 | public function setStyle($style) 121 | { 122 | $style = (int)$style; 123 | $this->instance->SetStyle($style); 124 | return $this; 125 | } 126 | 127 | /** 128 | * 设置进程工作目录 129 | * @param string $path 130 | * @return $this 131 | * @throws Exception 132 | */ 133 | public function setWorkDir($path) 134 | { 135 | $path = realpath($path); 136 | if (!is_dir($path)) 137 | { 138 | throw new Exception("the path:{$path} is not exist"); 139 | } 140 | $this->instance->SetWorkDir($path); 141 | return $this; 142 | } 143 | 144 | /** 145 | * 设置等待关联进程退出 146 | * @param int $timeOut 超时时间 147 | * @return $this 148 | * @throws Exception 149 | */ 150 | public function setWaitForExit($timeOut = 1024) 151 | { 152 | $timeOut = (int)$timeOut; 153 | $this->instance->SetWaitForExit($timeOut); 154 | return $this; 155 | } 156 | 157 | /** 158 | * 获取进程ID 159 | * @return int 160 | */ 161 | public function getPid() 162 | { 163 | return $this->instance->GetPid(); 164 | } 165 | 166 | /** 167 | * 获取进程sessionId 168 | * @return int 169 | */ 170 | public function getSessionId() 171 | { 172 | return $this->instance->GetSessionId(); 173 | } 174 | 175 | /** 176 | * 获取程是否已经退出 177 | * @return bool 178 | */ 179 | public function hasExited() 180 | { 181 | return $this->instance->HasExited(); 182 | } 183 | 184 | /** 185 | * 获取进程名称 186 | * @return string 187 | */ 188 | public function getProcessName() 189 | { 190 | return $this->instance->GetProcessName(); 191 | } 192 | 193 | /** 194 | * 获取进程打开的资源句柄数 195 | * @return int 196 | */ 197 | public function getHandleCount() 198 | { 199 | return $this->instance->GetHandleCount(); 200 | } 201 | 202 | /** 203 | * 获取进程主窗口标题 204 | * @return string 205 | */ 206 | public function getMainWindowTitle() 207 | { 208 | return $this->instance->GetMainWindowTitle(); 209 | } 210 | 211 | /** 212 | * 获取进程启动时间 213 | * @return string 214 | */ 215 | public function getStartTime() 216 | { 217 | return $this->instance->GetStartTime(); 218 | } 219 | 220 | /** 221 | * 获取进程停止时间 222 | * @return string 223 | */ 224 | public function getStopTime() 225 | { 226 | return $this->instance->GetStopTime(); 227 | } 228 | 229 | /** 230 | * 启动进程 231 | * @return int 进程id 232 | */ 233 | public function start() 234 | { 235 | return $this->instance->Start(); 236 | } 237 | 238 | /** 239 | * 停止进程 240 | * @param int $force (1.正常停止 2.强制停止) 241 | */ 242 | public function stop($force = 1) 243 | { 244 | $this->instance->Stop($force); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/Wts.php: -------------------------------------------------------------------------------- 1 | lock = new Lock(); 31 | 32 | //创建进程信息文件 33 | $processFile = $this->getProcessInfoFile(); 34 | if (!file_exists($processFile)) 35 | { 36 | file_put_contents($processFile, ''); 37 | } 38 | } 39 | 40 | /** 41 | * 注册进程名称 42 | * @param string $name 43 | */ 44 | public function joinProcess($name) 45 | { 46 | $this->processNames[] = $name; 47 | $file = $this->getProcessFile($name); 48 | if (!file_exists($file)) 49 | { 50 | file_put_contents($file, $name); 51 | } 52 | } 53 | 54 | /** 55 | * 获取进程文件名 56 | * @param string $name 进程名称 57 | * @return string 58 | */ 59 | public function getProcessFile($name) 60 | { 61 | $runPath = Helper::getWinPath(); 62 | return $runPath . md5($name) . '.win'; 63 | } 64 | 65 | /** 66 | * 获取进程保存信息的文件名 67 | * @return string 68 | */ 69 | public function getProcessInfoFile() 70 | { 71 | $runPath = Helper::getWinPath(); 72 | $infoFile = md5(__FILE__) . '.win'; 73 | return $runPath . $infoFile; 74 | } 75 | 76 | /** 77 | * 获取进程状态 78 | * @param string $name 进程名称 79 | * @return bool 80 | */ 81 | public function getProcessStatus($name) 82 | { 83 | $file = $this->getProcessFile($name); 84 | if (!file_exists($file)) 85 | { 86 | return false; 87 | } 88 | $fp = fopen($file, "r"); 89 | if (flock($fp, LOCK_EX | LOCK_NB)) 90 | { 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | /** 97 | * 获取进程信息(非阻塞) 98 | * @return array 99 | */ 100 | public function getProcessInfo() 101 | { 102 | $file = $this->getProcessInfoFile(); 103 | $info = file_get_contents($file); 104 | $info = json_decode($info, true); 105 | return is_array($info) ? $info : []; 106 | } 107 | 108 | /** 109 | * 清理进程信息 110 | */ 111 | public function cleanProcessInfo() 112 | { 113 | //加锁执行 114 | $this->lock->execute(function () { 115 | @file_put_contents($this->getProcessInfoFile(), ''); 116 | }); 117 | } 118 | 119 | /** 120 | * 保存进程信息 121 | * @param array $info 122 | */ 123 | public function saveProcessInfo($info) 124 | { 125 | //加锁执行 126 | $this->lock->execute(function () use ($info) { 127 | 128 | //进程信息文件 129 | $name = $info['name']; 130 | $file = $this->getProcessInfoFile(); 131 | 132 | //读取原数据 133 | $content = @file_get_contents($file); 134 | $oldInfo = $content ? json_decode($content, true) : [$name => $info]; 135 | 136 | //追加数据 137 | $oldInfo ? $oldInfo[$name] = $info : $oldInfo = $info; 138 | file_put_contents($file, json_encode($oldInfo)); 139 | }); 140 | } 141 | 142 | /** 143 | * 分配进程 144 | * @param Closure $func 145 | * @return bool 146 | */ 147 | public function allocateProcess($func) 148 | { 149 | $processNames = $this->processNames; 150 | foreach ($processNames as $name) 151 | { 152 | $file = $this->getProcessFile($name); 153 | $fp = fopen($file, 'w'); 154 | if (flock($fp, LOCK_EX | LOCK_NB)) 155 | { 156 | $func($name); 157 | flock($fp, LOCK_UN); 158 | return true; 159 | } 160 | fclose($fp); 161 | } 162 | return false; 163 | } 164 | } --------------------------------------------------------------------------------