Build plugin "bs-super-cache" into core
This commit is contained in:
parent
6c31b3465f
commit
3a4844a5dd
@ -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)
|
||||
|
@ -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);
|
||||
|
25
app/Listeners/CacheAvatarPreview.php
Normal file
25
app/Listeners/CacheAvatarPreview.php
Normal 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);
|
||||
}
|
||||
}
|
41
app/Listeners/CachePlayerExists.php
Normal file
41
app/Listeners/CachePlayerExists.php
Normal 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}");
|
||||
}
|
||||
}
|
35
app/Listeners/CachePlayerJson.php
Normal file
35
app/Listeners/CachePlayerJson.php
Normal 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);
|
||||
}
|
||||
}
|
33
app/Listeners/CacheSkinPreview.php
Normal file
33
app/Listeners/CacheSkinPreview.php
Normal 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)
|
||||
]);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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: 启用「角色存在与否」的缓存
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
32
tests/CacheTest/CacheAvatarPreviewTest.php
Normal file
32
tests/CacheTest/CacheAvatarPreviewTest.php
Normal 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"));
|
||||
}
|
||||
}
|
43
tests/CacheTest/CachePlayerExistsTest.php
Normal file
43
tests/CacheTest/CachePlayerExistsTest.php
Normal 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}");
|
||||
}
|
||||
}
|
39
tests/CacheTest/CachePlayerJsonTest.php
Normal file
39
tests/CacheTest/CachePlayerJsonTest.php
Normal 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);
|
||||
}
|
||||
}
|
40
tests/CacheTest/CacheSkinPreviewTest.php
Normal file
40
tests/CacheTest/CacheSkinPreviewTest.php
Normal 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"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user