mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-12 16:57:12 +08:00
refactor(system/file): 重构文件管理相关代码,完善文件夹场景
This commit is contained in:
@@ -16,9 +16,11 @@
|
||||
|
||||
package top.continew.admin.system.config.file;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
@@ -32,7 +34,9 @@ import top.continew.admin.system.model.entity.StorageDO;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.URLUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件记录实现类
|
||||
@@ -58,11 +62,11 @@ public class FileRecorderImpl implements FileRecorder {
|
||||
// 方便文件上传完成后获取文件信息
|
||||
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());
|
||||
String prefix = StrUtil.blankToDefault(storage.getDomain(), storage.getEndpoint());
|
||||
String url = URLUtil.completeUrl(prefix, fileInfo.getUrl());
|
||||
fileInfo.setUrl(url);
|
||||
if (StrUtil.isNotBlank(fileInfo.getThUrl())) {
|
||||
fileInfo.setThUrl(URLUtil.normalize(prefix + fileInfo.getThUrl()));
|
||||
fileInfo.setThUrl(URLUtil.completeUrl(prefix, fileInfo.getThUrl()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -81,7 +85,10 @@ public class FileRecorderImpl implements FileRecorder {
|
||||
@Override
|
||||
public boolean delete(String url) {
|
||||
FileDO file = this.getFileByUrl(url);
|
||||
return fileMapper.lambdaUpdate().eq(FileDO::getUrl, file.getUrl()).remove();
|
||||
if (null == file) {
|
||||
return false;
|
||||
}
|
||||
return fileMapper.lambdaUpdate().eq(FileDO::getId, file.getId()).remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,10 +113,32 @@ public class FileRecorderImpl implements FileRecorder {
|
||||
* @return 文件信息
|
||||
*/
|
||||
private FileDO getFileByUrl(String url) {
|
||||
Optional<FileDO> fileOptional = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).oneOpt();
|
||||
return fileOptional.orElseGet(() -> fileMapper.lambdaQuery()
|
||||
.likeLeft(FileDO::getUrl, StrUtil.subAfter(url, StringConstants.SLASH, true))
|
||||
.oneOpt()
|
||||
.orElse(null));
|
||||
LambdaQueryChainWrapper<FileDO> queryWrapper = fileMapper.lambdaQuery()
|
||||
.eq(FileDO::getName, StrUtil.subAfter(url, StringConstants.SLASH, true));
|
||||
// 非 HTTP URL 场景
|
||||
if (!URLUtils.isHttpUrl(url)) {
|
||||
return queryWrapper.eq(FileDO::getPath, StrUtil.prependIfMissing(StrUtil
|
||||
.subBefore(url, StringConstants.SLASH, true), StringConstants.SLASH)).one();
|
||||
}
|
||||
// HTTP URL 场景
|
||||
List<FileDO> list = queryWrapper.list();
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return null;
|
||||
}
|
||||
if (list.size() == 1) {
|
||||
return list.get(0);
|
||||
}
|
||||
// 结合存储配置进行匹配
|
||||
List<StorageDO> storageList = storageMapper.selectByIds(list.stream().map(FileDO::getStorageId).toList());
|
||||
Map<Long, StorageDO> storageMap = storageList.stream()
|
||||
.collect(Collectors.toMap(StorageDO::getId, storage -> storage));
|
||||
return list.stream().filter(file -> {
|
||||
// http://localhost:8000/file/user/avatar/6825e687db4174e7a297a5f8.png => http://localhost:8000/file/user/avatar
|
||||
String urlPrefix = StrUtil.subBefore(url, StringConstants.SLASH, true);
|
||||
// http://localhost:8000/file/ + /user/avatar => http://localhost:8000/file/user/avatar
|
||||
StorageDO storage = storageMap.get(file.getStorageId());
|
||||
String prefix = StrUtil.blankToDefault(storage.getDomain(), storage.getEndpoint());
|
||||
return urlPrefix.equals(URLUtil.normalize(prefix + file.getPath(), false, true));
|
||||
}).findFirst().orElse(null);
|
||||
}
|
||||
}
|
@@ -16,7 +16,6 @@
|
||||
|
||||
package top.continew.admin.system.config.file;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -25,8 +24,6 @@ import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
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.resp.StorageResp;
|
||||
import top.continew.admin.system.service.StorageService;
|
||||
|
||||
import java.util.List;
|
||||
@@ -46,12 +43,10 @@ public class FileStorageConfigLoader implements ApplicationRunner {
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
StorageQuery query = new StorageQuery();
|
||||
query.setStatus(DisEnableStatusEnum.ENABLE);
|
||||
List<StorageResp> storageList = storageService.list(query, null);
|
||||
if (CollUtil.isEmpty(storageList)) {
|
||||
List<StorageDO> list = storageService.lambdaQuery().eq(StorageDO::getStatus, DisEnableStatusEnum.ENABLE).list();
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
storageList.forEach(storage -> storageService.load(BeanUtil.copyProperties(storage, StorageDO.class)));
|
||||
list.forEach(storageService::load);
|
||||
}
|
||||
}
|
||||
|
@@ -43,14 +43,15 @@ public enum StorageTypeEnum implements BaseEnum<Integer> {
|
||||
@Override
|
||||
public void validate(StorageReq req) {
|
||||
ValidationUtils.validate(req, ValidationGroup.Storage.Local.class);
|
||||
ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "访问路径格式不正确");
|
||||
ValidationUtils.throwIf(StrUtil.isNotBlank(req.getDomain()) && !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));
|
||||
// 本地存储路径需要以 “/” 结尾
|
||||
req.setBucketName(StrUtil.appendIfMissing(req.getBucketName(), StringConstants.SLASH));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -61,7 +62,8 @@ public enum StorageTypeEnum implements BaseEnum<Integer> {
|
||||
@Override
|
||||
public void validate(StorageReq req) {
|
||||
ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class);
|
||||
ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "域名格式不正确");
|
||||
ValidationUtils.throwIf(StrUtil.isNotBlank(req.getDomain()) && !URLUtils.isHttpUrl(req
|
||||
.getDomain()), "域名格式不正确");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,6 +83,9 @@ public enum StorageTypeEnum implements BaseEnum<Integer> {
|
||||
* @param req 请求参数
|
||||
*/
|
||||
public void pretreatment(StorageReq req) {
|
||||
req.setDomain(StrUtil.removeSuffix(req.getDomain(), StringConstants.SLASH));
|
||||
// 域名需要以 “/” 结尾
|
||||
if (StrUtil.isNotBlank(req.getDomain())) {
|
||||
req.setDomain(StrUtil.appendIfMissing(req.getDomain(), StringConstants.SLASH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,8 +17,6 @@
|
||||
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.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@@ -28,7 +26,6 @@ import org.dromara.x.file.storage.core.FileInfo;
|
||||
import top.continew.admin.common.model.entity.BaseDO;
|
||||
import top.continew.admin.system.enums.FileTypeEnum;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.StrUtils;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Map;
|
||||
@@ -52,25 +49,20 @@ public class FileDO extends BaseDO {
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 原始名称
|
||||
*/
|
||||
private String originalName;
|
||||
|
||||
/**
|
||||
* 大小(字节)
|
||||
*/
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* URL
|
||||
* 存储路径
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 上级目录
|
||||
*/
|
||||
private String parentPath;
|
||||
|
||||
/**
|
||||
* 绝对路径
|
||||
*/
|
||||
private String absPath;
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 扩展名
|
||||
@@ -88,7 +80,7 @@ public class FileDO extends BaseDO {
|
||||
private FileTypeEnum type;
|
||||
|
||||
/**
|
||||
* SHA256值
|
||||
* SHA256 值
|
||||
*/
|
||||
private String sha256;
|
||||
|
||||
@@ -97,16 +89,16 @@ public class FileDO extends BaseDO {
|
||||
*/
|
||||
private String metadata;
|
||||
|
||||
/**
|
||||
* 缩略图名称
|
||||
*/
|
||||
private String thumbnailName;
|
||||
|
||||
/**
|
||||
* 缩略图大小(字节)
|
||||
*/
|
||||
private Long thumbnailSize;
|
||||
|
||||
/**
|
||||
* 缩略图 URL
|
||||
*/
|
||||
private String thumbnailUrl;
|
||||
|
||||
/**
|
||||
* 缩略图元数据
|
||||
*/
|
||||
@@ -123,21 +115,21 @@ public class FileDO extends BaseDO {
|
||||
* @param fileInfo {@link FileInfo} 文件信息
|
||||
*/
|
||||
public FileDO(FileInfo fileInfo) {
|
||||
this.name = FileNameUtil.getPrefix(EscapeUtil.unescape(fileInfo.getOriginalFilename()));
|
||||
this.name = fileInfo.getFilename();
|
||||
this.originalName = fileInfo.getOriginalFilename();
|
||||
this.size = fileInfo.getSize();
|
||||
this.url = fileInfo.getUrl();
|
||||
this.absPath = StrUtil.isEmpty(fileInfo.getPath())
|
||||
// 如果为空,则为 /;如果不为空,则调整格式为:/xxx
|
||||
this.path = 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;
|
||||
: StrUtil.removeSuffix(StrUtil.prependIfMissing(fileInfo
|
||||
.getPath(), StringConstants.SLASH), 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.thumbnailName = fileInfo.getThFilename();
|
||||
this.thumbnailSize = fileInfo.getThSize();
|
||||
this.thumbnailUrl = fileInfo.getThUrl();
|
||||
this.thumbnailMetadata = JSONUtil.toJsonStr(fileInfo.getThMetadata());
|
||||
this.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
|
||||
}
|
||||
@@ -151,27 +143,24 @@ public class FileDO extends BaseDO {
|
||||
public FileInfo toFileInfo(StorageDO storage) {
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setPlatform(storage.getCode());
|
||||
fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH)
|
||||
? StrUtil.subAfter(this.url, StringConstants.SLASH, true)
|
||||
: this.url);
|
||||
fileInfo.setOriginalFilename(StrUtils
|
||||
.blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex));
|
||||
fileInfo.setFilename(this.name);
|
||||
fileInfo.setOriginalFilename(this.originalName);
|
||||
// 暂不使用,所以保持空
|
||||
fileInfo.setBasePath(StringConstants.EMPTY);
|
||||
fileInfo.setSize(this.size);
|
||||
fileInfo.setUrl(this.url);
|
||||
fileInfo.setPath(StringConstants.SLASH.equals(this.absPath)
|
||||
fileInfo.setPath(StringConstants.SLASH.equals(this.path)
|
||||
? StringConstants.EMPTY
|
||||
: StrUtil.removePrefix(this.absPath, StringConstants.SLASH));
|
||||
: StrUtil.appendIfMissing(StrUtil.removePrefix(this.path, StringConstants.SLASH), StringConstants.SLASH));
|
||||
fileInfo.setExt(this.extension);
|
||||
fileInfo.setContentType(this.contentType);
|
||||
if (StrUtil.isNotBlank(this.metadata)) {
|
||||
fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
|
||||
}
|
||||
fileInfo.setUrl(fileInfo.getPath() + fileInfo.getFilename());
|
||||
// 缩略图信息
|
||||
fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH)
|
||||
? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true)
|
||||
: this.thumbnailUrl);
|
||||
fileInfo.setThFilename(this.thumbnailName);
|
||||
fileInfo.setThSize(this.thumbnailSize);
|
||||
fileInfo.setThUrl(this.thumbnailUrl);
|
||||
fileInfo.setThUrl(fileInfo.getPath() + fileInfo.getThFilename());
|
||||
if (StrUtil.isNotBlank(this.thumbnailMetadata)) {
|
||||
fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class));
|
||||
}
|
||||
|
@@ -41,16 +41,15 @@ public class FileQuery implements Serializable {
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "名称", example = "图片")
|
||||
@Schema(description = "名称", example = "example")
|
||||
@Query(type = QueryType.LIKE)
|
||||
private String name;
|
||||
private String originalName;
|
||||
|
||||
/**
|
||||
* 绝对路径
|
||||
* 存储路径
|
||||
*/
|
||||
@Schema(description = "绝对路径", example = "/2025")
|
||||
@Query(type = QueryType.EQ)
|
||||
private String absPath;
|
||||
@Schema(description = "存储路径", example = "/")
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
|
@@ -40,14 +40,14 @@ public class FileReq implements Serializable {
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "名称", example = "test123")
|
||||
@Schema(description = "名称", example = "example")
|
||||
@NotBlank(message = "文件名称不能为空")
|
||||
@Length(max = 255, message = "文件名称长度不能超过 {max} 个字符")
|
||||
private String name;
|
||||
private String originalName;
|
||||
|
||||
/**
|
||||
* 上级目录
|
||||
* 存储路径
|
||||
*/
|
||||
@Schema(description = "上级目录", example = "25")
|
||||
private String parentPath;
|
||||
@Schema(description = "存储路径", example = "/")
|
||||
private String path;
|
||||
}
|
@@ -41,9 +41,15 @@ public class FileResp extends BaseDetailResp {
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@Schema(description = "名称", example = "example")
|
||||
@Schema(description = "名称", example = "6824afe8408da079832dcfb6.jpg")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 原始名称
|
||||
*/
|
||||
@Schema(description = "原始名称", example = "example.jpg")
|
||||
private String originalName;
|
||||
|
||||
/**
|
||||
* 大小(字节)
|
||||
*/
|
||||
@@ -53,20 +59,14 @@ public class FileResp extends BaseDetailResp {
|
||||
/**
|
||||
* URL
|
||||
*/
|
||||
@Schema(description = "URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg")
|
||||
@Schema(description = "URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/2025/2/25/6824afe8408da079832dcfb6.jpg")
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 上级目录
|
||||
* 存储路径
|
||||
*/
|
||||
@Schema(description = "上级目录", example = "25")
|
||||
private String parentPath;
|
||||
|
||||
/**
|
||||
* 绝对路径
|
||||
*/
|
||||
@Schema(description = "绝对路径", example = "/2025/2/25")
|
||||
private String absPath;
|
||||
@Schema(description = "上级目录", example = "/2025/2/25")
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 扩展名
|
||||
@@ -89,7 +89,7 @@ public class FileResp extends BaseDetailResp {
|
||||
/**
|
||||
* SHA256 值
|
||||
*/
|
||||
@Schema(description = "SHA256值", example = "722f185c48bed892d6fa12e2b8bf1e5f8200d4a70f522fb62112b6caf13cb74e")
|
||||
@Schema(description = "SHA256 值", example = "722f185c48bed892d6fa12e2b8bf1e5f8200d4a70f522fb62112b6caf13cb74e")
|
||||
private String sha256;
|
||||
|
||||
/**
|
||||
@@ -98,24 +98,30 @@ public class FileResp extends BaseDetailResp {
|
||||
@Schema(description = "元数据", example = "{width:1024,height:1024}")
|
||||
private String metadata;
|
||||
|
||||
/**
|
||||
* 缩略图名称
|
||||
*/
|
||||
@Schema(description = "缩略图名称", example = "example.jpg.min.jpg")
|
||||
private String thumbnailName;
|
||||
|
||||
/**
|
||||
* 缩略图大小(字节)
|
||||
*/
|
||||
@Schema(description = "缩略图大小(字节)", example = "1024")
|
||||
private Long thumbnailSize;
|
||||
|
||||
/**
|
||||
* 缩略图 URL
|
||||
*/
|
||||
@Schema(description = "缩略图 URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg.min.jpg")
|
||||
private String thumbnailUrl;
|
||||
|
||||
/**
|
||||
* 缩略图元数据
|
||||
*/
|
||||
@Schema(description = "缩略图文件元数据", example = "{width:100,height:100}")
|
||||
private String thumbnailMetadata;
|
||||
|
||||
/**
|
||||
* 缩略图 URL
|
||||
*/
|
||||
@Schema(description = "缩略图 URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/2025/2/25/example.jpg.min.jpg")
|
||||
private String thumbnailUrl;
|
||||
|
||||
/**
|
||||
* 存储 ID
|
||||
*/
|
||||
|
@@ -25,7 +25,6 @@ import top.continew.admin.system.model.resp.file.FileResp;
|
||||
import top.continew.admin.system.model.resp.file.FileStatisticsResp;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.data.mp.service.IService;
|
||||
import top.continew.starter.extension.crud.model.resp.IdResp;
|
||||
import top.continew.starter.extension.crud.service.BaseService;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -103,7 +102,7 @@ public interface FileService extends BaseService<FileResp, FileResp, FileQuery,
|
||||
* @param req 请求参数
|
||||
* @return ID
|
||||
*/
|
||||
IdResp<Long> createDir(FileReq req);
|
||||
Long createDir(FileReq req);
|
||||
|
||||
/**
|
||||
* 获取默认文件路径
|
||||
|
@@ -18,6 +18,7 @@ package top.continew.admin.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
@@ -43,12 +44,11 @@ import top.continew.admin.system.service.FileService;
|
||||
import top.continew.admin.system.service.StorageService;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.StrUtils;
|
||||
import top.continew.starter.core.util.URLUtils;
|
||||
import top.continew.starter.core.validation.CheckUtils;
|
||||
import top.continew.starter.extension.crud.model.resp.IdResp;
|
||||
import top.continew.starter.extension.crud.service.BaseServiceImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -75,8 +75,19 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
|
||||
StorageDO storage = storageService.getById(entry.getKey());
|
||||
for (FileDO file : entry.getValue()) {
|
||||
FileInfo fileInfo = file.toFileInfo(storage);
|
||||
fileStorageService.delete(fileInfo);
|
||||
if (!FileTypeEnum.DIR.equals(file.getType())) {
|
||||
FileInfo fileInfo = file.toFileInfo(storage);
|
||||
fileStorageService.delete(fileInfo);
|
||||
} else {
|
||||
// 不允许删除非空文件夹
|
||||
String separator = StringConstants.SLASH.equals(file.getPath())
|
||||
? StringConstants.EMPTY
|
||||
: StringConstants.SLASH;
|
||||
boolean exists = baseMapper.lambdaQuery()
|
||||
.eq(FileDO::getPath, file.getPath() + separator + file.getName())
|
||||
.exists();
|
||||
CheckUtils.throwIf(exists, "文件夹 [{}] 不为空,请先删除文件夹下的内容", file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +128,8 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
log.info("上传结束");
|
||||
}
|
||||
});
|
||||
// 创建父级目录
|
||||
this.createDir(path, storage);
|
||||
// 上传
|
||||
return uploadPretreatment.upload();
|
||||
}
|
||||
@@ -152,40 +165,30 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdResp<Long> createDir(FileReq req) {
|
||||
public Long createDir(FileReq req) {
|
||||
StorageDO storage = storageService.getDefaultStorage();
|
||||
FileDO fileDo = new FileDO();
|
||||
fileDo.setName(req.getName());
|
||||
fileDo.setSize(0L);
|
||||
fileDo.setUrl(storage.getDomain() + req.getParentPath() + req.getName());
|
||||
String absPath = req.getParentPath();
|
||||
String tempAbsPath = absPath.length() > 1 ? StrUtil.removeSuffix(absPath, StringConstants.SLASH) : absPath;
|
||||
String[] pathArr = tempAbsPath.split(StringConstants.SLASH);
|
||||
if (pathArr.length > 1) {
|
||||
fileDo.setParentPath(pathArr[pathArr.length - 1]);
|
||||
} else {
|
||||
fileDo.setParentPath(StringConstants.SLASH);
|
||||
}
|
||||
fileDo.setAbsPath(tempAbsPath);
|
||||
fileDo.setExtension("dir");
|
||||
fileDo.setContentType("");
|
||||
fileDo.setType(FileTypeEnum.DIR);
|
||||
fileDo.setSha256("");
|
||||
fileDo.setStorageId(storage.getId());
|
||||
baseMapper.insert(fileDo);
|
||||
return new IdResp<>(fileDo.getId());
|
||||
FileDO file = new FileDO();
|
||||
file.setName(req.getOriginalName());
|
||||
file.setOriginalName(req.getOriginalName());
|
||||
file.setSize(0L);
|
||||
file.setPath(req.getPath());
|
||||
file.setType(FileTypeEnum.DIR);
|
||||
file.setStorageId(storage.getId());
|
||||
baseMapper.insert(file);
|
||||
return file.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fill(Object obj) {
|
||||
super.fill(obj);
|
||||
if (obj instanceof FileResp fileResp && !URLUtils.isHttpUrl(fileResp.getUrl())) {
|
||||
if (obj instanceof FileResp fileResp) {
|
||||
StorageDO storage = storageService.getById(fileResp.getStorageId());
|
||||
String prefix = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH);
|
||||
String url = URLUtil.normalize(prefix + fileResp.getUrl());
|
||||
String prefix = StrUtil.blankToDefault(storage.getDomain(), storage.getEndpoint());
|
||||
String path = fileResp.getPath();
|
||||
String url = URLUtil.normalize(prefix + path + StringConstants.SLASH + fileResp.getName(), false, true);
|
||||
fileResp.setUrl(url);
|
||||
String thumbnailUrl = StrUtils.blankToDefault(fileResp.getThumbnailUrl(), url, thUrl -> URLUtil
|
||||
.normalize(prefix + thUrl));
|
||||
String thumbnailUrl = StrUtils.blankToDefault(fileResp.getThumbnailName(), url, thName -> URLUtil
|
||||
.normalize(prefix + path + StringConstants.SLASH + thName, false, true));
|
||||
fileResp.setThumbnailUrl(thumbnailUrl);
|
||||
fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode()));
|
||||
}
|
||||
@@ -214,4 +217,49 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
}
|
||||
return StrUtil.appendIfMissing(StrUtil.removePrefix(path, StringConstants.SLASH), StringConstants.SLASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(支持多级)
|
||||
*
|
||||
* <p>
|
||||
* user/avatar/ => user(path:/)、avatar(path:/user)
|
||||
* </p>
|
||||
*
|
||||
* @param dirPath 路径
|
||||
* @param storage 存储配置
|
||||
*/
|
||||
private void createDir(String dirPath, StorageDO storage) {
|
||||
if (StrUtil.isBlank(dirPath) || StringConstants.SLASH.equals(dirPath)) {
|
||||
return;
|
||||
}
|
||||
// user/avatar/ => user:/、avatar:path:/user
|
||||
String[] paths = StrUtil.split(dirPath, StringConstants.SLASH, false, true).toArray(String[]::new);
|
||||
Map<String, String> pathMap = MapUtil.newHashMap(paths.length, true);
|
||||
for (int i = 0; i < paths.length; i++) {
|
||||
String key = paths[i];
|
||||
String path = (i == 0)
|
||||
? StringConstants.SLASH
|
||||
: StringConstants.SLASH + String.join(StringConstants.SLASH, Arrays.copyOfRange(paths, 0, i));
|
||||
pathMap.put(key, path);
|
||||
}
|
||||
// 创建文件夹
|
||||
for (Map.Entry<String, String> entry : pathMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String path = entry.getValue();
|
||||
if (!baseMapper.lambdaQuery()
|
||||
.eq(FileDO::getPath, path)
|
||||
.eq(FileDO::getName, key)
|
||||
.eq(FileDO::getType, FileTypeEnum.DIR)
|
||||
.exists()) {
|
||||
FileDO file = new FileDO();
|
||||
file.setName(key);
|
||||
file.setOriginalName(key);
|
||||
file.setSize(0L);
|
||||
file.setPath(path);
|
||||
file.setType(FileTypeEnum.DIR);
|
||||
file.setStorageId(storage.getId());
|
||||
baseMapper.insert(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -179,7 +179,7 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
|
||||
return this.getDefaultStorage();
|
||||
}
|
||||
StorageDO storage = baseMapper.lambdaQuery().eq(StorageDO::getCode, code).one();
|
||||
CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", code);
|
||||
CheckUtils.throwIfNotExists(storage, "存储", "code", code);
|
||||
return storage;
|
||||
}
|
||||
|
||||
@@ -193,8 +193,9 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
|
||||
config.setStoragePath(storage.getBucketName());
|
||||
fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections
|
||||
.singletonList(config)));
|
||||
SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage
|
||||
.getBucketName()));
|
||||
// 注册资源映射
|
||||
SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(StrUtil.removeSuffix(storage
|
||||
.getDomain(), StringConstants.SLASH)).getPath(), storage.getBucketName()));
|
||||
}
|
||||
case OSS -> {
|
||||
FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config();
|
||||
|
Reference in New Issue
Block a user