refactor(system/storage): 重构存储管理,新增设置默认存储、修改状态接口

S3 => OSS
This commit is contained in:
2025-03-04 21:29:56 +08:00
parent 98569ae205
commit 37d6efb70e
14 changed files with 209 additions and 74 deletions

View File

@@ -30,15 +30,15 @@ import top.continew.starter.core.enums.BaseEnum;
@RequiredArgsConstructor
public enum StorageTypeEnum implements BaseEnum<Integer> {
/**
* 兼容S3协议存储
*/
S3(1, "兼容S3协议存储"),
/**
* 本地存储
*/
LOCAL(2, "本地存储"),;
LOCAL(1, "本地存储"),
/**
* 对象存储
*/
OSS(2, "对象存储");
private final Integer value;
private final String description;

View File

@@ -54,24 +54,24 @@ public class StorageDO extends BaseDO {
private StorageTypeEnum type;
/**
* Access Key(访问密钥)
* Access Key
*/
@FieldEncrypt
private String accessKey;
/**
* Secret Key(私有密钥)
* Secret Key
*/
@FieldEncrypt
private String secretKey;
/**
* Endpoint(终端节点)
* Endpoint
*/
private String endpoint;
/**
* 桶名称
* Bucket
*/
private String bucketName;

View File

@@ -19,6 +19,7 @@ package top.continew.admin.system.model.query;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.enums.StorageTypeEnum;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
@@ -50,4 +51,10 @@ public class StorageQuery implements Serializable {
*/
@Schema(description = "状态", example = "1")
private DisEnableStatusEnum status;
/**
* 类型
*/
@Schema(description = "类型", example = "2")
private StorageTypeEnum type;
}

View File

@@ -16,6 +16,7 @@
package top.continew.admin.system.model.req;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@@ -67,43 +68,45 @@ public class StorageReq implements Serializable {
private StorageTypeEnum type;
/**
* 访问密钥
* Access Key
*/
@Schema(description = "访问密钥", example = "")
@Length(max = 255, message = "访问密钥长度不能超过 {max} 个字符")
@NotBlank(message = "访问密钥不能为空", groups = ValidationGroup.Storage.S3.class)
@Schema(description = "Access Key", example = "")
@Length(max = 255, message = "Access Key长度不能超过 {max} 个字符")
@NotBlank(message = "Access Key不能为空", groups = ValidationGroup.Storage.OSS.class)
private String accessKey;
/**
* 私有密钥
* Secret Key
*/
@Schema(description = "私有密钥", example = "")
@NotBlank(message = "私有密钥不能为空", groups = ValidationGroup.Storage.S3.class)
@Schema(description = "Secret Key", example = "")
@NotBlank(message = "Secret Key不能为空", groups = ValidationGroup.Storage.OSS.class)
private String secretKey;
/**
* 终端节点
* Endpoint
*/
@Schema(description = "终端节点", example = "")
@Length(max = 255, message = "终端节点长度不能超过 {max} 个字符")
@NotBlank(message = "终端节点不能为空", groups = ValidationGroup.Storage.S3.class)
@Schema(description = "Endpoint", example = "")
@Length(max = 255, message = "Endpoint长度不能超过 {max} 个字符")
@NotBlank(message = "Endpoint不能为空", groups = ValidationGroup.Storage.OSS.class)
private String endpoint;
/**
* 桶名称
* Bucket/存储路径
*/
@Schema(description = "桶名称", example = "C:/continew-admin/data/file/")
@Length(max = 255, message = "桶名称长度不能超过 {max} 个字符")
@NotBlank(message = "桶名称不能为空", groups = ValidationGroup.Storage.S3.class)
@Schema(description = "Bucket/存储路径", example = "C:/continew-admin/data/file/")
@Length(max = 255, message = "Bucket长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.OSS.class)
@Length(max = 255, message = "存储路径长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.Local.class)
@NotBlank(message = "Bucket不能为空", groups = ValidationGroup.Storage.OSS.class)
@NotBlank(message = "存储路径不能为空", groups = ValidationGroup.Storage.Local.class)
private String bucketName;
/**
* 域名
* 域名/访问路径
*/
@Schema(description = "域名", example = "http://localhost:8000/file")
@Length(max = 255, message = "域名长度不能超过 {max} 个字符")
@NotBlank(message = "域名不能为空")
@Schema(description = "域名/访问路径", example = "http://localhost:8000/file")
@Length(max = 255, message = "域名长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.OSS.class)
@Length(max = 255, message = "访问路径长度不能超过 {max} 个字符", groups = ValidationGroup.Storage.Local.class)
@NotBlank(message = "访问路径不能为空", groups = ValidationGroup.Storage.Local.class)
private String domain;
/**
@@ -119,16 +122,15 @@ public class StorageReq implements Serializable {
@Length(max = 200, message = "描述长度不能超过 {max} 个字符")
private String description;
/**
* 是否为默认存储
*/
@Schema(description = "是否为默认存储", example = "true")
@NotNull(message = "是否为默认存储不能为空")
private Boolean isDefault;
/**
* 状态
*/
@Schema(description = "状态", example = "1")
private DisEnableStatusEnum status;
/**
* 是否为默认存储
*/
@JsonIgnore
private Boolean isDefault;
}

View File

@@ -63,32 +63,32 @@ public class StorageResp extends BaseDetailResp {
private StorageTypeEnum type;
/**
* 访问密钥
* Access Key
*/
@Schema(description = "访问密钥", example = "")
@Schema(description = "Access Key", example = "")
private String accessKey;
/**
* 私有密钥
* Secret Key
*/
@Schema(description = "私有密钥", example = "")
@Schema(description = "Secret Key", example = "")
@JsonMask(left = 4, right = 3)
private String secretKey;
/**
* 终端节点
* Endpoint
*/
@Schema(description = "终端节点", example = "")
@Schema(description = "Endpoint", example = "")
private String endpoint;
/**
* 桶名称
* Bucket/存储路径
*/
@Schema(description = "桶名称", example = "C:/continew-admin/data/file/")
@Schema(description = "Bucket/存储路径", example = "C:/continew-admin/data/file/")
private String bucketName;
/**
* 域名
* 域名/访问路径
*/
@Schema(description = "域名", example = "http://localhost:8000/file")
private String domain;

View File

@@ -16,6 +16,7 @@
package top.continew.admin.system.service;
import top.continew.admin.common.model.req.CommonStatusUpdateReq;
import top.continew.admin.system.model.entity.StorageDO;
import top.continew.admin.system.model.query.StorageQuery;
import top.continew.admin.system.model.req.StorageReq;
@@ -31,6 +32,21 @@ import top.continew.starter.extension.crud.service.BaseService;
*/
public interface StorageService extends BaseService<StorageResp, StorageResp, StorageQuery, StorageReq>, IService<StorageDO> {
/**
* 修改状态
*
* @param req 状态信息
* @param id ID
*/
void updateStatus(CommonStatusUpdateReq req, Long id);
/**
* 设置默认存储
*
* @param id ID
*/
void setDefault(Long id);
/**
* 查询默认存储
*

View File

@@ -27,7 +27,9 @@ import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.FileStorageServiceBuilder;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.model.req.CommonStatusUpdateReq;
import top.continew.admin.common.util.SecureUtils;
import top.continew.admin.system.enums.StorageTypeEnum;
import top.continew.admin.system.mapper.StorageMapper;
@@ -67,10 +69,13 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
@Override
public void beforeAdd(StorageReq req) {
this.decodeSecretKey(req, null);
CheckUtils.throwIf(Boolean.TRUE.equals(req.getIsDefault()) && this.isDefaultExists(null), "请先取消原有默认存储");
String code = req.getCode();
CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code);
this.load(req);
// 单独指定默认存储
req.setIsDefault(false);
if (DisEnableStatusEnum.ENABLE.equals(req.getStatus())) {
this.load(req);
}
}
@Override
@@ -78,16 +83,12 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
StorageDO oldStorage = super.getById(id);
CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码");
CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型");
this.decodeSecretKey(req, oldStorage);
DisEnableStatusEnum newStatus = req.getStatus();
CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE
.equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName());
this.decodeSecretKey(req, oldStorage);
// 重新加载存储引擎
DisEnableStatusEnum oldStatus = oldStorage.getStatus();
if (Boolean.TRUE.equals(req.getIsDefault())) {
CheckUtils.throwIf(this.isDefaultExists(id), "请先取消原有默认存储");
CheckUtils.throwIf(!DisEnableStatusEnum.ENABLE.equals(oldStatus) && !DisEnableStatusEnum.ENABLE
.equals(newStatus), "请先启用该存储");
}
// 先卸载
if (DisEnableStatusEnum.ENABLE.equals(oldStatus)) {
this.unload(BeanUtil.copyProperties(oldStorage, StorageReq.class));
@@ -103,7 +104,7 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
CheckUtils.throwIf(fileService.countByStorageIds(ids) > 0, "所选存储存在文件关联,请删除文件后重试");
List<StorageDO> storageList = baseMapper.lambdaQuery().in(StorageDO::getId, ids).list();
storageList.forEach(s -> {
CheckUtils.throwIfEqual(Boolean.TRUE, s.getIsDefault(), "[{}] 是默认存储,不允许禁用", s.getName());
CheckUtils.throwIfEqual(Boolean.TRUE, s.getIsDefault(), "[{}] 是默认存储,不允许删除", s.getName());
// 卸载启用状态的存储
if (DisEnableStatusEnum.ENABLE.equals(s.getStatus())) {
this.unload(BeanUtil.copyProperties(s, StorageReq.class));
@@ -111,6 +112,43 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStatus(CommonStatusUpdateReq req, Long id) {
StorageDO storage = super.getById(id);
// 状态未改变
DisEnableStatusEnum newStatus = req.getStatus();
if (storage.getStatus().equals(newStatus)) {
return;
}
// 修改状态
baseMapper.lambdaUpdate().eq(StorageDO::getId, id).set(StorageDO::getStatus, newStatus).update();
// 加载、卸载存储引擎
StorageReq storageReq = BeanUtil.copyProperties(storage, StorageReq.class);
switch (newStatus) {
case ENABLE:
this.load(storageReq);
break;
case DISABLE:
CheckUtils.throwIfEqual(Boolean.TRUE, storage.getIsDefault(), "[{}] 是默认存储,不允许禁用", storage.getName());
this.unload(storageReq);
break;
default:
break;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void setDefault(Long id) {
StorageDO storage = super.getById(id);
if (Boolean.TRUE.equals(storage.getIsDefault())) {
return;
}
baseMapper.lambdaUpdate().eq(StorageDO::getIsDefault, true).set(StorageDO::getIsDefault, false).update();
baseMapper.lambdaUpdate().eq(StorageDO::getId, id).set(StorageDO::getIsDefault, true).update();
}
@Override
public StorageDO getDefaultStorage() {
return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).one();
@@ -138,8 +176,8 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections
.singletonList(config)));
SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), bucketName));
} else if (StorageTypeEnum.S3.equals(type)) {
ValidationUtils.validate(req, ValidationGroup.Storage.S3.class);
} else if (StorageTypeEnum.OSS.equals(type)) {
ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class);
FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config();
config.setPlatform(req.getCode());
config.setAccessKey(req.getAccessKey());
@@ -169,7 +207,7 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
* @param storage 存储信息
*/
private void decodeSecretKey(StorageReq req, StorageDO storage) {
if (!StorageTypeEnum.S3.equals(req.getType())) {
if (!StorageTypeEnum.OSS.equals(req.getType())) {
return;
}
// 修改时,如果 SecretKey 不修改,需要手动修正

View File

@@ -37,9 +37,9 @@ public interface ValidationGroup extends Default {
}
/**
* 兼容S3协议存储
* 对象存储
*/
interface S3 extends Storage {
interface OSS extends Storage {
}
}
}