Laravel6.X如何创建用户资料创建和修改页面?(前端与后端)

上次我们在用户注册页面添加了一个字段username,并且用这个username来登录。在我们了解了如何做Laravel脚手架(scaffold)及基本设置后,在这个基础上我们可以建立一个单个用户的编辑,修改页面供用户查看和修改个人资料用。这个是目前很常见的一个功能了。用户在这个页面上可以修改个人头像(有默认头像),生日,昵称等个人信息。

1. 单个用户资料的展示和修改页面

首先让我们来做一个路由和控制器来展示和编辑用户的个人信息。在routes/web.php里面增加一行:

Route::resource('users', 'UsersController', ['only' => ['update', 'edit']]);

上面这一行我们要了解的知识点是laravel的资源控制器,如果对这一段知识不熟悉的话可以参见Laravel的官网,这里就不再赘述了。
我们不能赋予用户删除的权限,所以移除了删除权限。上一句就等于:

Route::get('/users/{user}/edit', 'UsersController@edit')->name('users.edit');
Route::patch('/users/{user}', 'UsersController@update')->name('users.update');

这里是edit是展示页面,update进行数据的修改,然后我们执行下如下命令来创建控制器:

php artisan make:controller UsersController

我们打开创建好的控制器,并把这一个方法放进去:

class UsersController extends Controller
{
    //展示个人资料页面方法
    public function edit(User $user)
    {
        return view('users.edit', compact('user'));
    }
}

当然我们要在文件的头部引入模型文件App\User来做数据支持。

use App\User;

接下来我们做下个人资料页面的视图文件,compact('user')这个函数就会把user数据传到视图中。
我们的设计是展示和编辑个人资料的页面放在一个页面上,方便管理。下面是edit.blade.php的代码:

@extends('layouts.app')
@section('content')
<div class="container">
  <div class="col-md-8 offset-md-2">

    <div class="card">
      <div class="card-header">
        <h4>
           编辑个人资料
        </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="well well-sm">
            <button type="submit" class="btn btn-primary">保存</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>

@endsection
资料修改页面

接下来我们还要写具体的处理资料更新的方法,在控制器中增加如下方法:

public function update(UserRequest $request, User $user)
    {
        $user->update($request->all());
        return redirect()->route('users.edit', $user->id)->with('success', '个人资料更新成功!');
    }

这里的UserRequest是要我们单独创建的。在这个类中,我们会处理三个问题,1.是否是认证使用。2.验证规则。3.错误信息。下面我们就来创建它吧:

php artisan make:request UserRequest

这个命令会生成App\Http\Requests\UserRequest.php,我们直接把代码展示下:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use 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 [
            'username' => 'required|between:3,25|regex:/^[A-Za-z0-9\-\_]+$/|unique:users,username,' . Auth::id(),
            'email' => 'required|email'
        ];
    }

    public function messages()
    {
        return [
            'username.unique' => '用户名已被占用,请重新填写',
            'username.regex' => '用户名只支持英文、数字、横杠和下划线。',
            'username.between' => '用户名必须介于 3 - 25 个字符之间。',
            'username.required' => '用户名不能为空。',
        ];
    }

}

authorize方法中return true使得用户必须在登录情况下才能操作;rules是进行验证规则的;messages是展示错误信息的。

这些做完了之后别忘了把引入到控制里面也就是UsersController.php里。

use App\Http\Requests\UserRequest.php

我们要在edit.blade.php的头部插入这个代码来展示其错误信息。

...
<input type="hidden" name="_token" value="{{ csrf_token() }}">
@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> {{ $error }}</li>
    @endforeach
  </ul>
</div>
@endif
<div class="form-group">

2.安全隐患

在这种情况下,你如果访问单个用户的编辑页面,不管登不登陆的状况下都可以看见并编辑,规则是单个用户只能查看和修改自己的资料。如果其他用户登录,通过修改ID看到别人的资料也是不行的。所以这就有很大的安全隐患,通常情况下我们在控制器加一个构造函数便可以解决这种情况。

public function __construct()
    {
        $this->middleware('auth');
    }

加完这个方法之后再访问这个页面的话我们就会看到, 在访问users/1/edit这个路由的时候就会跳到登录页面,这个就是此中间件的作用。如果想让某个路由移除这个规则,那么就可以改成:

public function __construct()
    {
        $this->middleware('auth', ['except' => ['show']]);
    }

上面的语句意味着类似users/show/1这个路由是可以不用登录就能访问的。当然中间件也可以直接在路由上加上如

Route::get('/home', 'HomeController@index')->name('home')->middleware('auth');

这种方式。这时我们会发现一个问题,例如,用户1和2注册后,当用户1登录以后可以看到交叉看到用户2的个人页面甚至修改它,只要知道用户2的ID即可,这样肯定是不行的,这里我们会提供两种方法。第一是直接在某个方法的开头插入能返回true或者false的一段判断代码。例如:

if($user->id != Auth()->id()){
      return "you are not authorized.";
};

这段代码的意思就是说如果当前登录用户和模型的用户不一致的话即可停止执行代码。当然laravel有一个机制叫policy来解决这个问题。下面我们来创建policy。

php artisan make:policy UsersPolicy

然后我们在里面定义我们的授权方法。如:

public function view_user(User $currentUser, User $user)
    {
        return $currentUser->id === $user->id;
    }

$currentUser返回的是当前登录的用户,所以和模型里的ID用户相匹配的返回为真,否则是false。然后我们需要在app/Providers里面,修改AuthServiceProvider.php里面插入:

protected $policies = [
        'App\User' => 'App\Policies\UserPolicy'
    ];

左面的是模型,右面的是对应的政策。然后我们需要在控制器中的对应方法里写下:

$this->authorizie('view_user', $user);

第一个是政策里面的方法名,第二个是模型。这时我们再交叉查看会员资料的时候,就会发现显示的是403。从 Laravel 5.8 起,我们可以定义一个回调函数来让 Laravel 自己去寻找模型所对应的授权策略文件:

 public function boot()
    {
        $this->registerPolicies();
    }

这样的话我们不需要在AuthServiceProvider.php里面进行模型和策略对应的操作了。

3.添加用户头像功能

图片得上传和文字得提交有很多处是不一样的,有时候要复杂得多,比如验证格式,大小,裁剪等等,所以我们要单独拿出来说。
首先我们要添加一个自定义字段,把头像的URL放进去。在上一篇文章中提到如何添加一个自定义字段到用户注册表单,而这一次这个字段不是注册的时候填的,所以有些不一样。如何添加呢?我们现在数据库迁移文件加一个头像的字段,然后进行迁移命令:

php artisan make:migration add_avatar_to_users_table --table=users

在迁移文件中的两个方法中分别添加这两行:

public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('avatar')->nullable();
        });
    }

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

后面的nullable()表面这个字段可以为空。我们在前端做一个默认头像,如果头像为空的时候就显示这个默认头像,如果头像不为空则显示用户上传的头像。然后我们再运行迁移命令:

php artisan migrate

这样用户表就多了一个字段avatar. 接下来我们需在 User 模型里将 avatar 字段加入到允许修改的白名单 $fillable 中,打开app/user.php, 在19行和20行添加:

protected $fillable = [
        'name', 'email', 'username','password','avatar',
    ];

edit.blade.php

@extends('layouts.app')

@section('content')

    <div class="container">
        <div class="col-md-8 offset-md-2">

            <div class="card">
                <div class="card-header">
                    <h4>
                        编辑个人资料
                    </h4>
                </div>

                <div class="card-body">

                    <form action="{{ route('users.update', $user->id) }}" method="POST" accept-charset="UTF-8" enctype="multipart/form-data">
                        <input type="hidden" name="_method" value="PUT">
                        <input type="hidden" name="_token" value="{{ csrf_token() }}">
                        @if(Session::has('success'))
                                <div class="alert alert-success">
                                    <div class="alert alert-success"> {{ Session::get('success') }}</div>
                                </div>
                        @endif
                        @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
                        @if($user->avatar)
                        <div class="avatar">
                            <img src="{{$user->avatar}}" width="150" height="150">
                        </div>
                        @else
                            <div class="avatar">
                                <img src="{{ asset('img/user.jpg') }}" width="150" height="150">
                            </div>
                        @endif
                         <div class="form-group">
                            <label for="avatar">头像上传</label>
                            <input type="file" name="avatar" id="avatar" class="form-control-file">
                        </div>
                         <input type="file" name="avatar" class="form-control-file">
                        <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="username-field">用户名</label>
                            <input class="form-control" type="text" name="username" id="username-field" value="{{ old('username', $user->username) }}" />
                        </div>
                        <div class="well well-sm">
                            <button type="submit" class="btn btn-primary">保存</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>

@endsection

这里要显示成功信息也要显示错误信息,成功信息是控制器传过来的,成功信息存储在session里面。

@if(Session::has('success'))
                                <div class="alert alert-success">
                                     {{ Session::get('success') }}
                                </div>
 @endif

错误信息可能有多个,所以我们要遍历:

@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>{{ $error }}</li>
                                    @endforeach
                                </ul>
                            </div>
@endif

这里别忘了要在表单上加一个enctype="multipart/form-data",否额无法上传图片。
我们现在看下图片:


34452.png

图片上传要经过一些复杂的步骤,我们要验证是否是图片,也就是图片扩展名验证,还有就是图片文件的最大大小等等,但我们可以用laravel的验证机制来一下子解决这些问题。
我们在上面的UserRequest.php里面的验证机制上添加:

'avatar' => 'image|mimes:jpeg,png,jpg,gif|max:2048'

在违反此验证规则的返回错误信息里写上:

'avatar.image' => '上传的必须是图片类型',
'avatar.mimes' => '上传格式只支持jpeg,png,jpg,gif',
'avatar.max' => '图片大小不能超过2兆'

这个验证规则不言而喻大家一看就懂,在此就不再赘述了,最后我们要解决的是如何储存到相应位置的问题。上传图片的时候通常也要进行重命名,在此我们就拿用户的user id和时间戳组成的字符串来命名吧。然后我们把绝对路径放到数据库里,然后储存图片就可以了。为此我们把UserController.php里面的update方法改成:

public function update(UserRequest $request, User $user)

    {
        $data = $request->all();
        if($request->avatar){
            $user_id = Auth::user()->id;
            $imageName = $user_id.'_'.time().'.'.request()->avatar->getClientOriginalExtension();
            request()->avatar->move(public_path('img'), $imageName);
            $data["avatar"] = env('APP_URL')."/img/".$imageName;
        }
        $user->update($data);
        return redirect()->route('users.edit', $user->id)->with('success', '个人资料更新成功!');
    }

我们在public文件夹下面创建一个img文件夹作为上传的目标文件夹。命名规则,用户名加时间戳加扩展名。在数据库里存入的是绝对路径。当然图片我们没有进行裁剪和缩小,至于更详细地讲解让Laravel如何处理图片,我们要在专门的教程里讲解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,273评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,349评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,709评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,520评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,515评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,158评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,755评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,660评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,203评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,287评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,427评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,122评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,801评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,272评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,808评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,440评论 2 359