Bladeren bron

基于 Redis 有序集合实现 Laravel 热门浏览文章排行榜功能

chenlong 4 jaren geleden
bovenliggende
commit
70b113c71c

+ 56 - 0
app/Console/Commands/MockViewPosts.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\Post;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Redis;
+
+class MockViewPosts extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'mock:view-posts';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Mock View Posts';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        // 1、先清空 posts 表
+        Post::truncate();
+        // 2、删除对应的 Redis 键
+        Redis::del('popular_posts');
+        // 3、生成 100 篇测试文章
+        Post::factory()->count(100)->create();
+        // 4、模拟对所有文章进行 10000 次随机访问
+        for ($i = 0; $i < 1000; $i++) {
+            $postId = mt_rand(1, 100);
+            $response = Http::get('http://localhost/posts/' . $postId);
+            $this->info($response->body());
+        }
+    }
+}

+ 38 - 0
app/Http/Controllers/PostController.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Models\Post;
+use Illuminate\Support\Facades\Redis;
+
+class PostController extends Controller
+{
+    public function show(Post $post)
+    {
+        $post->increment('views');
+        if ($post->save()) {
+            // 将当前文章浏览数 +1,存储到对应 Sorted Set 的 score 字段
+            Redis::zincrby('popular_posts', 1, $post->id);
+        }
+        return 'Show Post #' . $post->id;
+    }
+
+    // 获取热门文章排行榜
+    public function popular()
+    {
+        // 获取浏览器最多的前十篇文章
+        $postIds = Redis::zrevrange('popular_posts', 0, 9);
+        if ($postIds) {
+            $idsStr = implode(',', $postIds);
+            // 查询结果排序必须和传入时的 ID 排序一致
+            $posts = Post::whereIn('id', $postIds)
+                ->select(['id', 'title', 'views'])
+                ->orderByRaw('field(`id`, ' . $idsStr . ')')
+                ->get();
+        } else {
+            $posts = null;
+        }
+        dd($posts->toArray());
+    }
+}

+ 11 - 0
app/Models/Post.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Post extends Model
+{
+    use HasFactory;
+}

+ 1 - 1
composer.json

@@ -11,7 +11,7 @@
         "php": "^7.3|^8.0",
         "fideloper/proxy": "^4.4",
         "fruitcake/laravel-cors": "^2.0",
-        "guzzlehttp/guzzle": "^7.0.1",
+        "guzzlehttp/guzzle": "^7.2",
         "laravel/framework": "^8.12",
         "laravel/tinker": "^2.5",
         "predis/predis": "^1.1"

+ 1 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "6caecce430d09548ce0b9a2e23213d27",
+    "content-hash": "aa373c5398a159d92226df4ac5de3a08",
     "packages": [
         {
             "name": "asm89/stack-cors",

+ 29 - 0
database/factories/PostFactory.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Database\Factories;
+
+use App\Models\Post;
+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' => trim($this->faker->sentence, '.'),
+            'content' => $this->faker->paragraphs(3, true),
+        ];
+    }
+}

+ 34 - 0
database/migrations/2021_02_19_082943_create_posts_table.php

@@ -0,0 +1,34 @@
+<?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->text('content');
+            $table->integer('views')->unsigned()->default(0);
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('posts');
+    }
+}

+ 4 - 0
routes/web.php

@@ -2,6 +2,7 @@
 
 use Illuminate\Support\Facades\Route;
 use Illuminate\Support\Facades\Redis;
+use App\Http\Controllers\PostController;
 /*
 |--------------------------------------------------------------------------
 | Web Routes
@@ -24,3 +25,6 @@ Route::get('/connection', function () {
 Route::get('/site_visits', function () {
     return '网站全局访问量:' . \Illuminate\Support\Facades\Redis::get('site_total_visits');
 });
+
+Route::get('/posts/popular', [PostController::class, 'popular']);
+Route::get('/posts/{post}', [PostController::class, 'show']);