Laravel 13 进程管理完全指南

Laravel 提供了强大的进程管理功能,包括 Artisan 命令、进程调度和外部进程调用。本文将深入探讨 Laravel 13 中进程管理的各种用法。

Artisan 命令

创建命令

1
2
php artisan make:command SendEmails
php artisan make:command ProcessUsers --command=users:process

命令结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php

namespace App\Console\Commands;

use App\Models\User;
use App\Services\MailService;
use Illuminate\Console\Command;

class SendEmails extends Command
{
protected $signature = 'email:send
{user : 用户ID或邮箱}
{--queue : 是否使用队列发送}
{--subject= : 邮件主题}';

protected $description = '发送邮件给指定用户';

public function handle(MailService $mailer): int
{
$user = $this->resolveUser($this->argument('user'));

if (!$user) {
$this->error('用户不存在');
return self::FAILURE;
}

$subject = $this->option('subject') ?? '欢迎邮件';

if ($this->option('queue')) {
$mailer->queue($user, $subject);
$this->info('邮件已加入队列');
} else {
$mailer->send($user, $subject);
$this->info('邮件已发送');
}

return self::SUCCESS;
}

protected function resolveUser($identifier): ?User
{
if (is_numeric($identifier)) {
return User::find($identifier);
}

return User::where('email', $identifier)->first();
}
}

命令签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 必需参数
protected $signature = 'user:create {name} {email}';

// 可选参数
protected $signature = 'user:create {name} {email?}';

// 带默认值的参数
protected $signature = 'user:create {name} {email=default@example.com}';

// 数组参数
protected $signature = 'user:notify {user*}';

// 选项
protected $signature = 'email:send {user} {--queue}';

// 带值的选项
protected $signature = 'email:send {user} {--subject=}';

// 带默认值的选项
protected $signature = 'email:send {user} {--subject=欢迎}';

// 选项简写
protected $signature = 'email:send {user} {--Q|queue}';

// 复杂签名
protected $signature = 'report:generate
{type : 报告类型}
{--start= : 开始日期}
{--end= : 结束日期}
{--format=pdf : 输出格式}
{--queue : 使用队列处理}
{--notify=* : 通知邮箱}';

输入输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public function handle(): int
{
// 获取参数
$name = $this->argument('name');
$arguments = $this->arguments();

// 获取选项
$queue = $this->option('queue');
$options = $this->options();

// 输出信息
$this->info('操作成功');
$this->error('操作失败');
$this->warn('警告信息');
$this->line('普通输出');
$this->comment('注释信息');

// 表格输出
$this->table(
['ID', 'Name', 'Email'],
User::all(['id', 'name', 'email'])->toArray()
);

// 进度条
$users = User::all();
$bar = $this->output->createProgressBar($users->count());

foreach ($users as $user) {
$this->processUser($user);
$bar->advance();
}

$bar->finish();
$this->newLine();

return self::SUCCESS;
}

交互式输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public function handle(): int
{
$name = $this->ask('请输入用户名');
$password = $this->secret('请输入密码');
$confirm = $this->confirm('确定要继续吗?');
$choice = $this->choice(
'请选择角色',
['admin', 'editor', 'user'],
'user'
);
$multiSelect = $this->choice(
'选择权限',
['read', 'write', 'delete'],
null,
null,
true
);

$anticipate = $this->anticipate('输入邮箱', [
'admin@example.com',
'user@example.com',
]);

return self::SUCCESS;
}

进程调度

定义调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/Console/Kernel.php
use Illuminate\Support\Facades\Schedule;
use App\Console\Commands\SendEmails;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$schedule->command('emails:send')
->daily()
->at('09:00');

$schedule->job(new ProcessPodcasts())
->hourly();

$schedule->exec('node /path/to/script.js')
->daily();

$schedule->call(function () {
User::where('expired', true)->delete();
})->daily();
}
}

调度频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 常用频率
$schedule->command('report:generate')->everyMinute();
$schedule->command('report:generate')->everyFiveMinutes();
$schedule->command('report:generate')->everyTenMinutes();
$schedule->command('report:generate')->everyFifteenMinutes();
$schedule->command('report:generate')->everyThirtyMinutes();
$schedule->command('report:generate')->hourly();
$schedule->command('report:generate')->hourlyAt(17);
$schedule->command('report:generate')->daily();
$schedule->command('report:generate')->dailyAt('13:00');
$schedule->command('report:generate')->twiceDaily(1, 13);
$schedule->command('report:generate')->weekly();
$schedule->command('report:generate')->weeklyOn(1, '8:00');
$schedule->command('report:generate')->monthly();
$schedule->command('report:generate')->monthlyOn(4, '15:00');
$schedule->command('report:generate')->twiceMonthly(1, 16);
$schedule->command('report:generate')->quarterly();
$schedule->command('report:generate')->yearly();

// 工作日
$schedule->command('report:generate')->weekdays();
$schedule->command('report:generate')->weekends();
$schedule->command('report:generate')->mondays();
$schedule->command('report:generate')->tuesdays();
$schedule->command('report:generate')->wednesdays();
$schedule->command('report:generate')->thursdays();
$schedule->command('report:generate')->fridays();
$schedule->command('report:generate')->saturdays();
$schedule->command('report:generate')->sundays();

// Cron 表达式
$schedule->command('report:generate')->cron('0 0 * * *');

调度约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$schedule->command('report:generate')
->daily()
->when(function () {
return date('N') <= 5;
});

$schedule->command('report:generate')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});

$schedule->command('report:generate')
->daily()
->environments(['production']);

$schedule->command('report:generate')
->daily()
->days([Schedule::MONDAY, Schedule::WEDNESDAY]);

防止重叠

1
2
3
4
5
6
7
8
9
10
11
$schedule->command('report:generate')
->daily()
->withoutOverlapping(60);

$schedule->command('report:generate')
->daily()
->onOneServer();

$schedule->command('report:generate')
->daily()
->runInBackground();

维护模式

1
2
3
4
5
6
7
8
9
$schedule->command('report:generate')
->daily()
->evenInMaintenanceMode();

$schedule->command('report:generate')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});

任务钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$schedule->command('report:generate')
->daily()
->before(function () {
// 任务执行前
})
->after(function () {
// 任务执行后
})
->onSuccess(function () {
// 任务成功时
})
->onFailure(function () {
// 任务失败时
});

任务输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$schedule->command('report:generate')
->daily()
->sendOutputTo(storage_path('logs/report.log'));

$schedule->command('report:generate')
->daily()
->appendOutputTo(storage_path('logs/report.log'));

$schedule->command('report:generate')
->daily()
->emailOutputTo('admin@example.com');

$schedule->command('report:generate')
->daily()
->emailOutputOnFailure('admin@example.com');

进程执行

Process 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Illuminate\Support\Facades\Process;

// 执行命令
$result = Process::run('ls -la');

echo $result->output();
echo $result->errorOutput();
echo $result->exitCode();

// 检查执行结果
if ($result->successful()) {
// 成功
}

if ($result->failed()) {
// 失败
}

实时输出

1
2
3
Process::run('ls -la', function (string $type, string $output) {
echo $output;
});

异步进程

1
2
3
4
5
6
7
8
$process = Process::start('npm run build');

while ($process->running()) {
echo $process->latestOutput();
sleep(1);
}

$result = $process->wait();

超时设置

1
$result = Process::timeout(120)->run('long-running-command');

环境变量

1
2
3
$result = Process::withEnvironment([
'NODE_ENV' => 'production',
])->run('npm run build');

工作目录

1
$result = Process::path('/path/to/project')->run('npm install');

并行进程

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Process\Pool;

[$first, $second, $third] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('ls -la');
$pool->command('cat file.txt');
$pool->command('echo "Hello World"');
});

echo $first->output();
echo $second->output();
echo $third->output();

管道操作

1
2
3
4
Process::pipe(function ($pipe) {
$pipe->command('echo "Hello"');
$pipe->command('sed "s/Hello/Goodbye/"');
});

命令调用

从代码调用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Illuminate\Support\Facades\Artisan;

// 调用命令
Artisan::call('email:send', [
'user' => 1,
'--queue' => true,
]);

// 获取输出
$output = Artisan::output();

// 使用队列
Artisan::queue('email:send', [
'user' => 1,
])->onQueue('commands');

命令实例调用

1
2
3
use App\Console\Commands\SendEmails;

Artisan::call(new SendEmails(1, 'welcome'));

自定义命令示例

数据库备份命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Process;

class DatabaseBackup extends Command
{
protected $signature = 'db:backup
{--database=mysql : 数据库连接}
{--path= : 备份路径}
{--compress : 是否压缩}';

protected $description = '备份数据库';

public function handle(): int
{
$connection = $this->option('database');
$config = config("database.connections.{$connection}");

$filename = sprintf(
'backup_%s_%s.sql',
$connection,
now()->format('Y-m-d_His')
);

$path = $this->option('path') ?? storage_path('backups');
$filepath = "{$path}/{$filename}";

$this->info("开始备份数据库: {$connection}");

$command = sprintf(
'mysqldump -h%s -u%s -p%s %s > %s',
$config['host'],
$config['username'],
$config['password'],
$config['database'],
$filepath
);

$result = Process::run($command);

if ($result->failed()) {
$this->error('备份失败: ' . $result->errorOutput());
return self::FAILURE;
}

if ($this->option('compress')) {
$this->info('压缩备份文件...');
Process::run("gzip {$filepath}");
$filepath .= '.gz';
}

$this->info("备份完成: {$filepath}");

return self::SUCCESS;
}
}

数据导入命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

namespace App\Console\Commands;

use App\Imports\UsersImport;
use Illuminate\Console\Command;
use Maatwebsite\Excel\Facades\Excel;

class ImportUsers extends Command
{
protected $signature = 'users:import
{file : 导入文件路径}
{--chunk=1000 : 分块大小}
{--validate : 仅验证不导入}';

protected $description = '导入用户数据';

public function handle(): int
{
$file = $this->argument('file');

if (!file_exists($file)) {
$this->error("文件不存在: {$file}");
return self::FAILURE;
}

$import = new UsersImport(
chunkSize: $this->option('chunk'),
validateOnly: $this->option('validate')
);

$this->info('开始导入用户数据...');

$bar = $this->output->createProgressBar();
$import->setProgressBar($bar);

try {
Excel::import($import, $file);
} catch (\Exception $e) {
$this->error('导入失败: ' . $e->getMessage());
return self::FAILURE;
}

$this->newLine();
$this->info("导入完成:");
$this->line(" 成功: {$import->successCount}");
$this->line(" 失败: {$import->failureCount}");

if ($import->failures->isNotEmpty()) {
$this->warn('失败记录:');
foreach ($import->failures as $failure) {
$this->line(" 行 {$failure->row()}: {$failure->errors()[0]}");
}
}

return self::SUCCESS;
}
}

总结

Laravel 13 的进程管理提供了:

  • 强大的 Artisan 命令系统
  • 灵活的任务调度
  • 现代化的进程执行组件
  • 完善的输入输出控制
  • 丰富的交互式功能

掌握进程管理是构建复杂命令行应用和自动化任务的基础。