Laravel Vuejs 实战:开发知乎 (13)实现编辑问题

1.在question的show blade文件中提供编辑问题的入口【判断用户是否有权编辑,没有权限不显示编辑按钮】:

使用Policy,执行命令:

  1 php artisan make:Policy QuestionPolicy

生成 QuestionPolicy.php文件:

  1 <?php
  2 
  3 namespace App\Policies;
  4 
  5 use App\Models\Question;
  6 use App\User;
  7 use Illuminate\Auth\Access\HandlesAuthorization;
  8 
  9 class QuestionPolicy
 10 {
 11     use HandlesAuthorization;
 12 
 13     /**
 14      * Create a new policy instance.
 15      *
 16      * @return void
 17      */
 18     public function __construct()
 19     {
 20         //
 21     }
 22 
 23 
 24     /**
 25      * 判断用户是否有权编辑更新问题
 26      * @param User $user
 27      * @param Question $question
 28      * @return bool
 29      */
 30     public function update(User $user, Question $question)
 31     {
 32         return $user->id === $question->user_id;
 33     }
 34 }
QuestionPolicy.php

AuthServiceProviderpolicies 数组属性里添加授权映射关系:

  1 <?php
  2 
  3 namespace App\Providers;
  4 
  5 use App\Models\Question;
  6 use App\Policies\QuestionPolicy;
  7 use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
  8 use Illuminate\Support\Facades\Gate;
  9 
 10 class AuthServiceProvider extends ServiceProvider
 11 {
 12     /**
 13      * The policy mappings for the application.
 14      *
 15      * @var array
 16      */
 17     protected $policies = [
 18         // 'App\Model' => 'App\Policies\ModelPolicy',
 19         //添加授权映射关系
 20         Question::class => QuestionPolicy::class,
 21     ];
 22 
 23     /**
 24      * Register any authentication / authorization services.
 25      *
 26      * @return void
 27      */
 28     public function boot()
 29     {
 30         $this->registerPolicies();
 31 
 32         //
 33     }
 34 }
 35 
 36 
AuthServiceProvider.php

使用方法:

视图中:

  1 @can('update', $question)
  2 <a href="#">编辑</a>
  3 @endcan

Controller文件中:

  1 auth()->user()->can('update', $question)

关于Policy可以参考:Laravel 用户授权 Gate和Policy  ,Laravel Policy 使用Laravel-权限系统


2.在请求编辑交由QuestionController edit方法处理的时候,判断用户是否有权编辑,返回问题编辑的页面view,并传入question参数,

  1 /**判断权限 返回视图
  2  * @param Question $question
  3  * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
  4  */
  5 public function edit(Question $question)
  6 {
  7     if (auth()->user()->can('update', $question)) //判断当前用户是否有权编辑更新该question实例
  8     {
  9         //返回编辑视图
 10         return view('questions.edit', compact('question'));
 11     } else {
 12         //返回警告 没有权限
 13         return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
 14     }
 15 }
 16 

跳转没有权限需要给一个提示给用户,所以show.blade.php修改如下:

  1 @extends('layouts.app')
  2 @section('content')
  3     <div class="container">
  4         <div class="row">
  5             <div class="col-md-8 col-md offset-2">
  6                 <div class="card">
  7                     <div class="card-header">
  8                         {{ $question->title }}
  9                         @if(session()->has('warning'))
 10                             <div class="alert alert-warning">{{ session()->get('warning') }}</div>
 11                         @endif
 12                         @can('update',$question)
 13                             <a href="{{ route('questions.edit',$question) }}" class="btn btn-danger">编辑</a>
 14                         @endcan
 15                         @forelse($question->topics as $topic)
 16                             <button class="btn btn-secondary float-md-right m-1">{{ $topic->name }}</button>
 17                         @empty
 18                             <p class="text text-warning float-md-right"> "No Topics"</p>
 19                         @endforelse
 20                     </div>
 21                     <div class="card-body">
 22                         {!! $question->content !!}
 23                     </div>
 24                 </div>
 25             </div>
 26         </div>
 27     </div>
 28     <style scoped>
 29         .card-body img {
 30             width: 100%;
 31         }
 32     </style>
 33 @endsection
show.blade.php

3.展示question edit编辑视图view

如果有权限的,视图如下:

  1 @extends('layouts.app')
  2 @section('content')
  3     <div class="container">
  4         <div class="row">
  5             <div class="col-md-8 col-md-offset-2">
  6                 <div class="card">
  7                     <div class="card-header">
  8                         发布问题
  9                     </div>
 10                     <div class="card-body">
 11                         <form action="{{ route('questions.update',$question) }}" method="post">
 12                             {{--注意要有csrftoken--}}
 13                             @csrf
 14                             @method('PUT')
 15                             <div class="form-group">
 16                                 <label for="title">标题</label>
 17                                 <input type="text" name="title" class="form-control" placeholder="标题" id="title"
 18                                        value="{{ $question->title }}">
 19                                 <p class="text text-danger"> @error('title') {{ $message }} @enderror </p>
 20                             </div>
 21                             <!-- Select2 Topic Select -->
 22                             <div class="form-group">
 23                                 <label for="topic_list">选择主题</label>
 24                                 <select id="topic_list" class="js-example-basic-multiple form-control"
 25                                         name="topics[]" multiple>
 26                                     @forelse( $question->topics as $topic )
 27                                         <option value="{{ $topic->id }}" selected>{{ $topic->name }}</option>
 28                                     @empty
 29                                     @endforelse
 30                                 </select>
 31                             </div>
 32                             <!-- 编辑器容器 -->
 33                             <script id="container" name="content" type="text/plain"
 34                                     style="width: 100%">{!! $question->content !!}</script>
 35                             <p class="text text-danger"> @error('content') {{ $message }} @enderror </p>
 36                             <!--发布按钮-->
 37                             <button type="submit" class="btn btn-primary mt-2 float-md-right">更新问题</button>
 38                         </form>
 39                     </div>
 40                 </div>
 41             </div>
 42         </div>
 43     </div>
 44 @endsection
 45 @section('footer-js')
 46     <script type="text/javascript">
 47         // 实例化编辑器
 48         var ue = UE.getEditor('container', {
 49             toolbars: [
 50                 ['bold', 'italic', 'underline', 'strikethrough', 'blockquote', 'insertunorderedlist', 'insertorderedlist', 'justifyleft', 'justifycenter', 'justifyright', 'link', 'insertimage', 'fullscreen']
 51             ],
 52             elementPathEnabled: false,
 53             enableContextMenu: false,
 54             autoClearEmptyNode: true,
 55             wordCount: false,
 56             imagePopup: false,
 57             autotypeset: {indent: true, imageBlockLine: 'center'}
 58         });
 59         ue.ready(function () {
 60             ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token.
 61         });
 62         $(document).ready(function () {
 63             // Select2多选js
 64             $('.js-example-basic-multiple').select2({
 65                 // 设置属性及初始化值
 66                 tags: true,
 67                 placeholder: '选择相关话题',
 68                 miniumInputLength: 2,
 69 
 70                 ajax: {
 71                     url: '/api/topics',
 72                     dataType: 'json',
 73                     // Additional AJAX parameters go here; see the end of this chapter for the full code of this example
 74                     delay: 250,
 75                     data: function (params) {
 76                         return {
 77                             // term : The current search term in the search box.
 78                             //     q : Contains the same contents as term.
 79                             // _type: A "request type". Will usually be query, but changes to query_append for paginated requests.
 80                             // page : The current page number to request. Only sent for paginated (infinite scrolling) searches.
 81                             q: params.term, // search term
 82                             // page: params.page 暂时不需要分页
 83                         };
 84                     },
 85                     processResults: function (data, params) {
 86                         // 解析结果为Select2期望的格式
 87                         // parse the results into the format expected by Select2
 88                         // since we are using custom formatting functions we do not need to
 89                         // alter the remote JSON data, except to indicate that infinite
 90                         // scrolling can be used
 91                         return {
 92                             results: data
 93                         };
 94                     },
 95                     cache: true,
 96                 },
 97                 //模板样式
 98                 templateResult: formatTopic,
 99                 //模板样式 【选择项】
100                 templateSelection: formatTopicSelection,
101                 escapeMarkup: function (markup) {
102                     return markup;
103                 }
104             });
105         });
106 
107         //格式化话题
108         function formatTopic(topic) {
109             return "<div class='select2-result-repository clearfix'>" +
110             "<div class='select2-result-repository__meta'>" +
111             "<div class='select2-result-repository__title'>" +
112             topic.name ? topic.name : "Laravel" +
113                 "</div></div></div>";
114         }
115 
116         //格式化话题选项
117         function formatTopicSelection(topic) {
118             return topic.name || topic.text;
119         }
120     </script>
121 @endsection
edit.blade.php

更改说明:

批注 2020-02-29 134625

我们原样输出了之前问题里的数据,然后使用method put来进行更新【method域】,更改了action 属性值为questions.update路由,路由传入了$question参数

注:option因为是用户之前已经选过,所以要selected

4.提交更新结果到QuestionController的update方法内部处理逻辑:

  1 /** Update the specified resource in storage.
  2  * @param QuestionStoreRequest $questionStoreRequest
  3  * @param Question $question
  4  * @return \Illuminate\Http\RedirectResponse
  5  */
  6 public function update(QuestionStoreRequest $questionStoreRequest, Question $question)
  7 {
  8     //更新前 判断下权限
  9     if (!(auth()->user()->can('update', $question))) {
 10         //返回警告 没有权限
 11         return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
 12     }
 13 
 14     //取得更新的字段 使用Eloquent提供的update方法执行问题更新
 15     $question->update([
 16         'title' => $questionStoreRequest->get('title'),
 17         'content' => $questionStoreRequest->get('content'),
 18     ]);
 19 
 20 
 21     //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略
 22     //存储topics
 23     $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics'));
 24     //使用我们再question model里面添加的topics方法获得 topics关联,
 25     //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】
 26     $question->topics()->sync($topics);
 27 
 28     //更新完成,跳转回去
 29     return redirect()->back();
 30 }
QuestionController中update方法

注:可以参考 推荐使用 更新一下授权判断:

  1 //更新前 判断下权限
  2 if (!(auth()->user()->can('update', $question))) {
  3     //返回警告 没有权限
  4     return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
  5 }
  6 

改为

  1 //更新前 判断下权限
  2 if (!($this->authorize('update', $question))) {
  3     //返回警告 没有权限
  4     return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
  5 }

这样用户不具有更新问题的权限时候会直接跳403权限不足:

批注 2020-02-29 140138

更新后代码:

  1 <?php
  2 
  3 namespace App\Http\Controllers;
  4 
  5 use App\Http\Requests\QuestionStoreRequest;
  6 use App\Models\Question;
  7 use App\Repositories\QuestionRepository;
  8 
  9 
 10 class QuestionController extends Controller
 11 {
 12 
 13     /**
 14      * @var QuestionRepository
 15      */
 16     private $questionRepository;
 17 
 18     public function __construct(QuestionRepository $questionRepository)
 19     {
 20         $this->middleware(
 21             'auth',
 22             [
 23                 'except' =>
 24                     [
 25                         'index',
 26                         'show',
 27                     ]//非注册用户只能查看不能编辑添加更改删除
 28             ]
 29         );
 30 
 31         $this->questionRepository = $questionRepository;
 32     }
 33 
 34     /**
 35      * Display a listing of the resource.
 36      *
 37      * @return \Illuminate\Http\Response
 38      */
 39     public function index()
 40     {
 41         //
 42 
 43     }
 44 
 45 
 46     /**
 47      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 48      */
 49     public function create()
 50     {
 51         //
 52         return view('questions.create');
 53     }
 54 
 55 
 56     /**
 57      * @param QuestionStoreRequest $request
 58      * @return \Illuminate\Http\RedirectResponse
 59      */
 60     public function store(QuestionStoreRequest $request)//依赖注入QuestionStoreRequest实例
 61     {
 62         //
 63 //        $data = $request->validate([
 64 //            'title' => 'required|min:8',
 65 //            'content' => 'required|min:28',
 66 //        ]);
 67         //存储topics
 68         $topics = $this->questionRepository->normalizeTopics($request->get('topics'));
 69         //初始化question要用到的数据
 70         $data = $request->all();
 71         $data['user_id'] = auth()->user()->id;
 72 
 73 //        $question=Question::create($data); 被下方代码取代
 74         $question = $this->questionRepository->create($data);
 75 
 76         //使用我们再question model里面添加的topics方法获得 topics关联,再使用attach方法
 77         $question->topics()->attach($topics);
 78 
 79         return redirect()->route('questions.show', $question);
 80     }
 81 
 82 
 83     /**
 84      * @param Question $question
 85      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 86      */
 87     public function show(Question $question)
 88     {
 89         //使用关系关联加载,with方法会将分类之下的商品一起查询出来,而且不会出现N+1影响性能的问题
 90         $question->with('topics')->get();
 91 
 92         return view('questions.show', compact('question'));
 93     }
 94 
 95 
 96     /**判断权限 返回视图
 97      * @param Question $question
 98      * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
 99      */
100     public function edit(Question $question)
101     {
102         if ($this->authorize('update', $question)) //判断当前用户是否有权编辑更新该question实例
103         {
104             //返回编辑视图
105             return view('questions.edit', compact('question'));
106         } else {
107             //返回警告 没有权限
108             return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
109         }
110     }
111 
112 
113     /** Update the specified resource in storage.
114      * @param QuestionStoreRequest $questionStoreRequest
115      * @param Question $question
116      * @return \Illuminate\Http\RedirectResponse
117      */
118     public function update(QuestionStoreRequest $questionStoreRequest, Question $question)
119     {
120         //更新前 判断下权限
121         if (!($this->authorize('update', $question))) {
122             //返回警告 没有权限
123             return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
124         }
125 
126         //取得更新的字段 使用Eloquent提供的update方法执行问题更新
127         $question->update([
128             'title' => $questionStoreRequest->get('title'),
129             'content' => $questionStoreRequest->get('content'),
130         ]);
131 
132 
133         //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略
134         //存储topics
135         $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics'));
136         //使用我们再question model里面添加的topics方法获得 topics关联,
137         //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】
138         $question->topics()->sync($topics);
139 
140         //更新完成,跳转回去
141         return redirect()->back();
142     }
143 
144     /**
145      * Remove the specified resource from storage.
146      *
147      * @param int $id
148      * @return \Illuminate\Http\Response
149      */
150     public function destroy($id)
151     {
152         //
153     }
154 
155 
156 }
157 
QuestionController.php

我这里暂时不去实现authorized失败之后页面的定制,所以没有使用$this->authorize方法。

最后QuestionContoller.php代码如下:

  1 <?php
  2 
  3 namespace App\Http\Controllers;
  4 
  5 use App\Http\Requests\QuestionStoreRequest;
  6 use App\Models\Question;
  7 use App\Repositories\QuestionRepository;
  8 
  9 
 10 class QuestionController extends Controller
 11 {
 12 
 13     /**
 14      * @var QuestionRepository
 15      */
 16     private $questionRepository;
 17 
 18     public function __construct(QuestionRepository $questionRepository)
 19     {
 20         $this->middleware(
 21             'auth',
 22             [
 23                 'except' =>
 24                     [
 25                         'index',
 26                         'show',
 27                     ]//非注册用户只能查看不能编辑添加更改删除
 28             ]
 29         );
 30 
 31         $this->questionRepository = $questionRepository;
 32     }
 33 
 34     /**
 35      * Display a listing of the resource.
 36      *
 37      * @return \Illuminate\Http\Response
 38      */
 39     public function index()
 40     {
 41         //
 42 
 43     }
 44 
 45 
 46     /**
 47      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 48      */
 49     public function create()
 50     {
 51         //
 52         return view('questions.create');
 53     }
 54 
 55 
 56     /**
 57      * @param QuestionStoreRequest $request
 58      * @return \Illuminate\Http\RedirectResponse
 59      */
 60     public function store(QuestionStoreRequest $request)//依赖注入QuestionStoreRequest实例
 61     {
 62         //
 63 //        $data = $request->validate([
 64 //            'title' => 'required|min:8',
 65 //            'content' => 'required|min:28',
 66 //        ]);
 67         //存储topics
 68         $topics = $this->questionRepository->normalizeTopics($request->get('topics'));
 69         //初始化question要用到的数据
 70         $data = $request->all();
 71         $data['user_id'] = auth()->user()->id;
 72 
 73 //        $question=Question::create($data); 被下方代码取代
 74         $question = $this->questionRepository->create($data);
 75 
 76         //使用我们再question model里面添加的topics方法获得 topics关联,再使用attach方法
 77         $question->topics()->attach($topics);
 78 
 79         return redirect()->route('questions.show', $question);
 80     }
 81 
 82 
 83     /**
 84      * @param Question $question
 85      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 86      */
 87     public function show(Question $question)
 88     {
 89         //使用关系关联加载,with方法会将分类之下的商品一起查询出来,而且不会出现N+1影响性能的问题
 90         $question->with('topics')->get();
 91 
 92         return view('questions.show', compact('question'));
 93     }
 94 
 95 
 96     /**判断权限 返回视图
 97      * @param Question $question
 98      * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
 99      */
100     public function edit(Question $question)
101     {
102         if (auth()->user()->can('update', $question)) //判断当前用户是否有权编辑更新该question实例
103         {
104             //返回编辑视图
105             return view('questions.edit', compact('question'));
106         } else {
107             //返回警告 没有权限
108             return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
109         }
110     }
111 
112 
113     /** Update the specified resource in storage.
114      * @param QuestionStoreRequest $questionStoreRequest
115      * @param Question $question
116      * @return \Illuminate\Http\RedirectResponse
117      */
118     public function update(QuestionStoreRequest $questionStoreRequest, Question $question)
119     {
120         //更新前 判断下权限
121         if (!(auth()->user()->can('update', $question))) {
122             //返回警告 没有权限
123             return redirect()->back()->with('warning', '你不能编辑不属于你的问题!');
124         }
125         //取得更新的字段 使用Eloquent提供的update方法执行问题更新
126         $question->update([
127             'title' => $questionStoreRequest->get('title'),
128             'content' => $questionStoreRequest->get('content'),
129         ]);
130 
131 
132         //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略
133         //存储topics
134         $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics'));
135         //使用我们再question model里面添加的topics方法获得 topics关联,
136         //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】
137         $question->topics()->sync($topics);
138 
139         //更新完成,跳转回去
140         return redirect()->back();
141     }
142 
143     /**
144      * Remove the specified resource from storage.
145      *
146      * @param int $id
147      * @return \Illuminate\Http\Response
148      */
149     public function destroy($id)
150     {
151         //
152     }
153 
154 
155 }
156 
157 
QuestionController.php

效果示例:

打开 http://zhihu.test/questions/16

批注 2020-02-29 141216

打开 http://zhihu.test/questions/16/edit

批注 2020-02-29 141246

打开没有权限的示例【我用tinker设置了user_id=10,当前用户id是3】:

打开http://zhihu.test/questions/18 可以看到,没有编辑按钮

批注 2020-02-29 141421

手动输入url打开http://zhihu.test/questions/18/edit 看到提示,【注意代码bug因为上面有redirect()->back()如果之前是从http://zhihu.test/questions/18/edit 打开 然后一直会跳回 导致请求跳转过多异常,不过实际使用这种情况很少见这里就忽略了】

批注 2020-02-29 141332

posted @ 2020-02-29 14:11  dzkjz  阅读(331)  评论(0编辑  收藏  举报