29、Laravel 实时通信与任务调度全解析

Laravel 实时通信与任务调度全解析

1. 实时通信基础

在实时通信方面,若要使用 Pusher 或 Redis 进行广播,需要引入相应的依赖:
- Pusher: pusher/pusher-php-server:~2.0
- Redis: predis/predis:~1.0

当你部署应用后,若满足特定条件,就能在 JavaScript 窗口的控制台近乎实时地看到事件日志弹出。这些条件包括:在一个窗口中访问嵌入了特定 JavaScript 的页面,在另一个窗口或终端推送广播事件,运行队列监听器或使用同步驱动,并且所有认证信息都正确设置。借助这种能力,能轻松让用户在使用应用时及时了解其数据的最新情况,还能通知用户其他用户的操作、长时间运行的进程完成情况,以及应用对外部操作(如传入的电子邮件或 Webhook)的响应。

2. 高级广播工具 - Laravel Echo

Laravel Echo 是由高级框架特性和 JavaScript 包组成的工具,它能让你在事件广播中进行更复杂的交互。它可以与 Pusher 和 Redis 配合使用,下面将以 Pusher 为例进行介绍。

2.1 排除当前用户接收广播事件

每个与 Pusher 的连接都会被分配一个唯一的“socket ID”,可通过以下步骤排除当前用户接收特定广播事件:
1. 当 WebSocket 连接初始化时,设置 JavaScript 向 /broadcasting/socket 发送 POST 请求,将 socket_id 附加到 Laravel 会话中。Echo 会自动完成此操作,也可手动实现,可查看 Echo 源码了解具体实现方式。
2. 更新 JavaScript 发出的每个请求,使其包含一个 X-Socket-ID 头,其中包含 socket_id 。示例代码如下:

// Vue
Vue.http.interceptors.push((request, next) => {
    request.headers['X-Socket-Id'] = Echo.socketId();
    next();
});
// jQuery
$.ajaxSetup({
    headers: {
        'X-Socket-Id': Echo.socketId()
    }
});

完成上述操作后,可使用 broadcast() 全局辅助函数代替 event() 全局辅助函数,并在其后链式调用 toOthers() 方法,以排除触发事件的用户接收该事件:

broadcast(new UserSubscribed($user, $plan))->toOthers();
2.2 广播服务提供者

Echo 提供的其他功能要求 JavaScript 与服务器进行认证。在 App\Providers\BroadcastServiceProvider 中,你可以定义如何授权用户访问私有和存在频道。主要操作包括定义广播认证路由上使用的中间件,以及定义频道的授权设置。若要使用这些功能,需取消 config/app.php App\Providers\BroadcastServiceProvider::class 行的注释。若不使用 Laravel Echo 而使用这些功能,要么手动处理在认证请求中发送 CSRF 令牌,要么通过将 /broadcasting/auth /broadcasting/socket 添加到 VerifyCsrfToken 中间件的 $except 属性中来排除它们的 CSRF 保护。

2.3 WebSocket 频道的授权定义绑定

WebSocket 有三种类型的频道:公共、私有和存在频道。
- 公共频道:任何用户(无论是否经过认证)都可以订阅。
- 私有频道:最终用户的 JavaScript 需要与应用进行认证,以证明用户既经过认证又被授权加入该频道。
- 存在频道:是一种私有频道,不允许消息传递,只是跟踪哪些用户加入和离开频道,并将这些信息提供给应用的前端。

使用 Broadcast::channel() 方法定义授权规则,该方法接受两个参数:一个表示要匹配的频道的字符串,以及一个闭包,用于定义如何授权匹配该字符串的任何频道的用户。闭包的第一个参数是当前用户的 Eloquent 模型,任何匹配的 * 段作为额外参数。示例代码如下:

class BroadcastServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 定义如何认证私有频道
        Broadcast::channel('teams.*', function ($user, $teamId) {
            return $user->team_id == $teamId;
        });
        // 定义如何认证存在频道;返回你希望应用在频道中拥有的关于用户的任何数据
        Broadcast::channel('rooms.*', function ($user, $roomId) {
            if ($user->rooms->contains($roomId)) {
                return [
                    'name' => $user->name
                ];
            }
        });
    }
}

Pusher 的 JavaScript 库会向应用发送 POST 请求,默认会访问 /pusher/auth ,可将其自定义为 Laravel 的认证路由 /broadcasting/auth

var pusher = new Pusher(App.pusherKey, {
    authEndpoint: '/broadcasting/auth'
});

以下是不使用 Echo 前端组件时,Pusher JS 用于私有和存在频道的基本用法示例:

<script src="https://js.pusher.com/3.1/pusher.min.js"></script>
<script>
    var App = {
        'userId': {{ auth()->user()->id }},
        'pusherKey': 'your pusher key here'
    };
    var pusher = new Pusher(App.pusherKey, {
        authEndpoint: '/broadcasting/auth'
    });
    // 私有频道
    var privateChannel = pusher.subscribe('private-teams.1');
    privateChannel.bind('App\\Events\\UserSubscribed', (data) => {
        console.log(data.user, data.plan);
    });
    // 存在频道
    var presenceChannel = pusher.subscribe('presence-rooms.5');
    console.log(presenceChannel.members);
</script>
3. Laravel Echo 的 JavaScript 端使用
3.1 将 Echo 引入项目

要在项目的 JavaScript 中使用 Echo,可使用 npm 安装并添加到 package.json 中:

npm install pusher-js --save
npm install laravel-echo --save

假设你有一个基本的 Gulp 文件,通过 Webpack 运行 app.js 文件:

const elixir = require('laravel-elixir');
elixir(mix => {
    mix.webpack('app.js');
});

创建一个基本的 resources/assets/js/app.js 文件来引入依赖并初始化 Echo:

import Echo from "laravel-echo"
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key'
});
// 在此添加你的 Echo 绑定

为了进行 CSRF 保护,还需要在 HTML 模板中添加一个 csrf-token <meta> 标签:

<meta name="csrf-token" content="{{ csrf_token() }}">

当然,别忘了在 HTML 模板中链接编译后的 app.js

<script src="/js/app.js"></script>
3.2 使用 Echo 进行基本事件广播

使用 Echo 监听公共频道的基本事件信息示例如下:

var currentTeamId = 5; // 可能在其他地方设置
Echo.channel('teams.' + currentTeamId)
    .listen('UserSubscribed', (data) => {
        console.log(data);
    });

还可以链式调用 listen() 处理程序:

Echo.channel('teams.' + currentTeamId)
    .listen('UserSubscribed', (data) => {
        console.log(data);
    })
    .listen('UserCanceled', (data) => {
        console.log(data);
    });

如果尝试这些代码示例后在浏览器中没有看到任何变化,请确保运行 gulp (如果只运行一次)或 gulp watch (运行监听器)来编译代码,并确保在模板中包含了 app.js

3.3 私有频道和基本认证

Echo 提供了 private() 方法来订阅私有频道,它的工作方式与 channel() 相同,但需要在 BroadcastServiceProvider 中设置频道授权定义。示例代码如下:

var currentTeamId = 5; // 可能在其他地方设置
Echo.private('teams.' + currentTeamId)
    .listen('UserSubscribed', (data) => {
        console.log(data);
    });
3.4 存在频道

使用 join() 方法绑定到存在频道,示例代码如下:

var currentTeamId = 5; // 可能在其他地方设置
Echo.join('teams.' + currentTeamId)
    .here((members) => {
        console.log(members);
    });

还可以使用特定方法监听单个事件,可单独使用或链式调用:

var currentTeamId = 5; // 可能在其他地方设置
Echo.join('teams.' + currentTeamId)
    .then((members) => {
        // 当你加入时运行
        console.table(members);
    })
    .joining((joiningMember, members) => {
        // 当另一个成员加入时运行
        console.table(joiningMember);
    })
    .leaving((leavingMember, members) => {
        // 当另一个成员离开时运行
        console.table(leavingMember);
    });
3.5 排除当前用户

若要排除当前用户,使用 broadcast() 全局辅助函数代替 event() 全局辅助函数,并在广播调用后链式调用 toOthers() 方法。

3.6 使用 Echo 订阅通知

Laravel 的通知自带广播驱动,可使用 Echo.notification 订阅这些通知,示例代码如下:

Echo.private('App.User.' + userId)
    .notification((notification) => {
        console.log(notification.type);
    });
4. 任务调度

传统的 cron 作业语法繁琐且难以记忆,还无法存储在版本控制中。Laravel 的调度器使处理定时任务变得简单,只需在代码中编写定时任务,然后将一个 cron 作业指向应用,每分钟运行 php artisan schedule:run 。每次运行此 Artisan 命令时,Laravel 会检查调度定义,以确定是否有任何定时任务应该运行。定义该命令的 cron 作业如下:

* * * * * php /home/myapp.com/artisan schedule:run >> /dev/null 2>&1
4.1 可用的任务类型
  • 闭包:每分钟运行一次,示例代码如下:
// app/Consoles/Kernel.php
public function schedule($schedule)
{
    $schedule->call(function () {
        dispatch(new CalculateTotals);
    })->everyMinute();
}
  • Artisan 命令:通过传递与在命令行中调用它们时完全相同的语法来调度,示例代码如下:
$schedule->command('scores:tally --reset-cache')->everyMinute();
  • shell 命令:可以运行任何可以使用 PHP 的 exec() 方法运行的 shell 命令,示例代码如下:
$schedule->exec('/home/myapp.com/bin/build.sh')->everyMinute();
4.2 可用的时间框架

Laravel 可以在代码中定义任务的调度时间,提供了丰富的时间修饰符,如下表所示:
| 命令 | 描述 |
| ---- | ---- |
| ->timezone('America/Detroit') | 设置调度的时区 |
| ->cron('* * * * * *') | 使用传统的 cron 表示法定义调度 |
| ->everyMinute() | 每分钟运行一次 |
| ->everyFiveMinutes() | 每 5 分钟运行一次 |
| ->everyTenMinutes() | 每 10 分钟运行一次 |
| ->everyThirtyMinutes() | 每 30 分钟运行一次 |
| ->hourly() | 每小时运行一次 |
| ->daily() | 每天午夜运行一次 |
| ->dailyAt('14:00') | 每天 14:00 运行一次 |
| ->twiceDaily(1, 14) | 每天 1:00 和 14:00 运行一次 |
| ->weekly() | 每周(周日午夜)运行一次 |
| ->weeklyOn(5, '10:00') | 每周周五 10:00 运行一次 |
| ->monthly() | 每月(1 号午夜)运行一次 |
| ->monthlyOn(15, '23:00') | 每月 15 号 23:00 运行一次 |
| ->quarterly() | 每季度(1 月、4 月、7 月和 10 月 1 号午夜)运行一次 |
| ->yearly() | 每年(1 月 1 号午夜)运行一次 |
| ->when(closure) | 当闭包返回 true 时运行任务 |
| ->skip(closure) | 当闭包返回 false 时运行任务 |
| ->between('8:00', '12:00') | 在给定时间之间运行任务 |
| ->unlessBetween('8:00', '12:00') | 在给定时间之外运行任务 |
| ->weekdays() | 仅在工作日运行 |
| ->sundays() | 仅在周日运行 |
| ->mondays() | 仅在周一运行 |
| ->tuesdays() | 仅在周二运行 |
| ->wednesdays() | 仅在周三运行 |
| ->thursdays() | 仅在周四运行 |
| ->fridays() | 仅在周五运行 |
| ->saturdays() | 仅在周六运行 |

这些时间修饰符大多可以链式调用,以下是一些示例:

// 每周日 23:50 运行
$schedule->command('do:thing')->weeklyOn(0, '23:50');
$schedule->command('do:thing')->weekly()->sundays()->at('23:50');
// 每小时运行一次,工作日,8 点到 5 点
$schedule->command('do:thing')->weekdays()->hourly()->when(function () {
    return date('H') >= 8 && date('H') <= 17;
});
// 每小时运行一次,工作日,8 点到 5 点,使用新的 Laravel 5.3 "between"
$schedule->command('do:thing')->weekdays()->hourly()->between('8:00', '17:00');
$schedule->command('do:thing')->everyThirtyMinutes()->skip(function () {
    return app('SkipDetector')->shouldSkip();
});
4.3 任务阻塞和重叠处理

若要避免任务相互重叠,可在调度链的末尾使用 withoutOverlapping() 方法。如果前一个任务实例仍在运行,则跳过该任务,示例代码如下:

$schedule->command('do:thing')->everyMinute()->withoutOverlapping();
4.4 任务输出处理

如果需要将任务的返回输出写入文件,可使用 sendOutputTo() 方法,示例代码如下:

$schedule->command('do:thing')->daily()->sendOutputTo($filePath);

综上所述,Laravel 提供了强大的实时通信和任务调度功能,通过合理使用这些功能,可以提升应用的实时性和稳定性,为用户带来更好的体验。

Laravel 实时通信与任务调度全解析

5. 实时通信与任务调度的综合应用场景

在实际开发中,实时通信和任务调度往往不是孤立使用的,而是相互配合,以实现更复杂、更强大的应用功能。下面将介绍一些常见的综合应用场景及实现思路。

5.1 实时数据更新与定时统计

在一些数据密集型的应用中,需要实时更新数据展示给用户,同时还需要定时对数据进行统计分析。例如,在一个电商平台中,商品的库存数量需要实时更新,而每天凌晨需要对当天的销售数据进行统计。

实时数据更新可以通过 Laravel Echo 结合 Pusher 或 Redis 实现。当商品库存发生变化时,触发一个广播事件,前端使用 Echo 监听该事件并更新页面上的库存显示。示例代码如下:

// 前端监听库存更新事件
Echo.channel('products')
    .listen('InventoryUpdated', (data) => {
        // 更新页面上的库存显示
        document.getElementById('product-' + data.productId + '-inventory').textContent = data.inventory;
    });

定时统计任务可以使用 Laravel 的调度器实现。在 app/Console/Kernel.php 中定义一个定时任务,每天凌晨执行销售数据统计。示例代码如下:

// app/Console/Kernel.php
public function schedule($schedule)
{
    $schedule->command('sales:statistics')->dailyAt('00:00');
}
5.2 用户活动通知与定时提醒

在社交类应用中,当用户有新的活动(如收到新消息、被关注等)时,需要实时通知用户。同时,对于一些重要的事件(如会议、活动等),可以设置定时提醒。

实时用户活动通知可以通过 Laravel 的通知系统结合 Echo 实现。当有新的活动发生时,触发一个通知事件,并通过广播驱动将通知发送给相关用户。前端使用 Echo 监听通知事件并显示通知。示例代码如下:

// 前端监听用户通知事件
Echo.private('App.User.' + userId)
    .notification((notification) => {
        // 显示通知
        alert(notification.message);
    });

定时提醒任务可以使用 Laravel 的调度器实现。在 app/Console/Kernel.php 中定义一个定时任务,在指定时间触发提醒。示例代码如下:

// app/Console/Kernel.php
public function schedule($schedule)
{
    $schedule->call(function () {
        // 检查是否有需要提醒的事件
        $events = Event::where('remind_at', '<=', now())->get();
        foreach ($events as $event) {
            // 发送提醒通知
            $user = $event->user;
            $user->notify(new EventReminder($event));
        }
    })->everyMinute();
}
6. 性能优化与注意事项

在使用 Laravel 的实时通信和任务调度功能时,为了确保应用的性能和稳定性,需要注意以下几点:

6.1 实时通信性能优化
  • 合理选择广播驱动 :根据应用的需求和规模,选择合适的广播驱动。Pusher 适用于快速搭建实时通信功能,而 Redis 则更适合对性能要求较高、需要自定义配置的场景。
  • 减少不必要的广播事件 :避免频繁触发广播事件,只在必要时发送广播。可以通过批量处理数据或设置合理的触发条件来减少广播事件的数量。
  • 优化频道授权 :对于私有和存在频道,合理设置授权规则,避免不必要的授权请求。可以使用缓存来减少授权检查的次数。
6.2 任务调度性能优化
  • 避免任务重叠 :使用 withoutOverlapping() 方法避免任务相互重叠,确保每个任务按照预定的时间顺序执行。
  • 合理设置任务时间间隔 :根据任务的性质和重要性,合理设置任务的时间间隔。对于一些耗时较长的任务,可以适当增加时间间隔,避免对系统性能造成影响。
  • 监控任务执行情况 :定期检查任务的执行情况,及时发现并处理任务执行过程中出现的问题。可以使用日志记录或监控工具来监控任务的执行状态。
7. 总结与展望

Laravel 的实时通信和任务调度功能为开发者提供了强大的工具,能够轻松实现实时数据更新、用户活动通知、定时任务执行等功能。通过合理使用这些功能,可以提升应用的实时性、交互性和稳定性,为用户带来更好的体验。

在未来的发展中,随着技术的不断进步,Laravel 可能会进一步优化实时通信和任务调度功能,提供更多的特性和工具。例如,支持更多的广播驱动、提供更灵活的任务调度方式等。同时,开发者也可以结合其他技术(如微服务、容器化等),进一步提升应用的性能和可扩展性。

总之,掌握 Laravel 的实时通信和任务调度功能,对于开发高性能、高可用性的 Web 应用具有重要意义。希望本文能够帮助开发者更好地理解和使用这些功能,在实际项目中发挥出它们的最大价值。

8. 附录:常见问题解答

为了帮助开发者更好地使用 Laravel 的实时通信和任务调度功能,下面列出一些常见问题及解答。

问题 解答
为什么我的广播事件没有实时显示在前端? 首先,检查广播驱动的配置是否正确,确保 Pusher 或 Redis 的密钥、ID 等信息设置正确。其次,检查前端代码是否正确监听了广播事件,以及是否有授权问题。最后,检查服务器端是否正确触发了广播事件。
我的定时任务没有按时执行,怎么办? 检查 cron 作业的配置是否正确,确保每分钟都能运行 php artisan schedule:run 命令。其次,检查 app/Console/Kernel.php 中任务的定义是否正确,时间间隔设置是否合理。最后,检查任务执行过程中是否有错误发生,可以查看日志文件获取更多信息。
如何在实时通信中处理大量并发请求? 可以使用 Redis 作为广播驱动,因为 Redis 具有较高的并发处理能力。同时,优化前端代码,减少不必要的请求和数据传输。另外,可以考虑使用负载均衡器来分担服务器的压力。
如何在任务调度中处理任务失败的情况? 可以在任务中添加异常处理机制,当任务执行失败时,记录错误信息并进行相应的处理。例如,可以发送邮件通知管理员,或者重试任务。另外,可以使用 Laravel 的队列系统来处理任务,队列系统具有重试和失败处理的功能。

通过以上常见问题解答,希望能够帮助开发者解决在使用过程中遇到的一些问题。如果还有其他问题,可以参考 Laravel 的官方文档或在社区中寻求帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值