From 37027c774b2f65b7feec63657b2befda6a3896ae Mon Sep 17 00:00:00 2001 From: Charles7c Date: Thu, 15 May 2025 23:25:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor(system/file):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=AE=8C=E5=96=84=E6=96=87=E4=BB=B6=E5=A4=B9?= =?UTF-8?q?=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/config/file/FileRecorderImpl.java | 49 ++++++-- .../config/file/FileStorageConfigLoader.java | 11 +- .../admin/system/enums/StorageTypeEnum.java | 15 ++- .../admin/system/model/entity/FileDO.java | 69 +++++------ .../admin/system/model/query/FileQuery.java | 11 +- .../admin/system/model/req/FileReq.java | 10 +- .../system/model/resp/file/FileResp.java | 42 ++++--- .../admin/system/service/FileService.java | 3 +- .../system/service/impl/FileServiceImpl.java | 108 +++++++++++++----- .../service/impl/StorageServiceImpl.java | 7 +- .../controller/system/FileController.java | 5 +- .../db/changelog/mysql/main_data.sql | 4 +- .../db/changelog/mysql/main_table.sql | 20 ++-- .../db/changelog/postgresql/main_data.sql | 4 +- .../db/changelog/postgresql/main_table.sql | 21 ++-- 15 files changed, 222 insertions(+), 157 deletions(-) diff --git a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java index 4dc429be..ea0ac406 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java @@ -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 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 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 list = queryWrapper.list(); + if (CollUtil.isEmpty(list)) { + return null; + } + if (list.size() == 1) { + return list.get(0); + } + // 结合存储配置进行匹配 + List storageList = storageMapper.selectByIds(list.stream().map(FileDO::getStorageId).toList()); + Map 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); } } \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java index 8e3e26b0..fecf2503 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java @@ -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 storageList = storageService.list(query, null); - if (CollUtil.isEmpty(storageList)) { + List 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); } } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java b/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java index 54981411..beaf0bb2 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java @@ -43,14 +43,15 @@ public enum StorageTypeEnum implements BaseEnum { @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 { @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 { * @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)); + } } } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java b/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java index 6578b8d9..4ceaef57 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java @@ -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)); } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/query/FileQuery.java b/continew-module-system/src/main/java/top/continew/admin/system/model/query/FileQuery.java index 6018229e..39949c95 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/query/FileQuery.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/query/FileQuery.java @@ -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; /** * 类型 diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/req/FileReq.java b/continew-module-system/src/main/java/top/continew/admin/system/model/req/FileReq.java index a886c3bc..118c2d71 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/req/FileReq.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/req/FileReq.java @@ -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; } \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/resp/file/FileResp.java b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/file/FileResp.java index dea51907..502a431a 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/resp/file/FileResp.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/file/FileResp.java @@ -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 */ diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java b/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java index 192141b1..a90ba53a 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java @@ -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 createDir(FileReq req); + Long createDir(FileReq req); /** * 获取默认文件路径 diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java index 4b47e86e..23bf13c1 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java @@ -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> 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 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 + * user/avatar/ => user(path:/)、avatar(path:/user) + *

+ * + * @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 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 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); + } + } + } } \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java index 88adf51f..70fcde18 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java @@ -179,7 +179,7 @@ public class StorageServiceImpl extends BaseServiceImpl { FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config(); diff --git a/continew-webapi/src/main/java/top/continew/admin/controller/system/FileController.java b/continew-webapi/src/main/java/top/continew/admin/controller/system/FileController.java index 0611b2d0..6193cf5b 100644 --- a/continew-webapi/src/main/java/top/continew/admin/controller/system/FileController.java +++ b/continew-webapi/src/main/java/top/continew/admin/controller/system/FileController.java @@ -61,9 +61,8 @@ public class FileController extends BaseController createDir(@RequestBody FileReq req) { - return baseService.createDir(req); + return new IdResp<>(baseService.createDir(req)); } } \ No newline at end of file diff --git a/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql b/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql index fc4296d3..c950a8bd 100644 --- a/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql +++ b/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql @@ -273,8 +273,8 @@ INSERT INTO `sys_role_dept` (`role_id`, `dept_id`) VALUES (547888897925840927, 5 INSERT INTO `sys_storage` (`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`) VALUES -(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file', '本地存储', b'1', 1, 1, 1, NOW()), -(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file', '本地存储', b'0', 2, 2, 1, NOW()); +(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file/', '本地存储', b'1', 1, 1, 1, NOW()), +(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file/', '本地存储', b'0', 2, 2, 1, NOW()); -- 初始化客户端数据 INSERT INTO `sys_client` diff --git a/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql b/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql index e198df05..4911cb74 100644 --- a/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql +++ b/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql @@ -260,8 +260,8 @@ CREATE TABLE IF NOT EXISTS `sys_storage` ( `access_key` varchar(255) DEFAULT NULL COMMENT 'Access Key', `secret_key` varchar(255) DEFAULT NULL COMMENT 'Secret Key', `endpoint` varchar(255) DEFAULT NULL COMMENT 'Endpoint', - `bucket_name` varchar(255) DEFAULT NULL COMMENT 'Bucket', - `domain` varchar(255) NOT NULL DEFAULT '' COMMENT '域名', + `bucket_name` varchar(255) NOT NULL COMMENT 'Bucket', + `domain` varchar(255) DEFAULT NULL COMMENT '域名', `description` varchar(200) DEFAULT NULL COMMENT '描述', `is_default` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为默认存储', `sort` int NOT NULL DEFAULT 999 COMMENT '排序', @@ -279,17 +279,16 @@ CREATE TABLE IF NOT EXISTS `sys_storage` ( CREATE TABLE IF NOT EXISTS `sys_file` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `name` varchar(255) NOT NULL COMMENT '名称', + `original_name` varchar(255) NOT NULL COMMENT '原始名称', `size` bigint(20) NOT NULL COMMENT '大小(字节)', - `url` varchar(512) NOT NULL COMMENT 'URL', - `parent_path` varchar(512) DEFAULT '/' COMMENT '上级目录', - `abs_path` varchar(1024) NOT NULL COMMENT '绝对路径', - `extension` varchar(100) DEFAULT NULL COMMENT '扩展名', - `content_type` varchar(255) NOT NULL COMMENT '内容类型', + `path` varchar(512) NOT NULL COMMENT '存储路径', + `extension` varchar(32) DEFAULT NULL COMMENT '扩展名', + `content_type` varchar(255) DEFAULT NULL COMMENT '内容类型', `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(0: 目录;1:其他;2:图片;3:文档;4:视频;5:音频)', - `sha256` varchar(256) NOT NULL COMMENT 'SHA256值', + `sha256` varchar(256) DEFAULT NULL COMMENT 'SHA256值', `metadata` text DEFAULT NULL COMMENT '元数据', + `thumbnail_name` varchar(255) DEFAULT NULL COMMENT '缩略图名称', `thumbnail_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小(字节)', - `thumbnail_url` varchar(512) DEFAULT NULL COMMENT '缩略图URL', `thumbnail_metadata` text DEFAULT NULL COMMENT '缩略图元数据', `storage_id` bigint(20) NOT NULL COMMENT '存储ID', `create_user` bigint(20) NOT NULL COMMENT '创建人', @@ -297,9 +296,8 @@ CREATE TABLE IF NOT EXISTS `sys_file` ( `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', `update_time` datetime DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`), - INDEX `idx_url`(`url`), - INDEX `idx_sha256`(`sha256`), INDEX `idx_type`(`type`), + INDEX `idx_sha256`(`sha256`), INDEX `idx_storage_id`(`storage_id`), INDEX `idx_create_user`(`create_user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; diff --git a/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql b/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql index bded0c0e..53f9870e 100644 --- a/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql +++ b/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql @@ -273,8 +273,8 @@ INSERT INTO "sys_role_dept" ("role_id", "dept_id") VALUES (547888897925840927, 5 INSERT INTO "sys_storage" ("id", "name", "code", "type", "access_key", "secret_key", "endpoint", "bucket_name", "domain", "description", "is_default", "sort", "status", "create_user", "create_time") VALUES -(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file', '本地存储', true, 1, 1, 1, NOW()), -(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file', '本地存储', false, 2, 2, 1, NOW()); +(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file/', '本地存储', true, 1, 1, 1, NOW()), +(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file/', '本地存储', false, 2, 2, 1, NOW()); -- 初始化客户端数据 INSERT INTO "sys_client" diff --git a/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql b/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql index 42218f01..bd640481 100644 --- a/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql +++ b/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql @@ -428,8 +428,8 @@ CREATE TABLE IF NOT EXISTS "sys_storage" ( "access_key" varchar(255) DEFAULT NULL, "secret_key" varchar(255) DEFAULT NULL, "endpoint" varchar(255) DEFAULT NULL, - "bucket_name" varchar(255) DEFAULT NULL, - "domain" varchar(255) NOT NULL DEFAULT '', + "bucket_name" varchar(255) NOT NULL, + "domain" varchar(255) DEFAULT NULL, "description" varchar(200) DEFAULT NULL, "is_default" bool NOT NULL DEFAULT false, "sort" int4 NOT NULL DEFAULT 999, @@ -465,17 +465,16 @@ COMMENT ON TABLE "sys_storage" IS '存储表'; CREATE TABLE IF NOT EXISTS "sys_file" ( "id" int8 NOT NULL, "name" varchar(255) NOT NULL, + "original_name" varchar(255) NOT NULL, "size" int8 NOT NULL, - "url" varchar(512) NOT NULL, - "parent_path" varchar(512) NOT NULL DEFAULT '/', - "abs_path" varchar(512) NOT NULL, + "path" varchar(512) NOT NULL, "extension" varchar(100) DEFAULT NULL, - "content_type" varchar(255) NOT NULL, + "content_type" varchar(255) DEFAULT NULL, "type" int2 NOT NULL DEFAULT 1, "sha256" varchar(256) NOT NULL, "metadata" text DEFAULT NULL, + "thumbnail_name" varchar(255) DEFAULT NULL, "thumbnail_size" int8 DEFAULT NULL, - "thumbnail_url" varchar(512) DEFAULT NULL, "thumbnail_metadata" text DEFAULT NULL, "storage_id" int8 NOT NULL, "create_user" int8 NOT NULL, @@ -484,24 +483,22 @@ CREATE TABLE IF NOT EXISTS "sys_file" ( "update_time" timestamp DEFAULT NULL, PRIMARY KEY ("id") ); -CREATE INDEX "idx_file_url" ON "sys_file" ("url"); CREATE INDEX "idx_file_type" ON "sys_file" ("type"); CREATE INDEX "idx_file_sha256" ON "sys_file" ("sha256"); CREATE INDEX "idx_file_storage_id" ON "sys_file" ("storage_id"); CREATE INDEX "idx_file_create_user" ON "sys_file" ("create_user"); COMMENT ON COLUMN "sys_file"."id" IS 'ID'; COMMENT ON COLUMN "sys_file"."name" IS '名称'; +COMMENT ON COLUMN "sys_file"."original_name" IS '原始名称'; COMMENT ON COLUMN "sys_file"."size" IS '大小(字节)'; -COMMENT ON COLUMN "sys_file"."url" IS 'URL'; -COMMENT ON COLUMN "sys_file"."parent_path" IS '上级目录'; -COMMENT ON COLUMN "sys_file"."abs_path" IS '绝对路径'; +COMMENT ON COLUMN "sys_file"."path" IS '存储路径'; COMMENT ON COLUMN "sys_file"."extension" IS '扩展名'; COMMENT ON COLUMN "sys_file"."content_type" IS '内容类型'; COMMENT ON COLUMN "sys_file"."type" IS '类型(0: 目录;1:其他;2:图片;3:文档;4:视频;5:音频)'; COMMENT ON COLUMN "sys_file"."sha256" IS 'SHA256值'; COMMENT ON COLUMN "sys_file"."metadata" IS '元数据'; +COMMENT ON COLUMN "sys_file"."thumbnail_name" IS '缩略图名称'; COMMENT ON COLUMN "sys_file"."thumbnail_size" IS '缩略图大小(字节)'; -COMMENT ON COLUMN "sys_file"."thumbnail_url" IS '缩略图URL'; COMMENT ON COLUMN "sys_file"."thumbnail_metadata" IS '缩略图元数据'; COMMENT ON COLUMN "sys_file"."storage_id" IS '存储ID'; COMMENT ON COLUMN "sys_file"."create_user" IS '创建人';