使用 Nexmo 和 Laravel 实现实时消息

发布日期:作者:

Real-time messaging with Nexmo and Laravel image

欢迎回到 Deskmo 系列的第三部分,也是(目前)最后一部分。在 第一部分 中,我们构建了一个支持通过 SMS 发送和接收消息的帮助台应用程序。 第二部分 添加了对语音通话的支持,包括文本转语音和转录功能。

今天,我们将使用 Nexmo Stitch 添加应用内消息。Stitch 负责所有实时聊天的繁重工作,为您提供一个 WebSocket,您可以连接到它并监听与您的应用程序相关的事件。虽然我们今天使用 JavaScript,但 Stitch 在 Web、iOS 和 Android 上运行,这意味着您可以使用一项服务在三个不同的平台上支持实时消息!

先决条件

要完成本文的步骤,您需要一个 Nexmo 帐户和本系列第二部分的 Deskmo 应用程序,以及您通常的 Laravel 先决条件。

由于 Stitch 目前处于 开发者预览版,您需要安装 Nexmo CLI 和 Nexmo PHP 客户端的 beta 版本。在与 composer.json 相同的目录中运行以下命令来安装它们

npm install -g nexmo-cli@beta
composer require nexmo/client:1.3.0-beta5

我们还需要在终端中运行 php artisan serve 来启动我们的 Laravel 应用程序。

创建 Nexmo 用户

要使用 Stitch 的应用内消息,我们需要为每个用户创建一个 Nexmo 用户配置文件。为了确保所有用户都拥有 Nexmo 用户配置文件,我们将在注册流程中为每个用户创建配置文件。

需要注意的是,Nexmo 用户配置文件是一个概念,它可以帮助您通过将 Nexmo 配置文件链接到您的本地用户配置文件来跟踪您的用户。这不会为您的用户创建 Nexmo 帐户。

为此,我们需要编辑我们的 users 表以包含一个 nexmo_id 列,并更新 Auth\RegisterController@create 方法,以向 Nexmo 发出 API 调用并存储返回的 ID。让我们从创建迁移开始 - 运行 php artisan make:migration add_nexmo_id_to_users,并将创建的文件填充以下内容

public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('nexmo_id');
});
}
 
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('nexmo_id');
});
}

接下来,我们需要将 nexmo_id 属性添加到我们的 User 模型中。打开 app/User.php 并将 nexmo_id 添加到文件顶部的 $fillable 数组中。这将允许我们在 RegisterController 中创建用户时传递 nexmo_id

最后,是时候更新 app/Http/Controllers/Auth/RegisterController.php 来向 Nexmo 发出请求了。让我们从在文件顶部导入所有需要的类开始

use Nexmo;
use Nexmo\User\User as NexmoUser;

编辑 create 方法,创建一个新的 NexmoUser 对象,并使用 Nexmo::User()->create() 将用户发送到 Nexmo - 我们使用客户的电子邮件作为其配置文件名称,因为它保证是唯一的,而他们的真实姓名则不一定是唯一的。发出请求后,最后要做的就是在 User::create 语句中添加一行来保存 ID。

$user = (new NexmoUser())->setName($data['email']);
$nexmoUser = Nexmo::user()->create($user);
 
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'phone_number' => $data['phone_number'],
'nexmo_id' => $nexmoUser->getId(),
]);

小心!在下一段中,我们运行 migrate:refresh,这将重置您的数据库。我们需要这样做,因此请备份您可能想要保留的任何数据。

从现在开始,我们的应用程序中的任何新用户也将注册到 Nexmo。让我们现在尝试一下,通过运行 php artisan migrate:refresh 来销毁并重新创建我们的数据库。运行该命令后,访问 注册页面 并创建一个新帐户,以创建一个也具有 Nexmo ID 的 Deskmo 用户。此用户将是我们的帮助台代理(用户 ID 1)。您还需要创建一个第二个帐户,它将充当我们的客户(用户 ID 2)。

将应用内消息添加为通知方法

现在我们已经为用户创建了 Nexmo 配置文件,是时候更新工单创建流程,以允许代理选择应用内消息作为通知方法了。更新 resources/views/ticket/create.blade.php 以在我们的 voice 选项下方添加一个新的单选按钮

<div class="radio">
<label>
<input type="radio" name="notification_method" value="in-app-messaging">
In-App Messaging
</label>
</div>

完成此操作后,我们需要更新 app/Http/Controllers/TicketController.php 以添加另一个 elseif 块,该块检查代理是否选择了 in-app-messaging 作为通知方法(它将位于大约第 100 行)。

} elseif ($data['notification_method'] === 'in-app-messaging') {
// Trigger In-App Messaging
} else {
throw new \Exception('Invalid notification method provided');
}

我们的帮助台代理现在可以选择应用内消息作为通知方法,但它实际上不会做任何事情。要开始对话,我们需要告诉 Nexmo 我们希望在创建工单时指定的代理和用户之间进行对话。

我们从在文件顶部导入要使用的类开始

use Nexmo\Conversations\Conversation;

然后,我们可以创建对话并将我们的用户添加为参与者

} elseif ($data['notification_method'] === 'in-app-messaging') {
$conversation = (new Conversation())->setDisplayName('Ticket '.$ticket->id);
$conversation = Nexmo::conversation()->create($conversation);
 
// Add the users to the conversation
$users = Nexmo::user();
$conversation->addMember($users[$user->nexmo_id]);
$conversation->addMember($users[$cc->user->nexmo_id]);
}

此时,我们有一个对话,并且我们的两个用户被添加为参与者。我们需要在构建聊天界面时引用此对话,因此让我们将其存储在刚创建的 Ticket 中的数据库中。

我们需要创建一个迁移以添加一个名为 conversation_id 的新 nullable 字段。该字段是 nullable,因为并非每个工单都支持应用内消息。

php artisan make:migration add_conversation_id_to_ticket
public function up()
{
Schema::table('tickets', function (Blueprint $table) {
$table->string('conversation_id')->nullable();
});
}
 
public function down()
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn('conversation_id');
});
}

运行 php artisan migrate 来创建该列,然后再次编辑 app/Http/Controllers/TicketController.php 以将对话 ID 保存到我们的工单,方法是在 $conversation->addMember($users[$cc->user->nexmo_id]); 后添加以下代码

$ticket->conversation_id = $conversation->getId();
$ticket->save();

最后要做的就是在我们的工单列表中添加一个小通知,如果应用内消息可用,则会显示该通知。打开 resources/views/ticket/index.blade.php 并将以下单元格添加到表格中

<td>{{ $ticket->conversation_id ? "Live" : "" }}</td>

您可能还需要在标题中添加一个 <th>,以使内容排列整齐。如果刷新工单列表,您将在任何使用应用内消息的工单旁边看到 Live 一词。

安装 Stitch SDK

我们已经完成了所有后台工作,使我们能够使用应用内消息,但我们的用户仍然无法执行任何操作。让我们现在通过在我们的应用程序中添加一个实时聊天框来解决这个问题!Nexmo 通过 NPM 提供了一个预先构建的 SDK,您可以将其集成到您的应用程序中,该 SDK 为您完成了大部分工作

npm install nexmo-conversation --save-dev

这会将库安装到我们的 node_modules 文件夹中,该文件夹无法被我们的应用程序访问。我们需要更新我们的 Laravel Mix 配置以将该文件复制到 public/js 中。编辑 webpack.mix.js 并添加 conversationClient.js,使该文件看起来像以下内容

mix.js('resources/assets/js/app.js', 'public/js')
.js('node_modules/nexmo-conversation/dist/conversationClient.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');

我们将在下一节中更改我们的 Javascript,因此让我们通过在新的终端窗口中运行 npm run watch 来设置项目的自动重建。

最后,我们需要将 conversationClient.js 包含在我们的 HTML 页面中。编辑 resources/views/layouts/app.blade.php,并在包含 js/app.js 之前添加 <script src="{{ asset('js/conversationClient.js') }}"></script>

创建聊天界面

为了创建我们的聊天室,我们需要编辑resources/views/ticket/show.blade.phpapp/Http/Controllers/TicketController.php。让我们从填充TicketController::show方法开始,该方法包含连接到我们对话所需的所有信息。之前我们传入$ticket,因为这是我们唯一需要的,但要连接到Stitch,我们还需要从页面读取JSON Web Token (JWT)和对话 ID。

如果您不熟悉JWT,这看起来会有点混乱,因此我会先向您展示整个内容,然后逐行讲解。

return view('ticket.show', [
'ticket' => $ticket,
'user_jwt' => Nexmo::generateJwt([
'exp' => time() + 3600,
'sub' => Auth::user()->email,
'acl' => ["paths" => ["/v1/sessions/**" => (object)[], "/v1/users/**" => (object)[], "/v1/conversations/**" => (object)[]]],
]),
'conversation_id' => $ticket->conversation_id,
]);

这段代码执行以下操作:

  • 传递ticket信息。
  • 生成包含以下内容的JWT (user_jwt):
    • 当前时间加 1 小时的过期时间 (exp)。
    • 要验证的用户 (sub)。
    • JWT 有效的路径。在本例中,我们需要使用会话、用户和对话 (acl)。
  • 最后,传递conversation_id

生成的JWT将为所有用户提供对这些路径的无限访问权限。在现实世界中,我们希望限制用户的访问权限,以便他们无法使用JWT向对话中添加/删除用户。

这是Nexmo SDK连接到Stitch并开始发送消息所需的所有信息。

除了连接到API,我们还需要提供一个界面,以便通过我们的应用程序添加新的回复。打开resources/views/ticket/show.blade.php,并在.panel-body的结束div标签后添加以下内容,以创建一个表单,我们可以用它添加回复。

@if ($conversation_id)
<div class="panel-body">
<form action="" method="POST" id="add-reply">
<div class="form-group">
<label for="reply">Add a reply</label>
<textarea class="form-control" id="reply" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary mb-2" style="display:none;" id="reply-submit">Save</button>
</form>
</div>
@endif

除了创建表单,我们还必须使user_jwtconversation_idticket_id ID对Nexmo SDK可访问。为此,请在@endsection行之前添加以下内容,以创建三个稍后可以读取的JavaScript常量。

<script>
const USER_JWT = '{{$user_jwt}}';
const CONVERSATION_ID = '{{$conversation_id}}';
const TICKET_ID = '{{$ticket->id}}';
</script>

我们的应用程序的HTML部分都完成了!只剩下最后一件事要做,那就是编写一些JavaScript代码来将所有内容粘合在一起。打开resources/assets/js/app.js,并将其中的所有内容替换为以下内容。

require('./bootstrap');
 
if (typeof CONVERSATION_ID !== "undefined" && CONVERSATION_ID !== "") {
var replyInput = $("#reply");
// Use the JWT we defined to log in to Stitch
new ConversationClient({debug: false}).login(USER_JWT).then(app => {
// Connect to the conversation using the ID we provided earlier
app.getConversation(CONVERSATION_ID).then((conversation) => {
// Once the conversation is loaded, show the submit button
$("#reply-submit").show();
 
// Add an event listener so that whenever we receive a `text` event
// we add the text to our list of responses
conversation.on('text', (sender, message) => {
$(".panel-body:first").append("<strong>" + sender.user.name + " / web / In-App Message</strong><p>"+message.body.text+"</p><hr />");
})
 
// Add a listener to the form and prevent it submitting via HTTP POST
// Instead, send it via the Nexmo SDK using conversation.sendText()
$("#add-reply").submit(() => {
conversation.sendText(replyInput.val()).then(console.log).catch(console.log)
replyInput.val("");
return false;
});
});
});
}

这是使应用程序内消息传递正常工作所需的所有JavaScript代码!保存文件,然后打开一个使用应用程序内消息传递作为通知方法的工单进行测试。您在文本区域中输入的任何内容都将通过WebSocket发送到Nexmo,然后分发到所有其他连接。通过打开另一个浏览器、以第二个用户的身份登录并浏览相同的工单来进行测试。您在一个窗口中添加的任何内容都会立即显示在另一个窗口中。

现在我们已经实现了应用程序内聊天功能,是时候添加一些使界面更易于使用的细节,例如“有人正在输入”指示器。Nexmo也会为您处理所有这些内容 - 您只需要在正确的时间触发正确的事件。我们假设当文本输入处于焦点状态时,有人正在输入,并且当文本输入失去焦点时,他们停止输入。要启用此功能,请在$("#reply-submit").show();之后添加以下内容。

// We assume they're typing when the input is focused
replyInput.focus(() => conversation.startTyping().then(console.log).catch(console.log));
replyInput.blur(() => conversation.stopTyping().then(console.log).catch(console.log));
 
// Create an element to hold our typing message
let typingIndicator = $("<div>");
 
// Someone's typing, show the typingIndicator
conversation.on("text:typing:on", data => {
typingIndicator.text(data.user.name + " is typing...");
replyInput.after(typingIndicator);
});
 
// They stopped typing, remove the typingIndicator
conversation.on("text:typing:off", data => {
typingIndicator.remove();
});

这只是Nexmo可以处理的实用程序事件的一个示例。当成员加入和离开对话时,当消息被看到时,以及当对话详细信息被更新时,都会有事件。您可以在Stitch文档中了解更多有关可用事件的信息。如前所述,Stitch目前处于开发者预览阶段,因此如果您有兴趣了解更多有关可用事件的信息,请随时加入Nexmo社区Slack频道,与我们聊聊。

持久化回复

您可能已经注意到,我们的应用程序内消息传递解决方案存在一个主要问题 - 当我们刷新页面时,我们的对话会消失!谢天谢地,我们有两个选项可以帮助解决这个问题。

  1. 每次添加新消息时,向我们的TicketEntry端点发送一个POST请求,并将它们持久化到数据库中。
  2. 使用Nexmo SDK中的conversation.getEvents()方法列出对话中到目前为止的所有事件,并在页面加载时重建应用程序状态。

由于我们已经将SMS和语音回复存储在数据库中,因此我们将使用选项#1。要保存请求,我们需要再次编辑app.js。Laravel捆绑了axios,所以让我们用它向/ticket-entry发送一个包含发送者的nexmo_id、消息text和当前ticket_id的HTTP请求。将其直接添加到conversation.on('text')之后。

conversation.on('text', (sender, message) => {
axios.post('/ticket-entry', {
"nexmo_id": sender.user.id,
"text": message.body.text,
"ticket_id": TICKET_ID
});

这将处理将数据发送到我们的API,但目前该端点只适合来自Nexmo的入站SMS请求。现在让我们编辑app/Http/Controllers/TicketEntryController.php,并在store方法中添加对应用程序内消息的支持。

首先需要更改的是我们的验证。我们之前期望msisdn作为我们的标识符,但现在我们期望msisdnnexmo_id。Laravel提供了required_without_all验证器来帮助我们实现这一点。

$data = $this->validate($request, [
'nexmo_id' => 'required_without_all:msisdn',
'ticket_id' => 'required_without_all:msisdn',
'msisdn' => 'required_without_all:nexmo_id',
'text' => 'required'
]);

接下来,我们需要根据是否有用户的phone_numbernexmo_id来加载用户。在做这些事情的同时,我们也会获取最新的工单或使用请求中发送的工单。最后,我们将设置消息接收到的渠道。

if (isset($data['msisdn'])) {
$user = User::where('phone_number', $data['msisdn'])->firstOrFail();
$ticket = $user->latestTicketWithActivity();
$channel = 'sms';
} else {
$user = User::where('nexmo_id', $data['nexmo_id'])->firstOrFail();
$ticket = Ticket::findOrFail($data['ticket_id']);
$channel = 'web';
}

由于我们使用的是Ticket模型,因此我们也需要在文件顶部导入它。

use App\Ticket;

一旦处理完毕,我们只需要更新我们的TicketEntry以使用$channel变量,而不是硬编码sms

$entry = new TicketEntry([
'content' => $data['text'],
'channel' => $channel,
]);

我们的端点现在将支持入站SMS消息应用程序内消息。通过发送应用程序内消息来试一试,观察它如何在所有浏览器中同时出现,然后尝试刷新页面以查看从数据库中填充的所有条目。

结论

在这篇文章中,我们在不到100行代码中将实时消息传递功能添加到了我们的应用程序中。这要归功于Stitch,它为我们处理了核心功能,例如发送消息,以及您的存在指示器。如果您想了解更多关于Stitch的信息,请查看Nexmo开发者网站上的Stitch文档

遗憾的是,这将我们带到了Deskmo系列的结尾(目前!)。总之,我们构建了一个帮助台系统,它允许您使用SMS、语音或应用程序内消息与客户进行通信。如果您想自己尝试运行它,可以在Github上找到最终代码。

如果您想要一些Nexmo积分来完成这篇文章并测试平台,请通过[email protected]联系我们,并引用LaravelNews,我们会为您解决这个问题。

如果您有任何想法或问题,请随时联系Twitter上的@mheap或[email protected]


非常感谢Nexmo本周赞助Laravel News。

Michael Heap photo

Michael是Nexmo的PHP开发者倡导者。他使用各种语言和工具,在用户组和会议上向世界各地的观众分享他的技术专长。当他找到时间编写代码时,他喜欢降低系统复杂性并使它们更具可预测性。

分类于
Cube

Laravel 新闻

加入40,000多名其他开发者,绝不错过新的技巧、教程等等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的Laravel应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel开发者必备的代码运行器。使用AI、自动完成和本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

无妥协

Joel和Aaron,来自无妥协播客的两名经验丰富的开发者,现在可以为您的Laravel项目提供服务。 ⬧ 固定费率$7500/月。 ⬧ 没有冗长的销售流程。 ⬧ 没有合同。 ⬧ 100%退款保证。

无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保您的Web应用程序取得成功。

Kirschbaum
Shift logo

Shift

运行旧版本的Laravel?即时、自动化的Laravel升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

只需$2500/月,即可为您的项目配备一位拥有4-6年经验的资深Laravel开发者。获得160小时的专业技术支持和15天的无风险试用。立即预约电话!

Bacancy
Lucky Media logo

Lucky Media

现在就幸运 - Laravel开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel的电子商务。一个开源软件包,将现代无头电子商务功能的强大功能带到Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方的Laravel招聘网站

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动套件

SaaSykit是一个Laravel SaaS启动套件,包含运行现代SaaS所需的所有功能。支付、美丽的结账流程、管理面板、用户仪表板、身份验证、现成组件、统计数据、博客、文档等等。

SaaSykit: Laravel SaaS 启动套件
Rector logo

Rector

您无缝升级Laravel、降低成本和加速创新的合作伙伴,助力企业取得成功。

Rector
MongoDB logo

MongoDB

通过MongoDB和Laravel的强大集成增强您的PHP应用程序,使开发者能够轻松高效地构建应用程序。在使用熟悉的Eloquent API的同时,支持事务、搜索、分析和移动用例。探索灵活、现代的MongoDB数据库如何改变您的Laravel应用程序。

MongoDB

最新内容

查看全部 →
Maska is a Simple Zero-dependency Input Mask Library image

Maska是一个简单的零依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

在您的Laravel应用程序中添加Swagger UI

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在Laravel 11.19中断言响应的精确JSON结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用PHP和Laravel提示构建SSH应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用Laravel和Typesense构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用Commenter软件包向您的Laravel应用程序添加评论

阅读文章