diff --git a/app/Enums/ConfigKey.php b/app/Enums/ConfigKey.php index 4caa2d0e..37655376 100644 --- a/app/Enums/ConfigKey.php +++ b/app/Enums/ConfigKey.php @@ -31,9 +31,6 @@ final class ConfigKey /** @var string 账户是否需要验证 */ const IsUserNeedVerify = 'is_user_need_verify'; - /** @var string 是否启用缩略图功能 */ - const IsEnableThumbnail = 'is_enable_thumbnail'; - /** @var string 邮件配置 */ const MailConfigs = 'mail_configs'; diff --git a/app/Enums/GroupConfigKey.php b/app/Enums/GroupConfigKey.php index aea4001d..75fe939e 100644 --- a/app/Enums/GroupConfigKey.php +++ b/app/Enums/GroupConfigKey.php @@ -11,7 +11,16 @@ final class GroupConfigKey const ConcurrentUploadNum = 'concurrent_upload_num'; /** @var string 上传是否需要审查 */ - const IsUploadNeedsReview = 'is_upload_needs_review'; + const IsEnableReview = 'is_enable_review'; + + /** @var string 是否启用原图保护功能 */ + const IsEnableOriginalProtection = 'is_enable_original_protection'; + + /** @var string 上传是否启用水印功能 */ + const IsEnableWatermark = 'is_enable_watermark'; + + /** @var string 水印配置 */ + const WatermarkConfigs = 'watermark_configs'; /** @var string 每分钟上传限制 */ const LimitPerMinute = 'limit_per_minute'; diff --git a/app/Enums/GroupWatermarkConfigKey.php b/app/Enums/GroupWatermarkConfigKey.php new file mode 100644 index 00000000..decf0032 --- /dev/null +++ b/app/Enums/GroupWatermarkConfigKey.php @@ -0,0 +1,8 @@ +where('user_id', $user->id) ->when($request->query('order') ?: 'newest', function (Builder $builder, $order) { switch ($order) { @@ -93,14 +94,23 @@ class ImageController extends Controller { /** @var Image $image */ $image = Image::query() + ->with('group') ->where('key', $request->route('key')) ->where('extension', $request->route('extension')) ->firstOr(fn() => abort(404)); + if (! $image->group->configs->get(GroupConfigKey::IsEnableOriginalProtection)) { + abort(404); + } try { $contents = $image->filesystem()->read($image->pathname); } catch (FilesystemException $e) { abort(404); } + // 是否启用了水印功能 + if ($image->group->configs->get(GroupConfigKey::IsEnableWatermark)) { + // GroupConfigKey::WatermarkConfigs + // TODO 动态生成水印并缓存 + } return \response()->stream(function () use ($contents) { echo $contents; }, headers: ['Content-type' => $image->mimetype]); diff --git a/app/Models/Group.php b/app/Models/Group.php index 31a07aef..61fea8a8 100644 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -2,7 +2,9 @@ namespace App\Models; +use App\Enums\ConfigKey; use App\Enums\GroupConfigKey; +use App\Utils; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -41,19 +43,7 @@ class Group extends Model */ public static function getDefaultConfigs(): Collection { - return collect([ - GroupConfigKey::MaximumFileSize => 5120, - GroupConfigKey::ConcurrentUploadNum => 3, - GroupConfigKey::IsUploadNeedsReview => false, - GroupConfigKey::LimitPerMinute => 20, - GroupConfigKey::LimitPerHour => 100, - GroupConfigKey::LimitPerDay => 300, - GroupConfigKey::LimitPerWeek => 600, - GroupConfigKey::LimitPerMonth => 999, - GroupConfigKey::AcceptedFileSuffixes => ['jpg', 'jpeg', 'gif', 'png', 'apng', 'bmp', 'ico'], - GroupConfigKey::PathNamingRule => '{Y}/{m}/{d}', - GroupConfigKey::FileNamingRule => '{uniqid}', - ]); + return Utils::config(ConfigKey::GuestGroupConfigs); } protected static function booted() diff --git a/app/Models/Image.php b/app/Models/Image.php index ab9f44a5..89644397 100644 --- a/app/Models/Image.php +++ b/app/Models/Image.php @@ -2,7 +2,9 @@ namespace App\Models; +use App\Enums\GroupConfigKey; use App\Enums\Strategy\LocalOption; +use App\Enums\StrategyKey; use App\Service\ImageService; use Carbon\Carbon; use Illuminate\Database\Eloquent\Casts\Attribute; @@ -18,6 +20,7 @@ use League\Flysystem\Filesystem; * @property int $id * @property int $user_id * @property int $album_id + * @property int $group_id * @property int $strategy_id * @property string $key * @property string $path @@ -43,6 +46,7 @@ use League\Flysystem\Filesystem; * @property Carbon $created_at * @property-read User $user * @property-read Album $album + * @property-read Group $group * @property-read Strategy $strategy */ class Image extends Model @@ -70,6 +74,7 @@ class Image extends Model protected $hidden = [ 'user_id', 'album_id', + 'group_id', 'strategy_id', 'is_unhealthy', 'permission', @@ -120,10 +125,10 @@ class Image extends Model return Storage::disk('uploads')->url($this->pathname); } // 是否启用原图保护功能 - if ($this->strategy->configs->get(LocalOption::IsEnableOriginalProtection)) { + if ($this->group->configs->get(GroupConfigKey::IsEnableOriginalProtection)) { return asset("{$this->key}.{$this->extension}"); } else { - return rtrim($this->strategy->configs->get(LocalOption::Domain), '/').'/'.$this->pathname; + return rtrim($this->strategy->configs->get('domain'), '/').'/'.$this->pathname; } }); } @@ -162,9 +167,20 @@ class Image extends Model return $this->belongsTo(Album::class, 'album_id', 'id'); } + public function group(): BelongsTo + { + return $this->belongsTo(Group::class, 'group_id', 'id')->withDefault(function (Group $group) { + $group->name = '系统默认组'; + $group->configs = $group::getDefaultConfigs(); + }); + } + public function strategy(): BelongsTo { - return $this->belongsTo(Strategy::class, 'strategy_id', 'id'); + return $this->belongsTo(Strategy::class, 'strategy_id', 'id')->withDefault(function (Strategy $strategy) { + $strategy->key = StrategyKey::Local; + $strategy->configs = collect([LocalOption::Root => config('filesystems.disks.uploads.root')]); + }); } private function generateKey($length = 6): string diff --git a/app/Models/Strategy.php b/app/Models/Strategy.php index dee26fcf..194fa856 100644 --- a/app/Models/Strategy.php +++ b/app/Models/Strategy.php @@ -51,7 +51,6 @@ class Strategy extends Model static::creating(function (self $strategy) { $strategy->configs = collect([ LocalOption::Domain => env('APP_URL'), - LocalOption::IsEnableOriginalProtection => true, ]); }); } diff --git a/app/Service/ImageService.php b/app/Service/ImageService.php index af90d9cf..f99635ee 100644 --- a/app/Service/ImageService.php +++ b/app/Service/ImageService.php @@ -24,11 +24,15 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Intervention\Image\Facades\Image as InterventionImage; +use Intervention\Image\Gd\Font; +use Intervention\Image\ImageManager; +use JetBrains\PhpStorm\NoReturn; use League\Flysystem\Filesystem; use League\Flysystem\FilesystemAdapter; use League\Flysystem\FilesystemException; use League\Flysystem\Local\LocalFilesystemAdapter; use Overtrue\Flysystem\Qiniu\QiniuAdapter; +use Psr\Http\Message\StreamInterface; class ImageService { @@ -68,6 +72,7 @@ class ImageService // 如果该用户有角色组,覆盖默认组、上传策略配置 if ($user->group) { + $image->group_id = $user->group_id; $configs = $user->group->configs; // 获取策略列表,根据用户所选的策略上传 $strategies = $user->group->strategies()->get(); @@ -155,15 +160,13 @@ class ImageService $builder->where('strategy_id', $id); })->where('md5', $image->md5)->where('sha1', $image->sha1)->first(); if (is_null($existing)) { - $handle = fopen($file, 'r'); + $handle = $img->stream(); try { - $filesystem->writeStream($pathname, $handle); + $filesystem->writeStream($pathname, $handle->detach()); } catch (FilesystemException $e) { throw new UploadException('图片上传失败'); } - if (! fclose($handle)) { - throw new UploadException('资源关闭失败'); - } + $handle->close(); } else { $image->fill($existing->only('path', 'name')); } diff --git a/app/Utils.php b/app/Utils.php index 67580f18..18a0a16d 100644 --- a/app/Utils.php +++ b/app/Utils.php @@ -27,7 +27,6 @@ class Utils case ConfigKey::IsEnableGallery: case ConfigKey::IsEnableRegistration: case ConfigKey::IsUserNeedVerify: - case ConfigKey::IsEnableThumbnail: $value = (bool) $value; break; case ConfigKey::MailConfigs: diff --git a/composer.json b/composer.json index 6893ae10..51a7be57 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "laravel/framework": "^9.0", "laravel/sanctum": "^2.14", "laravel/tinker": "^2.7", - "overtrue/flysystem-qiniu": "^2.0" + "overtrue/flysystem-qiniu": "^2.0", + "topthink/think-image": "^1.0" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.6", diff --git a/composer.lock b/composer.lock index 99edfe3e..ebf09842 100644 --- a/composer.lock +++ b/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": "c36bb3f58e9cf016cf72e03a16057222", + "content-hash": "afa63033085cb52b18c21975da51c685", "packages": [ { "name": "asm89/stack-cors", @@ -5577,6 +5577,56 @@ }, "time": "2021-12-08T09:12:39+00:00" }, + { + "name": "topthink/think-image", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-image.git", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Image Package", + "support": { + "issues": "https://github.com/top-think/think-image/issues", + "source": "https://github.com/top-think/think-image/tree/master" + }, + "time": "2016-09-29T06:05:43+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v5.4.1", diff --git a/database/migrations/2021_12_11_191158_create_images_table.php b/database/migrations/2021_12_11_191158_create_images_table.php index 0f50aedd..23c6a537 100644 --- a/database/migrations/2021_12_11_191158_create_images_table.php +++ b/database/migrations/2021_12_11_191158_create_images_table.php @@ -21,6 +21,7 @@ class CreateImagesTable extends Migration $table->id(); $table->foreignId('user_id')->nullable()->comment('用户')->constrained('users')->onDelete('set null'); $table->foreignId('album_id')->nullable()->comment('相册')->constrained('albums')->onDelete('set null'); + $table->foreignId('group_id')->nullable()->comment('角色组')->constrained('groups')->onDelete('set null'); $table->foreignId('strategy_id')->nullable()->comment('策略')->constrained('strategies')->onDelete('set null'); $table->string('key')->unique()->comment('key'); $table->string('path')->comment('保存路径'); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index e686c3ee..9cec211a 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use App\Enums\ConfigKey; +use App\Enums\GroupConfigKey; use App\Enums\Mail\SmtpOption; use App\Models\Group; use Illuminate\Database\Seeder; @@ -29,7 +30,6 @@ class DatabaseSeeder extends Seeder ConfigKey::IsAllowGuestUpload => 1, ConfigKey::UserInitialCapacity => 512000, ConfigKey::IsUserNeedVerify => 1, - ConfigKey::IsEnableThumbnail => 1, ConfigKey::MailConfigs => json_encode([ 'default' => 'smtp', 'mailers' => [ @@ -42,7 +42,22 @@ class DatabaseSeeder extends Seeder SmtpOption::AuthMode => null, ], ]), - ConfigKey::GuestGroupConfigs => Group::getDefaultConfigs()->toJson(), + ConfigKey::GuestGroupConfigs => collect([ + GroupConfigKey::MaximumFileSize => 5120, + GroupConfigKey::ConcurrentUploadNum => 3, + GroupConfigKey::IsEnableReview => false, + GroupConfigKey::IsEnableWatermark => false, + GroupConfigKey::IsEnableOriginalProtection => false, + GroupConfigKey::WatermarkConfigs => new \stdClass(), + GroupConfigKey::LimitPerMinute => 20, + GroupConfigKey::LimitPerHour => 100, + GroupConfigKey::LimitPerDay => 300, + GroupConfigKey::LimitPerWeek => 600, + GroupConfigKey::LimitPerMonth => 999, + GroupConfigKey::AcceptedFileSuffixes => ['jpg', 'jpeg', 'gif', 'png', 'apng', 'bmp', 'ico'], + GroupConfigKey::PathNamingRule => '{Y}/{m}/{d}', + GroupConfigKey::FileNamingRule => '{uniqid}', + ])->toJson(), ])->transform(function ($value, $key) use ($date) { return ['name' => $key, 'value' => $value, 'updated_at' => $date, 'created_at' => $date]; })->values()->toArray(); diff --git a/resources/views/images.blade.php b/resources/views/images.blade.php index 77d8019c..6e071c62 100644 --- a/resources/views/images.blade.php +++ b/resources/views/images.blade.php @@ -723,7 +723,7 @@ }, open: { text: '新窗口打开', - action: e => window.open($(e).find('img').attr('src')), + action: e => window.open($(e).data('json').url), visible: () => ds.getSelection().length === 1, }, copies: {