280 lines
10 KiB
PHP
280 lines
10 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
namespace App\Service;
|
|||
|
|
|
|||
|
|
use App\BaseApp\BaseService;
|
|||
|
|
use App\Enum\OssDriverEnum;
|
|||
|
|
use App\Models\oss\OssConfigModel;
|
|||
|
|
use App\Service\common\FieldEncryptService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* OSS 配置管理:驱动选项、启用切换、配置 CRUD(密钥入库加密)
|
|||
|
|
*/
|
|||
|
|
class OssConfigService extends BaseService
|
|||
|
|
{
|
|||
|
|
public function __construct()
|
|||
|
|
{
|
|||
|
|
parent::__construct();
|
|||
|
|
$this->model = OssConfigModel::class;
|
|||
|
|
$this->selectField = [
|
|||
|
|
'id', 'driver', 'name', 'endpoint', 'region', 'bucket', 'domain',
|
|||
|
|
'path_prefix', 'is_active', 'status', 'remark', 'sort', 'created_at', 'updated_at',
|
|||
|
|
];
|
|||
|
|
$this->queryField = [
|
|||
|
|
'driver' => '=',
|
|||
|
|
'name' => 'like',
|
|||
|
|
'status' => '=',
|
|||
|
|
'is_active' => '=',
|
|||
|
|
];
|
|||
|
|
$this->orderBy = [
|
|||
|
|
'name' => 'sort',
|
|||
|
|
'sort' => 'desc',
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 驱动枚举选项(前端表单 / 卡片文案)
|
|||
|
|
*/
|
|||
|
|
public function driverOptions(): array
|
|||
|
|
{
|
|||
|
|
return OssDriverEnum::options();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 运行时页数据:配置卡片 + 当前启用 ID(不回显密钥明文)
|
|||
|
|
*/
|
|||
|
|
public function getRuntimeOptions(): array
|
|||
|
|
{
|
|||
|
|
$items = $this->formatConfigRows(
|
|||
|
|
OssConfigModel::where('deleted_at', 0)
|
|||
|
|
->orderByDesc('sort')
|
|||
|
|
->orderByDesc('id')
|
|||
|
|
->get()
|
|||
|
|
->toArray()
|
|||
|
|
);
|
|||
|
|
$activeId = 0;
|
|||
|
|
foreach ($items as $item) {
|
|||
|
|
if ((int) ($item['is_active'] ?? 0) === 1) {
|
|||
|
|
$activeId = (int) $item['id'];
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if ($activeId <= 0) {
|
|||
|
|
$activeId = (int) SystemConfigService::getInstance()->getValue('oss_active_config_id', 0);
|
|||
|
|
}
|
|||
|
|
return [
|
|||
|
|
'drivers' => OssDriverEnum::options(),
|
|||
|
|
'items' => $items,
|
|||
|
|
'active_id' => $activeId,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 切换当前启用的 OSS 配置
|
|||
|
|
* 为什么双写 is_active + system_config:上传链路优先读 kv,列表页靠 is_active 展示徽章
|
|||
|
|
*/
|
|||
|
|
public function saveRuntime(array $params): array
|
|||
|
|
{
|
|||
|
|
$id = (int) ($params['id'] ?? 0);
|
|||
|
|
if ($id <= 0) {
|
|||
|
|
$this->utils->errorThrow('请选择要启用的配置');
|
|||
|
|
}
|
|||
|
|
$info = OssConfigModel::where('id', $id)->where('deleted_at', 0)->first();
|
|||
|
|
if (!$info) {
|
|||
|
|
$this->utils->notFound('配置不存在');
|
|||
|
|
}
|
|||
|
|
if ((int) $info->status !== 1) {
|
|||
|
|
$this->utils->errorThrow('该配置已禁用,无法启用');
|
|||
|
|
}
|
|||
|
|
$now = time();
|
|||
|
|
OssConfigModel::where('deleted_at', 0)->update(['is_active' => 0, 'updated_at' => $now]);
|
|||
|
|
OssConfigModel::where('id', $id)->update(['is_active' => 1, 'updated_at' => $now]);
|
|||
|
|
SystemConfigService::getInstance()->setValue('oss_active_config_id', (string) $id, '当前启用的 OSS 配置 ID');
|
|||
|
|
return ['id' => $id];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 配置列表(卡片,无密钥明文)
|
|||
|
|
*/
|
|||
|
|
public function listConfigs(): array
|
|||
|
|
{
|
|||
|
|
$items = $this->formatConfigRows(
|
|||
|
|
OssConfigModel::where('deleted_at', 0)
|
|||
|
|
->orderByDesc('sort')
|
|||
|
|
->orderByDesc('id')
|
|||
|
|
->get()
|
|||
|
|
->toArray()
|
|||
|
|
);
|
|||
|
|
return [
|
|||
|
|
'page' => 1,
|
|||
|
|
'size' => count($items),
|
|||
|
|
'page_count' => 1,
|
|||
|
|
'total' => count($items),
|
|||
|
|
'items' => $items,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 新增 OSS 配置;云驱动要求填写密钥
|
|||
|
|
*/
|
|||
|
|
public function createConfig(array $params): mixed
|
|||
|
|
{
|
|||
|
|
$driver = trim((string) ($params['driver'] ?? ''));
|
|||
|
|
$name = trim((string) ($params['name'] ?? ''));
|
|||
|
|
if (!OssDriverEnum::isValid($driver)) {
|
|||
|
|
$this->utils->errorThrow('不支持的存储驱动');
|
|||
|
|
}
|
|||
|
|
if ($name === '') {
|
|||
|
|
$this->utils->errorThrow('请填写配置别名');
|
|||
|
|
}
|
|||
|
|
$enum = OssDriverEnum::from($driver);
|
|||
|
|
$accessKey = trim((string) ($params['access_key'] ?? ''));
|
|||
|
|
$secretKey = trim((string) ($params['secret_key'] ?? ''));
|
|||
|
|
if ($enum->needsSecret() && ($accessKey === '' || $secretKey === '')) {
|
|||
|
|
$this->utils->errorThrow('该驱动需要填写 AccessKey 与 SecretKey');
|
|||
|
|
}
|
|||
|
|
$encrypt = FieldEncryptService::getInstance();
|
|||
|
|
$isActive = (int) ($params['is_active'] ?? 0);
|
|||
|
|
$now = time();
|
|||
|
|
if ($isActive === 1) {
|
|||
|
|
OssConfigModel::where('deleted_at', 0)->update(['is_active' => 0, 'updated_at' => $now]);
|
|||
|
|
}
|
|||
|
|
$id = OssConfigModel::insertGetId([
|
|||
|
|
'driver' => $driver,
|
|||
|
|
'name' => $name,
|
|||
|
|
'access_key' => $accessKey !== '' ? $encrypt->encryptForStorage($accessKey) : '',
|
|||
|
|
'secret_key' => $secretKey !== '' ? $encrypt->encryptForStorage($secretKey) : '',
|
|||
|
|
'endpoint' => trim((string) ($params['endpoint'] ?? '')),
|
|||
|
|
'region' => trim((string) ($params['region'] ?? '')),
|
|||
|
|
'bucket' => trim((string) ($params['bucket'] ?? '')),
|
|||
|
|
'domain' => trim((string) ($params['domain'] ?? '')),
|
|||
|
|
'path_prefix' => trim((string) ($params['path_prefix'] ?? '')),
|
|||
|
|
'extra_json' => null,
|
|||
|
|
'is_active' => $isActive,
|
|||
|
|
'status' => (int) ($params['status'] ?? 1),
|
|||
|
|
'remark' => trim((string) ($params['remark'] ?? '')),
|
|||
|
|
'sort' => (int) ($params['sort'] ?? 0),
|
|||
|
|
'created_at' => $now,
|
|||
|
|
'updated_at' => 0,
|
|||
|
|
'deleted_at' => 0,
|
|||
|
|
]);
|
|||
|
|
if ($isActive === 1) {
|
|||
|
|
SystemConfigService::getInstance()->setValue('oss_active_config_id', (string) $id, '当前启用的 OSS 配置 ID');
|
|||
|
|
}
|
|||
|
|
return ['id' => $id];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新配置;access_key/secret_key 空串表示不改密钥
|
|||
|
|
*/
|
|||
|
|
public function updateConfig(int $id, array $params): mixed
|
|||
|
|
{
|
|||
|
|
$info = OssConfigModel::where('id', $id)->where('deleted_at', 0)->first();
|
|||
|
|
if (!$info) {
|
|||
|
|
$this->utils->notFound('配置不存在');
|
|||
|
|
}
|
|||
|
|
$data = [];
|
|||
|
|
if (array_key_exists('driver', $params)) {
|
|||
|
|
$driver = trim((string) $params['driver']);
|
|||
|
|
if (!OssDriverEnum::isValid($driver)) {
|
|||
|
|
$this->utils->errorThrow('不支持的存储驱动');
|
|||
|
|
}
|
|||
|
|
$data['driver'] = $driver;
|
|||
|
|
}
|
|||
|
|
if (array_key_exists('name', $params)) {
|
|||
|
|
$name = trim((string) $params['name']);
|
|||
|
|
if ($name === '') {
|
|||
|
|
$this->utils->errorThrow('配置别名不能为空');
|
|||
|
|
}
|
|||
|
|
$data['name'] = $name;
|
|||
|
|
}
|
|||
|
|
foreach (['endpoint', 'region', 'bucket', 'domain', 'path_prefix', 'remark'] as $field) {
|
|||
|
|
if (array_key_exists($field, $params)) {
|
|||
|
|
$data[$field] = trim((string) $params[$field]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (array_key_exists('status', $params)) {
|
|||
|
|
$data['status'] = (int) $params['status'];
|
|||
|
|
}
|
|||
|
|
if (array_key_exists('sort', $params)) {
|
|||
|
|
$data['sort'] = (int) $params['sort'];
|
|||
|
|
}
|
|||
|
|
$encrypt = FieldEncryptService::getInstance();
|
|||
|
|
$accessKey = trim((string) ($params['access_key'] ?? ''));
|
|||
|
|
if ($accessKey !== '') {
|
|||
|
|
$data['access_key'] = $encrypt->encryptForStorage($accessKey);
|
|||
|
|
}
|
|||
|
|
$secretKey = trim((string) ($params['secret_key'] ?? ''));
|
|||
|
|
if ($secretKey !== '') {
|
|||
|
|
$data['secret_key'] = $encrypt->encryptForStorage($secretKey);
|
|||
|
|
}
|
|||
|
|
$now = time();
|
|||
|
|
if (array_key_exists('is_active', $params) && (int) $params['is_active'] === 1) {
|
|||
|
|
OssConfigModel::where('deleted_at', 0)->where('id', '<>', $id)->update(['is_active' => 0, 'updated_at' => $now]);
|
|||
|
|
$data['is_active'] = 1;
|
|||
|
|
SystemConfigService::getInstance()->setValue('oss_active_config_id', (string) $id, '当前启用的 OSS 配置 ID');
|
|||
|
|
} elseif (array_key_exists('is_active', $params)) {
|
|||
|
|
$data['is_active'] = (int) $params['is_active'];
|
|||
|
|
}
|
|||
|
|
if (empty($data)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
$data['updated_at'] = $now;
|
|||
|
|
return OssConfigModel::where('id', $id)->where('deleted_at', 0)->update($data);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 软删除;若删的是当前启用项则回落到本地配置,避免上传无可用驱动
|
|||
|
|
*/
|
|||
|
|
public function deleteConfigs(array $ids): mixed
|
|||
|
|
{
|
|||
|
|
$ids = array_values(array_filter(array_map('intval', $ids)));
|
|||
|
|
if (empty($ids)) {
|
|||
|
|
$this->utils->errorThrow('参数错误');
|
|||
|
|
}
|
|||
|
|
$activeId = (int) SystemConfigService::getInstance()->getValue('oss_active_config_id', 0);
|
|||
|
|
$deletingActive = ($activeId > 0 && in_array($activeId, $ids, true))
|
|||
|
|
|| OssConfigModel::whereIn('id', $ids)->where('is_active', 1)->where('deleted_at', 0)->exists();
|
|||
|
|
$fallbackId = 0;
|
|||
|
|
if ($deletingActive) {
|
|||
|
|
$local = OssConfigModel::where('driver', 'local')
|
|||
|
|
->where('deleted_at', 0)
|
|||
|
|
->where('status', 1)
|
|||
|
|
->whereNotIn('id', $ids)
|
|||
|
|
->orderBy('id')
|
|||
|
|
->first();
|
|||
|
|
if (!$local) {
|
|||
|
|
$this->utils->errorThrow('不能删除当前启用的配置:请先切换,或保留至少一条本地存储');
|
|||
|
|
}
|
|||
|
|
$fallbackId = (int) $local->id;
|
|||
|
|
}
|
|||
|
|
$ok = OssConfigModel::whereIn('id', $ids)->where('deleted_at', 0)->update([
|
|||
|
|
'deleted_at' => time(),
|
|||
|
|
'updated_at' => time(),
|
|||
|
|
'is_active' => 0,
|
|||
|
|
]);
|
|||
|
|
if ($fallbackId > 0) {
|
|||
|
|
$this->saveRuntime(['id' => $fallbackId]);
|
|||
|
|
}
|
|||
|
|
return $ok;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 行格式化:补驱动名、密钥是否已配置标记
|
|||
|
|
*/
|
|||
|
|
private function formatConfigRows(array $rows): array
|
|||
|
|
{
|
|||
|
|
foreach ($rows as &$row) {
|
|||
|
|
$driver = (string) ($row['driver'] ?? '');
|
|||
|
|
$enum = OssDriverEnum::tryFrom($driver);
|
|||
|
|
$row['driver_name'] = $enum?->label() ?? $driver;
|
|||
|
|
$row['driver_desc'] = $enum?->description() ?? '';
|
|||
|
|
$row['has_access_key'] = trim((string) ($row['access_key'] ?? '')) !== '';
|
|||
|
|
$row['has_secret_key'] = trim((string) ($row['secret_key'] ?? '')) !== '';
|
|||
|
|
unset($row['access_key'], $row['secret_key']);
|
|||
|
|
}
|
|||
|
|
unset($row);
|
|||
|
|
return $rows;
|
|||
|
|
}
|
|||
|
|
}
|