Build plugin "bs-super-cache" into core

This commit is contained in:
Pig Fang 2019-03-21 12:44:15 +08:00
parent 6c31b3465f
commit 3a4844a5dd
16 changed files with 414 additions and 25 deletions

View File

@ -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)

View File

@ -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);

View File

@ -0,0 +1,25 @@
<?php
namespace App\Listeners;
use Cache;
use Storage;
use Minecraft;
use App\Events\GetAvatarPreview;
class CacheAvatarPreview
{
public function handle(GetAvatarPreview $event)
{
$texture = $event->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);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Listeners;
use Cache;
use Storage;
use App\Events;
use App\Models\Player;
use Illuminate\Events\Dispatcher;
class CachePlayerExists
{
public function subscribe(Dispatcher $events)
{
$events->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}");
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Listeners;
use Cache;
use Storage;
use App\Models\Player;
use App\Events\GetPlayerJson;
use App\Events\PlayerProfileUpdated;
use Illuminate\Contracts\Events\Dispatcher;
class CachePlayerJson
{
public function subscribe(Dispatcher $events)
{
$events->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);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Listeners;
use Cache;
use Storage;
use Minecraft;
class CacheSkinPreview
{
public function handle(\App\Events\GetSkinPreview $event)
{
$texture = $event->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)
]);
}
}

View File

@ -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);
}
}
}

View File

@ -491,3 +491,15 @@ if (! function_exists('nl2p')) {
return str_replace('<p></p>', '', $result);
}
}
if (! function_exists('png')) {
function png($resource)
{
ob_start();
imagepng($resource);
$image = ob_get_contents();
ob_end_clean();
imagedestroy($resource);
return $image;
}
}

View File

@ -146,6 +146,8 @@ meta:
meta_extras:
title: Other Custom <meta> 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

View File

@ -146,6 +146,8 @@ meta:
meta_extras:
title: 其它自定义 <meta> 标签
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: 启用「角色存在与否」的缓存

View File

@ -16,13 +16,21 @@
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-12">
<div class="callout callout-warning">@lang('options.res-warning')</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{!! $forms['resources']->render() !!}
{!! $forms['redis']->render() !!}
</div>
<div class="col-md-6">
{!! $forms['redis']->render() !!}
{!! $forms['cache']->render() !!}
</div>
</div>

View File

@ -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()

View File

@ -0,0 +1,32 @@
<?php
namespace Tests;
use Cache;
use Event;
use Mockery;
use Storage;
use App\Models\Texture;
use App\Events\GetAvatarPreview;
use Illuminate\Http\UploadedFile;
use App\Listeners\CacheAvatarPreview;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CacheAvatarPreviewTest extends TestCase
{
use DatabaseTransactions;
public function testHandle()
{
Event::listen(GetAvatarPreview::class, CacheAvatarPreview::class);
$texture = factory(Texture::class)->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"));
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Tests;
use Cache;
use Event;
use App\Events;
use App\Models\Player;
use Illuminate\Http\UploadedFile;
use App\Listeners\CachePlayerExists;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CachePlayerExistsTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
Event::subscribe(CachePlayerExists::class);
}
public function testRemember()
{
$player = factory(Player::class)->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}");
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Tests;
use Cache;
use Event;
use App\Models\Player;
use App\Events\GetPlayerJson;
use Illuminate\Http\UploadedFile;
use App\Listeners\CachePlayerJson;
use App\Events\PlayerProfileUpdated;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CachePlayerJsonTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
Event::subscribe(CachePlayerJson::class);
}
public function testRemember()
{
$player = factory(Player::class)->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);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Tests;
use Cache;
use Event;
use Mockery;
use Storage;
use App\Models\Texture;
use App\Events\GetSkinPreview;
use Illuminate\Http\UploadedFile;
use App\Listeners\CacheSkinPreview;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CacheSkinPreviewTest extends TestCase
{
use DatabaseTransactions;
public function testHandle()
{
Event::listen(GetSkinPreview::class, CacheSkinPreview::class);
$skin = factory(Texture::class)->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"));
}
}