推薦閱讀
建表
接上一篇提到的,通過專題( collection ), 來實現推薦閱讀的編寫.
按照慣例,先來看看 專題的設計稿 ,然後設計出表結構.
Schema::create('collections', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('avatar');
$table->string('description');
$table->unsignedInteger('post_count')->default(0);
$table->unsignedInteger('fans_count')->default(0);
$table->unsignedInteger('user_id')->comment('建立者');
$table->timestamps();
});
複製程式碼
專題存在管理員( collection_admin )/投稿作者( collection_author )/關注者( collection_follower ) /帖子( collection_post ) 此處以 collection_post 為例看一下中間表的設計,其是 collection 和 post 中間表.
Schema::create('collection_post', function (Blueprint $table) {
$table->unsignedInteger('post_id');
$table->unsignedInteger('collection_id');
$table->timestamp('passed_at')->nullable()->comment('稽核通過時間');
$table->timestamps();
$table->index('post_id');
$table->index('collection_id');
$table->unique(['post_id', 'collection_id']);
});
複製程式碼
建好表之後記得填充 seeder 哦.
建模
# Collection.php
<?php
namespace App\Models;
class Collection extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class, 'collection_post');
}
}
複製程式碼
# Post.php
<?php
namespace App\Models;
class Post extends Model
{
// ...
public function collections()
{
return $this->belongsToMany(Collection::class, 'collection_post');
}
}
複製程式碼
有了 Collection ,接下來就能夠實現帖子詳情頁設計稿的最後一部分啦
專題收入
首先是專題收錄部分, 按照 RESTful 的規範,我們可以設計出這樣一條 API
test.com/api/posts/{… , 此處編碼較為簡單,參考原始碼即可
推薦閱讀
首先還是按照 RESTful 規範 來設計 API
相應的控制器程式碼
# PostController.php
public function indexOfRecommend($post)
{
$collectionIds = $post->collections()->pluck('id');
$query = Post::whereHas('collections', function ($query) use ($collectionIds) {
$query->whereIn('collection_id', $collectionIds);
});
// 排序問題
$posts = $query->columns()->paginate();
return PostResource::make($posts);
}
複製程式碼
這裡需要說明一下, laravel 提供的 whereHas 會生成一個效率不高的 SQL 語句,需要載入全表.但是系列的目的是編寫具有描述性的 RESTful API ,所以此處不做進一步優化.
Observer
Observer 既 觀察者,可以用於程式碼解耦,保持控制器簡潔. 接下來的兩個邏輯會涉及 Observer 的使用場景.
熱度
$posts = $query->columns()->paginate();
這行語句在沒有指定 orderBy 時, MySQL 會按照 id , asc 的順序取出帖子,但是在一般的社群網站中,通常會有一個熱度,然後按照熱度將帖子取出來.
這部分的排序演算法又很多,按照產品給定的公式計算即可
下文假定熱度計算公式為 heat = a * (timestamp - 1546300800) + b * read_count + c * like_count
a/b/c 代表每一個特徵所佔的權重,可根據運營需求隨時調整, 由於時間戳過大,所以通過 減去 2019-01-01的時間戳 1546300800 ,來縮小時間戳數字, 當然即使如此依舊會得到一個很大的數字,所以 a 的值會很小
Schema::create('posts', function (Blueprint $table) {
// ...
$table->integer('heat')->index()->comment('熱度');
// ...
});
複製程式碼
由於專案在開發階段,所以直接修改原有的 migration , 新增 heat 欄位.然後執行
> php artisan migrate:refresh --seed
heat 欄位的維護原則是,**檢測到 read_count 或者 like_count 發生變化時,則更新相關的熱度.**因此此處會用 observe來實現相關的功能.
按照文件建立觀察者並註冊後,可以編寫相關的程式碼
> php artisan make:observer PostObserver --model=Models/Post
class PostObserver
{
/**
* @param Post $post
*/
public function saving(Post $post)
{
if ($post->isDirty(['like_count', 'read_count'])) {
$heat = 0.001 * ($post->created_at->timestamp - 1546300800)
+ 10 * $post->read_count
+ 1000 * $post->like_count;
$post->heat = (integer)$heat;
}
}
}
複製程式碼
呼叫 $model->save/update/create
都會在持久化到資料庫之前觸發 saving 方法.
建立評論
基礎編碼
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Comment;
use App\Resources\CommentResource;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class CommentController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|Response
*/
public function store(Request $request)
{
$data = $request->all();
$data['user_id'] = \Auth::id();
$data['floor'] = Comment::where('post_id', $request->input('post_id'))->max('floor') + 1;
$comment = Comment::create($data);
// RESTful 規範中,建立成功應該返回201狀態碼
return \response(CommentResource::make($comment), 201);
}
}
複製程式碼
Model
<?php
namespace App\Models;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
class Comment extends Model
{
use HasEagerLimit;
protected $fillable = ['content', 'user_id', 'post_id', 'floor', 'selected'];
public function getLikeCountAttribute()
{
return $this->attributes['like_count'] ?? 0;
}
public function getReplyCountAttribute()
{
return $this->attributes['reply_count'] ?? 0;
}
複製程式碼
由於使用了create 方法進行建立,因此需要在模型中宣告 $fillable
由於建表的時候為 like_count 和 reply_count 設定了預設值為 0 , 所以 在 create 時沒有設定 like_count , reply_count .但是這樣會造成控制器中的 store 方法中的 $comment
不存在 like_count , 和 reply_count 這兩個 key , 這對前端是非常不友好的. 例如在 vue 中此處通常的做法是 this.comments.push(comment)
.有兩個辦法解決這個問題
-
create 時新增
$data['like_count'] = 0
和$data['reply_count'] = 0
-
使用模型修改器設定這兩個 key 的預設值(上面的 Comment 模型中演示了該方法)
使用上述任意一種方法都能夠保證查詢與建立時的資料一致性.
API 展示, 相應的 Postman 文件附加在文末
在控制器程式碼中, 將相應的 Model 交給了 tree-ql 處理, 所以這裡依舊可以使用 include , 從而保證相應資料一致性.
posts 表中冗餘了 comment_count ,因此當建立一條評論時,還需要相應的 post.comment_count + 1
. 建立並註冊 CommentObserver. 然後完成相應的編碼
# CommentObserver.php
<?php
namespace App\Observers;
use App\Models\Comment;
class CommentObserver
{
public function created(Comment $comment)
{
$comment->post()->increment('comment_count');
}
}
複製程式碼
補充
帖子的釋出流程
一個可能存在的問題是,一篇已經發布的帖子當使用者想去再次修改它,此時如果修改到一半的帖子觸發了自動儲存機制,則會出現修改了一半的帖子被展示在首頁等.
因此一張 posts 表並不能滿足實際的需求,還需要增加一張 drafts 表來作為草稿箱, 使用者的建立與修改操作都是在該表下進行的,只有使用者點選發布時, 將相應的 drafts 同步到 posts 表即可. 相關流程參考簡書即可.
釋出流程編碼示例
# DraftController.php
public function published(Draft $draft)
{
Validator::make($draft->getAttributes(), [
'title' => 'required|max:255',
'content' => 'required'
])->validate();
$draft->published();
return response(null, 201);
}
複製程式碼
public function published()
{
if (!$this->post_id) {
$post = Post::create([
'user_id' => $this->user_id,
'title' => $this->title,
'content' => $this->content,
'published_at' => $this->freshTimestampString(),
]);
$this->post_id = $post->id;
$this->save();
} else {
$post = Post::findOrFail($this->post_id);
$post->title = $this->title;
$post->content = $this->content;
$post->save();
}
}
複製程式碼
其餘部分參考原始碼,相關 API 參考 Postman 文件.
相關
- Api Document documenter.getpostman.com/view/150062…
- 線上除錯工具 telescope
- 本節原始碼 weiwenhao/community-api
- Api Tool weiwenhao/tree-ql
- 上一節 編寫具有描述性的 RESTful API (一): Workflow