在前段时间发布的 ubisend 中,急需一个方案解决用户可以邀请其他人员管理其账号的问题。
想想当下的 CMS 系统,如果只有一个用户拥有发布内容的权限将不能使得系统物尽其用。当然你也不希望通过共享密码的方式让其他用户可发布内容。
同样地,假设当你在搭建一个多租户应用程序,同时想实现让你的用户拥有邀请其他用户加入他们团队的功能。该怎么做呢?
最直接的当然是使用各种增删改查来管理用户,但相比于邮件邀请,允许这些用户设置自己的密码安全性较低。
基石
在多种实现方法中,最简单的是——创建一个新用户记录(未激活的),然后存储跟激活操作相关联的 token 值。
然而,这里我们将介绍另一种方法。通过添加额外的 invite 表——存储用户邮箱地址和用于激活的 token 值来解决这一问题。激活后,将使用该表中的数据来创建新用户。
全新安装 laravel5.4 后,先配置好数据库和邮箱等设置。
迁移
框架安装时自动添加了 user 表的迁移文件,现在只要将其中的 name 和 password 字段删除便可,删除完文件内容如下所示。
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('email')->unique();
$table->rememberToken();
$table->timestamps();
});
}
在控制台项目的根目录下执行命令(接下来简称为执行命令):php artisan make:migration create_invites_table
,创建 invite 表的迁移文件。
编辑 database/migrations
目录下的 create_invite_table.php
。添加自增 ID,用户邮箱和用于唯一识别接受邀请用户的 token 等字段。
public function up()
{
Schema::create('invites', function (Blueprint $table) {
$table->increments('id');
$table->string('email');
$table->string('token', 16)->unique();
$table->timestamps();
});
}
public function down()
{
Schema::drop('invites');
}
执行命令:php artisan migrate
。随之,数据库将完成迁移。
模型
我们需要创建 Eloquent 模型来管理团队和用户。但 laravel 默认含 user 表的 Eloquent 模型,所以这里不需要再创建了。
执行命令:php artisan make:model Invite
,创建 invite 表的模型。
按照下述在 app
目录下的 invite.php
中编辑白名单,从而实现批量创建和更新数据表数据。
protected $fillable = [
'email', 'token',
];
路由
本次教程中,将为接下来的情况定义三个路由。
- 显示邀请新用户的表单页面
- 处理所提交的表单
- 接受邀请
在 app/routes/web.php
文件中,添加如下路由:
Route::get('invite', 'InviteController@invite')->name('invite');
Route::post('invite', 'InviteController@process')->name('process');
// {token} 必须传递给控制器中方法的参数
Route::get('accept/{token}', 'InviteController@accept')->name('accept');
控制器
眼尖的读者会发现,上述的路由中都是通过 InviteController
来处理所有请求。
执行命令:php artisan make:controller InviteController
,创建控制器。
在 app/Http/Controllers/InviteController.php
文件中定义这些方法:
public function invite()
{
// 为用户展示填写邀请邮箱的表单
}
public function process()
{
// 处理用户所提交的表单和发送邮件到邀请邮箱
}
public function accept($token)
{
// 可通过 URl 中提供的 token 值来查找用户
}
这样一来,万事齐全。只要我们理清逻辑填写上述方法便可实现用户邀请系统了!
业务逻辑
接下来将逐一阐述每个方法的实现逻辑。
invite()
这段代码比较简单,只需要给使用者一个可以填写被邀请者邮箱的表单即可。
类似于:
public function invite()
{
return view('invite');
}
新建 resources/views/invite.blade.php
,视图中的表单通过 POST 方法将填入的
email 发送到路由 invite
上。
// 使用命名的路由,以防 URL 发生变化
// 表单不会中断
<form action="{{ route('invite') }}" method="post">
{{ csrf_field() }}
<input type="email" name="email" />
<button type="submit">Send invite</button>
</form>
process()
注意,前方高能。这是整个流程的核心方法!
前提:使用 Laravel 的 mailables 来给新用户发送邀请通知。
首先,执行命令 php artisan make:mail InviteCreated
,创建 maliable 类。
编辑 app/Mail
目录下 InviteCreated
文件。修改该类中的构造方法,依赖注入 Invite 模型并且将其设置为 public。
use App\Invite;
public function __construct(Invite $invite)
{
$this->invite = $invite;
}
接着设置邮件是谁发送的,邮件发送的内容。
public function build()
{
return $this->from('you@example.com')
->view('emails.invite');
}
resources/views/invite.blade.php
视图的简单例子:
<p>Hi,</p>
<p>Someone has invited you to access their account.</p>
<a href="{{ route('accept', $invite->token) }}">Click here</a> to activate!
你可能会担心在视图中如何获得 $invite
的值。放心吧,laravel 它会将 mailable
类中 public 属性的值自动传递到视图上。
回到 InviteController
,我们需要生成当新用户接受邀请后用来识别其身份的唯一随机数——token。然后,将该 token 和用户输入的 email 值将存到 invite 表中。
use App\Invite;
use App\Mail\InviteCreated;
use Illuminate\Support\Facades\Mail;
...
public function process(Request $request)
{
// 验证请求中的数据
do {
// 使用 laravel 的 str_random() 辅助方法来生成token随机数
$token = str_random();
} //校验 token 值若已经存在,则重新生成
while (Invite::where('token', $token)->first());
// 在 Invite 表中创建新纪录
$invite = Invite::create([
'email' => $request->get('email'),
'token' => $token
]);
// 发送邮件
Mail::to($request->get('email'))->send(new InviteCreated($invite));
// 返回上一级
return redirect()
->back();
}
因此,新用户不仅接收到通知,数据还被存储在 invite 表中。
accept()
最后,还差一个方法来处理新用户接收邀请的逻辑。
通常情况下,你想要获取密码和其他可能会用到的用户数据。但是,现在只要把流程走通便可,怎么简单怎么来吧。逻辑为——基于验证 token 值来决定是否创建新用户。
记住,URL 中的 token 必须作为参数传回。
use App\User;
use App\Invite;
use App\Mail\InviteCreated;
use Illuminate\Support\Facades\Mail;
...
public function accept($token)
{
// 查找邀请记录
if (!$invite = Invite::where('token', $token)->first()) {
//if the invite doesn't exist do something more graceful than this
abort(404);
}
// 根据 invite 表中的数据创建新用户
User::create(['email' => $invite->email]);
// 删除 invite 表中的该条记录
$invite->delete();
// 这里你可能会实现让用户登录,然后让其进入首页的功能。但是现在我们只要确保它能顺利执行
return 'Good job! Invite accepted!';
}
运行
在浏览器中点击邀请网址 ( 例如 http://example.com/invite ),输入邀请对象邮箱并提交表单。
打开 invite 表将看到一条含唯一 token 值的新纪录将被创建。打开邮箱将会发现含数据库中已存在的 token 值的激活链接。
当点击完激活链接后将会弹出“干的漂亮!邀请被接收!”的标志。再校验一下,是否按预期流程处理。数据库中 invite 表的相应记录被删除,相反地,user 表将创建一条新记录。
扩展
你已经成功实现了用户邀请系统。
虽然这是一个简单的例子,但是它奠定了良好的基础。
可基于该例子扩展当用户接收邀请时捕获用户信息以及拒绝和重新发送邀请等功能。
另外,你也可修改代码从而支持多租户或团队/用户关系——虽然这实现起来有点复杂。但是相信没有什么是不能通过你的智慧去实现的。