使用 Nexmo 发送和接收 Laravel 通知短信
发布日期:作者: Michael Heap
这是 Michael Heap 的一系列教程的第一部分,介绍如何使用 Laravel 构建多渠道帮助台系统。在这篇文章中,我们将从一个 简单的基于 Web 的帮助台系统 开始,并扩展它以使用内置的 Laravel 通知系统(由 Nexmo 提供支持)发送和接收 SMS 消息。
先决条件
要完成这篇文章,您需要一个 Nexmo 帐户 和已安装并配置的 Nexmo 命令行工具,以及您通常的 Laravel 先决条件。
在文章的后半部分,当您收到 SMS 消息时,我们将从 Nexmo 接收 Webhook,因此您需要以某种方式公开您的本地 Web 服务器。我们建议为此使用 ngrok。
入门
搭建应用程序并生成模型和迁移很繁琐,让您创建表单和视图就更糟糕了。为了让每个人都免受痛苦,我们创建了一个快速入门仓库,您可以克隆并运行它,它为您完成了所有枯燥的工作。
首先 fork Deskmo 仓库,这样您就可以在进行了一些更改后将它们提交回 Github。然后,将本地机器上的 initial-scaffolding
分支克隆到您的本地机器上。此分支包含预生成的模型、迁移和表单。
git clone -b initial-scaffolding https://github.com/<YOUR_USER>/deskmocd deskmo
将仓库放到您的机器上后,将 .env.example
复制到 .env
,并编辑您可能需要为应用程序运行而更改的任何值(例如数据库凭据)。
接下来要做的就是运行 composer install
来获取所有依赖项。这是一个普通的 Laravel 安装,除了添加了 laravelcollective/html
用于表单生成之外。
Composer 完成后,就可以运行应用程序了。我们需要生成一个新的应用程序密钥,运行数据库迁移并启动内置的 Web 服务器
php artisan key:generatephp artisan migratephp artisan serve
要使用该应用程序,您需要 注册一个帐户(别担心,这只是在您的本地数据库中!)。您可能会注意到注册表单有一个额外的字段 phone_number
。这是我们稍后将向其发送 SMS 的号码,因此请确保您以国际格式输入您的号码,省略前导 0(例如 14155550100)。如果您有兴趣了解如何向注册表单添加更多字段,请查看 此提交。
创建帐户会自动登录您并带您到一个页面,上面写着没有票证。单击右上角的新建票证并创建一个新票证。
您的“收件人用户 ID”将是“1”。这可能在某个时候成为一个很好的自动完成,但现在输入用户 ID 也可以。
单击提交后,它应该带您回到票证列表,但这次会显示您刚刚创建的票证。
我们终于可以开始编写代码了!虽然我们有一个允许您创建票证的系统,但它还有很多其他功能。在这篇文章的剩余部分,我们将购买一个 Nexmo 电话号码,在每次创建票证时发送 SMS 通知,并允许客户通过 SMS 回复票证。
购买 Nexmo 电话号码
我们之前提到过我们需要一个 Nexmo 电话号码来接收 SMS 消息,这些消息会作为 Webhook 发送给我们。为了方便人们回复,我们也使用与我们的出站号码相同的号码。
您可以通过 Nexmo 仪表板 购买和配置号码,但今天我们将使用 Nexmo CLI。
要购买号码,我们使用 number:search
命令查找具有 SMS 和语音支持的号码,然后使用 number:buy
命令购买该号码。我们在这里购买的是美国号码,但您可以将 US 替换为 Nexmo 有本地号码的任何 45 个以上国家/地区 中的任何一个。
nexmo number:search US --sms --voicenexmo number:buy <NUMBER> --confirm
记下返回的号码,因为我们很快就会用到它。
配置 Nexmo
要通过 Laravel 的 通知系统 发送 SMS 通知,我们需要使用 composer 安装 nexmo/client
并使用我们的 Nexmo API 密钥和私钥 以及我们刚刚购买的号码来配置我们的应用程序。
通过运行 composer require nexmo/client
来安装 nexmo/client
。接下来,编辑 config/services.php
并添加以下配置选项
'nexmo' => [ 'key' => env('NEXMO_KEY'), 'secret' => env('NEXMO_SECRET'), 'sms_from' => env('NEXMO_NUMBER'),],
最后,编辑 .env
并提供您的 API 密钥、私钥和 Nexmo 号码。它应该如下所示
NEXMO_KEY=<YOUR_KEY>NEXMO_SECRET=<YOUR_SECRET>NEXMO_NUMBER=<YOUR_NUMBER>
这就是将 Nexmo 集成到 Laravel 的通知系统所需的全部操作 - 一个 composer install
和三个配置值。
发送通知
我们现在可以发送通知了,但我们还没有要发送的通知!通过运行 php artisan make:notification TicketCreated
生成一个新的通知,然后打开您新创建的文件(app/Notifications/TicketCreated.php
)。
首先要更改的是第 32 行,via
函数返回 mail
。我们不想通过电子邮件发送它,因此我们将值更改为 nexmo
以将其作为 SMS 发送。
通知类知道如何为电子邮件格式化消息,但不知道如何为 SMS 格式化消息。为了解决这个问题,添加一个具有以下内容的新方法
public function toNexmo($notifiable){ return (new NexmoMessage) ->content($this->entry->content);}
此方法使用两个新对象 - NexmoMessage
和 $this->entry
。现在让我们将它们添加到类中,方法是将以下内容添加到您的导入中
use App\TicketEntry;use Illuminate\Notifications\Messages\NexmoMessage;
您还需要更新您的 __construct()
方法以接受 TicketEntry
并将 $entry
添加为类变量
protected $entry; public function __construct(TicketEntry $entry){ $this->entry = $entry;}
我们现在有一个可以发送给用户的通知,但我们还没有完全完成,因为我们不知道需要将其发送给谁。
一个票证可以有多个订阅用户,一个用户可以订阅多个票证。要公开这种关系,我们需要在我们的 Ticket
类中添加一个新方法。我们将 ticket_subscriptions
指定为第二个参数,因为这是一个非标准的中间表名称(这些用户不拥有票证,他们只是订阅了该票证的通知)
public function subscribedUsers(){ return $this->belongsToMany(User::class, 'ticket_subscriptions');}
完成此操作后,打开 TicketController
并将以下内容添加到您的导入列表中,以便我们可以发送我们刚刚创建的通知
use App\Notifications\TicketCreated;use Notification;
最后,我们需要实际发送通知。在 store
方法的底部,在我们重定向回索引页面之前,添加以下代码
Notification::send($ticket->subscribedUsers()->get(), new TicketCreated($entry));
这将获取所有订阅了该工单的用户,并为新创建的条目分发一个新的TicketCreated
通知。保存您的更改,然后尝试创建一个新的工单,接收者 ID 为 1(应该是您的帐户)。您应该在几秒钟内收到包含条目内容的短信。
恭喜!我们刚刚在应用程序中添加了短信通知。配置好所有内容后,它非常简单,只需四个步骤。
- 使用
php artisan make:notification
生成一个通知。 - 更改
via
方法以使用nexmo
。 - 添加一个
toNexmo()
方法,以便 Laravel 知道如何渲染消息。 - 在我们的应用程序中添加
Notification::send()
来发送通知。
此部分的代码在 Github 上的 send-sms-notification 分支 上。
接收短信回复
所以,我们的客户已经收到了工单内容的短信,接下来呢?他们必须登录到电脑才能回复工单吗?
实际上,我们还没有让他们回复的方法,如果我们不得不花时间构建一些东西,让我们尽可能地简化他们的操作——让我们添加一种让他们通过短信回复的方法。
这篇文章假设您的 Webhook HTTP 方法已在您的 Nexmo 设置 中设置为 POST。默认情况下它设置为 GET,所以去检查一下吧!
当 Nexmo 收到发送到您手机号码的短信时,他们会向您的应用程序发送一个 HTTP 请求。不幸的是,您的应用程序目前正在您的本地机器上运行,Nexmo 无法访问它。这就是ngrok
发挥作用的地方。如果您还没有安装它,阅读这篇介绍,然后在安装完成后回来。
要将您的本地服务器公开到互联网,请运行ngrok http 8000
。这将启动一个ngrok
会话,其中包含大量信息。您唯一需要的是您的ngrok
主机名,它将在单词Forwarding旁边,看起来像http://abc123.ngrok.io
。
Session Status onlineAccount Michael (Plan: Free)Version 2.2.8Region United States (us)Web Interface http://127.0.0.1:4040Forwarding http://abc123.ngrok.io -> localhost:8000Forwarding https://abc123.ngrok.io -> localhost:8000 Connections ttl opn rt1 rt5 p50 p90 6 0 0.00 0.00 0.11 0.78
获得ngrok
URL 后,您需要使用 Nexmo CLI 工具告诉 Nexmo 您的公共 URL 是什么。运行以下命令,将占位符替换为您的 Nexmo 号码和ngrok
URL。
nexmo link:sms <NEXMO_NUMBER> http://<NGROK_URL>/ticket-entry
这会将 Nexmo 配置为将该号码的所有 Webhook 发送到http://<NGROK_URL>/ticket-entry
。让我们快速测试一下,以确保一切配置正确。打开TicketEntryController
,并将store
方法替换为以下内容。
public function store(Request $request){ error_log(print_r($request->all(), true));}
这将在我们运行php artisan serve
的终端控制台中记录所有传入的请求,但它现在还不能工作。在我们可以测试我们的短信接收器之前,还需要进行一项更改——我们需要为ticket-entry
路由禁用 CSRF 保护。
默认情况下,Laravel 通过在每个表单提交时验证 CSRF 令牌来保护我们免受跨站点攻击(这是件好事!),但 Nexmo 不知道要使用的令牌。要解决这个问题,请编辑Http/Middleware/VerifyCsrfToken.php
并将ticket-entry
添加到$except
数组中,如下所示。
protected $except = [ '/ticket-entry'];
如果您现在回复之前收到的其中一条短信,您应该在屏幕上看到一个信息数组(可能需要一分钟才会出现)。
( [msisdn] => 14155550201 [to] => 14155550100 [messageId] => 0C0103008660C470 [text] => This is a test [type] => text [keyword] => THIS [message-timestamp] => 2018-01-09 02:11:40 [timestamp] => 1515464263 [nonce] => e143f53f-dev2-42f5-9103-f81e56a406c3 [sig] => ef52cbcabcc2e631eff3853ec1bfe38b)
这意味着我们的集成配置正确,并且该号码每收到一条短信,Nexmo 就会向我们的应用程序发送一个 HTTP 请求。
最后要做的是用一些代码替换print_r
语句,这些代码会将条目添加到工单中。由于 SMS 消息没有回复的概念,我们所能做的就是查找用户正在关注的具有最新活动的工单,并假设他们的回复是针对该工单的。
由于这种逻辑相当复杂,让我们将其添加到我们的User
模型中以供将来重用。打开app/User.php
并添加以下方法。
public function latestTicketWithActivity() { // Get all tickets this user is watching $watchedTickets = TicketSubscription::select('ticket_id')->where('user_id', $this->id)->get()->pluck('ticket_id'); // Grab the latest ticket with activity that's in this list $latestTicketEntry = TicketEntry::select("ticket_id")->whereIn('ticket_id', $watchedTickets)->orderBy('created_at', 'desc')->limit(1)->first(); // Fetch the actual ticket return $latestTicketEntry->ticket()->first();}
这将返回具有给定用户的最新TicketEntry
的Ticket
对象。这就是我们将传入消息附加到工单所需的一切。
回到TicketEntryController
,将print_r
行替换为以下代码,这将根据用户的电话号码查找用户,获取最新的工单,并将一个包含传入消息内容的新条目附加到该工单。
// Make sure it's in the correct format$data = $this->validate($request, [ 'msisdn' => 'required', 'text' => 'required']); // Find the user based on their phone number$user = User::where('phone_number', $data['msisdn'])->firstOrFail(); // And then find their latest ticket$ticket = $user->latestTicketWithActivity(); // Create a new entry with the incoming SMS content$entry = new TicketEntry([ 'content' => $data['text'], 'channel' => 'sms',]); // Attach this entry to the user and ticket, then save$entry->user()->associate($user);$entry->ticket()->associate($ticket);$entry->save(); return response('', 204);
最后,我们需要为我们在上面代码中使用的类添加两个额外的导入。
use App\User;use App\TicketEntry;
保存并发送短信回复,然后刷新最新的工单,您将看到您的消息出现了。
接收短信比发送短信还要容易!我们从零能力到通过 Webhook 回复短信,只用了五个小步骤。
- 为一个号码配置 Nexmo Webhook。
- 运行
ngrok
公开您的本地系统。 - 为 Webhook 终结点禁用 CSRF 保护。
- 获取最新的活动工单。
- 将传入消息保存为一个新条目。
此部分的代码在 Github 上的 receive-sms-reply 分支 上。
结论
在这篇文章中,我们介绍了通过 Laravel 通知系统和 Nexmo 发送通知,以及接收传入的短信并将它们存储在数据库中。
在本系列的下一部分中,我们将研究如何将语音通话支持添加到我们的帮助台应用程序。响铃的电话比短信更难忽视,对于时间敏感的问题,它可能是正确选择。
如果您想要一些 Nexmo 积分来完成这篇文章并测试平台,请通过 [email protected] 联系我们,并引用 LaravelNews,我们会帮您解决这个问题。
如果您有任何想法或问题,请随时通过 Twitter 上的 @mheap 或 [email protected] 联系我们。
非常感谢 Nexmo 在 Laravel News 上赞助本教程系列。
Michael 是 Nexmo 的 PHP 开发人员倡导者。他使用各种语言和工具,在用户组和会议上与来自世界各地的观众分享他的技术专长。当他抽出时间编写代码时,他喜欢减少系统中的复杂性,使其更加可预测。