定义路由
资源路由
css
Route::resource('users', 'UsersController', ['only' => ['show', 'update', 'edit']]);
创建控制器
go
php artisan make:controller UsersController
进入app/Http/Controllers/UsersController.php
scala
use App\Models\User;
class UsersController extends Controller
{
// 个人主页
public function show(User $user)
{
return view('users.show', compact('user'));
}
}
创建视图
来新建一个用户个人页面。
resources/views/users/show.blade.php
xml
@extends('layouts.app')
@section('title', $user->name . ' 的个人中心')
@section('content')
<div class="row">
<div class="col-lg-3 col-md-3 hidden-sm hidden-xs user-info">
<div class="card ">
<img class="card-img-top" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F89e47fdc-a132-4ce7-a46d-86a0777eb6af%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1712478362&t=0d57949efcef42ff9ba4d755af700353">
<div class="card-body">
<h5><strong>个人简介</strong></h5>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
<hr>
<h5><strong>注册于</strong></h5>
<p>January 01 1901</p>
</div>
</div>
</div>
<div class="col-lg-9 col-md-9 col-sm-12 col-xs-12">
<div class="card ">
<div class="card-body">
<h1 class="mb-0" style="font-size:22px;">{{ $user->name }} <small>{{ $user->email }}</small></h1>
</div>
</div>
<hr>
{{-- 用户发布的内容 --}}
<div class="card ">
<div class="card-body">
暂无数据 ~_~
</div>
</div>
</div>
</div>
@stop
这时候我们再访问用户个人页面,便能够看到基本的数据展示:
编辑个人资料
作为个人中心页面 ,查看用户表相关的迁移文件: database/migrations/2014_10_12_000000_create_users_table.php,我们需要增加『头像』和『个人简介』字段
scss
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
新建迁移文件
ini
php artisan make:migration add_avatar_and_introduction_to_users_table --table=users
现在我们来为新增的迁移文件加上这两个字段:
database/migrations/[timestamp]_add_avatar_and_introduction_to_users_table.php
php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddAvatarAndIntroductionToUsersTable extends Migration
{
/**
* 执行迁移
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('avatar')->nullable();
$table->string('introduction')->nullable();
});
}
/**
* 回滚迁移
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('avatar');
$table->dropColumn('introduction');
});
}
}
运行迁移
php artisan migrate
打开数据库查看工具,即可看到我们新添加的两个字段:
增加入口
接下来我们需要增加一个页面链接入口,让登录用户可以很方便地进入到自己的『资料编辑页面』: resources/views/layouts/_header.blade.php
ini
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('users.show', Auth::id()) }}">个人中心</a>
<a class="dropdown-item" href="{{ route('users.edit', Auth::id()) }}">编辑资料</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" id="logout" href="#">
<form action="{{ route('logout') }}" method="POST">
@csrf
<button class="btn btn-block btn-danger" type="submit" name="button">退出</button>
</form>
</a>
</div>
编辑页面
我们需要前往 UsersController 控制器里创建 edit() 方法:
php
// 编辑页面
public function edit(User $user)
{
return view('users.edit', compact('user'));
}
来创建编辑视图文件: resources/views/users/edit.blade.php
ini
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-md-8 offset-md-2">
<div class="card">
<div class="card-header">
<h4>
<i class="glyphicon glyphicon-edit"></i> 编辑个人资料
</h4>
</div>
<div class="card-body">
<form action="{{ route('users.update', $user->id) }}" method="POST" accept-charset="UTF-8">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="name-field">用户名</label>
<input class="form-control" type="text" name="name" id="name-field"
value="{{ old('name', $user->name) }}"/>
</div>
<div class="form-group">
<label for="email-field">邮 箱</label>
<input class="form-control" type="text" name="email" id="email-field"
value="{{ old('email', $user->email) }}"/>
</div>
<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 mb-4">
<label for="" class="avatar-label">用户头像</label>
<input type="file" name="avatar" class="form-control-file">
</div>
<div class="well well-sm">
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
表单请求验证
我们创建 UserRequest ,使用以下命令:
go
php artisan make:request UserRequest
执行成功后会生成以下文件:
app/Http/Requests/UserRequest.php
php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|between:3,25|regex:/^[A-Za-z0-9-_]+$/|unique:users,name,' . Auth::id(),
'email' => 'required|email',
'introduction' => 'max:80',
];
}
}
更新用户信息
未编写处理表单提交的方法,接下来我们先创建此方法:
app/Http/Controllers/UsersController.php
php
use App\Http\Requests\UserRequest;
// 执行编辑
public function update(UserRequest $request, User $user)
{
$user->update($request->all());
return redirect()->route('users.show', $user->id)->with('success', '个人资料更新成功!');
}
接下来我们创建 error.blade.php 文件来渲染错误提示:
resources/views/shared/_error.blade.php
less
@if (count($errors) > 0)
<div class="alert alert-danger">
<div class="mt-2"><b>有错误发生:</b></div>
<ul class="mt-2 mb-2">
@foreach ($errors->all() as $error)
<li><i class="glyphicon glyphicon-remove"></i> {{ $error }}</li>
@endforeach
</ul>
</div>
@endif
我们需要自定义表单的提示信息,修改 UserRequest,新增方法 messages() :
app/Http/Requests/UserRequest.php
dart
public function messages()
{
return [
'name.unique' => '用户名已被占用,请重新填写',
'name.regex' => '用户名只支持英文、数字、横杠和下划线。',
'name.between' => '用户名必须介于 3 - 25 个字符之间。',
'name.required' => '用户名不能为空。',
];
}
来到App/Models/User,添加过滤字段introduction
ini
protected $fillable = [
'name', 'email', 'password', 'introduction',
];
效果
再次测试下编辑资料功能,请在『个人简介』里填入测试内容 ------ php,是世界最好的语言. ,点击保存按钮:
结果:
显示个人资料
在个人中心里显示出来:
resources/views/users/show.blade.php
xml
<div class="card-body">
<h5><strong>个人简介</strong></h5>
<p>{{ $user->introduction }}</p>
<hr>
<h5><strong>注册于</strong></h5>
<p>{{ $user->created_at->diffForHumans() }}</p>
</div>
效果:
上传头像
模型文件修改
首先我们需在 User 模型里将 avatar 字段加入到允许修改的白名单 $fillable 中:
ini
protected $fillable = [
'name', 'email', 'password', 'introduction', 'avatar',
];
编辑页面
接下来我们在 的『个人简介』编辑框下面,增加头像上传的选项:
resources/views/users/edit.blade.php
ini
<form action="{{ route('users.update', $user->id) }}" method="POST" accept-charset="UTF-8"
enctype="multipart/form-data">
存储用户上传图片
接下来是对图片进行存储,本项目中,我们不止上传头像需要用到『图片上传功能』,在后面发布帖子功能中,我们也将会允许用户上传图片,所以此处我们需要预先设计一下图片上传相关的逻辑,我们可以将『图片上传』核心操作做成一个工具类:
app/Handlers/ImageUploadHandler.php
php
<?php
namespace App\Handlers;
use Illuminate\Support\Str;
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' => "/$folder_name/$filename"
];
}
}
还可以通过前端对上传的文件后缀进行控制
python
<input type="file" name="avatar" class="form-control-file" accept="image/png,image/gif,image/jpeg,image/jpeg">
我们将使用 app/Handlers 文件夹来存放本项目的工具类,『工具类(utility class)』是指一些跟业务逻辑相关性不强的类, Handlers 意为 处理器 ,ImageUploadHandler 意为图片上传处理器,简单易懂。
接下来我们需要在 UsersController 里调用(注意顶部 use 引入):
app/Http/Controllers/UsersController.php
php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use App\Http\Requests\UserRequest;
use App\Handlers\ImageUploadHandler;
class UsersController extends Controller
{
// 执行编辑 UserRequest为表单验证
public function update(UserRequest $request, ImageUploadHandler $uploader, User $user){
$data = $request->all();
//dd($data);
if($request->avatar){
$result = $uploader->save($request->avatar, 'avatars', $user->id);
if($result){
$data['avatar'] = $result['path'];
}else{
//上传有错误 withErrors可以携带回错误信
return back()->withErrors(['上传图片格式只支持png, jpg, gif, jpeg这四种格式']);
}
}
$user->update($data);
return redirect()->route('users.show', $user->id)->with('success', '个人资料更新成功');
}
}
效果
提交成功,打开项目文件夹,一步步寻找下去,找到我们刚刚上传的图片:
显示头像
目前我们有两个地方用到用户头像,第一个是个人空间,第二个是顶部导航栏。
修改个人空间,将头像的 src 属性修改为 {{ $user->avatar }} :
resources/views/users/show.blade.php
ini
<div class="card ">
<img class="card-img-top" src="{{ $user->avatar }}" alt="{{ $user->name }}">
<div class="card-body">
效果如下:
接下来修改顶部导航:
resources/views/layouts/_header.blade.php
ini
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<img
src="{{ Auth::user()->avatar }}"
class="img-responsive img-circle" width="30px" height="30px">
{{ Auth::user()->name }}
</a>
效果:
限制头像分辨率
图片验证
当用户上传分辨率太小的图片时,会影响网站的美观,所以我们需要对图片的分辨率大小加以限制。在 UserRequest 中增加图片验证规则即可:
app/Http/Requests/UserRequest.php
php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|between:3,25|regex:/^[A-Za-z0-9-_]+$/|unique:users,name,' . Auth::id(),
'email' => 'required|email',
'introduction' => 'max:80',
'avatar' => 'mimes:jpeg,bmp,png,gif|dimensions:min_width=208,min_height=208',
];
}
public function messages()
{
return [
'avatar.mimes' =>'头像必须是 jpeg, bmp, png, gif 格式的图片',
'avatar.dimensions' => '图片的清晰度不够,宽和高需要 208px 以上',
'name.unique' => '用户名已被占用,请重新填写',
'name.regex' => '用户名只支持英文、数字、横杠和下划线。',
'name.between' => '用户名必须介于 3 - 25 个字符之间。',
'name.required' => '用户名不能为空。',
];
}
}
测试:
上传一个208PX以下的照片
如上图可以看到我们自定义的错误消息提示。
裁剪头像
裁剪图片 我们还有一个地方要优化,用户有时会上传分辨率较大的图片,并且图片太大会拖慢页面的加载速度,所以接下来我们将对此进行优化。
我们将使用备受欢迎的 intervention/image 扩展包来处理图片裁切的逻辑,接下来我们需要先安装此扩展包;
安装扩展包
arduino
composer require intervention/image
配置信息
执行以下命令获取配置信息:
ini
php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravelRecent"
结果如下:
打开 config/image.php 文件可以看到只有一个驱动器的选项,支持的值有 GD库 和 imagick:
注:此处我们使用默认的 gd 即可,如果将要开发的项目需要较专业的图片,请考虑 ImageMagic
开始裁剪
我们将裁切的逻辑写在 ImageUploadHandler 中,请将以下代码替换:
app/Handlers/ImageUploadHandler.php
php
<?php
namespace App\Handlers;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
class ImageUploadHandler
{
// 只允许以下后缀名的图片文件上传
protected $allowed_ext = ["png", "jpg", "gif", 'jpeg'];
public function save($file, $folder, $file_prefix, $max_width = false)
{
// 构建存储的文件夹规则,值如: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);
// 如果限制了图片宽度,就进行裁剪
if ($max_width && $extension != 'gif') {
// 此类中封装的函数,用于裁剪图片
$this->reduceSize($upload_path . '/' . $filename, $max_width);
}
return [
'path' => "/$folder_name/$filename"
// 'path' => config('app.url') . "/$folder_name/$filename"
];
}
public function reduceSize($file_path, $max_width)
{
// 先实例化,传参是文件的磁盘物理路径
$image = Image::make($file_path);
// 进行大小调整的操作
$image->resize($max_width, null, function ($constraint) {
// 设定宽度是 $max_width,高度等比例双方缩放
$constraint->aspectRatio();
// 防止裁图时图片尺寸变大
$constraint->upsize();
});
// 对图片修改后进行保存
$image->save();
}
}
以上的 save() 方法中,我们新增了 $max_width 参数,用来指定最大图片宽度,我们修改 UsersController 的update() 方法中的调用,修改为:
ini
$result = $uploader->save($request->avatar, 'avatars', $user->id, 416);
开始测试 进入资料编辑页面 ,选择一张较大的图片,然后点击保存提交表单:
可以看到更新成功的提示:
至此图片上传功能开发完毕。