1.验证码
-
- 问题说明
我们的注册功能存在一个问题,因我们表单未添加任何防护,恶意用户可以轻易使用机器人自动化注册新用户。机器人自由注册,对我们站点稳定性来讲是巨大的威胁,恶意用户可以很轻易的通过机器人程序在短时间内,注册大量用户,甚至于填满我们的数据库。
-
- 验证码
验证码 是防止恶意破解密码、刷票、论坛灌水、刷页的手段。验证码有 多种类型。 本项目中我们将使用图片验证码,其原理是让用户输入一个扭曲变形的图片上所显示的文字或数字,扭曲变形是为了避免被光学字符识别软件(OCR)自动辨识。由于计算机无法识别验证码的图片,所以回答出问题的用户就可以被认为是人类。
接下来我们将使用验证码来防卫的用户注册功能。
-
- 安装扩展包
我们将以第三方扩展包 mews/captcha 作为基础来实现 Laravel 中的验证码功能。
使用 Composer 安装:(需要打开fileinfo扩展)
composer require "mews/captcha:~2.0"
修改config/app.php,在providers添加
Mews\Captcha\CaptchaServiceProvider::class,
'providers' => [
// ...
Mews\Captcha\CaptchaServiceProvider::class,
]
在aliases添加
'Captcha' => Mews\Captcha\Facades\Captcha::class,
'aliases' => [
// ...
'Captcha' => Mews\Captcha\Facades\Captcha::class,
]
运行以下命令生成配置文件 config/captcha.php:
php artisan vendor:publish
然后选择7
我们可以打开配置文件,查看其内容:
config/captcha.php
<?php
return [
'characters' => '2346789abcdefghjmnpqrtuxyzABCDEFGHJMNPQRTUXYZ',
'default' => [
'length' => 5,
'width' => 120,
'height' => 36,
'quality' => 90,
],
'flat' => [
'length' => 6,
'width' => 160,
'height' => 46,
'quality' => 90,
'lines' => 6,
'bgImage' => false,
'bgColor' => '#ecf2f4',
'fontColors'=> ['#2c3e50', '#c0392b', '#16a085', '#c0392b', '#8e44ad', '#303f9f', '#f57c00', '#795548'],
'contrast' => -5,
],
'mini' => [
'length' => 3,
'width' => 60,
'height' => 32,
],
'inverse' => [
'length' => 5,
'width' => 120,
'height' => 36,
'quality' => 90,
'sensitive' => true,
'angle' => 12,
'sharpen' => 10,
'blur' => 2,
'invert' => true,
'contrast' => -5,
]
];
可以看到这些配置选项都非常通俗易懂,characters 选项是用来显示给用户的所有字符串,default, flat, mini, inverse 分别是定义的四种验证码类型,你可以在此修改对应选项自定义验证码的长度、背景颜色、文字颜色等属性,在此不做过多叙述。
-
- 页面嵌入
此扩展包的使用分为两步:
前端展示 —— 生成验证码给用户展示,并收集用户输入的答案;
后端验证 —— 接收答案,检测用户输入的验证码是否正确。
-
-
- 前端展示
-
<input id="captcha" class="form-control" name="captcha" >
<img class="thumbnail captcha" src="{{ captcha_src('flat') }}" οnclick="this.src='/captcha/flat?'+Math.random()" title="点击图片重新获取验证码">
captcha_src() 方法是 mews/captcha 提供的辅助方法,用于生成验证码图片链接;
『验证码』区块中 onclick() 是 JavaScript 代码,实现了点击图片重新获取验证码的功能,允许用户在验证码太难识别的情况下换一张图片试试。
-
-
- 后端验证
-
前端展示部分我们已经开发完毕,接下来处理后端验证逻辑。 mews/captcha 是专门为 Laravel 量身定制的扩展包,能很好的兼容 Laravel 生成的注册逻辑。我们只需要在注册的时候,添加上表单验证规则即可:
app/Http/Controllers/Auth/RegisterController.php
<?php
.
.
.
class RegisterController extends Controller
{
.
.
.
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
'captcha' => 'required|captcha',
], [
'captcha.required' => '验证码不能为空',
'captcha.captcha' => '请输入正确的验证码',
]);
}
.
.
.
}
我们添加了验证规则:
'captcha' => 'required|captcha',
表达式里的第二个 captcha 是 mews/captcha 自定义的表单验证规则。扩展包非常巧妙地利用了 Laravel 表单验证器提供的 自定义表单验证规则 功能。令我们在开发验证码时非常方便。
Validator 表单验证的 make() 方法第三个参数是自定义错误提示,这里我们对验证码的错误提示进行自定义。
- 图片上传
给数据表添加相应的字段
上传头像
接下来我们将开发个人资料里的头像上传功能。
模型文件修改
首先我们需在 User 模型里将 avatar 字段加入到允许修改的白名单 $fillable 中:
编辑页面
接下来我们在 资料编辑页面 的『个人简介』编辑框下面,增加头像上传的选项:
resources/views/users/edit.blade.php.
<div class="form-group">
<label for="introduction-field">个人简介</label>
<textarea name="introduction" id="introduction-field" class="form-control" rows="3">{{ old('introduction', $user->introduction ) }}</textarea>
</div>
<div class="form-group">
<label for="" class="avatar-label">用户头像</label>
<input type="file" name="avatar">
@if($user->avatar)
<br>
<img class="thumbnail img-responsive" src="{{ $user->avatar }}" width="200" />
@endif
</div>
在 Laravel 中,我们可直接通过 请求对象(Request) 来获取用户上传的文件,如以下两种方法:
// 第一种方法
$file = $request->file('avatar');
// 第二种方法,可读性更高
$file = $request->avatar;
接下来我们将在 UsersController 的 update() 方法中,利用 Laravel 的 dd() 调试方法,来查看文件上传的情况:
app/Http/Controllers/UsersController.php
<?php
class UsersController extends Controller
{
public function update(UserRequest $request, User $user)
{
dd($request->avatar);
$user->update($request->all());
return redirect()->route('users.show', $user->id)->with('success', '个人资料更新成功!');
}
}
测试一下:
访问 资料编辑页面 ;
点击 『choose file』 按钮,选择图片;
点击『保存』按钮提交表单:
打印的结果居然为空。
经过一番仔细检查后,发现是因为我们忘记为表单添加 enctype="multipart/form-data" 声明了。请记住,在图片或者文件上传时,为表单添加此句声明是必须的。那我们再次修改下:
resources/views/users/edit.blade.php
<form action="{{ route('users.update', $user->id) }}" method="POST" accept-charset="UTF-8" enctype="multipart/form-data">
Laravel 的『用户上传文件对象』底层使用了 Symfony 框架的 UploadedFile 对象进行渲染,为我们提供了便捷的文件读取和管理接口,我们将在后面使用这些方法。
现在我们已经能得到用户上传的图片数据了,接下来是对图片进行存储。
存储用户上传图片
//保存文件
$fileName = $request->file('cover')->store('public/cover');
//获取文件路径
var_dump(Storage::url($fileName));
由于图片是保存在storage目录,浏览器无法直接访问,所有需要
使用 Artisan 命令 storage:link 来创建符号链接:
php artisan storage:link
本项目中,我们不止上传头像需要用到『图片上传功能』,在后面其他功能中,我们也将会允许用户上传图片,所以此处我们需要预先设计一下图片上传相关的逻辑,我们可以将『图片上传』核心操作做成一个工具类:
app/Handlers/ImageUploadHandler.php
<?php
namespace App\Handlers;
class ImageUploadHandler
{
// 只允许以下后缀名的图片文件上传
protected $allowed_ext = ["png", "jpg", "gif", 'jpeg'];
public function save($file, $folder, $file_prefix)
{
// 构建存储的文件夹规则,值如:uploads/images/avatars/201709/21/
// 文件夹切割能让查找效率更高。
$folder_name = "uploads/images/$folder/" . date("Ym/d/", time());
// 文件具体存储的物理路径,`public_path()` 获取的是 `public` 文件夹的物理路径。
// 值如:/home/vagrant/Code/larabbs/public/uploads/images/avatars/201709/21/
$upload_path = public_path() . '/' . $folder_name;
// 获取文件的后缀名,因图片从剪贴板里黏贴时后缀名为空,所以此处确保后缀一直存在
$extension = strtolower($file->getClientOriginalExtension()) ?: 'png';
// 拼接文件名,加前缀是为了增加辨析度,前缀可以是相关数据模型的 ID
// 值如:1_1493521050_7BVc9v9ujP.png
$filename = $file_prefix . '_' . time() . '_' . str_random(10) . '.' . $extension;
// 如果上传的不是图片将终止操作
if ( ! in_array($extension, $this->allowed_ext)) {
return false;
}
// 将图片移动到我们的目标存储路径中
$file->move($upload_path, $filename);
return [
'path' => config('app.url') . "/$folder_name$filename"
];
}
}
我们将使用 app/Handlers 文件夹来存放本项目的工具类,『工具类(utility class)』是指一些跟业务逻辑相关性不强的类,Handlers 意为 处理器 ,ImageUploadHandler 意为图片上传处理器,简单易懂。
接下来我们需要在 UsersController 里调用:
app/Http/Controllers/UsersController.php
<?php
.
.
.
use App\Handlers\ImageUploadHandler;
class UsersController extends Controller
{
.
.
.
public function update(UserRequest $request, ImageUploadHandler $uploader, User $user)
{
$data = $request->all();
if ($request->avatar) {
$result = $uploader->save($request->avatar, 'avatars', $user->id);
if ($result) {
$data['avatar'] = $result['path'];
}
}
$user->update($data);
return redirect()->route('users.show', $user->id)->with('success', '个人资料更新成功!');
}
}
因为我们使用了命名空间,所以需要在顶部加载 use App\Handlers\ImageUploadHandler;;
$data = $request->all(); 赋值 $data 变量,以便对更新数据的操作;
以下代码处理了图片上传的逻辑,注意 if ($result) 的判断是因为 ImageUploadHandler 对文件后缀名做了限定,不允许的情况下将返回 false:
if ($request->avatar) {
$result = $uploader->save($request->avatar, 'avatars', $user->id);
if ($result) {
$data['avatar'] = $result['path'];
}
}
$user->update($data); 这一步才是执行更新。