mirror of
https://github.com/lsky-org/lsky-pro.git
synced 2025-01-09 04:19:32 +08:00
✨ 完成sftp储存策略
This commit is contained in:
parent
2a7dd080ef
commit
68a1a8664a
33
app/Enums/Strategy/SftpOption.php
Normal file
33
app/Enums/Strategy/SftpOption.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums\Strategy;
|
||||
|
||||
final class SftpOption
|
||||
{
|
||||
/** @var string 访问url */
|
||||
const Url = 'url';
|
||||
|
||||
/** @var string 根目录 */
|
||||
const Root = 'root';
|
||||
|
||||
/** @var string 主机地址 */
|
||||
const Host = 'host';
|
||||
|
||||
/** @var string 连接名称 */
|
||||
const Username = 'username';
|
||||
|
||||
/** @var string 密码 */
|
||||
const Password = 'password';
|
||||
|
||||
/** @var string 私钥 */
|
||||
const PrivateKey = 'private_key';
|
||||
|
||||
/** @var string 密钥口令 */
|
||||
const Passphrase = 'passphrase';
|
||||
|
||||
/** @var string 连接端口 */
|
||||
const Port = 'port';
|
||||
|
||||
/** @var string 使用代理 */
|
||||
const UseAgent = 'use_agent';
|
||||
}
|
@ -82,6 +82,16 @@ class StrategyRequest extends FormRequest
|
||||
'configs.secret_key' => 'required',
|
||||
'configs.bucket' => 'required',
|
||||
],
|
||||
StrategyKey::Sftp => [
|
||||
'configs.root' => 'required',
|
||||
'configs.host' => 'required',
|
||||
'configs.port' => 'integer',
|
||||
'configs.username' => 'required',
|
||||
'configs.password' => 'required_if:configs.private_key,null',
|
||||
'configs.private_key' => 'required_if:configs.password,null',
|
||||
'configs.passphrase' => '',
|
||||
'configs.use_agent' => 'boolean',
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@ -110,6 +120,16 @@ class StrategyRequest extends FormRequest
|
||||
'configs.secret_key' => 'SecretKey',
|
||||
'configs.bucket' => 'Bucket',
|
||||
],
|
||||
StrategyKey::Sftp => [
|
||||
'configs.root' => '储存路径',
|
||||
'configs.host' => '主机地址',
|
||||
'configs.port' => '连接端口',
|
||||
'configs.username' => '用户名',
|
||||
'configs.password' => '密码',
|
||||
'configs.private_key' => '密钥',
|
||||
'configs.passphrase' => '密钥口令',
|
||||
'configs.use_agent' => '使用代理',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use App\Enums\ImagePermission;
|
||||
use App\Enums\Scan\AliyunOption;
|
||||
use App\Enums\Strategy\CosOption;
|
||||
use App\Enums\Strategy\KodoOption;
|
||||
use App\Enums\Strategy\SftpOption;
|
||||
use App\Enums\StrategyKey;
|
||||
use App\Enums\UserConfigKey;
|
||||
use App\Enums\UserStatus;
|
||||
@ -28,6 +29,7 @@ use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Facades\Image as InterventionImage;
|
||||
use Intervention\Image\Imagick\Font;
|
||||
@ -36,6 +38,9 @@ use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\FilesystemAdapter;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use League\Flysystem\Local\LocalFilesystemAdapter;
|
||||
use League\Flysystem\PhpseclibV2\SftpAdapter;
|
||||
use League\Flysystem\PhpseclibV2\SftpConnectionProvider;
|
||||
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||
use Overtrue\Flysystem\Cos\CosAdapter;
|
||||
use Overtrue\Flysystem\Qiniu\QiniuAdapter;
|
||||
|
||||
@ -151,7 +156,8 @@ class ImageService
|
||||
try {
|
||||
$filesystem->writeStream($pathname, $handle);
|
||||
} catch (FilesystemException $e) {
|
||||
throw new UploadException('图片上传失败');
|
||||
Log::error('保存图片时出现异常', ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
|
||||
throw new UploadException(config('app.debug', false) ? $e->getMessage() : '图片上传失败');
|
||||
}
|
||||
@fclose($handle);
|
||||
} else {
|
||||
@ -211,6 +217,27 @@ class ImageService
|
||||
bucket: $configs->get(KodoOption::Bucket),
|
||||
domain: $configs->get(KodoOption::Url),
|
||||
),
|
||||
StrategyKey::Sftp => new SftpAdapter(new SftpConnectionProvider(
|
||||
host: $configs->get(SftpOption::Host),
|
||||
username: $configs->get(SftpOption::Username),
|
||||
password: $configs->get(SftpOption::Password),
|
||||
privateKey: $configs->get(SftpOption::PrivateKey),
|
||||
passphrase: (string)$configs->get(SftpOption::Passphrase),
|
||||
port: $configs->get(SftpOption::Port),
|
||||
useAgent: (bool)$configs->get(SftpOption::UseAgent)
|
||||
),
|
||||
root: $configs->get(SftpOption::Root),
|
||||
visibilityConverter: PortableVisibilityConverter::fromArray([
|
||||
'file' => [
|
||||
'public' => 0640,
|
||||
'private' => 0604,
|
||||
],
|
||||
'dir' => [
|
||||
'public' => 0740,
|
||||
'private' => 7604,
|
||||
],
|
||||
])
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -40,26 +40,20 @@
|
||||
<form action="{{ route('admin.settings.save') }}">
|
||||
<div class="relative p-4 rounded-md bg-white mb-8 space-y-4">
|
||||
<x-fieldset title="是否启用注册" faq="启用或关闭系统注册功能">
|
||||
<input type="hidden" name="is_enable_registration" value="0">
|
||||
<x-switch name="is_enable_registration" value="1" :checked="(bool) $configs['is_enable_registration']" />
|
||||
</x-fieldset>
|
||||
<x-fieldset title="是否启用画廊" faq="启用或关闭画廊功能,画廊只有已登录的用户可见,画廊中的图片均为所有用户公开的图片。">
|
||||
<input type="hidden" name="is_enable_gallery" value="0">
|
||||
<x-switch name="is_enable_gallery" value="1" :checked="(bool) $configs['is_enable_gallery']" />
|
||||
</x-fieldset>
|
||||
<x-fieldset title="是否启用接口" faq="启用或关闭接口功能,关闭后将无法通过接口上传图片、管理图片等操作。">
|
||||
<input type="hidden" name="is_enable_api" value="0">
|
||||
<x-switch name="is_enable_api" value="1" :checked="(bool) $configs['is_enable_api']" />
|
||||
</x-fieldset>
|
||||
<x-fieldset title="是否允许游客上传" faq="启用或关闭游客上传功能,游客上传受「系统默认组」控制。">
|
||||
<input type="hidden" name="is_allow_guest_upload" value="0">
|
||||
<x-switch name="is_allow_guest_upload" value="1" :checked="(bool) $configs['is_allow_guest_upload']" />
|
||||
</x-fieldset>
|
||||
<x-fieldset title="账号验证" faq="是否强制用户验证邮箱,开启后用户必须经过验证邮箱后才能上传图片,请确保邮件配置正常。">
|
||||
<input type="hidden" name="is_user_need_verify" value="0">
|
||||
<x-switch name="is_user_need_verify" value="1" :checked="(bool) $configs['is_user_need_verify']" />
|
||||
</x-fieldset>
|
||||
|
||||
<div class="text-right">
|
||||
<x-button type="submit">保存更改</x-button>
|
||||
</div>
|
||||
|
@ -100,6 +100,47 @@
|
||||
<x-input type="text" name="configs[bucket]" id="configs[bucket]" placeholder="请输入 Bucket" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 mb-4 hidden" data-driver="{{ \App\Enums\StrategyKey::Sftp }}">
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[url]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>访问域名</label>
|
||||
<x-input type="url" name="configs[url]" id="configs[url]" placeholder="请输入图片访问域名 http(s)://" />
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-6 sm:col-span-3 mb-4">
|
||||
<label for="configs[root]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>根目录</label>
|
||||
<x-input type="text" name="configs[root]" id="configs[root]" autocomplete="text" placeholder="请输入根目录路径" value="/" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[host]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>主机地址</label>
|
||||
<x-input type="text" name="configs[host]" id="configs[host]" placeholder="请输入主机地址,例如:127.0.0.1" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[port]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>连接端口</label>
|
||||
<x-input type="number" name="configs[port]" id="configs[port]" placeholder="请输入连接端口" value="22" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[username]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>用户名</label>
|
||||
<x-input type="text" name="configs[username]" id="configs[username]" placeholder="请输入用户名" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[password]" class="block text-sm font-medium text-gray-700"><span class="text-yellow-500">*</span>密码</label>
|
||||
<x-input type="password" name="configs[password]" id="configs[password]" placeholder="如果使用私钥连接,可为空" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[private_key]" class="block text-sm font-medium text-gray-700"><span class="text-yellow-500">*</span>私钥</label>
|
||||
<x-textarea name="configs[private_key]" id="configs[private_key]" placeholder="输入私钥文本内容,如果使用密码连接,可为空"></x-textarea>
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[passphrase]" class="block text-sm font-medium text-gray-700">私钥口令</label>
|
||||
<x-input type="password" name="configs[passphrase]" id="configs[passphrase]" placeholder="如果未设置私钥或私钥未设置口令,可为空" />
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<label for="configs[use_agent]" class="block text-sm font-medium mb-2 text-gray-700">是否使用代理</label>
|
||||
<x-switch id="configs[use_agent]" name="configs[use_agent]" value="1"></x-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||
<x-button type="button" class="bg-gray-500" onclick="history.go(-1)">取消</x-button>
|
||||
|
@ -37,6 +37,7 @@
|
||||
<option value="{{ $key }}" {{ $key === $strategy->key ? 'selected' : '' }}>{{ $driver }}</option>
|
||||
@endforeach
|
||||
</x-select>
|
||||
<input type="hidden" name="key" value="{{ $strategy->key }}">
|
||||
<small class="text-gray-500"><i class="fas fa-exclamation-circle"></i> 为了确保已存在于该储存上的图片能够在平台正常预览,已创建的策略无法继续更改储存方式。</small>
|
||||
</div>
|
||||
|
||||
@ -108,6 +109,49 @@
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($strategy->key === \App\Enums\StrategyKey::Sftp)
|
||||
<div class="col-span-6 mb-4" data-driver="{{ \App\Enums\StrategyKey::Sftp }}">
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[url]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>访问域名</label>
|
||||
<x-input type="url" name="configs[url]" id="configs[url]" placeholder="请输入图片访问域名 http(s)://" value="{{ $strategy->configs['url'] }}" />
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-6 sm:col-span-3 mb-4">
|
||||
<label for="configs[root]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>根目录</label>
|
||||
<x-input type="text" name="configs[root]" id="configs[root]" autocomplete="text" placeholder="请输入根目录路径" value="{{ $strategy->configs['root'] }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[host]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>主机地址</label>
|
||||
<x-input type="text" name="configs[host]" id="configs[host]" placeholder="请输入主机地址,例如:127.0.0.1" value="{{ $strategy->configs['host'] }}" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[port]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>连接端口</label>
|
||||
<x-input type="number" name="configs[port]" id="configs[port]" placeholder="请输入连接端口" value="{{ $strategy->configs['port'] }}" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[username]" class="block text-sm font-medium text-gray-700"><span class="text-red-600">*</span>用户名</label>
|
||||
<x-input type="text" name="configs[username]" id="configs[username]" placeholder="请输入用户名" value="{{ $strategy->configs['username'] }}" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[password]" class="block text-sm font-medium text-gray-700"><span class="text-yellow-500">*</span>密码</label>
|
||||
<x-input type="password" name="configs[password]" id="configs[password]" placeholder="如果使用私钥连接,可为空" value="{{ $strategy->configs['password'] }}" />
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[private_key]" class="block text-sm font-medium text-gray-700"><span class="text-yellow-500">*</span>私钥</label>
|
||||
<x-textarea name="configs[private_key]" id="configs[private_key]" placeholder="输入私钥文本内容,如果使用密码连接,可为空">{{ $strategy->configs['private_key'] }}</x-textarea>
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-2 mb-4">
|
||||
<label for="configs[passphrase]" class="block text-sm font-medium text-gray-700">私钥口令</label>
|
||||
<x-input type="password" name="configs[passphrase]" id="configs[passphrase]" placeholder="如果未设置私钥或私钥未设置口令,可为空" value="{{ $strategy->configs['passphrase'] }}" />
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<label for="configs[use_agent]" class="block text-sm font-medium mb-2 text-gray-700">是否使用代理</label>
|
||||
<x-switch id="configs[use_agent]" name="configs[use_agent]" value="1" :checked="(bool)$strategy->configs['use_agent']"></x-switch>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||
<x-button type="button" class="bg-gray-500" onclick="history.go(-1)">取消</x-button>
|
||||
|
@ -49,7 +49,7 @@
|
||||
</div>
|
||||
<div class="relative flex w-full">
|
||||
<div class="w-10 h-10 bg-gray-200 rounded-lg cursor-pointer overflow-hidden">
|
||||
<img class="w-full h-full" src="__src__">
|
||||
<img class="w-full h-full object-cover" src="__src__">
|
||||
</div>
|
||||
<div class="flex justify-end flex-col ml-2 w-[80%] opacity-70">
|
||||
<p class="text-sm truncate">__name__</p>
|
||||
|
@ -48,12 +48,17 @@
|
||||
</body>
|
||||
<script>
|
||||
// 开关组件默认值
|
||||
$('.switch input[type=checkbox]').click(function () {
|
||||
if (this.checked) {
|
||||
$(this).closest('.switch').find('input[type=hidden]').remove();
|
||||
let setSwitch = function (e) {
|
||||
if (e.checked) {
|
||||
$(e).closest('.switch').find('input[type=hidden]').remove();
|
||||
} else {
|
||||
$(this).before('<input type="hidden" name="'+this.name+'" value="0" />');
|
||||
$(e).before('<input type="hidden" name="'+e.name+'" value="0" />');
|
||||
}
|
||||
}
|
||||
$('.switch input[type=checkbox]').each(function () {
|
||||
setSwitch(this);
|
||||
}).click(function () {
|
||||
setSwitch(this);
|
||||
});
|
||||
</script>
|
||||
@stack('scripts')
|
||||
|
Loading…
Reference in New Issue
Block a user