<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Laravel 后端博客文章數(shù)據(jù)相關(guān) API 接口提供

          共 22552字,需瀏覽 46分鐘

           ·

          2021-03-31 22:20

          在前兩篇教程中,我們已經(jīng)為博客單頁(yè)面應(yīng)用準(zhǔn)備好了前端路由和頁(yè)面組件,在這篇教程中,我們將通過(guò) Laravel 后端 API 接口提供文章數(shù)據(jù)來(lái)渲染前端頁(yè)面。

          一、模型類(lèi)和數(shù)據(jù)庫(kù)遷移

          開(kāi)始之前,先啟動(dòng) MySQL 數(shù)據(jù)庫(kù),創(chuàng)建本項(xiàng)目對(duì)應(yīng)的數(shù)據(jù)庫(kù) demo_spa,并在 .env 配置好數(shù)據(jù)庫(kù)連接信息:

          DB_CONNECTION=mysql
          DB_HOST=127.0.0.1
          DB_PORT=3306
          DB_DATABASE=demo_spa
          DB_USERNAME=root
          DB_PASSWORD=root

          接下來(lái),我們使用 Laravel Artisan 命令為博客文章創(chuàng)建模型類(lèi)、數(shù)據(jù)庫(kù)遷移文件和控制器:

          php artisan make:model Post -mc

          創(chuàng)建完成后,編寫(xiě)剛剛生成的 posts 表對(duì)應(yīng)的數(shù)據(jù)庫(kù)遷移類(lèi)代碼如下:

          <?php

          use Illuminate\Database\Migrations\Migration;
          use Illuminate\Database\Schema\Blueprint;
          use Illuminate\Support\Facades\Schema;

          class CreatePostsTable extends Migration
          {
              /**
               * Run the migrations.
               *
               * @return void
               */

              public function up()
              
          {
                  Schema::create('posts'function (Blueprint $table) {
                      $table->id();
                      $table->string('title');
                      $table->string('summary');
                      $table->text('content');
                      $table->string('image_url');
                      $table->smallInteger('category_id')->unsigned()->default(0)->index();
                      $table->bigInteger('user_id')->unsigned()->default(0)->index();
                      $table->timestamps();
                  });
              }

              /**
               * Reverse the migrations.
               *
               * @return void
               */

              public function down()
              
          {
                  Schema::dropIfExists('posts');
              }
          }

          posts 表中,定義了兩個(gè)邏輯外鍵 —— user_idcategory_id,分別對(duì)應(yīng)用戶 ID 和分類(lèi) ID(用戶和文章、分類(lèi)和文章都是一對(duì)多關(guān)聯(lián)),由于后續(xù)進(jìn)行數(shù)據(jù)庫(kù)關(guān)聯(lián)查詢時(shí)不可避免地要使用這兩個(gè)外鍵字段,所以我們?yōu)槠湓O(shè)置了索引。

          由于 Laravel 默認(rèn)已經(jīng)包含了用戶模型類(lèi)和數(shù)據(jù)表遷移文件,所以我們只需要再創(chuàng)建分類(lèi)模型類(lèi)和數(shù)據(jù)庫(kù)遷移文件即可:

          php artisan make:model Category -m  

          編寫(xiě)分類(lèi)表對(duì)應(yīng)的數(shù)據(jù)庫(kù)遷移類(lèi)代碼如下:

          <?php

          use Illuminate\Database\Migrations\Migration;
          use Illuminate\Database\Schema\Blueprint;
          use Illuminate\Support\Facades\Schema;

          class CreateCategoriesTable extends Migration
          {
              /**
               * Run the migrations.
               *
               * @return void
               */

              public function up()
              
          {
                  Schema::create('categories'function (Blueprint $table) {
                      $table->smallIncrements('id');
                      $table->string('name'30)->unique();
                      $table->timestamps();
                  });
              }

              /**
               * Reverse the migrations.
               *
               * @return void
               */

              public function down()
              
          {
                  Schema::dropIfExists('categories');
              }
          }

          只包含了分類(lèi) ID、分類(lèi)名稱(chēng)、創(chuàng)建和更新時(shí)間,非常簡(jiǎn)單,對(duì)于個(gè)人博客應(yīng)用而言,分類(lèi)數(shù)量一般不會(huì)過(guò)百,所以這里使用了 small integer 作為主鍵 ID 類(lèi)型降低空間占用,分類(lèi)名是唯一的,所以設(shè)置了唯一索引。

          運(yùn)行 php artisan migrate 命令讓數(shù)據(jù)庫(kù)遷移生效,這樣,就可以在數(shù)據(jù)庫(kù)中看到對(duì)應(yīng)的數(shù)據(jù)表都已經(jīng)生成了:

          二、關(guān)聯(lián)關(guān)系和 API 接口編寫(xiě)

          接下來(lái),我們?cè)谖恼隆⒎诸?lèi)、用戶模型類(lèi)中定義它們之間的關(guān)聯(lián)關(guān)系

          首先是 Post 模型類(lèi),它與分類(lèi)和用戶模型之間是逆向一對(duì)多歸屬關(guān)聯(lián)關(guān)系:

          <?php

          namespace App\Models;

          use Illuminate\Database\Eloquent\Factories\HasFactory;
          use Illuminate\Database\Eloquent\Model;

          class Post extends Model
          {
              use HasFactory;

              protected $fillable = ['title''summary''content''image_url''category_id'];

              public function category()
              
          {
                  return $this->belongsTo(Category::class);
              }

              public function author()
              
          {
                  return $this->belongsTo(User::class, 'user_id');
              }
          }

          接著在 Category 模型類(lèi)中定義其與 Post 模型類(lèi)的一對(duì)多關(guān)聯(lián):

          <?php

          namespace App\Models;

          use Illuminate\Database\Eloquent\Factories\HasFactory;
          use Illuminate\Database\Eloquent\Model;

          class Category extends Model
          {
              use HasFactory;

              public function posts()
              
          {
                  return $this->hasMany(Post::class);
              }
          }

          以及在 User 模型類(lèi)中定義它與 Post 模型類(lèi)的一對(duì)多關(guān)聯(lián):

          public function posts()
          {
              return $this->hasMany(Post::class);
          }

          定義好這些關(guān)聯(lián)關(guān)系后,就可以在 PostController 中編寫(xiě)返回博客首頁(yè)、分類(lèi)列表頁(yè)和文章詳情頁(yè)數(shù)據(jù)的接口方法了:

          <?php

          namespace App\Http\Controllers;

          use App\Models\Category;
          use App\Models\Post;

          class PostController extends Controller
          {
              // 博客首頁(yè)
              public function index()
              
          {
                  return Post::with(['author:id,name,email''category'])
                      ->select(['id''title''summary''image_url''category_id''user_id''created_at'])
                      ->orderByDesc('id')->paginate();
              }

              // 分類(lèi)頁(yè)面
              public function category($name)
              
          {
                  $category = Category::whereName($name)->firstOrFail();
                  $posts = Post::with(['author:id,name,email'])
                      ->where('category_id', $category->id)
                      ->orderByDesc('id')
                      ->paginate(10);
                  return $posts;
              }

              // 文章詳情頁(yè)
              public function show(Post $post)
              
          {
                  return $post->load(['author:id,name,email''category']);
              }
          }

          非常簡(jiǎn)單,其中使用了渴求式加載獲取關(guān)聯(lián)模型數(shù)據(jù)和分頁(yè)方法獲取分頁(yè)器實(shí)例(列表頁(yè)數(shù)據(jù)需要分頁(yè)獲取)。

          這樣一來(lái),就初步完成了后端文章接口的編寫(xiě)工作,當(dāng)然,還需要在 routes/api.php 中注冊(cè)相應(yīng)的 API 路由才能被外部訪問(wèn):

          use App\Http\Controllers\PostController;
          Route::get('/posts', [PostController::class, 'index']);
          Route::get('/posts/category/{name}', [PostController::class, 'category']);
          // 使用隱式路由模型綁定獲取文章詳情
          Route::get('/posts/{post}', [PostController::class, 'show']);

          三、通過(guò)模型工廠填充測(cè)試數(shù)據(jù)

          為了測(cè)試上述博客文章 API 接口是否可以正常訪問(wèn),我們來(lái)編寫(xiě)模型工廠數(shù)據(jù)庫(kù)填充器填充測(cè)試數(shù)據(jù)。

          Laravel 默認(rèn)已經(jīng)提供了用戶類(lèi)對(duì)應(yīng)的模型工廠,我們只需要編寫(xiě)分類(lèi)模型和文章模型對(duì)應(yīng)的模型工廠即可。

          首先是 Category 模型工廠,使用如下 Artisan 命令創(chuàng)建對(duì)應(yīng)的模型工廠類(lèi):

          php artisan make:factory CategoryFactory

          然后編寫(xiě) CategoryFactory 類(lèi)代碼如下:

          <?php

          namespace Database\Factories;

          use App\Models\Category;
          use Illuminate\Database\Eloquent\Factories\Factory;

          class CategoryFactory extends Factory
          {
              /**
               * The name of the factory's corresponding model.
               *
               * @var string
               */

              protected $model = Category::class;

              /**
               * Define the model's default state.
               *
               * @return array
               */

              public function definition()
              
          {
                  return [
                      'name' => $this->faker->unique()->word
                  ];
              }
          }

          分類(lèi)名是唯一的,所以獲取「?jìng)卧斓摹狗诸?lèi)名之前調(diào)用了 unique 函數(shù)。

          使用同樣的步驟創(chuàng)建 Post 模型工廠類(lèi):

          php artisan make:factory PostFactory

          編寫(xiě)對(duì)應(yīng)的模型工廠代碼如下:

          <?php

          namespace Database\Factories;

          use App\Models\Category;
          use App\Models\Post;
          use App\Models\User;
          use Illuminate\Database\Eloquent\Factories\Factory;

          class PostFactory extends Factory
          {
              /**
               * The name of the factory's corresponding model.
               *
               * @var string
               */

              protected $model = Post::class;

              /**
               * Define the model's default state.
               *
               * @return array
               */

              public function definition()
              
          {
                  return [
                      'title' => rtrim($this->faker->sentence, '.'),
                      'summary' => $this->faker->text,
                      'content' => $this->faker->paragraphs(3true),
                      'user_id' => User::factory(),
                      'category_id' => Category::factory(),
                      'image_url' => $this->faker->imageUrl()
                  ];
              }
          }

          文章表字段較多,所以更復(fù)雜一些,具體的偽造字段生成邏輯可以通過(guò)結(jié)合官方文檔和查看對(duì)應(yīng)字段源碼了解,關(guān)聯(lián)字段可以直接通過(guò)調(diào)用關(guān)聯(lián)模型的工廠方法生成對(duì)應(yīng)的模型后再返回主鍵 ID 作為對(duì)應(yīng)的字段值。

          最后,我們還需要編寫(xiě)一個(gè)數(shù)據(jù)庫(kù)填充器,組合上述模型工廠生成測(cè)試數(shù)據(jù):

          php artisan make:seeder BlogSeeder

          編寫(xiě)這個(gè)填充器類(lèi) BlogSeeder 的實(shí)現(xiàn)代碼如下:

          <?php

          namespace Database\Seeders;

          use App\Models\Category;
          use App\Models\Post;
          use App\Models\User;
          use Illuminate\Database\Seeder;

          class BlogSeeder extends Seeder
          {
              /**
               * Run the database seeds.
               *
               * @return void
               */

              public function run()
              
          {
                  // 開(kāi)始之前先清空相關(guān)數(shù)據(jù)表
                  User::truncate();
                  Category::truncate();
                  Post::truncate();

                  // 創(chuàng)建一個(gè)測(cè)試用戶
                  $user = User::factory([
                      'name' => '測(cè)試賬號(hào)',
                      'email' => '[email protected]',
                  ])->create();

                  // 創(chuàng)建三個(gè)測(cè)試分類(lèi)
                  $cnames = ['PHP''Golang''Javascript'];
                  foreach ($cnames as $cname) {
                      $category = Category::factory(['name' => $cname])->create();
                      // 為每個(gè)分類(lèi)創(chuàng)建 100 篇文章
                      Post::factory([
                              'category_id' => $category->id,
                              'user_id' => $user->id
                          ])
                          ->count(100)
                          ->create();
                  }
              }
          }

          具體含義在注釋里解釋地很清楚了,運(yùn)行下面這個(gè) Artisan 命令通過(guò)偽造數(shù)據(jù)填充相關(guān)數(shù)據(jù)表:

          php artisan db:seed --class=BlogSeeder  

          四、訪問(wèn)博客文章 API 接口

          你可以去 demo_spa 數(shù)據(jù)庫(kù)驗(yàn)證對(duì)應(yīng)的數(shù)據(jù)表是否已經(jīng)成功填充數(shù)據(jù),然后在瀏覽器中訪問(wèn)博客文章 API 接口驗(yàn)證這些接口是否可以正常工作并返回正確的接口數(shù)據(jù):



          可以看到接口都可以正常工作并返回正確的數(shù)據(jù)。

          不過(guò)還有一點(diǎn)問(wèn)題,就是不同的接口返回的數(shù)據(jù)格式不統(tǒng)一,列表頁(yè)分頁(yè)器返回的數(shù)據(jù)包裝在 data 字段里,而詳情頁(yè)直接則返回所有接口數(shù)據(jù),這會(huì)給前端接口調(diào)用者造成困擾。

          另外,有些接口字段值返回給調(diào)用方之后,需要進(jìn)行二次處理,比如時(shí)間、用戶頭像、文章詳情等,我們可以在后端進(jìn)行一致性處理后返回給前端,讓他們拿到之后可以直接用,降低客戶端處理成本和不同端處理風(fēng)格不統(tǒng)一的問(wèn)題。

          五、引入 API 資源類(lèi)處理接口數(shù)據(jù)格式

          以上問(wèn)題可以通過(guò) Laravel 提供的 API 資源類(lèi)解決。

          我們通過(guò)如下 Artisan 命令為接口返回的所有資源(模型類(lèi))創(chuàng)建對(duì)應(yīng)的 API 資源類(lèi):

          其中文章資源還需要處理文章列表,所以創(chuàng)建了對(duì)應(yīng)的 API 資源集合類(lèi)。

          用戶和分類(lèi)資源都是嵌套在文章資源里的,所以我們化繁為簡(jiǎn),逐個(gè)拆解,先編寫(xiě)用戶資源類(lèi):

          <?php

          namespace App\Http\Resources;

          use Illuminate\Http\Resources\Json\JsonResource;

          class User extends JsonResource
          {
              /**
               * Transform the resource into an array.
               *
               * @param  \Illuminate\Http\Request  $request
               * @return array
               */

              public function toArray($request)
              
          {
                  return [
                      'id' => $this->id,
                      'name' => $this->name,
                      'email' => $this->email,
                      'avatar_url' => 'https://i.pravatar.cc/150?u=' . $this->email
                  ];
              }
          }

          在這里,我們僅處理并返回用戶 ID、用戶名、郵箱和頭像鏈接字段(使用 pravatar.cc 網(wǎng)站提供的頭像生成服務(wù)為用戶生成唯一頭像)。

          再編寫(xiě)分類(lèi)資源類(lèi)代碼:

          <?php

          namespace App\Http\Resources;

          use Illuminate\Http\Resources\Json\JsonResource;

          class Category extends JsonResource
          {
              /**
               * Transform the resource into an array.
               *
               * @param  \Illuminate\Http\Request  $request
               * @return array
               */

              public function toArray($request)
              
          {
                  return [
                      'id' => $this->id,
                      'name' => $this->name
                  ];
              }
          }

          我們僅返回了分類(lèi) ID 和分類(lèi)名稱(chēng),時(shí)間相關(guān)的字段舍棄掉,反正也沒(méi)有用到。

          最后,再來(lái)編寫(xiě)文章資源類(lèi)處理代碼:

          <?php

          namespace App\Http\Resources;

          use GrahamCampbell\Markdown\Facades\Markdown;
          use Illuminate\Http\Resources\Json\JsonResource;

          class Post extends JsonResource
          {
              /**
               * Transform the resource into an array.
               *
               * @param  \Illuminate\Http\Request  $request
               * @return array
               */

              public function toArray($request)
              
          {
                  return [
                      'id' => $this->id,
                      'title' => $this->title,
                      'summary' => $this->summary,
                      'content' => empty($this->content) ? '' : Markdown::convertToHtml($this->content),
                      'image_url' => $this->image_url,
                      'author' => User::make($this->author),
                      'category' => Category::make($this->category),
                      'created_at' => $this->created_at->diffForHumans()
                  ];
              }
          }

          這里我們對(duì)文章詳情字段值做了 Markdown 解析(如果有的話,這里使用了 Laravel-Markdown 擴(kuò)展包提供的門(mén)面方法),對(duì)文章創(chuàng)建時(shí)間進(jìn)行了轉(zhuǎn)化,對(duì)于嵌套的關(guān)聯(lián)模型,則基于對(duì)應(yīng)的模型資源類(lèi)來(lái)處理這些模型實(shí)例即可。

          此外,還要編寫(xiě)文章資源集合類(lèi),很簡(jiǎn)單,只需要通過(guò) $collects 屬性指定集合中每個(gè)元素對(duì)應(yīng)的資源處理類(lèi)即可,這里顯然是 Post 資源類(lèi):

          <?php

          namespace App\Http\Resources;

          use Illuminate\Http\Resources\Json\ResourceCollection;

          class Posts extends ResourceCollection
          {
              public $collects = Post::class;

              /**
               * Transform the resource collection into an array.
               *
               * @param  \Illuminate\Http\Request  $request
               * @return array
               */

              public function toArray($request)
              
          {
                  return parent::toArray($request);
              }
          }

          編寫(xiě)好所有的 API 資源類(lèi)處理代碼之后,在 PostController 中使用相應(yīng)的資源類(lèi)來(lái)包裝之前返回的接口數(shù)據(jù)即可,Laravel 會(huì)自動(dòng)按照資源類(lèi)中定義的處理方法對(duì)接口數(shù)據(jù)進(jìn)行統(tǒng)一處理(這可以算作是 PHP 不支持注解的情況下實(shí)現(xiàn)的裝飾器模式,如果通過(guò)注解實(shí)現(xiàn)的話,更加簡(jiǎn)潔優(yōu)雅):

          <?php

          namespace App\Http\Controllers;

          use App\Models\Category;
          use App\Models\Post;
          use App\Http\Resources\Post as PostResource;
          use App\Http\Resources\Posts as PostCollection;

          class PostController extends Controller
          {
              // 博客首頁(yè)
              public function index()
              
          {
                  return new PostCollection(
                      Post::with(['author:id,name,email''category'])
                      ->select(['id''title''summary''image_url''category_id''user_id''created_at'])
                      ->orderByDesc('id')
                      ->simplePaginate(10)
                  );
              }

              // 分類(lèi)頁(yè)面
              public function category($name)
              
          {
                  $category = Category::whereName($name)->firstOrFail();
                  return new PostCollection(
                      Post::with(['author:id,name,email'])
                      ->select(['id''title''summary''image_url''category_id''user_id''created_at'])
                      ->where('category_id', $category->id)
                      ->orderByDesc('id')
                      ->simplePaginate(10)
                  );
              }

              // 文章詳情頁(yè)
              public function show(Post $post)
              
          {
                  return new PostResource(
                      $post->load(['author:id,name,email''category'])
                  );
              }
          }

          六、再次訪問(wèn)博客文章 API 接口

          好了,再次訪問(wèn)博客應(yīng)用所有文章相關(guān) API 接口,現(xiàn)在所有文章數(shù)據(jù)都統(tǒng)一被封裝到 data 字段中,并且客戶端不需要再做其他處理,根據(jù)返回的接口數(shù)據(jù)就可以滿足前端的渲染需求:



          下篇教程,我們將在前端 Vue 組件中根據(jù)這些文章 API 接口返回?cái)?shù)據(jù)來(lái)渲染博客應(yīng)用前端頁(yè)面。

          所有源碼可以通過(guò) Github 代碼倉(cāng)庫(kù)中獲取:https://github.com/nonfu/demo-spa.git。

          本系列教程首發(fā)在Laravel學(xué)院(laravelacademy.org),你可以點(diǎn)擊頁(yè)面左下角閱讀原文鏈接查看最新更新的教程。

          瀏覽 81
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产大尺度视频 | 91香蕉视频在线播放 | 97色亚洲| 肏屄视频在线免费观看 | 尤 物视频在线播放 |