mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +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