From 3a4844a5dde126483ed688cfb5a878cf7d4fe512 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Thu, 21 Mar 2019 12:44:15 +0800 Subject: [PATCH] Build plugin "bs-super-cache" into core --- app/Http/Controllers/AdminController.php | 29 +++++++++++++-- app/Http/Controllers/TextureController.php | 24 ++---------- app/Listeners/CacheAvatarPreview.php | 25 +++++++++++++ app/Listeners/CachePlayerExists.php | 41 +++++++++++++++++++++ app/Listeners/CachePlayerJson.php | 35 ++++++++++++++++++ app/Listeners/CacheSkinPreview.php | 33 +++++++++++++++++ app/Providers/EventServiceProvider.php | 16 ++++++++ app/helpers.php | 12 ++++++ resources/lang/en/options.yml | 20 ++++++++++ resources/lang/zh_CN/options.yml | 20 ++++++++++ resources/views/admin/resource.blade.php | 10 ++++- tests/AdminControllerTest.php | 20 ++++++++++ tests/CacheTest/CacheAvatarPreviewTest.php | 32 ++++++++++++++++ tests/CacheTest/CachePlayerExistsTest.php | 43 ++++++++++++++++++++++ tests/CacheTest/CachePlayerJsonTest.php | 39 ++++++++++++++++++++ tests/CacheTest/CacheSkinPreviewTest.php | 40 ++++++++++++++++++++ 16 files changed, 414 insertions(+), 25 deletions(-) create mode 100644 app/Listeners/CacheAvatarPreview.php create mode 100644 app/Listeners/CachePlayerExists.php create mode 100644 app/Listeners/CachePlayerJson.php create mode 100644 app/Listeners/CacheSkinPreview.php create mode 100644 tests/CacheTest/CacheAvatarPreviewTest.php create mode 100644 tests/CacheTest/CachePlayerExistsTest.php create mode 100644 tests/CacheTest/CachePlayerJsonTest.php create mode 100644 tests/CacheTest/CacheSkinPreviewTest.php diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index cdb2201e..d06280b0 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use Cache; use Redis; use Option; use Carbon\Carbon; @@ -234,7 +235,7 @@ class AdminController extends Controller ->with('forms', compact('general', 'announ', 'meta')); } - public function resource() + public function resource(Request $request) { $resources = Option::form('resources', OptionForm::AUTO_DETECT, function ($form) { $form->checkbox('force_ssl')->label()->hint(); @@ -261,7 +262,7 @@ class AdminController extends Controller ->handle(); $redis = Option::form('redis', 'Redis', function ($form) { - $form->checkbox('enable_redis')->label(); + $form->checkbox('enable_redis')->label()->description(); }); if (option('enable_redis')) { @@ -278,8 +279,30 @@ class AdminController extends Controller $redis->handle(); + $cache = Option::form('cache', OptionForm::AUTO_DETECT, function ($form) { + $form->checkbox('enable_avatar_cache')->label(); + $form->checkbox('enable_preview_cache')->label(); + $form->checkbox('enable_json_cache', 'JSON Profile')->label(); + $form->checkbox('enable_notfound_cache', '404')->label(); + }) + ->type('warning') + ->addButton([ + 'text' => trans('options.cache.clear'), + 'type' => 'a', + 'class' => 'pull-right', + 'style' => 'warning', + 'href' => '?clear-cache', + ]) + ->addMessage(trans('options.cache.driver', ['driver' => config('cache.default')]), 'info'); + + if ($request->has('clear-cache')) { + Cache::flush(); + $cache->addMessage(trans('options.cache.cleared'), 'success'); + } + $cache->handle(); + return view('admin.resource') - ->with('forms', compact('resources', 'redis')); + ->with('forms', compact('resources', 'redis', 'cache')); } public function getUserData(Request $request) diff --git a/app/Http/Controllers/TextureController.php b/app/Http/Controllers/TextureController.php index fbd9491f..e44ecc5b 100644 --- a/app/Http/Controllers/TextureController.php +++ b/app/Http/Controllers/TextureController.php @@ -111,14 +111,7 @@ class TextureController extends Controller return $responses[0]; // @codeCoverageIgnore } else { $png = Minecraft::generateAvatarFromSkin(Storage::disk('textures')->read($t->hash), $size); - - ob_start(); - imagepng($png); - imagedestroy($png); - $image = ob_get_contents(); - ob_end_clean(); - - return Response::png($image); + return Response::png(png($png)); } } } catch (Exception $e) { @@ -168,13 +161,7 @@ class TextureController extends Controller $png = Minecraft::generatePreviewFromSkin($binary, $size, ($t->type == 'alex'), 'both', 4); } - ob_start(); - imagepng($png); - imagedestroy($png); - $image = ob_get_contents(); - ob_end_clean(); - - return Response::png($image); + return Response::png(png($png)); } } } catch (Exception $e) { @@ -216,13 +203,8 @@ class TextureController extends Controller Storage::disk('textures')->read($hash), $size ); - ob_start(); - imagepng($png); - $image = ob_get_contents(); - ob_end_clean(); - imagedestroy($png); - return Response::png($image); + return Response::png(png($png)); } return abort(404); diff --git a/app/Listeners/CacheAvatarPreview.php b/app/Listeners/CacheAvatarPreview.php new file mode 100644 index 00000000..f49d2539 --- /dev/null +++ b/app/Listeners/CacheAvatarPreview.php @@ -0,0 +1,25 @@ +texture; + $size = $event->size; + $key = "avatar-{$texture->tid}-$size"; + + $content = Cache::rememberForever($key, function () use ($texture, $size) { + $res = Storage::disk('textures')->read($texture->hash); + return png(Minecraft::generateAvatarFromSkin($res, $size)); + }); + + return response()->png($content); + } +} diff --git a/app/Listeners/CachePlayerExists.php b/app/Listeners/CachePlayerExists.php new file mode 100644 index 00000000..f8e9a530 --- /dev/null +++ b/app/Listeners/CachePlayerExists.php @@ -0,0 +1,41 @@ +listen(Events\CheckPlayerExists::class, [$this, 'remember']); + $events->listen(Events\PlayerWasAdded::class, [$this, 'forget']); + } + + public function remember(Events\CheckPlayerExists $event) + { + $key = "notfound-{$event->playerName}"; + + if ($event->playerName && is_null(Cache::get($key))) { + $player = Player::where('name', $event->playerName)->first(); + + if (! $player) { + Cache::forever($key, '1'); + return false; + } else { + return true; + } + } else { + return false; + } + } + + public function forget(Events\PlayerWasAdded $event) + { + Cache::forget("notfound-{$event->player->name}"); + } +} diff --git a/app/Listeners/CachePlayerJson.php b/app/Listeners/CachePlayerJson.php new file mode 100644 index 00000000..d88df8e4 --- /dev/null +++ b/app/Listeners/CachePlayerJson.php @@ -0,0 +1,35 @@ +listen(GetPlayerJson::class, [$this, 'remember']); + $events->listen(PlayerProfileUpdated::class, [$this, 'forget']); + } + + public function remember(GetPlayerJson $event) + { + $key = "json-{$event->player->pid}-{$event->apiType}"; + $content = Cache::rememberForever($key, function () use ($event) { + return $event->player->generateJsonProfile($event->apiType); + }); + + return $content; + } + + public function forget(PlayerProfileUpdated $event) + { + Cache::forget("json-{$event->player->pid}-".Player::CSL_API); + Cache::forget("json-{$event->player->pid}-".Player::USM_API); + } +} diff --git a/app/Listeners/CacheSkinPreview.php b/app/Listeners/CacheSkinPreview.php new file mode 100644 index 00000000..0e10b083 --- /dev/null +++ b/app/Listeners/CacheSkinPreview.php @@ -0,0 +1,33 @@ +texture; + $size = $event->size; + $key = "preview-{$texture->tid}-{$size}"; + + $content = Cache::rememberForever($key, function () use ($texture, $size) { + $res = Storage::disk('textures')->read($texture->hash); + + if ($texture->type == 'cape') { + $png = Minecraft::generatePreviewFromCape($res, $size * 0.8, $size * 1.125, $size); + } else { + $png = Minecraft::generatePreviewFromSkin($res, $size, $texture->type == 'alex', 'both', 4); + } + + return png($png); + }); + + return response()->png($content, 200, [ + 'Last-Modified' => Storage::disk('textures')->lastModified($texture->hash) + ]); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 6a8e58cc..05ed2e35 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,9 @@ namespace App\Providers; +use Event; +use App\Events; +use App\Listeners; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider @@ -23,5 +26,18 @@ class EventServiceProvider extends ServiceProvider public function boot() { parent::boot(); + + if (option('enable_avatar_cache')) { + Event::listen(Events\GetAvatarPreview::class, Listeners\CacheAvatarPreview::class); + } + if (option('enable_preview_cache')) { + Event::listen(Events\GetSkinPreview::class, Listeners\CacheSkinPreview::class); + } + if (option('enable_notfound_cache')) { + Event::subscribe(Listeners\CachePlayerExists::class); + } + if (option('enable_json_cache')) { + Event::subscribe(Listeners\CachePlayerJson::class); + } } } diff --git a/app/helpers.php b/app/helpers.php index 2a58a566..99cee619 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -491,3 +491,15 @@ if (! function_exists('nl2p')) { return str_replace('

', '', $result); } } + +if (! function_exists('png')) { + function png($resource) + { + ob_start(); + imagepng($resource); + $image = ob_get_contents(); + ob_end_clean(); + imagedestroy($resource); + return $image; + } +} diff --git a/resources/lang/en/options.yml b/resources/lang/en/options.yml index d7886900..627462dc 100644 --- a/resources/lang/en/options.yml +++ b/resources/lang/en/options.yml @@ -146,6 +146,8 @@ meta: meta_extras: title: Other Custom Tags +res-warning: This page is ONLY for advanced users. If you aren't familiar with these, please don't modify them! + resources: title: Resource Files hint: Please check these options if you enabled CDN for your site. @@ -177,6 +179,24 @@ redis: enable_redis: title: Enable label: Enable Redis + description: Redis will be used to store cache, session and etc. connect: success: Connected to Redis server successfully. failed: 'Failed to connect Redis server. Error: :msg' + +cache: + title: Cache Configuration + clear: Clear Cache + cleared: Cache has been cleared. + driver: Current cache driver is 「:driver」. + + enable_avatar_cache: + title: Avatar + label: Enable caching avatar + enable_preview_cache: + title: Texture Preivew + label: Enable caching texture preivew + enable_json_cache: + label: Enable caching Json Profile + enable_notfound_cache: + label: Enable caching whether player is existed or not diff --git a/resources/lang/zh_CN/options.yml b/resources/lang/zh_CN/options.yml index 68e5e292..c7a4bead 100644 --- a/resources/lang/zh_CN/options.yml +++ b/resources/lang/zh_CN/options.yml @@ -146,6 +146,8 @@ meta: meta_extras: title: 其它自定义 标签 +res-warning: 本页面仅供高级用户使用。如果您不清楚这些设置的含义,请不要随意修改它们! + resources: title: 资源文件配置 hint: 如果启用了 CDN 缓存请适当修改这些配置 @@ -176,6 +178,24 @@ redis: enable_redis: title: 启用 label: 使用 Redis + description: Redis 将被用于存储缓存和 Session 等。 connect: success: 成功连接 Redis 服务器。 failed: '连接 Redis 服务器失败。错误消息: :msg' + +cache: + title: 缓存配置 + clear: 清除缓存 + cleared: 缓存已清除。 + driver: 当前缓存驱动为 「:driver」 + + enable_avatar_cache: + title: 头像 + label: 启用头像缓存 + enable_preview_cache: + title: 材质预览 + label: 启用材质预览缓存 + enable_json_cache: + label: 启用 Json Profile 缓存 + enable_notfound_cache: + label: 启用「角色存在与否」的缓存 diff --git a/resources/views/admin/resource.blade.php b/resources/views/admin/resource.blade.php index f059d4c8..15e27f6b 100644 --- a/resources/views/admin/resource.blade.php +++ b/resources/views/admin/resource.blade.php @@ -16,13 +16,21 @@
+
+
+
@lang('options.res-warning')
+
+
+
{!! $forms['resources']->render() !!} + + {!! $forms['redis']->render() !!}
- {!! $forms['redis']->render() !!} + {!! $forms['cache']->render() !!}
diff --git a/tests/AdminControllerTest.php b/tests/AdminControllerTest.php index a3b698a1..dd32e8ee 100644 --- a/tests/AdminControllerTest.php +++ b/tests/AdminControllerTest.php @@ -2,6 +2,7 @@ namespace Tests; +use Cache; use Redis; use App\Models\User; use App\Models\Player; @@ -189,6 +190,25 @@ class AdminControllerTest extends BrowserKitTestCase Redis::shouldReceive('ping')->once()->andThrow(new \Exception('fake')); $this->visit('/admin/resource') ->see(trans('options.redis.connect.failed', ['msg' => 'fake'])); + + option(['enable_redis' => false]); + + $this->visit('/admin/resource') + ->see(trans('options.cache.driver', ['driver' => config('cache.default')])) + ->check('enable_avatar_cache') + ->check('enable_preview_cache') + ->check('enable_json_cache') + ->check('enable_notfound_cache') + ->press('submit_cache'); + $this->assertTrue(option('enable_avatar_cache')); + $this->assertTrue(option('enable_preview_cache')); + $this->assertTrue(option('enable_json_cache')); + $this->assertTrue(option('enable_notfound_cache')); + + Cache::shouldReceive('flush'); + $this->visit('/admin/resource') + ->click(trans('options.cache.clear')) + ->see(trans('options.cache.cleared')); } public function testUsers() diff --git a/tests/CacheTest/CacheAvatarPreviewTest.php b/tests/CacheTest/CacheAvatarPreviewTest.php new file mode 100644 index 00000000..fe0ece54 --- /dev/null +++ b/tests/CacheTest/CacheAvatarPreviewTest.php @@ -0,0 +1,32 @@ +create(); + Storage::disk('textures') + ->putFileAs('.', UploadedFile::fake()->image($texture->hash), $texture->hash); + $mock = Mockery::mock('overload:Minecraft'); + $mock->shouldReceive('generateAvatarFromSkin')->andReturn(imagecreatetruecolor(1, 1)); + + event(new GetAvatarPreview($texture, 45)); + $this->assertTrue(Cache::has("avatar-{$texture->tid}-45")); + } +} diff --git a/tests/CacheTest/CachePlayerExistsTest.php b/tests/CacheTest/CachePlayerExistsTest.php new file mode 100644 index 00000000..9d04bf36 --- /dev/null +++ b/tests/CacheTest/CachePlayerExistsTest.php @@ -0,0 +1,43 @@ +create(); + Cache::shouldReceive('get') + ->times(2) + ->andReturn(null) + ->andReturn(null); + Cache::shouldReceive('forever')->once()->with('notfound-nope', '1'); + + event(new Events\CheckPlayerExists(null)); + event(new Events\CheckPlayerExists($player->name)); + event(new Events\CheckPlayerExists('nope')); + } + + public function testForget() + { + $player = factory(Player::class)->create(); + event(new Events\PlayerWasAdded($player)); + Cache::shouldReceive('forget')->with("notfound-{$player->name}"); + } +} diff --git a/tests/CacheTest/CachePlayerJsonTest.php b/tests/CacheTest/CachePlayerJsonTest.php new file mode 100644 index 00000000..f7fe9781 --- /dev/null +++ b/tests/CacheTest/CachePlayerJsonTest.php @@ -0,0 +1,39 @@ +create(); + event(new GetPlayerJson($player, Player::CSL_API)); + $this->assertTrue(Cache::has("json-{$player->pid}-".Player::CSL_API)); + } + + public function testForget() + { + $player = factory(Player::class)->create(); + event(new PlayerProfileUpdated($player)); + Cache::shouldReceive('forget') + ->with("json-{$player->pid}-".Player::CSL_API) + ->with("json-{$player->pid}-".Player::USM_API); + } +} diff --git a/tests/CacheTest/CacheSkinPreviewTest.php b/tests/CacheTest/CacheSkinPreviewTest.php new file mode 100644 index 00000000..af442c7c --- /dev/null +++ b/tests/CacheTest/CacheSkinPreviewTest.php @@ -0,0 +1,40 @@ +create(); + Storage::disk('textures') + ->putFileAs('.', UploadedFile::fake()->image($skin->hash), $skin->hash); + $mock = Mockery::mock('overload:Minecraft'); + $mock->shouldReceive('generatePreviewFromSkin')->andReturn(imagecreatetruecolor(1, 1)); + + event(new GetSkinPreview($skin, 45)); + $this->assertTrue(Cache::has("preview-{$skin->tid}-45")); + + $cape = factory(Texture::class, 'cape')->create(); + Storage::disk('textures') + ->putFileAs('.', UploadedFile::fake()->image($cape->hash), $cape->hash); + $mock->shouldReceive('generatePreviewFromCape')->andReturn(imagecreatetruecolor(1, 1)); + + event(new GetSkinPreview($cape, 45)); + $this->assertTrue(Cache::has("preview-{$cape->tid}-45")); + } +}