Build a naïve Blog with Laravel | Ignorance Notebook

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需求宣讲、分组、数据库设计确认项目需求、完成数据库设计
day1springboot通用开发流程确认项目UI、完成登录与注册开发
day2spring interceptor开发完成登录的interceptor开发
day3-4layui+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.

MethodURLAction
GET/posts/{post_uuid}Display the specified post content.
GET/posts/{post_uuid}/editDisplay the editor for specified post.
DELETE/posts/{post_uuid}Delete the specified post.(Soft Delete)
POST/postsCreate 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' => "nova@nova.moe",
        '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>