Build a naïve Blog with Laravel #
项目 #
基于springboot的个人博客系统
知识点 #
- *前端layui
- *springboot通用开发流程
- springmvc
- hibernate-jpa
- web-service-dal
- mustache模板引擎
- *Springboot Interceptor开发
- *富文本编辑器 simeditor
- *博客热词词云 https://github.com/kennycason/kumo
- *图片上传组件 及解决方案
- 前端layui.upload https://www.layui.com/doc/modules/upload.html
- spring文件上传支持
- ng代理/tomcat vdir
- 图像处理
- *EXIF 读取及记录 https://github.com/drewnoakes/metadata-extractor
- 百度图像识别API 获取信息及记录 http://ai.baidu.com/docs#/ImageClassify-API/141c7bfa
- *图像timeline展示 https://www.layui.com/doc/element/timeline.html
- 部署相关
- spring-boot的打包
- spring.profiles.active
- linux基本命令
- 部署依赖(JDK、MySQL、ng)
实训计划 #
时间
知识点
进度
day0
需求宣讲、分组、数据库设计
确认项目需求、完成数据库设计
day1
springboot通用开发流程
确认项目UI、完成登录与注册开发
day2
spring interceptor开发
完成登录的interceptor开发
day3-4
layui+simeditor
完成博客撰写页\完成首页及详情页
day5-6
图片上传组件 及解决方案
完成相册页
day7
其他知识点提示
集成各功能
For log propose, do not follow steps below in production project.
From 2019/01/04 ~ 2019/01/11 ChinaSofti software development, the final goal is to design a blog system.
The whole system can be describe as below:
User Consideration #
The Admin User will be the only person has the permission to Add/Edit/Delete Post, Add/Edit/Delete Post, the normal user has only the permission to post comment.
Comment Consideration #
The Comments has no in-line reply function as the comments for one post is just listed at date ascend.(Single foreign key referring the post’s primary key.)
Post Consideration #
For better editing the pages, and the support rich-content requisite, Markdown is used to store post content, on-the-fly Markdown to HTML rendering is needed for displaying the content for end user.
The primary key for post will be UUID (without slashes), so the post URL style will look like : /posts/ad65b02b1e6c4ea1ba7af94f69aeec1f/
which is way better than the /posts/2
.
For this need, we need to import the UUID repo, as below:
composer require webpatser/laravel-uuid
Since our DB stores the Markdown data, we will need to use a Class to convert Markdown formatted text to HTML for front-end rendering.
composer require graham-campbell/markdown
Register them in config/app.php
:
'Uuid' => Webpatser\\Uuid\\Uuid::class,
'Markdown' => GrahamCampbell\\Markdown\\Facades\\Markdown::class,
In related controllers(such as app/Http/Controllers/PostController.php
), use the needed package:
use GrahamCampbell\\Markdown\\Facades\\Markdown;
Init the Laravel Project #
laravel new laralog
and fill it with default user authentication system.
php artisan make:auth
then use php artisan serve
for open a dev server at localhost:8000
, this should look like as below.
Routing #
Since this is only a blog, we don’t need specified routing system, just hard-code the page route in route.php
and the rest of the posts can be accessed by /posts/{post_id}
which should be controlled by PostController
.
As CRUD for blog post, we define the following RESTful design pattern.
Method
URL
Action
GET
/posts/{post_uuid}
Display the specified post content.
GET
/posts/{post_uuid}/edit
Display the editor for specified post.
DELETE
/posts/{post_uuid}
Delete the specified post.(Soft Delete)
POST
/posts
Create a post.
PUT
/posts/{post_uuid}
As an interface for receiving JSON formatted post content.
We define the post route here, let’s create PostController
first with php artisan make:controller PostController
:
Route::post('/posts/{post\_uuid}','PostController@create\_post');
Route::get('/posts/{post\_uuid}','PostController@get\_post');
Route::get('/posts/{post\_uuid}/edit','PostController@get\_post\_edit');
Route::put('/posts/{post\_uuid}','PostController@update\_post');
Route::delete('/posts/{post\_uuid}','PostController@delete\_post');
Pages routing will be hard-coded (About page as example):
Route::get('/about','PageController@index');
In app/Http/Controllers/PostController.php
:
public function index()
{
return view('about');
}
Build User Seed along with some DB tables #
User migration, user should login with email and password:
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('role')->default("user");
$table->string('email')->unique();
$table->timestamp('email\_verified\_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Create the Model with Post migration:
php artisan make:model Post
php artisan make:migration create\_blog\_post
Post Model (app/Post.php
):
class Post extends Model
{
protected $table = 'post';
}
Post migration(database/migrations/2019_01_05_014913_create_blog_post.php
):
Schema::create('posts', function (Blueprint $table) {
$table->primary('post\_uuid');
$table->uuid('post\_uuid');
$table->string('post\_title');
$table->string('post\_type')->default('post');
$table->mediumText('post\_content');
$table->timestamps();
});
Create Post Seeder:
php artisan make:seeder PostSeeder
In database/seeds/PostSeeder.php
:
public function run()
{
for ($i=1; $i < 10; $i++) {
DB::table('posts')->insert(\[
'post\_uuid' => (string) Uuid::generate(4),
'post\_title' => str\_random(20),
'post\_type' => "post",
'post\_content' => str\_random(300),
\]);
}
DB::table('posts')->insert(\[
'post\_uuid' => (string) Uuid::generate(4),
'post\_title' => str\_random(20),
'post\_type' => "gallery",
'post\_content' => "!\[bird-3732867\_1920.jpg\](https://i.loli.net/2019/01/07/5c32efd9098c5.jpg)",
\]);
}
Since there is only one Admin, we will need to insert the Admin user on migration seed section:
php artisan make:seeder UserSeeder
in database/seeds/UserSeeder.php
we define our Admin user here:
public function run()
{
DB::table('users')->insert(\[
'name' => "Nova Kwok",
'role' => "admin",
'email' => "[email protected]",
'password' => bcrypt("opennova"),
\]);
}
Here comes the comment part, as I mentioned above, Post -> Comment has a One-To-Many relation, there is a need to declare the “foreign key” in the migration file, in database/migrations/2019_01_06_060322_create_comments.php
.
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->uuid('commented\_on');
$table->string('commented\_by');
$table->mediumText('comment\_content');
$table->timestamps();
});
Run the seeds to seed the DB:
php artisan db:seed --class=UserSeeder
php artisan db:seed --class=PostSeeder
Index Page Blade #
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
@foreach ($all\_posts as $key => $post)
<div class="card">
<div class="card-header"><a href="/posts/{{ $post->post\_uuid }}">{{ $post->post\_title }}</a></div>
<div class="card-body">
{{ $post->post\_content }}
</div>
</div>
<br>
@endforeach
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">TukiBlog</div>
<div class="card-body">
<ul>
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
<li><a href="https://nova.moe">Nova Kwok's Blog</a></li>
<li><a href="htt\[ps://keshane.moe">Keshane's Blog</a></li>
</ul>
</div>
</div>
<br>
<div class="card">
<div class="card-header">Pages</div>
<div class="card-body">
<ul>
<li><a href="/about">About</a></li>
</ul>
</div>
</div>
<br>
<div class="card">
<div class="card-header">Pages</div>
<div class="card-body">
<ul>
<li><a href="/about">About</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
@endsection
Adding the Post_edit Page with Editor #
HTML Part:
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="">Post Title</span>
</div>
<input type="text" name="post\_title" class="form-control" value="{{ $post->post\_title }}">
</div>
<br>
<div class="card">
<div class="card-body">
<small>The Post URL will be: <a href="/posts/{{ $post->post\_uuid }}">/posts/{{ $post->post\_uuid }}</a> </small>
<form class="" action="/posts/{{ $post->post\_uuid }}" method="POST" enctype="multipart/form-data">
@csrf
@method('put')
<textarea id="md-text" name="post\_content" rows="8">{{ $post->post\_content }}</textarea>
<div class="row justify-content-center">
<div class="col-lg-6 col-sm-6 col-xs-12">
<button class="btn btn-block btn-raised btn-success pull-right" input type="submit">Edit</button>
</div>
</div>
</form>
</div>
</div>
Adding the SimpleMDE Editor:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.js"></script>
<script type="text/javascript">
var simplemde = new SimpleMDE({
element: document.getElementById("md-text"),
toolbar: \["table", "heading", "|", "preview"\]
});
</script>
Defining the Middleware & Securing the Route #
Create a middleware as guard for guarding the Admin Panel.
php artisan make:middleware IsAdmin
In app/Http/Middleware/IsAdmin.php
public function handle($request, Closure $next)
{
$role = auth()->user()->role;
if($role != "admin")
{
return response()->view('errors.403');
}
return $next($request);
}
And register it in the Kernel(app/Http/Kernel.php
):
protected $routeMiddleware = \[
'auth' => \\App\\Http\\Middleware\\Authenticate::class,
...
'admin' => \\App\\Http\\Middleware\\IsAdmin::class,
\];
Applying Middleware to the protected route (routes/web.php
) so when a non-admin user tries to visit protected page will meet an 403 error.
Route::get('/posts/{post\_uuid}/edit','PostController@get\_post\_edit')->middleware('auth')->middleware('admin');
Pagination #
Laravel has provide a simple pagination function, we define pagination in the index function:
public function index($page = 1)
{
$all\_posts = Post::paginate(5);
$post = Post::paginate(5);
$pre\_url = $post->previousPageUrl();
$next\_url = $post->nextPageUrl();
return view('index')
->with('pre\_url',$pre\_url)
->with('next\_url',$next\_url)
->with('all\_posts',$all\_posts);
}
And in the resources/views/index.blade.php
, we define the following front-end.
<nav>
<ul class="pagination">
<li class="page-item"><a class="page-link" href="{{ $pre\_url }}">Previous</a></li>
<li class="page-item"><a class="page-link" href="{{ $next\_url }}">Next</a></li>
</ul>
</nav>