Răsfoiți Sursa

基于 Redis + 资源库模式实现 Laravel 应用缓存功能

chenlong 4 ani în urmă
părinte
comite
198e2437e9

+ 3 - 0
.idea/php.xml

@@ -109,6 +109,9 @@
       <path value="$PROJECT_DIR$/vendor/psr/http-message" />
       <path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
       <path value="$PROJECT_DIR$/vendor/psr/http-client" />
+      <path value="$PROJECT_DIR$/vendor/barryvdh/laravel-debugbar" />
+      <path value="$PROJECT_DIR$/vendor/maximebf/debugbar" />
+      <path value="$PROJECT_DIR$/vendor/symfony/debug" />
     </include_path>
   </component>
   <component name="PhpProjectSharedConfiguration" php_language_level="7.3">

+ 3 - 0
.idea/redis-demo.iml

@@ -114,6 +114,9 @@
       <excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
       <excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
       <excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/barryvdh/laravel-debugbar" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/maximebf/debugbar" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/debug" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />

+ 16 - 21
app/Http/Controllers/PostController.php

@@ -2,37 +2,32 @@
 
 namespace App\Http\Controllers;
 
-use Illuminate\Http\Request;
-use App\Models\Post;
+use App\Repos\PostRepo;
 use Illuminate\Support\Facades\Redis;
 
 class PostController extends Controller
 {
-    public function show(Post $post)
+    protected PostRepo $postRepo;
+
+    public function __construct(PostRepo $postRepo)
     {
-        $post->increment('views');
-        if ($post->save()) {
-            // 将当前文章浏览数 +1,存储到对应 Sorted Set 的 score 字段
-            Redis::zincrby('popular_posts', 1, $post->id);
-        }
-        return 'Show Post #' . $post->id;
+        $this->postRepo = $postRepo;
+    }
+
+    // 浏览文章
+    public function show($id)
+    {
+        $post = $this->postRepo->getById($id);
+        $views = $this->postRepo->addViews($post);
+        return "Show Post #{$post->id}, Views: {$views}";
     }
 
     // 获取热门文章排行榜
     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;
+        $posts = $this->postRepo->trending(10);
+        if ($posts) {
+            dump($posts->toArray());
         }
-        dd($posts->toArray());
     }
 }

+ 67 - 0
app/Repos/PostRepo.php

@@ -0,0 +1,67 @@
+<?php
+namespace App\Repos;
+
+use App\Models\Post;
+use Illuminate\Support\Facades\Redis;
+
+class PostRepo
+{
+    protected Post $post;
+    protected string $trendingPostsKey = 'popular_posts';
+
+    public function __construct(Post $post)
+    {
+        $this->post = $post;
+    }
+
+    public function getById(int $id, array $columns = ['*'])
+    {
+        $cacheKey = 'post_' . $id;
+        if (Redis::exists($cacheKey)) {
+            return unserialize(Redis::get($cacheKey));
+        }
+        $post = $this->post->select($columns)->find($id);
+        if (!$post) {
+            return null;
+        }
+        Redis::setex($cacheKey, 1 * 60 * 60, serialize($post));  // 缓存 1 小时
+        return $post;
+    }
+
+    public function getByManyId(array $ids, array $columns = ['*'], callable $callback = null)
+    {
+        $query = $this->post->select($columns)->whereIn('id', $ids);
+        if ($query) {
+            $query = $callback($query);
+        }
+        return $query->get();
+    }
+    public function addViews(Post $post)
+    {
+        $post->increment('views');
+        if ($post->save()) {
+            // 将当前文章浏览数 +1,存储到对应 Sorted Set 的 score 字段
+            Redis::zincrby('popular_posts', 1, $post->id);
+        }
+        return $post->views;
+    }
+
+    // 热门文章排行榜
+    public function trending($num = 10)
+    {
+        $cacheKey = $this->trendingPostsKey . '_' . $num;
+        if (Redis::exists($cacheKey)) {
+            return unserialize(Redis::get($cacheKey));
+        }
+        $postIds = Redis::zrevrange($this->trendingPostsKey, 0, $num - 1);
+        if (!$postIds) {
+            return null;
+        }
+        $idsStr = implode(',', $postIds);
+        $posts = $this->getByManyId($postIds, ['*'], function ($query) use ($idsStr) {
+            return $query->orderByRaw('field(`id`, ' . $idsStr . ')');
+        });
+        Redis::setex($cacheKey, 10 * 60, serialize($posts));  // 缓存 10 分钟
+        return $posts;
+    }
+}

+ 1 - 0
composer.json

@@ -17,6 +17,7 @@
         "predis/predis": "^1.1"
     },
     "require-dev": {
+        "barryvdh/laravel-debugbar": "^3.5",
         "facade/ignition": "^2.5",
         "fakerphp/faker": "^1.9.1",
         "laravel/sail": "^1.0.1",

+ 216 - 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": "aa373c5398a159d92226df4ac5de3a08",
+    "content-hash": "3636104131aa6be907a64cb77d82811e",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -4944,6 +4944,87 @@
         }
     ],
     "packages-dev": [
+        {
+            "name": "barryvdh/laravel-debugbar",
+            "version": "v3.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/barryvdh/laravel-debugbar.git",
+                "reference": "cae0a8d1cb89b0f0522f65e60465e16d738e069b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/cae0a8d1cb89b0f0522f65e60465e16d738e069b",
+                "reference": "cae0a8d1cb89b0f0522f65e60465e16d738e069b",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/routing": "^6|^7|^8",
+                "illuminate/session": "^6|^7|^8",
+                "illuminate/support": "^6|^7|^8",
+                "maximebf/debugbar": "^1.16.3",
+                "php": ">=7.2",
+                "symfony/debug": "^4.3|^5",
+                "symfony/finder": "^4.3|^5"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.3.3",
+                "orchestra/testbench-dusk": "^4|^5|^6",
+                "phpunit/phpunit": "^8.5|^9.0",
+                "squizlabs/php_codesniffer": "^3.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.5-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Barryvdh\\Debugbar\\ServiceProvider"
+                    ],
+                    "aliases": {
+                        "Debugbar": "Barryvdh\\Debugbar\\Facade"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Barryvdh\\Debugbar\\": "src/"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "PHP Debugbar integration for Laravel",
+            "keywords": [
+                "debug",
+                "debugbar",
+                "laravel",
+                "profiler",
+                "webprofiler"
+            ],
+            "support": {
+                "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
+                "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-01-06T14:21:44+00:00"
+        },
         {
             "name": "doctrine/instantiator",
             "version": "1.4.0",
@@ -5441,6 +5522,71 @@
             },
             "time": "2021-02-09T16:44:58+00:00"
         },
+        {
+            "name": "maximebf/debugbar",
+            "version": "v1.16.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/maximebf/php-debugbar.git",
+                "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6d51ee9e94cff14412783785e79a4e7ef97b9d62",
+                "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1|^8",
+                "psr/log": "^1.0",
+                "symfony/var-dumper": "^2.6|^3|^4|^5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5.20 || ^9.4.2"
+            },
+            "suggest": {
+                "kriswallsmith/assetic": "The best way to manage assets",
+                "monolog/monolog": "Log using Monolog",
+                "predis/predis": "Redis storage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.16-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "DebugBar\\": "src/DebugBar/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maxime Bouroumeau-Fuseau",
+                    "email": "maxime.bouroumeau@gmail.com",
+                    "homepage": "http://maximebf.com"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Debug bar in the browser for php application",
+            "homepage": "https://github.com/maximebf/php-debugbar",
+            "keywords": [
+                "debug",
+                "debugbar"
+            ],
+            "support": {
+                "issues": "https://github.com/maximebf/php-debugbar/issues",
+                "source": "https://github.com/maximebf/php-debugbar/tree/v1.16.5"
+            },
+            "time": "2020-12-07T11:07:24+00:00"
+        },
         {
             "name": "mockery/mockery",
             "version": "1.4.2",
@@ -7380,6 +7526,75 @@
             ],
             "time": "2020-09-28T06:39:44+00:00"
         },
+        {
+            "name": "symfony/debug",
+            "version": "v4.4.19",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/debug.git",
+                "reference": "af4987aa4a5630e9615be9d9c3ed1b0f24ca449c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/af4987aa4a5630e9615be9d9c3ed1b0f24ca449c",
+                "reference": "af4987aa4a5630e9615be9d9c3ed1b0f24ca449c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "psr/log": "~1.0",
+                "symfony/polyfill-php80": "^1.15"
+            },
+            "conflict": {
+                "symfony/http-kernel": "<3.4"
+            },
+            "require-dev": {
+                "symfony/http-kernel": "^3.4|^4.0|^5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Debug\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools to ease debugging PHP code",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/debug/tree/v4.4.19"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-01-27T09:09:26+00:00"
+        },
         {
             "name": "theseer/tokenizer",
             "version": "1.2.0",

+ 2 - 0
routes/web.php

@@ -28,3 +28,5 @@ Route::get('/site_visits', function () {
 
 Route::get('/posts/popular', [PostController::class, 'popular']);
 Route::get('/posts/{post}', [PostController::class, 'show']);
+
+Route::get('/posts/{id}', [PostController::class, 'show'])->where('id', '[0-9]+');

+ 2 - 0
storage/debugbar/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore