├── 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 | }
--------------------------------------------------------------------------------