mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-11-04 10:57:10 +08:00 
			
		
		
		
	refactor(system): 重构存储配置及文件上传相关代码
存储配置自动处理:domain 不能以 / 结尾,bucket 必须以 / 结尾 文件上传:path 自动处理 Closes #IC6V43
This commit is contained in:
		@@ -16,26 +16,21 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.continew.admin.system.config.file;
 | 
					package top.continew.admin.system.config.file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.hutool.core.date.DateUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.util.ClassUtil;
 | 
					import cn.hutool.core.util.ClassUtil;
 | 
				
			||||||
import cn.hutool.core.util.EscapeUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.util.StrUtil;
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
import cn.hutool.core.util.URLUtil;
 | 
					import cn.hutool.core.util.URLUtil;
 | 
				
			||||||
import cn.hutool.json.JSONUtil;
 | 
					 | 
				
			||||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 | 
					 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.dromara.x.file.storage.core.FileInfo;
 | 
					import org.dromara.x.file.storage.core.FileInfo;
 | 
				
			||||||
import org.dromara.x.file.storage.core.recorder.FileRecorder;
 | 
					import org.dromara.x.file.storage.core.recorder.FileRecorder;
 | 
				
			||||||
import org.dromara.x.file.storage.core.upload.FilePartInfo;
 | 
					import org.dromara.x.file.storage.core.upload.FilePartInfo;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
import top.continew.admin.common.context.UserContextHolder;
 | 
					 | 
				
			||||||
import top.continew.admin.system.enums.FileTypeEnum;
 | 
					 | 
				
			||||||
import top.continew.admin.system.mapper.FileMapper;
 | 
					import top.continew.admin.system.mapper.FileMapper;
 | 
				
			||||||
import top.continew.admin.system.mapper.StorageMapper;
 | 
					import top.continew.admin.system.mapper.StorageMapper;
 | 
				
			||||||
import top.continew.admin.system.model.entity.FileDO;
 | 
					import top.continew.admin.system.model.entity.FileDO;
 | 
				
			||||||
import top.continew.admin.system.model.entity.StorageDO;
 | 
					import top.continew.admin.system.model.entity.StorageDO;
 | 
				
			||||||
import top.continew.starter.core.constant.StringConstants;
 | 
					import top.continew.starter.core.constant.StringConstants;
 | 
				
			||||||
 | 
					import top.continew.starter.core.util.URLUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,53 +47,24 @@ public class FileRecorderImpl implements FileRecorder {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private final FileMapper fileMapper;
 | 
					    private final FileMapper fileMapper;
 | 
				
			||||||
    private final StorageMapper storageMapper;
 | 
					    private final StorageMapper storageMapper;
 | 
				
			||||||
    private final IdentifierGenerator identifierGenerator;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 文件信息存储
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param fileInfo 文件信息对象
 | 
					 | 
				
			||||||
     * @return 是否保存成功
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean save(FileInfo fileInfo) {
 | 
					    public boolean save(FileInfo fileInfo) {
 | 
				
			||||||
        FileDO file = new FileDO();
 | 
					        // 保存文件信息
 | 
				
			||||||
        Number id = identifierGenerator.nextId(fileInfo);
 | 
					        FileDO file = new FileDO(fileInfo);
 | 
				
			||||||
        file.setId(id.longValue());
 | 
					 | 
				
			||||||
        fileInfo.setId(String.valueOf(id.longValue()));
 | 
					 | 
				
			||||||
        String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename());
 | 
					 | 
				
			||||||
        file.setName(StrUtil.contains(originalFilename, StringConstants.DOT)
 | 
					 | 
				
			||||||
            ? StrUtil.subBefore(originalFilename, StringConstants.DOT, true)
 | 
					 | 
				
			||||||
            : originalFilename);
 | 
					 | 
				
			||||||
        StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
 | 
					        StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
 | 
				
			||||||
        String filePath = StrUtil.appendIfMissing(fileInfo.getPath(), StringConstants.SLASH);
 | 
					 | 
				
			||||||
        // 处理fileInfo中存储的地址
 | 
					 | 
				
			||||||
        fileInfo.setUrl(URLUtil.normalize(storage.getDomain() + filePath + fileInfo.getFilename()));
 | 
					 | 
				
			||||||
        fileInfo.setThUrl(URLUtil.normalize(storage.getDomain() + filePath + fileInfo.getThFilename()));
 | 
					 | 
				
			||||||
        file.setUrl(fileInfo.getUrl());
 | 
					 | 
				
			||||||
        file.setSize(fileInfo.getSize());
 | 
					 | 
				
			||||||
        String absPath = fileInfo.getPath();
 | 
					 | 
				
			||||||
        String tempAbsPath = absPath.length() > 1 ? StrUtil.removeSuffix(absPath, StringConstants.SLASH) : absPath;
 | 
					 | 
				
			||||||
        String[] pathArr = tempAbsPath.split(StringConstants.SLASH);
 | 
					 | 
				
			||||||
        if (pathArr.length > 1) {
 | 
					 | 
				
			||||||
            file.setParentPath(pathArr[pathArr.length - 1]);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            file.setParentPath(StringConstants.SLASH);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        file.setAbsPath(tempAbsPath);
 | 
					 | 
				
			||||||
        file.setExtension(fileInfo.getExt());
 | 
					 | 
				
			||||||
        file.setType(FileTypeEnum.getByExtension(file.getExtension()));
 | 
					 | 
				
			||||||
        file.setContentType(fileInfo.getContentType());
 | 
					 | 
				
			||||||
        file.setSha256(fileInfo.getHashInfo().getSha256());
 | 
					 | 
				
			||||||
        file.setMetadata(JSONUtil.toJsonStr(fileInfo.getMetadata()));
 | 
					 | 
				
			||||||
        file.setThumbnailUrl(fileInfo.getThUrl());
 | 
					 | 
				
			||||||
        file.setThumbnailSize(fileInfo.getThSize());
 | 
					 | 
				
			||||||
        file.setThumbnailMetadata(JSONUtil.toJsonStr(fileInfo.getThMetadata()));
 | 
					 | 
				
			||||||
        file.setStorageId(storage.getId());
 | 
					        file.setStorageId(storage.getId());
 | 
				
			||||||
        file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
 | 
					 | 
				
			||||||
        file.setUpdateUser(UserContextHolder.getUserId());
 | 
					 | 
				
			||||||
        file.setUpdateTime(file.getCreateTime());
 | 
					 | 
				
			||||||
        fileMapper.insert(file);
 | 
					        fileMapper.insert(file);
 | 
				
			||||||
 | 
					        // 方便文件上传完成后获取文件信息
 | 
				
			||||||
 | 
					        fileInfo.setId(String.valueOf(file.getId()));
 | 
				
			||||||
 | 
					        if (!URLUtils.isHttpUrl(fileInfo.getUrl())) {
 | 
				
			||||||
 | 
					            String prefix = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH);
 | 
				
			||||||
 | 
					            String url = URLUtil.normalize(prefix + fileInfo.getUrl());
 | 
				
			||||||
 | 
					            fileInfo.setUrl(url);
 | 
				
			||||||
 | 
					            if (StrUtil.isNotBlank(fileInfo.getThUrl())) {
 | 
				
			||||||
 | 
					                fileInfo.setThUrl(URLUtil.normalize(prefix + fileInfo.getThUrl()));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,8 @@ import org.springframework.boot.ApplicationArguments;
 | 
				
			|||||||
import org.springframework.boot.ApplicationRunner;
 | 
					import org.springframework.boot.ApplicationRunner;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
 | 
					import top.continew.admin.common.enums.DisEnableStatusEnum;
 | 
				
			||||||
 | 
					import top.continew.admin.system.model.entity.StorageDO;
 | 
				
			||||||
import top.continew.admin.system.model.query.StorageQuery;
 | 
					import top.continew.admin.system.model.query.StorageQuery;
 | 
				
			||||||
import top.continew.admin.system.model.req.StorageReq;
 | 
					 | 
				
			||||||
import top.continew.admin.system.model.resp.StorageResp;
 | 
					import top.continew.admin.system.model.resp.StorageResp;
 | 
				
			||||||
import top.continew.admin.system.service.StorageService;
 | 
					import top.continew.admin.system.service.StorageService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,6 +52,6 @@ public class FileStorageConfigLoader implements ApplicationRunner {
 | 
				
			|||||||
        if (CollUtil.isEmpty(storageList)) {
 | 
					        if (CollUtil.isEmpty(storageList)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        storageList.forEach(s -> storageService.load(BeanUtil.copyProperties(s, StorageReq.class)));
 | 
					        storageList.forEach(storage -> storageService.load(BeanUtil.copyProperties(storage, StorageDO.class)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,9 +16,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.continew.admin.system.enums;
 | 
					package top.continew.admin.system.enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
import lombok.Getter;
 | 
					import lombok.Getter;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					import top.continew.admin.system.model.req.StorageReq;
 | 
				
			||||||
 | 
					import top.continew.admin.system.validation.ValidationGroup;
 | 
				
			||||||
 | 
					import top.continew.starter.core.constant.StringConstants;
 | 
				
			||||||
import top.continew.starter.core.enums.BaseEnum;
 | 
					import top.continew.starter.core.enums.BaseEnum;
 | 
				
			||||||
 | 
					import top.continew.starter.core.util.URLUtils;
 | 
				
			||||||
 | 
					import top.continew.starter.core.validation.ValidationUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 存储类型枚举
 | 
					 * 存储类型枚举
 | 
				
			||||||
@@ -33,13 +39,48 @@ public enum StorageTypeEnum implements BaseEnum<Integer> {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 本地存储
 | 
					     * 本地存储
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    LOCAL(1, "本地存储"),
 | 
					    LOCAL(1, "本地存储") {
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void validate(StorageReq req) {
 | 
				
			||||||
 | 
					            ValidationUtils.validate(req, ValidationGroup.Storage.Local.class);
 | 
				
			||||||
 | 
					            ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "访问路径格式不正确");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void pretreatment(StorageReq req) {
 | 
				
			||||||
 | 
					            super.pretreatment(req);
 | 
				
			||||||
 | 
					            req.setBucketName(StrUtil.appendIfMissing(req.getBucketName()
 | 
				
			||||||
 | 
					                .replace(StringConstants.BACKSLASH, StringConstants.SLASH), StringConstants.SLASH));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 对象存储
 | 
					     * 对象存储
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    OSS(2, "对象存储");
 | 
					    OSS(2, "对象存储") {
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void validate(StorageReq req) {
 | 
				
			||||||
 | 
					            ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class);
 | 
				
			||||||
 | 
					            ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "域名格式不正确");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Integer value;
 | 
					    private final Integer value;
 | 
				
			||||||
    private final String description;
 | 
					    private final String description;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 校验
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param req 请求参数
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public abstract void validate(StorageReq req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 处理参数
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param req 请求参数
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public void pretreatment(StorageReq req) {
 | 
				
			||||||
 | 
					        req.setDomain(StrUtil.removeSuffix(req.getDomain(), StringConstants.SLASH));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,20 +16,21 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.continew.admin.system.model.entity;
 | 
					package top.continew.admin.system.model.entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.date.DateUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.io.file.FileNameUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.util.EscapeUtil;
 | 
				
			||||||
import cn.hutool.core.util.StrUtil;
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
import cn.hutool.json.JSONUtil;
 | 
					import cn.hutool.json.JSONUtil;
 | 
				
			||||||
import com.baomidou.mybatisplus.annotation.TableName;
 | 
					import com.baomidou.mybatisplus.annotation.TableName;
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import lombok.SneakyThrows;
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
import org.dromara.x.file.storage.core.FileInfo;
 | 
					import org.dromara.x.file.storage.core.FileInfo;
 | 
				
			||||||
import top.continew.admin.common.model.entity.BaseDO;
 | 
					import top.continew.admin.common.model.entity.BaseDO;
 | 
				
			||||||
import top.continew.admin.system.enums.FileTypeEnum;
 | 
					import top.continew.admin.system.enums.FileTypeEnum;
 | 
				
			||||||
import top.continew.admin.system.enums.StorageTypeEnum;
 | 
					 | 
				
			||||||
import top.continew.starter.core.constant.StringConstants;
 | 
					import top.continew.starter.core.constant.StringConstants;
 | 
				
			||||||
import top.continew.starter.core.util.StrUtils;
 | 
					import top.continew.starter.core.util.StrUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.Serial;
 | 
					import java.io.Serial;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -39,6 +40,7 @@ import java.util.Map;
 | 
				
			|||||||
 * @since 2023/12/23 10:38
 | 
					 * @since 2023/12/23 10:38
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
 | 
					@NoArgsConstructor
 | 
				
			||||||
@TableName("sys_file")
 | 
					@TableName("sys_file")
 | 
				
			||||||
public class FileDO extends BaseDO {
 | 
					public class FileDO extends BaseDO {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -116,65 +118,63 @@ public class FileDO extends BaseDO {
 | 
				
			|||||||
    private Long storageId;
 | 
					    private Long storageId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 转换为 X-File-Storage 文件信息对象
 | 
					     * 基于 {@link FileInfo} 构建文件信息对象
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param storageDO 存储桶信息
 | 
					     * @param fileInfo {@link FileInfo} 文件信息
 | 
				
			||||||
     * @return X-File-Storage 文件信息对象
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public FileInfo toFileInfo(StorageDO storageDO) {
 | 
					    public FileDO(FileInfo fileInfo) {
 | 
				
			||||||
 | 
					        this.name = FileNameUtil.getPrefix(EscapeUtil.unescape(fileInfo.getOriginalFilename()));
 | 
				
			||||||
 | 
					        this.size = fileInfo.getSize();
 | 
				
			||||||
 | 
					        this.url = fileInfo.getUrl();
 | 
				
			||||||
 | 
					        this.absPath = StrUtil.isEmpty(fileInfo.getPath())
 | 
				
			||||||
 | 
					            ? StringConstants.SLASH
 | 
				
			||||||
 | 
					            : StrUtil.prependIfMissing(fileInfo.getPath(), StringConstants.SLASH);
 | 
				
			||||||
 | 
					        String[] pathAttr = this.absPath.split(StringConstants.SLASH);
 | 
				
			||||||
 | 
					        this.parentPath = pathAttr.length > 1 ? pathAttr[pathAttr.length - 1] : StringConstants.SLASH;
 | 
				
			||||||
 | 
					        this.extension = fileInfo.getExt();
 | 
				
			||||||
 | 
					        this.contentType = fileInfo.getContentType();
 | 
				
			||||||
 | 
					        this.type = FileTypeEnum.getByExtension(this.extension);
 | 
				
			||||||
 | 
					        this.sha256 = fileInfo.getHashInfo().getSha256();
 | 
				
			||||||
 | 
					        this.metadata = JSONUtil.toJsonStr(fileInfo.getMetadata());
 | 
				
			||||||
 | 
					        this.thumbnailSize = fileInfo.getThSize();
 | 
				
			||||||
 | 
					        this.thumbnailUrl = fileInfo.getThUrl();
 | 
				
			||||||
 | 
					        this.thumbnailMetadata = JSONUtil.toJsonStr(fileInfo.getThMetadata());
 | 
				
			||||||
 | 
					        this.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 转换为 {@link FileInfo} 文件信息对象
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param storage 存储配置信息
 | 
				
			||||||
 | 
					     * @return {@link FileInfo} 文件信息对象
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public FileInfo toFileInfo(StorageDO storage) {
 | 
				
			||||||
        FileInfo fileInfo = new FileInfo();
 | 
					        FileInfo fileInfo = new FileInfo();
 | 
				
			||||||
        fileInfo.setUrl(this.url);
 | 
					        fileInfo.setPlatform(storage.getCode());
 | 
				
			||||||
        fileInfo.setSize(this.size);
 | 
					 | 
				
			||||||
        fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH)
 | 
					        fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH)
 | 
				
			||||||
            ? StrUtil.subAfter(this.url, StringConstants.SLASH, true)
 | 
					            ? StrUtil.subAfter(this.url, StringConstants.SLASH, true)
 | 
				
			||||||
            : this.url);
 | 
					            : this.url);
 | 
				
			||||||
        fileInfo.setOriginalFilename(StrUtils
 | 
					        fileInfo.setOriginalFilename(StrUtils
 | 
				
			||||||
            .blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex));
 | 
					            .blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex));
 | 
				
			||||||
        fileInfo.setBasePath(StringConstants.EMPTY);
 | 
					        fileInfo.setBasePath(StringConstants.EMPTY);
 | 
				
			||||||
        // 优化 path 处理
 | 
					        fileInfo.setSize(this.size);
 | 
				
			||||||
        fileInfo.setPath(extractRelativePath(this.url, storageDO));
 | 
					        fileInfo.setUrl(this.url);
 | 
				
			||||||
 | 
					        fileInfo.setPath(StringConstants.SLASH.equals(this.absPath)
 | 
				
			||||||
 | 
					            ? StringConstants.EMPTY
 | 
				
			||||||
 | 
					            : StrUtil.removePrefix(this.absPath, StringConstants.SLASH));
 | 
				
			||||||
        fileInfo.setExt(this.extension);
 | 
					        fileInfo.setExt(this.extension);
 | 
				
			||||||
        fileInfo.setPlatform(storageDO.getCode());
 | 
					        if (StrUtil.isNotBlank(this.metadata)) {
 | 
				
			||||||
        fileInfo.setThUrl(this.thumbnailUrl);
 | 
					            fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 缩略图信息
 | 
				
			||||||
        fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH)
 | 
					        fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH)
 | 
				
			||||||
            ? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true)
 | 
					            ? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true)
 | 
				
			||||||
            : this.thumbnailUrl);
 | 
					            : this.thumbnailUrl);
 | 
				
			||||||
        fileInfo.setThSize(this.thumbnailSize);
 | 
					        fileInfo.setThSize(this.thumbnailSize);
 | 
				
			||||||
 | 
					        fileInfo.setThUrl(this.thumbnailUrl);
 | 
				
			||||||
        if (StrUtil.isNotBlank(this.thumbnailMetadata)) {
 | 
					        if (StrUtil.isNotBlank(this.thumbnailMetadata)) {
 | 
				
			||||||
            fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class));
 | 
					            fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (StrUtil.isNotBlank(this.metadata)) {
 | 
					 | 
				
			||||||
            fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return fileInfo;
 | 
					        return fileInfo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 将文件路径处理成资源路径
 | 
					 | 
				
			||||||
     * 例如:
 | 
					 | 
				
			||||||
     * http://domain.cn/bucketName/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/
 | 
					 | 
				
			||||||
     * http://bucketName.domain.cn/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param url       文件路径
 | 
					 | 
				
			||||||
     * @param storageDO 存储桶信息
 | 
					 | 
				
			||||||
     * @return
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @SneakyThrows
 | 
					 | 
				
			||||||
    private static String extractRelativePath(String url, StorageDO storageDO) {
 | 
					 | 
				
			||||||
        url = StrUtil.subBefore(url, StringConstants.SLASH, true) + StringConstants.SLASH;
 | 
					 | 
				
			||||||
        if (storageDO.getType().equals(StorageTypeEnum.LOCAL)) {
 | 
					 | 
				
			||||||
            return url;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // 提取 URL 中的路径部分
 | 
					 | 
				
			||||||
        String fullPath = new URL(url).getPath();
 | 
					 | 
				
			||||||
        // 移除开头的斜杠
 | 
					 | 
				
			||||||
        String relativePath = fullPath.startsWith(StringConstants.SLASH) ? fullPath.substring(1) : fullPath;
 | 
					 | 
				
			||||||
        // 如果路径以 bucketName 开头,则移除 bucketName 例如: bucketName/2024/11/27/ -> 2024/11/27/
 | 
					 | 
				
			||||||
        if (relativePath.startsWith(storageDO.getBucketName())) {
 | 
					 | 
				
			||||||
            return StrUtil.subAfter(relativePath, storageDO.getBucketName(), false);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return relativePath;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,17 +89,6 @@ public interface FileService extends BaseService<FileResp, FileResp, FileQuery,
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    FileStatisticsResp statistics();
 | 
					    FileStatisticsResp statistics();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 获取默认文件路径
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return 默认文件路径
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    default String getDefaultFilePath() {
 | 
					 | 
				
			||||||
        LocalDate today = LocalDate.now();
 | 
					 | 
				
			||||||
        return today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today
 | 
					 | 
				
			||||||
            .getDayOfMonth() + StringConstants.SLASH;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 检查文件是否存在
 | 
					     * 检查文件是否存在
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@@ -115,4 +104,19 @@ public interface FileService extends BaseService<FileResp, FileResp, FileQuery,
 | 
				
			|||||||
     * @return ID
 | 
					     * @return ID
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    IdResp<Long> createDir(FileReq req);
 | 
					    IdResp<Long> createDir(FileReq req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取默认文件路径
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 默认文件路径:yyyy/MM/dd/
 | 
				
			||||||
 | 
					     * </p>
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return 默认文件路径
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default String getDefaultFilePath() {
 | 
				
			||||||
 | 
					        LocalDate today = LocalDate.now();
 | 
				
			||||||
 | 
					        return today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today
 | 
				
			||||||
 | 
					            .getDayOfMonth() + StringConstants.SLASH;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -50,29 +50,29 @@ public interface StorageService extends BaseService<StorageResp, StorageResp, St
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 查询默认存储
 | 
					     * 查询默认存储
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return 存储信息
 | 
					     * @return 存储配置
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    StorageDO getDefaultStorage();
 | 
					    StorageDO getDefaultStorage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据编码查询
 | 
					     * 根据编码查询(如果编码为空,则返回默认存储)
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param code 编码
 | 
					     * @param code 编码
 | 
				
			||||||
     * @return 存储信息
 | 
					     * @return 存储配置
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    StorageDO getByCode(String code);
 | 
					    StorageDO getByCode(String code);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 加载存储
 | 
					     * 加载存储引擎
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param req 存储信息
 | 
					     * @param storage 存储配置
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    void load(StorageReq req);
 | 
					    void load(StorageDO storage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 卸载存储
 | 
					     * 卸载存储引擎
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param req 存储信息
 | 
					     * @param storage 存储配置
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    void unload(StorageReq req);
 | 
					    void unload(StorageDO storage);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -30,6 +30,7 @@ import org.dromara.x.file.storage.core.ProgressListener;
 | 
				
			|||||||
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
 | 
					import org.dromara.x.file.storage.core.upload.UploadPretreatment;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
import org.springframework.web.multipart.MultipartFile;
 | 
					import org.springframework.web.multipart.MultipartFile;
 | 
				
			||||||
 | 
					import top.continew.admin.common.enums.DisEnableStatusEnum;
 | 
				
			||||||
import top.continew.admin.system.enums.FileTypeEnum;
 | 
					import top.continew.admin.system.enums.FileTypeEnum;
 | 
				
			||||||
import top.continew.admin.system.mapper.FileMapper;
 | 
					import top.continew.admin.system.mapper.FileMapper;
 | 
				
			||||||
import top.continew.admin.system.model.entity.FileDO;
 | 
					import top.continew.admin.system.model.entity.FileDO;
 | 
				
			||||||
@@ -68,7 +69,7 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
 | 
				
			|||||||
    private StorageService storageService;
 | 
					    private StorageService storageService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected void beforeDelete(List<Long> ids) {
 | 
					    public void beforeDelete(List<Long> ids) {
 | 
				
			||||||
        List<FileDO> fileList = baseMapper.lambdaQuery().in(FileDO::getId, ids).list();
 | 
					        List<FileDO> fileList = baseMapper.lambdaQuery().in(FileDO::getId, ids).list();
 | 
				
			||||||
        Map<Long, List<FileDO>> fileListGroup = fileList.stream().collect(Collectors.groupingBy(FileDO::getStorageId));
 | 
					        Map<Long, List<FileDO>> fileListGroup = fileList.stream().collect(Collectors.groupingBy(FileDO::getStorageId));
 | 
				
			||||||
        for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
 | 
					        for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
 | 
				
			||||||
@@ -87,23 +88,17 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
 | 
				
			|||||||
        List<String> allExtensions = FileTypeEnum.getAllExtensions();
 | 
					        List<String> allExtensions = FileTypeEnum.getAllExtensions();
 | 
				
			||||||
        CheckUtils.throwIf(!allExtensions.contains(extName), "不支持的文件类型,仅支持 {} 格式的文件", String
 | 
					        CheckUtils.throwIf(!allExtensions.contains(extName), "不支持的文件类型,仅支持 {} 格式的文件", String
 | 
				
			||||||
            .join(StringConstants.CHINESE_COMMA, allExtensions));
 | 
					            .join(StringConstants.CHINESE_COMMA, allExtensions));
 | 
				
			||||||
        // 获取存储信息
 | 
					 | 
				
			||||||
        StorageDO storage;
 | 
					 | 
				
			||||||
        if (StrUtil.isBlank(storageCode)) {
 | 
					 | 
				
			||||||
            storage = storageService.getDefaultStorage();
 | 
					 | 
				
			||||||
            CheckUtils.throwIfNull(storage, "请先指定默认存储");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            storage = storageService.getByCode(storageCode);
 | 
					 | 
				
			||||||
            CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", storageCode);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // 构建上传预处理对象
 | 
					        // 构建上传预处理对象
 | 
				
			||||||
 | 
					        StorageDO storage = storageService.getByCode(storageCode);
 | 
				
			||||||
 | 
					        CheckUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(storage.getStatus()), "请先启用存储 [{}]", storage.getCode());
 | 
				
			||||||
        UploadPretreatment uploadPretreatment = fileStorageService.of(file)
 | 
					        UploadPretreatment uploadPretreatment = fileStorageService.of(file)
 | 
				
			||||||
            .setPlatform(storage.getCode())
 | 
					            .setPlatform(storage.getCode())
 | 
				
			||||||
            .setHashCalculatorSha256(true)
 | 
					            .setHashCalculatorSha256(true)
 | 
				
			||||||
            .putAttr(ClassUtil.getClassName(StorageDO.class, false), storage)
 | 
					            .putAttr(ClassUtil.getClassName(StorageDO.class, false), storage)
 | 
				
			||||||
            .setPath(path);
 | 
					            .setPath(this.pretreatmentPath(path));
 | 
				
			||||||
        // 图片文件生成缩略图
 | 
					        // 图片文件生成缩略图
 | 
				
			||||||
        if (FileTypeEnum.IMAGE.getExtensions().contains(extName)) {
 | 
					        if (FileTypeEnum.IMAGE.getExtensions().contains(extName)) {
 | 
				
			||||||
 | 
					            uploadPretreatment.setIgnoreThumbnailException(true, true);
 | 
				
			||||||
            uploadPretreatment.thumbnail(img -> img.size(100, 100));
 | 
					            uploadPretreatment.thumbnail(img -> img.size(100, 100));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        uploadPretreatment.setProgressMonitor(new ProgressListener() {
 | 
					        uploadPretreatment.setProgressMonitor(new ProgressListener() {
 | 
				
			||||||
@@ -122,6 +117,7 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
 | 
				
			|||||||
                log.info("上传结束");
 | 
					                log.info("上传结束");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        // 上传
 | 
				
			||||||
        return uploadPretreatment.upload();
 | 
					        return uploadPretreatment.upload();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -194,4 +190,28 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
 | 
				
			|||||||
            fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode()));
 | 
					            fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 处理路径
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 1.如果 path 为空,则使用 {@link FileService#getDefaultFilePath()} 作为默认值 <br />
 | 
				
			||||||
 | 
					     * 2.如果 path 为 {@code /},则设置为空 <br />
 | 
				
			||||||
 | 
					     * 3.如果 path 不以 {@code /} 结尾,则添加后缀 {@code /} <br />
 | 
				
			||||||
 | 
					     * 4.如果 path 以 {@code /} 开头,则移除前缀 {@code /} <br />
 | 
				
			||||||
 | 
					     * 示例:yyyy/MM/dd/
 | 
				
			||||||
 | 
					     * </p>
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param path 路径
 | 
				
			||||||
 | 
					     * @return 处理路径
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String pretreatmentPath(String path) {
 | 
				
			||||||
 | 
					        if (StrUtil.isBlank(path)) {
 | 
				
			||||||
 | 
					            return this.getDefaultFilePath();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (StringConstants.SLASH.equals(path)) {
 | 
				
			||||||
 | 
					            return StringConstants.EMPTY;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return StrUtil.appendIfMissing(StrUtil.removePrefix(path, StringConstants.SLASH), StringConstants.SLASH);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -16,7 +16,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package top.continew.admin.system.service.impl;
 | 
					package top.continew.admin.system.service.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.hutool.core.bean.BeanUtil;
 | 
					 | 
				
			||||||
import cn.hutool.core.map.MapUtil;
 | 
					import cn.hutool.core.map.MapUtil;
 | 
				
			||||||
import cn.hutool.core.util.StrUtil;
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
import cn.hutool.core.util.URLUtil;
 | 
					import cn.hutool.core.util.URLUtil;
 | 
				
			||||||
@@ -39,10 +38,8 @@ import top.continew.admin.system.model.req.StorageReq;
 | 
				
			|||||||
import top.continew.admin.system.model.resp.StorageResp;
 | 
					import top.continew.admin.system.model.resp.StorageResp;
 | 
				
			||||||
import top.continew.admin.system.service.FileService;
 | 
					import top.continew.admin.system.service.FileService;
 | 
				
			||||||
import top.continew.admin.system.service.StorageService;
 | 
					import top.continew.admin.system.service.StorageService;
 | 
				
			||||||
import top.continew.admin.system.validation.ValidationGroup;
 | 
					 | 
				
			||||||
import top.continew.starter.core.constant.StringConstants;
 | 
					import top.continew.starter.core.constant.StringConstants;
 | 
				
			||||||
import top.continew.starter.core.util.ExceptionUtils;
 | 
					import top.continew.starter.core.util.ExceptionUtils;
 | 
				
			||||||
import top.continew.starter.core.util.URLUtils;
 | 
					 | 
				
			||||||
import top.continew.starter.core.validation.CheckUtils;
 | 
					import top.continew.starter.core.validation.CheckUtils;
 | 
				
			||||||
import top.continew.starter.core.validation.ValidationUtils;
 | 
					import top.continew.starter.core.validation.ValidationUtils;
 | 
				
			||||||
import top.continew.starter.extension.crud.service.BaseServiceImpl;
 | 
					import top.continew.starter.extension.crud.service.BaseServiceImpl;
 | 
				
			||||||
@@ -68,33 +65,55 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void beforeCreate(StorageReq req) {
 | 
					    public void beforeCreate(StorageReq req) {
 | 
				
			||||||
        this.decodeSecretKey(req, null);
 | 
					        // 解密密钥
 | 
				
			||||||
 | 
					        if (StorageTypeEnum.OSS.equals(req.getType())) {
 | 
				
			||||||
 | 
					            req.setSecretKey(this.decryptSecretKey(req.getSecretKey(), null));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 指定配置参数校验及预处理
 | 
				
			||||||
 | 
					        StorageTypeEnum storageType = req.getType();
 | 
				
			||||||
 | 
					        storageType.validate(req);
 | 
				
			||||||
 | 
					        storageType.pretreatment(req);
 | 
				
			||||||
 | 
					        // 校验存储编码
 | 
				
			||||||
        String code = req.getCode();
 | 
					        String code = req.getCode();
 | 
				
			||||||
        CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code);
 | 
					        CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code);
 | 
				
			||||||
        // 单独指定默认存储
 | 
					        // 需要独立操作来指定默认存储
 | 
				
			||||||
        req.setIsDefault(false);
 | 
					        req.setIsDefault(false);
 | 
				
			||||||
        if (DisEnableStatusEnum.ENABLE.equals(req.getStatus())) {
 | 
					    }
 | 
				
			||||||
            this.load(req);
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void afterCreate(StorageReq req, StorageDO entity) {
 | 
				
			||||||
 | 
					        // 加载存储引擎
 | 
				
			||||||
 | 
					        if (DisEnableStatusEnum.ENABLE.equals(entity.getStatus())) {
 | 
				
			||||||
 | 
					            this.load(entity);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void beforeUpdate(StorageReq req, Long id) {
 | 
					    public void beforeUpdate(StorageReq req, Long id) {
 | 
				
			||||||
 | 
					        // 解密密钥
 | 
				
			||||||
        StorageDO oldStorage = super.getById(id);
 | 
					        StorageDO oldStorage = super.getById(id);
 | 
				
			||||||
        CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码");
 | 
					        if (StorageTypeEnum.OSS.equals(req.getType())) {
 | 
				
			||||||
 | 
					            req.setSecretKey(this.decryptSecretKey(req.getSecretKey(), oldStorage));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 校验存储编码、存储类型、状态
 | 
				
			||||||
        CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型");
 | 
					        CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型");
 | 
				
			||||||
        this.decodeSecretKey(req, oldStorage);
 | 
					        CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码");
 | 
				
			||||||
        DisEnableStatusEnum newStatus = req.getStatus();
 | 
					        DisEnableStatusEnum newStatus = req.getStatus();
 | 
				
			||||||
        CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE
 | 
					        CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE
 | 
				
			||||||
            .equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName());
 | 
					            .equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName());
 | 
				
			||||||
        // 重新加载配置
 | 
					        // 指定配置参数校验及预处理
 | 
				
			||||||
        // 先卸载
 | 
					        StorageTypeEnum storageType = req.getType();
 | 
				
			||||||
        if (fileStorageService.getFileStorage(req.getCode()) != null) {
 | 
					        storageType.validate(req);
 | 
				
			||||||
            this.unload(BeanUtil.copyProperties(oldStorage, StorageReq.class));
 | 
					        storageType.pretreatment(req);
 | 
				
			||||||
 | 
					        // 卸载存储引擎
 | 
				
			||||||
 | 
					        this.unload(oldStorage);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        // 再加载
 | 
					
 | 
				
			||||||
        if (DisEnableStatusEnum.ENABLE.equals(newStatus)) {
 | 
					    @Override
 | 
				
			||||||
            this.load(req);
 | 
					    public void afterUpdate(StorageReq req, StorageDO entity) {
 | 
				
			||||||
 | 
					        // 加载存储引擎
 | 
				
			||||||
 | 
					        if (DisEnableStatusEnum.ENABLE.equals(entity.getStatus())) {
 | 
				
			||||||
 | 
					            this.load(entity);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,12 +121,10 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
 | 
				
			|||||||
    public void beforeDelete(List<Long> ids) {
 | 
					    public void beforeDelete(List<Long> ids) {
 | 
				
			||||||
        CheckUtils.throwIf(fileService.countByStorageIds(ids) > 0, "所选存储存在文件关联,请删除文件后重试");
 | 
					        CheckUtils.throwIf(fileService.countByStorageIds(ids) > 0, "所选存储存在文件关联,请删除文件后重试");
 | 
				
			||||||
        List<StorageDO> storageList = baseMapper.lambdaQuery().in(StorageDO::getId, ids).list();
 | 
					        List<StorageDO> storageList = baseMapper.lambdaQuery().in(StorageDO::getId, ids).list();
 | 
				
			||||||
        storageList.forEach(s -> {
 | 
					        storageList.forEach(storage -> {
 | 
				
			||||||
            CheckUtils.throwIfEqual(Boolean.TRUE, s.getIsDefault(), "[{}] 是默认存储,不允许删除", s.getName());
 | 
					            CheckUtils.throwIfEqual(Boolean.TRUE, storage.getIsDefault(), "[{}] 是默认存储,不允许删除", storage.getName());
 | 
				
			||||||
            // 卸载启用状态的存储
 | 
					            // 卸载存储引擎
 | 
				
			||||||
            if (DisEnableStatusEnum.ENABLE.equals(s.getStatus())) {
 | 
					            this.unload(storage);
 | 
				
			||||||
                this.unload(BeanUtil.copyProperties(s, StorageReq.class));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,14 +140,13 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
 | 
				
			|||||||
        // 修改状态
 | 
					        // 修改状态
 | 
				
			||||||
        baseMapper.lambdaUpdate().eq(StorageDO::getId, id).set(StorageDO::getStatus, newStatus).update();
 | 
					        baseMapper.lambdaUpdate().eq(StorageDO::getId, id).set(StorageDO::getStatus, newStatus).update();
 | 
				
			||||||
        // 加载、卸载存储引擎
 | 
					        // 加载、卸载存储引擎
 | 
				
			||||||
        StorageReq storageReq = BeanUtil.copyProperties(storage, StorageReq.class);
 | 
					 | 
				
			||||||
        switch (newStatus) {
 | 
					        switch (newStatus) {
 | 
				
			||||||
            case ENABLE:
 | 
					            case ENABLE:
 | 
				
			||||||
                this.load(storageReq);
 | 
					                this.load(storage);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case DISABLE:
 | 
					            case DISABLE:
 | 
				
			||||||
                CheckUtils.throwIfEqual(Boolean.TRUE, storage.getIsDefault(), "[{}] 是默认存储,不允许禁用", storage.getName());
 | 
					                CheckUtils.throwIfEqual(Boolean.TRUE, storage.getIsDefault(), "[{}] 是默认存储,不允许禁用", storage.getName());
 | 
				
			||||||
                this.unload(storageReq);
 | 
					                this.unload(storage);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
@@ -152,87 +168,86 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public StorageDO getDefaultStorage() {
 | 
					    public StorageDO getDefaultStorage() {
 | 
				
			||||||
        return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).eq(StorageDO::getStatus, DisEnableStatusEnum.ENABLE).one();
 | 
					        StorageDO storage = baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).one();
 | 
				
			||||||
 | 
					        CheckUtils.throwIfNull(storage, "请先指定默认存储");
 | 
				
			||||||
 | 
					        return storage;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public StorageDO getByCode(String code) {
 | 
					    public StorageDO getByCode(String code) {
 | 
				
			||||||
        return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).one();
 | 
					        if (StrUtil.isBlank(code)) {
 | 
				
			||||||
 | 
					            return this.getDefaultStorage();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        StorageDO storage = baseMapper.lambdaQuery().eq(StorageDO::getCode, code).one();
 | 
				
			||||||
 | 
					        CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", code);
 | 
				
			||||||
 | 
					        return storage;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void load(StorageReq req) {
 | 
					    public void load(StorageDO storage) {
 | 
				
			||||||
        CopyOnWriteArrayList<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
 | 
					        CopyOnWriteArrayList<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
 | 
				
			||||||
        String domain = req.getDomain();
 | 
					        switch (storage.getType()) {
 | 
				
			||||||
        ValidationUtils.throwIf(!URLUtils.isHttpUrl(domain), "域名格式不正确");
 | 
					            case LOCAL -> {
 | 
				
			||||||
        String bucketName = req.getBucketName();
 | 
					 | 
				
			||||||
        StorageTypeEnum type = req.getType();
 | 
					 | 
				
			||||||
        if (StorageTypeEnum.LOCAL.equals(type)) {
 | 
					 | 
				
			||||||
            ValidationUtils.validate(req, ValidationGroup.Storage.Local.class);
 | 
					 | 
				
			||||||
            req.setBucketName(StrUtil.appendIfMissing(bucketName
 | 
					 | 
				
			||||||
                .replace(StringConstants.BACKSLASH, StringConstants.SLASH), StringConstants.SLASH));
 | 
					 | 
				
			||||||
                FileStorageProperties.LocalPlusConfig config = new FileStorageProperties.LocalPlusConfig();
 | 
					                FileStorageProperties.LocalPlusConfig config = new FileStorageProperties.LocalPlusConfig();
 | 
				
			||||||
            config.setPlatform(req.getCode());
 | 
					                config.setPlatform(storage.getCode());
 | 
				
			||||||
            config.setStoragePath(bucketName);
 | 
					                config.setStoragePath(storage.getBucketName());
 | 
				
			||||||
                fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections
 | 
					                fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections
 | 
				
			||||||
                    .singletonList(config)));
 | 
					                    .singletonList(config)));
 | 
				
			||||||
            SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), bucketName));
 | 
					                SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage
 | 
				
			||||||
        } else if (StorageTypeEnum.OSS.equals(type)) {
 | 
					                    .getBucketName()));
 | 
				
			||||||
            ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class);
 | 
					            }
 | 
				
			||||||
 | 
					            case OSS -> {
 | 
				
			||||||
                FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config();
 | 
					                FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config();
 | 
				
			||||||
            config.setPlatform(req.getCode());
 | 
					                config.setPlatform(storage.getCode());
 | 
				
			||||||
            config.setAccessKey(req.getAccessKey());
 | 
					                config.setAccessKey(storage.getAccessKey());
 | 
				
			||||||
            config.setSecretKey(req.getSecretKey());
 | 
					                config.setSecretKey(storage.getSecretKey());
 | 
				
			||||||
            config.setEndPoint(req.getEndpoint());
 | 
					                config.setEndPoint(storage.getEndpoint());
 | 
				
			||||||
            config.setBucketName(bucketName);
 | 
					                config.setBucketName(storage.getBucketName());
 | 
				
			||||||
            config.setDomain(domain);
 | 
					                config.setDomain(storage.getDomain());
 | 
				
			||||||
                fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections
 | 
					                fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections
 | 
				
			||||||
                    .singletonList(config), null));
 | 
					                    .singletonList(config), null));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            default -> throw new IllegalArgumentException("不支持的存储类型:%s".formatted(storage.getType()));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void unload(StorageReq req) {
 | 
					    public void unload(StorageDO storage) {
 | 
				
			||||||
 | 
					        FileStorage fileStorage = fileStorageService.getFileStorage(storage.getCode());
 | 
				
			||||||
 | 
					        if (fileStorage == null) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        CopyOnWriteArrayList<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
 | 
					        CopyOnWriteArrayList<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
 | 
				
			||||||
        FileStorage fileStorage = fileStorageService.getFileStorage(req.getCode());
 | 
					 | 
				
			||||||
        fileStorageList.remove(fileStorage);
 | 
					        fileStorageList.remove(fileStorage);
 | 
				
			||||||
        fileStorage.close();
 | 
					        fileStorage.close();
 | 
				
			||||||
        SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), req
 | 
					        // 本地存储引擎需要移除资源映射
 | 
				
			||||||
 | 
					        if (StorageTypeEnum.LOCAL.equals(storage.getType())) {
 | 
				
			||||||
 | 
					            SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage
 | 
				
			||||||
                .getBucketName()));
 | 
					                .getBucketName()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 解密 SecretKey
 | 
					     * 解密 SecretKey
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param req     请求参数
 | 
					     * @param encryptSecretKey 加密的 SecretKey
 | 
				
			||||||
     * @param storage          存储信息
 | 
					     * @param storage          存储信息
 | 
				
			||||||
 | 
					     * @return 解密后的 SecretKey
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private void decodeSecretKey(StorageReq req, StorageDO storage) {
 | 
					    private String decryptSecretKey(String encryptSecretKey, StorageDO storage) {
 | 
				
			||||||
        if (!StorageTypeEnum.OSS.equals(req.getType())) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // 修改时,如果 SecretKey 不修改,需要手动修正
 | 
					        // 修改时,如果 SecretKey 不修改,需要手动修正
 | 
				
			||||||
        String newSecretKey = req.getSecretKey();
 | 
					        if (null != storage) {
 | 
				
			||||||
        boolean isSecretKeyNotUpdate = StrUtil.isBlank(newSecretKey) || newSecretKey.contains(StringConstants.ASTERISK);
 | 
					            boolean isSecretKeyNotUpdate = StrUtil.isBlank(encryptSecretKey) || encryptSecretKey
 | 
				
			||||||
        if (null != storage && isSecretKeyNotUpdate) {
 | 
					                .contains(StringConstants.ASTERISK);
 | 
				
			||||||
            req.setSecretKey(storage.getSecretKey());
 | 
					            if (isSecretKeyNotUpdate) {
 | 
				
			||||||
            return;
 | 
					                return storage.getSecretKey();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        // 新增时或修改了 SecretKey
 | 
					        }
 | 
				
			||||||
        String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(newSecretKey));
 | 
					        // 新增场景,直接解密 SecretKey
 | 
				
			||||||
 | 
					        String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(encryptSecretKey));
 | 
				
			||||||
        ValidationUtils.throwIfNull(secretKey, "私有密钥解密失败");
 | 
					        ValidationUtils.throwIfNull(secretKey, "私有密钥解密失败");
 | 
				
			||||||
        ValidationUtils.throwIf(secretKey.length() > 255, "私有密钥长度不能超过 255 个字符");
 | 
					        ValidationUtils.throwIf(secretKey.length() > 255, "私有密钥长度不能超过 255 个字符");
 | 
				
			||||||
        req.setSecretKey(secretKey);
 | 
					        return secretKey;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 默认存储是否存在
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param id ID
 | 
					 | 
				
			||||||
     * @return 是否存在
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private boolean isDefaultExists(Long id) {
 | 
					 | 
				
			||||||
        return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).ne(null != id, StorageDO::getId, id).exists();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,8 +35,6 @@ import top.continew.admin.system.enums.OptionCategoryEnum;
 | 
				
			|||||||
import top.continew.admin.system.model.query.*;
 | 
					import top.continew.admin.system.model.query.*;
 | 
				
			||||||
import top.continew.admin.system.model.resp.file.FileUploadResp;
 | 
					import top.continew.admin.system.model.resp.file.FileUploadResp;
 | 
				
			||||||
import top.continew.admin.system.service.*;
 | 
					import top.continew.admin.system.service.*;
 | 
				
			||||||
import top.continew.starter.core.constant.StringConstants;
 | 
					 | 
				
			||||||
import top.continew.starter.core.util.StrUtils;
 | 
					 | 
				
			||||||
import top.continew.starter.core.validation.ValidationUtils;
 | 
					import top.continew.starter.core.validation.ValidationUtils;
 | 
				
			||||||
import top.continew.starter.extension.crud.model.query.SortQuery;
 | 
					import top.continew.starter.extension.crud.model.query.SortQuery;
 | 
				
			||||||
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
 | 
					import top.continew.starter.extension.crud.model.resp.LabelValueResp;
 | 
				
			||||||
@@ -71,9 +69,7 @@ public class CommonController {
 | 
				
			|||||||
    @PostMapping("/file")
 | 
					    @PostMapping("/file")
 | 
				
			||||||
    public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file, String path) throws IOException {
 | 
					    public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file, String path) throws IOException {
 | 
				
			||||||
        ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
 | 
					        ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
 | 
				
			||||||
        String fixedPath = StrUtils.blankToDefault(path, StringConstants.SLASH, p -> StrUtil
 | 
					        FileInfo fileInfo = fileService.upload(file, path);
 | 
				
			||||||
            .appendIfMissing(p, StringConstants.SLASH));
 | 
					 | 
				
			||||||
        FileInfo fileInfo = fileService.upload(file, fixedPath);
 | 
					 | 
				
			||||||
        return FileUploadResp.builder()
 | 
					        return FileUploadResp.builder()
 | 
				
			||||||
            .id(fileInfo.getId())
 | 
					            .id(fileInfo.getId())
 | 
				
			||||||
            .url(fileInfo.getUrl())
 | 
					            .url(fileInfo.getUrl())
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user