参考链接:An Introduction to Laravel Authorization Gates

本文使用 Laravel 的 Gate 授权方式 实现一个基于用户角色的博客发布系统。

在系统包含两个用户角色(作者编辑),它们对应的角色权限如下:

  1. 作者能创建博客
  2. 作者能更新自己的博客
  3. 作者能发布/不发布自己的博客
  4. 作者能删除自己的博客
  5. 编辑能更新所有博客
  6. 编辑能发布/不发布所有博客
  7. 编辑能删除所有博客

创建项目

laravel new blog

> php artisan -V
Laravel Framework 5.4.21

或者使用 composer create-project

composer create-project --prefer-dist laravel/laravel blog

配置数据库连接

修改 .env 文件中的数据库连接信息。

...
APP_URL=http://localhost
...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=dbname
DB_USERNAME=dbuser
DB_PASSWORD=yoursecretdbuserpassword
...

数据库部分

创建 Post Model、Post 迁移文件和 Post 控制器。

> php artisan make:model Post -m -c
Model created successfully.
Created Migration: 2017_05_07_083335_create_posts_table
Controller created successfully.

打开迁移文件,补充 up 方法。

Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->boolean('published')->default(false);
$table->unsignedInteger('user_id');
$table->timestamps(); $table->foreign('user_id')->references('id')->on('users');
});

添加 roles 表和关系表 user_roles

  1. roles

创建 Role Model 和 Role 迁移文件

> php artisan make:model Role -m
Model created successfully.
Created Migration: 2017_05_07_083537_create_roles_table

打开迁移文件,补充 up 方法。

Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug')->unique();
$table->jsonb('permissions'); // jsonb deletes duplicates
$table->timestamps();
});
  1. user_roles

创建迁移文件

php artisan make:migration create_user_roles_table --create=user_roles

补充 up 方法

public function up()
{
Schema::create('user_roles', function (Blueprint $table) {
$table->unsignedInteger('user_id');
$table->unsignedInteger('role_id');
$table->timestamps(); $table->unique(['user_id','role_id']);
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
});
} public function down()
{
Schema::dropIfExists('user_roles');
}

种子类

创建 RolesTableSeeder

php artisan make:seeder RolesTableSeeder

补充 run 方法。

use App\Role;

class RolesTableSeeder extends Seeder
{ public function run()
{
$author = Role::create([
'name' => '作家',
'slug' => 'author',
'permissions' => [
'create-post' => true,
]
]);
$editor = Role::create([
'name' => '编辑',
'slug' => 'editor',
'permissions' => [
'update-post' => true,
'publish-post' => true,
'delete-post' => true,
]
]);
} }

DatabaseSeeder 中注册 RolesTableSeeder

$this->call(RolesTableSeeder::class);

UserRole Model

添加 Role Model 的内容。

class Role extends Model
{
protected $fillable = [
'name', 'slug', 'permissions',
];
protected $casts = [
'permissions' => 'array',
]; public function users()
{
return $this->belongsToMany(User::class, 'user_roles', 'role_id', 'user_id');
} public function hasAccess($permission)
{
return $this->hasPermission($permission);
} private function hasPermission($permission)
{
return isset($this->permissions[$permission]) ? $this->permissions[$permission] : false;
}
}

添加 User Model 的内容。

class User extends Authenticatable
{
use Notifiable; protected $fillable = [
'name', 'email', 'password',
]; protected $hidden = [
'password', 'remember_token',
]; public function roles()
{
return $this->belongsToMany(Role::class, 'user_roles', 'user_id', 'role_id');
} /**
* Checks if User has access to $permission.
*/
public function hasAccess($permission)
{
// check if the permission is available in any role
foreach ($this->roles as $role) {
if($role->hasAccess($permission)) {
return true;
}
}
return false;
} /**
* Checks if the user belongs to role.
*/
public function inRole($roleSlug)
{
return $this->roles()->where('slug', $roleSlug)->count() == 1;
}
}

执行迁移和数据注入

php artisan migrate --seed

认证系统

> php artisan make:auth
Authentication scaffolding generated successfully.

注册

Controllers/Auth/RegisterController.php 创建 showRegistrationForm 方法(覆盖掉在 RegistersUsers trait 中定义)。

Use App/Role;

...

public function showRegistrationForm()
{
$roles = Role::orderBy('name')->pluck('name', 'id');
return view('auth.register', compact('roles'));
}

编辑 resources/views/auth/register.blade.php,添加角色选择项。

...

<div class="form-group{{ $errors->has('role') ? ' has-error' : '' }}">
<label for="role" class="col-md-4 control-label">User role</label> <div class="col-md-6">
<select id="role" class="form-control" name="role" required>
@foreach($roles as $id => $role)
<option value="{{ $id }}">{{ $role }}</option>
@endforeach
</select> @if ($errors->has('role'))
<span class="help-block">
<strong>{{ $errors->first('role') }}</strong>
</span>
@endif
</div>
</div> ...

更新 RegisterController 中的 validator 方法。

...

protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
'role' => 'required|exists:roles,id', // validating role
]);
} ...

修改 create 方法,加入存储用户角色的业务逻辑。

...

protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$user->roles()->attach($data['role']);
return $user;
} ...

运行项目

浏览器地址栏输入: http://localhost/blog/public/ 。看到欢迎页面、注册用户。

定义策略

修改 app/Providers/AuthServiceProvider.php 文件。

use App\Post;

...

public function boot()
{
$this->registerPolicies();
$this->registerPostPolicies();
} public function registerPostPolicies()
{
Gate::define('create-post', function ($user) {
return $user->hasAccess('create-post');
});
Gate::define('update-post', function ($user, Post $post) {
return $user->hasAccess('update-post') or $user->id == $post->user_id;
});
Gate::define('publish-post', function ($user, Post $post) {
return $user->hasAccess('publish-post') or $user->id == $post->user_id;;
});
Gate::define('delete-post', function ($user, Post $post) {
return $user->hasAccess('delete-post') or $user->id == $post->user_id;
});
Gate::define('see-all-drafts', function ($user) {
return $user->inRole('editor');
});
}

定义路由

修改 routes/web.php

Route::get('/posts', 'PostController@index')->name('list_posts');
Route::group(['prefix' => 'posts'], function () {
Route::get('/drafts', 'PostController@drafts')
->name('list_drafts')
->middleware('auth');
Route::get('/show/{id}', 'PostController@show')
->name('show_post');
Route::get('/create', 'PostController@create')
->name('create_post')
->middleware('can:create-post');
Route::post('/create', 'PostController@store')
->name('store_post')
->middleware('can:create-post');
Route::get('/edit/{post}', 'PostController@edit')
->name('edit_post')
->middleware('can:update-post,post');
Route::post('/edit/{post}', 'PostController@update')
->name('update_post')
->middleware('can:update-post,post');
Route::post('/delete/{post}', 'PostController@destory')
->name('delete_post')
->middleware('can:delete-post,post');
// using get to simplify
Route::get('/publish/{post}', 'PostController@publish')
->name('publish_post,post')
->middleware('can:publish-post');
Route::get('/unpublish/{post}', 'PostController@unpublish')
->name('unpublish_post')
->middleware('can:publish-post,post');
});

博客

Post Model

...

class Post extends Model
{
protected $fillable = [
'title', 'slug', 'body', 'user_id',
]; public function owner()
{
return $this->belongsTo(User::class);
} public function scopePublished($query)
{
return $query->where('published', true);
} public function scopeUnpublished($query)
{
return $query->where('published', false);
}
}

PostController

博客列表

use App\Post;

...

public function index()
{
$posts = Post::published()->latest()->paginate();
return view('posts.index', compact('posts'));
} ...

创建 resources/views/posts/index.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
Posts
@can('create-post')
<a class="pull-right btn btn-sm btn-primary" href="{{ route('create_post') }}">New</a>
@endcan
</div> <div class="panel-body">
<div class="row">
@foreach($posts as $post)
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<div class="caption">
<h3><a href="{{ route('show_post', ['id' => $post->id]) }}">{{ $post->title }}</a></h3>
<p>{{ str_limit($post->body, 50) }}</p>
@can('update-post', $post)
<p>
<a href="{{ route('edit_post', ['id' => $post->id]) }}" class="btn btn-sm btn-default" role="button">Edit</a>
</p>
@endcan
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

创建博客

...

public function create()
{
return view('posts.create');
} ...

创建 posts\create.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">New Post</div> <div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ route('store_post') }}">
{{ csrf_field() }} <div class="form-group{{ $errors->has('title') ? ' has-error' : '' }}">
<label for="title" class="col-md-2 control-label">Title</label> <div class="col-md-9">
<input id="title" type="text" class="form-control" name="title" value="{{ old('title') }}" required autofocus>
@if ($errors->has('title'))
<span class="help-block">
<strong>{{ $errors->first('title') }}</strong>
</span>
@endif
</div>
</div> <div class="form-group{{ $errors->has('body') ? ' has-error' : '' }}">
<label for="body" class="col-md-2 control-label">Body</label> <div class="col-md-9">
<textarea name="body" id="body" cols="30" rows="10" class="form-control" required>{{ old('body') }}</textarea>
@if ($errors->has('body'))
<span class="help-block">
<strong>{{ $errors->first('body') }}</strong>
</span>
@endif
</div>
</div> <div class="form-group">
<div class="col-md-6 col-md-offset-2">
<button type="submit" class="btn btn-primary">
Create
</button>
<a href="{{ route('list_posts') }}" class="btn btn-primary">
Cancel
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

保存博客

use Auth;
use App\Http\Requests\StorePost as StorePostRequest; ... public function store(StorePostRequest $request)
{
$data = $request->only('title', 'body');
$data['slug'] = str_slug($data['title']);
$data['user_id'] = Auth::user()->id;
$post = Post::create($data);
return redirect()->route('edit_post', ['id' => $post->id]);
} ...

创建处理存储博客时使用的请求类 StorePostRequest

php artisan make:request StorePostRequest

编辑 app/Http/Requests/StorePost.php

public function authorize()
{
return true; // gate will be responsible for access
} public function rules()
{
return [
'title' => 'required|unique:posts',
'body' => 'required',
];
}

博客草稿列表

use Gate;

...

public function drafts()
{
$postsQuery = Post::unpublished();
if(Gate::denies('see-all-drafts')) {
$postsQuery = $postsQuery->where('user_id', Auth::user()->id);
}
$posts = $postsQuery->paginate();
return view('posts.drafts', compact('posts'));
} ...

创建 posts/drafts.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
Drafts <a class="btn btn-sm btn-default pull-right" href="{{ route('list_posts') }}">Return</a>
</div> <div class="panel-body">
<div class="row">
@foreach($posts as $post)
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<div class="caption">
<h3><a href="{{ route('show_post', ['id' => $post->id]) }}">{{ $post->title }}</a></h3>
<p>{{ str_limit($post->body, 50) }}</p>
<p>
@can('publish-post', $post)
<a href="{{ route('publish_post', ['id' => $post->id]) }}" class="btn btn-default" role="button">Publish</a>
@endcan
<a href="{{ route('edit_post', ['id' => $post->id]) }}" class="btn btn-default" role="button">Edit</a>
</p>
</div>
</div>
</div>
@endforeach
<div class="col-sm-12 col-md-12">
{{ $posts->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

修改 layouts/app.blade.php,添加“草稿”菜单。

...

<ul class="dropdown-menu" role="menu">
<li>
<a href="{{ route('list_drafts') }}">Drafts</a> ...

编辑博客

添加编辑博客的方法 update

use App\Http\Requests\UpdatePostRequest as UpdatePostRequest;

...

public function edit(Post $post)
{
return view('posts.edit', compact('post'));
} public function update(Post $post, UpdatePostRequest $request)
{
$data = $request->only('title', 'body');
$data['slug'] = str_slug($data['title']);
$post->fill($data)->save();
return back();
}

创建处理更新博客时使用的请求类 UpdatePostRequest

php artisan make:request UpdatePostRequest

编辑 app/Http/Requests/UpdatePostRequest.php

use Illuminate\Validation\Rule;

...

public function authorize()
{
return true;
} public function rules()
{
$id = $this->route('post')->id;
return [
'title' => [
'required',
Rule::unique('posts')->where('id', '<>', $id),
],
'body' => 'required',
];
}

创建视图 posts/edit.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Update Post</div> <div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ route('update_post', ['post' => $post->id]) }}">
{{ csrf_field() }} <div class="form-group{{ $errors->has('title') ? ' has-error' : '' }}">
<label for="title" class="col-md-2 control-label">Title</label> <div class="col-md-9">
<input id="title" type="text" class="form-control" name="title" value="{{ old('title', $post->title) }}" required autofocus> @if ($errors->has('title'))
<span class="help-block">
<strong>{{ $errors->first('title') }}</strong>
</span>
@endif
</div>
</div> <div class="form-group{{ $errors->has('body') ? ' has-error' : '' }}">
<label for="body" class="col-md-2 control-label">Body</label> <div class="col-md-9">
<textarea name="body" id="body" cols="30" rows="10" class="form-control" required>{{ old('body', $post->body) }}</textarea>
@if ($errors->has('body'))
<span class="help-block">
<strong>{{ $errors->first('body') }}</strong>
</span>
@endif
</div>
</div> <div class="form-group">
<div class="col-md-6 col-md-offset-2">
<button type="submit" class="btn btn-primary">
Update
</button>
@can('publish-post', $post)
@if (!$post->published)
<a href="{{ route('publish_post', ['post' => $post->id]) }}" class="btn btn-primary">
Publish
</a>
@else
<a href="{{ route('unpublish_post', ['post' => $post->id]) }}" class="btn btn-primary">
Unpublish
</a>
@endif
@endcan
<a href="{{ route('list_posts') }}" class="btn btn-primary">
Cancel
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

发布草稿/不发布草稿

PostController 添加方法 publishunpublish

...

public function publish(Post $post)
{
$post->published = true;
$post->save();
return redirect()->route('show_post', ['post' => $post->id]);
} public function unpublish(Post $post)
{
$post->published = false;
$post->save();
return back();
} ...

展示博客

public function show(Post $post)
{
$post = Post::findOrFail($id);
if ($post->published || $post->user_id == Auth::user()->id) {
return view('posts.show', compact('post'));
}
abort(403, 'Unauthorized.');
}

创建 posts/show.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
{{ $post->title }}
<a class="btn btn-sm btn-default pull-right" href="{{ route('list_posts') }}">Return</a>
</div> <div class="panel-body">
{{ $post->body }}
</div>
</div>
</div>
</div>
</div>
@endsection

删除博客

修改 PostController,添加 destory 方法。

public function destory(Post $post)
{
$post->delete();
return redirect()->route('list_post');;
}

修改 posts/edit.blade.php,添加删除按钮。

...

<div class="panel-heading">Update Post
@can('delete-post', $post)
<a class="pull-right btn btn-sm btn-danger" href="{{ route('delete_post', ['id' => $post->id]) }}"
onclick="if(confirm('确定删除吗?') === false) { return false; } else {
event.preventDefault(); document.getElementById('delete-post-form').submit();}">
删除
</a>
<form id="delete-post-form" action="{{ route('delete_post', ['post' => $post->id]) }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
@endcan
</div> ...

404 & 403

resources/views 下新建 errors 目录,再在该目录下新建 404.blade.php403.blade.php 页面。

404

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">404</div> <div class="panel-body">
<h2>Not Found</h2>
</div>
</div>
</div>
</div>
</div>
@endsection

403

@extends('layouts.app')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">403</div> <div class="panel-body">
<h2>This Action is Unauthorized!</h2>
</div>
</div>
</div>
</div>
</div>
@endsection

使用 Markdown

这里要安装依赖包 erusev/parsedown,使用它将 Markdown 文本装换为 HTML。

composer require erusev/parsedown

修改 posts/show.blade.php

<div class="panel-body">
{!! Parsedown::instance()->text($post->body) !!}
</div>

此刻,你就可以使用 Markdown 写博客了。

tags: Laravel 项目

使用 Laravel 实现微型博客系统的更多相关文章

  1. PHP学习之[第10讲]PHP 的 Mysql 数据库函数 (微型博客系统)II

    mysql结构如下: -- phpMyAdmin SQL Dump -- version 4.4.1.1 -- http://www.phpmyadmin.net -- -- Host: localh ...

  2. PHP学习之[第09讲]PHP 的 Mysql 数据库函数 (微型博客系统)

    一.数据库函数: mysql_connect(数据库地址,用户名,密码) mysql_select_db(数据库名) mysql_set_chartset(‘编码’) //PHP5.2.3以后的函数 ...

  3. 30分钟用 Laravel 实现一个博客

    介绍 Laravel 是一款 MVC架构. 目前最流行的 PHP框架. Laravel的优点在于: 丰富的composer类库支持, 优雅的代码, 未来的主流框架(目前市场占有率最高的框架) Lara ...

  4. 欢迎阅读daxnet的新博客:一个基于Microsoft Azure、ASP.NET Core和Docker的博客系统

    2008年11月,我在博客园开通了个人帐号,并在博客园发表了自己的第一篇博客.当然,我写博客也不是从2008年才开始的,在更早时候,也在CSDN和系统分析员协会(之后名为"希赛网" ...

  5. ASP.NET + EF + SQL Server搭建个人博客系统新手系列(一):界面展示

    第一次写博客,可能有些地方描述的不准确,还请大家将就.本人大四学生,学了半年C#,半年.net,但是很遗憾,学完之后只会写个hello word.老师教的过程中总是会套用一些模板,特别是后台,完全封装 ...

  6. 一个基于Microsoft Azure、ASP.NET Core和Docker的博客系统

    2008年11月,我在博客园开通了个人帐号,并在博客园发表了自己的第一篇博客.当然,我写博客也不是从2008年才开始的,在更早时候,也在CSDN和系统分析员协会(之后名为“希赛网”)个人空间发布过一些 ...

  7. 【完全开源】Django多人博客系统——支持MarkDown和tinyMce

    目录 说明 功能 如何使用 说明 这是一个用Django开发的多人博客系统,功能简单,但完全满足公司内部或个人的博客使用需求.支持普通富文本编辑器(tinyMCE)和MarkDown编辑器 由于嫌弃D ...

  8. 从零开始,搭建博客系统MVC5+EF6搭建框架(5),博客详情页、留言、轮播图管理、右侧统计博文

    一.博客系统进度回顾 上一遍博客介绍到,系统已经实现到了发布以及前台布局展示,接下来就是实现一些,详情页,留言.轮播图管理.右侧博文统计信息实现. 二.博客系统详情页实现 2.1先来看看详情页展示的效 ...

  9. 从零开始,搭建博客系统MVC5+EF6搭建框架(4)下,前后台布局实现、发布博客以及展示。

    一.博客系统进度回顾 目前已经完成了,前台展示,以及后台发布的功能,最近都在做这个,其实我在国庆的时候就可以弄完的,但是每天自己弄,突然最后国庆2天,连电脑都不想碰,所以就一直拖着,上一篇写了前端实现 ...

随机推荐

  1. 【SCOI2008】着色方案

    题目: http://oj.changjun.com.cn/problem/detail/pid/2027 pre.cjk { font-family: "Droid Sans Fallba ...

  2. 把GIF背景变透明

    准备软件: 1.Ps cs4 2.QuickTime Player 7.74 开始: 1. 2.弹出文件选择框,但是发现不能选择GIF格式. 3.没关系,在文件名框输入*.*回车,就发现可以选择GIF ...

  3. 一场完美的“秒杀”:API加速的业务逻辑

    清晨,我被一个客户电话惊醒,客户异常焦急,寻问CDN能不能帮助他们解决“秒杀”的问题,他们昨天刚刚进行了“整点秒杀活动”,结果并发量过大,导致服务宕机,用户投诉. 为了理清思路,我问了对方三个问题: ...

  4. html行内元素,块元素,空元素

    行内元素:img    span    input  a  b  br  lable   ; 块元素:dl  dt  dd  div  form  table  li  ol  ul  li  h1- ...

  5. 【一】Swift 3.0 新浪微博项目实战 -整体框架搭建

    最近要接手swift,所以找了个视频跟着做一下实战项目,在此记录一下过程和心得 框架搭建和目录拆分 关键词:MVVM 架构,桥接文件 桥接文件用于引入OC的头文件,Swift就可以正常使用(宏除外). ...

  6. Centos7配置文件共享服务器SAMBA三步曲(转)

    1.安装 yum install samba samba-client samba-common -y 2.配置 备份已有配置 mv /etc/samba/smb.conf /etc/samba/sm ...

  7. Modbus通信协议的压力测试

    最近物联网都比较的火,因此,特别为各位兄弟姐妹们,奉上一款Mobus协议的测试软件,可以用来做设备的压力测试,和通信测试. 起初软件开发缘由是我们最近在开发一款设备,需要将多个DS18B20并联起来, ...

  8. Java排序算法之直接选择排序

    Java排序算法之直接选择排序 基本过程:假设一序列为R[0]~R[n-1],第一次用R[0]和R[1]~R[n-1]相比较,若小于R[0],则交换至R[0]位置上.第二次从R[1]~R[n-1]中选 ...

  9. 数位dp初步——数位dp的两种方式

    数位dp:一类统计区间[L,R]内某种符合规定的数字个数的题目.特征是R的范围会很大,O(N)范围内无法完成. 一般而言,解决这类题目有两种方式,一种是递推,另一种是记忆化搜索. 递推: 1)利用dp ...

  10. Java基础之J2EE规范

    什么是J2EE? 在企业级应用中,都有一些通用企业需求模块,如数据库连接,邮件服务,事务处理等.既然很多企业级应用都需要这些模块,一些大公司便开发了自己的通用模块服务,即中间件.这样一来,就避免了重复 ...