From ad349bc3c977feeac341bbaac186994528da179e Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 1 May 2021 17:55:53 +0800 Subject: [PATCH 01/13] =?UTF-8?q?feat=20=E6=96=B0=E5=A2=9E=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=92=8C=E7=9B=AE=E5=BD=95=E7=9A=84=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/file/CopyController.java | 39 ++++++++ .../saltedfishcloud/helper/PathBuilder.java | 92 ++++++++++++++---- .../saltedfishcloud/po/file/FileInfo.java | 16 +++- .../service/file/FileRecordService.java | 93 ++++++++++++++++++- .../service/file/FileService.java | 26 +++++- .../service/file/StoreService.java | 71 +++++++++++++- .../service/file/path/PathHandler.java | 5 +- .../service/file/path/RawPathHandler.java | 20 +++- .../service/node/NodeService.java | 13 ++- .../helper/PathBuilderTest.java | 31 +++++++ .../service/file/FileRecordServiceTest.java | 27 ++++++ .../service/file/FileServiceTest.java | 12 +++ 12 files changed, 410 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java create mode 100644 src/test/java/com/xiaotao/saltedfishcloud/helper/PathBuilderTest.java create mode 100644 src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java new file mode 100644 index 00000000..80abb3bb --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java @@ -0,0 +1,39 @@ +package com.xiaotao.saltedfishcloud.controller.file; + +import com.xiaotao.saltedfishcloud.po.JsonResult; +import com.xiaotao.saltedfishcloud.service.file.FileService; +import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.utils.URLUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@RestController +@RequestMapping("/api") +public class CopyController { + + @Resource + private FileService fileService; + + /** + * 复制文件或目录到指定目录下 + * @param uid 用户ID + * @param name 文件名 + * @param target 目标目录 + * @param overwrite 覆盖同名文件 + * @TODO 使用数组传入需要复制的文件名以替代并发请求接口实现多文件粘贴的方式 + */ + @PostMapping("/copy/{uid}/**") + public JsonResult copy(@PathVariable("uid") int uid, + @RequestParam("name") String name, + @RequestParam("target") String target, + @RequestParam(value = "overwrite", defaultValue = "true") Boolean overwrite, + HttpServletRequest request) throws IOException { + UIDValidator.validate(uid); + String source = URLUtils.getRequestFilePath("/api/copy/" + uid, request); + fileService.copy(uid, source, target, uid, name, overwrite); + return JsonResult.getInstance(); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/helper/PathBuilder.java b/src/main/java/com/xiaotao/saltedfishcloud/helper/PathBuilder.java index e7333c34..9a2fda7e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/helper/PathBuilder.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/helper/PathBuilder.java @@ -2,10 +2,11 @@ package com.xiaotao.saltedfishcloud.helper; import com.xiaotao.saltedfishcloud.utils.OSInfo; import lombok.Data; +import org.apache.tomcat.jni.OS; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; -import java.util.concurrent.atomic.AtomicBoolean; /** * 用于构建URL @@ -13,18 +14,86 @@ import java.util.concurrent.atomic.AtomicBoolean; @Data public class PathBuilder { private LinkedList path; - private boolean prefix = true; + private boolean forcePrefix = false; public PathBuilder() { path = new LinkedList<>(); } + /** + * 清空路径信息并设置新的路径信息
等同于依次执行 {@link #clear()} 和 {@link #append(String)} + * @param path 路径字符串 + * @return 自己 + */ + public PathBuilder update(String path) { + this.path.clear(); + append(path); + return this; + } + + /** + * 在已有路径信息中获取从起始位置开始的指定节点长度的路径字符串 + * @see #range(int, int) + * @param length 若参数{@code length}为负数,则表示忽略末尾的长度 + * @return 咸鱼云网盘标准格式化路径 + */ + public String range(int length) { + return range(length, 0); + } + + /** + * 从已有路径信息中获取指定节点长度的路径字符串,若参数{@code length}为负数,则表示忽略末尾的长度 + * @param length + * 节点长度,若为负数,则表示丢弃的末尾节点数。
+ * 如节点长度为5,参数length为-2时,则返回前3个节点的咸鱼云网盘标准格式化路径
+ * + * @param index + * 起始索引,若为负数,则表示从末尾开始往前计数 + * + * @throws IndexOutOfBoundsException + * 当传入的length大于从指定的index到末尾的长度时抛出此异常 + * + * + * @return 咸鱼云网盘标准格式化路径 + */ + public String range(int length, int index) { + int absLength = Math.abs(length); + int finalIndex = index < 0 ? this.path.size() + index : index; + int finalLength = length < 0 ? this.path.size() + length : length; + if ( absLength > path.size() - finalIndex ) { + throw new IndexOutOfBoundsException(); + } + + + StringBuilder sb = new StringBuilder(); + boolean first = true; + int cnt = 0; + + Iterator iterator = this.path.listIterator(finalIndex); + while (iterator.hasNext()) { + if (++cnt > finalLength) break; + if (forcePrefix || + ( !first || !OSInfo.isWindows()) + )sb.append("/"); + + sb.append(iterator.next()); + + first = false; + } + + if (sb.length() == 0) { + return "/"; + } else { + return sb.toString(); + } + } + /** * 获取路径位置集合 * @return 目录路径集合 */ - public Collection getPath() { + public LinkedList getPath() { return path; } @@ -60,7 +129,7 @@ public class PathBuilder { */ public static String formatPath(String path, boolean prefix) { PathBuilder pb = new PathBuilder(); - pb.setPrefix(prefix); + pb.setForcePrefix(prefix); return pb.append(path).toString(); } @@ -88,19 +157,6 @@ public class PathBuilder { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - AtomicBoolean first = new AtomicBoolean(true); - path.forEach(node -> { - if (!OSInfo.isWindows() || !first.get()) { - sb.append("/"); - } - sb.append(node); - first.set(false); - }); - if (sb.length() == 0) { - return "/"; - } else { - return sb.toString(); - } + return range(path.size()); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/po/file/FileInfo.java b/src/main/java/com/xiaotao/saltedfishcloud/po/file/FileInfo.java index aa687d8e..d903ba19 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/po/file/FileInfo.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/po/file/FileInfo.java @@ -32,8 +32,20 @@ public class FileInfo extends BasicFileInfo{ * @return 文件信息对象 */ public static FileInfo getLocal(String path) { + return getLocal(path, true); + } + + /** + * 获取本地文件的文件信息 + * @param path 本地文件路径 + * @param computeMd5 是否计算MD5 + * @return 文件信息对象 + */ + public static FileInfo getLocal(String path, boolean computeMd5) { FileInfo info = new FileInfo(new File(path)); - info.updateMd5(); + if (computeMd5) { + info.updateMd5(); + } return info; } @@ -82,7 +94,7 @@ public class FileInfo extends BasicFileInfo{ if (isDir()) return; if (md5 == null) { try { - InputStream is = null; + InputStream is; if (originFile != null) is = new FileInputStream(originFile); else is = originFile2.getInputStream(); String res = DigestUtils.md5DigestAsHex(is); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java index 8f883d81..f3f5b188 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java @@ -3,19 +3,29 @@ package com.xiaotao.saltedfishcloud.service.file; import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.dao.FileDao; import com.xiaotao.saltedfishcloud.dao.NodeDao; +import com.xiaotao.saltedfishcloud.exception.HasResultException; +import com.xiaotao.saltedfishcloud.helper.PathBuilder; import com.xiaotao.saltedfishcloud.po.NodeInfo; +import com.xiaotao.saltedfishcloud.po.PathInfo; import com.xiaotao.saltedfishcloud.po.file.DirCollection; import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.node.NodeService; import com.xiaotao.saltedfishcloud.utils.FileUtils; import com.xiaotao.saltedfishcloud.utils.PathUtils; import com.xiaotao.saltedfishcloud.utils.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -38,6 +48,74 @@ public class FileRecordService { @Resource private FileService fileService; + /** + * 操作数据库复制网盘文件或目录到指定目录下 + * @param uid 用户ID + * @param source 要复制的文件或目录所在目录 + * @param target 复制到的目标目录 + * @param targetId 复制到的目标目录所属用户ID + * @param name 要复制的文件或目录名 + * @param overwrite 是否覆盖已存在的文件 + */ + @Transactional(rollbackFor = Exception.class) + public void copy(int uid, String source, String target, int targetId, String name, boolean overwrite) { + class PathIdPair { + public String path; + public String nid; + public PathIdPair(String path, String nid) {this.path = path;this.nid = nid;} + } + PathBuilder pathBuilder = new PathBuilder(); + pathBuilder.setForcePrefix(true); + int prefixLength = source.length() + 1 + name.length(); + + FileInfo sourceInfo = fileDao.getFileInfo(uid, name, nodeService.getNodeIdByPath(uid, source).getId()); + if (sourceInfo == null) { + throw new HasResultException(404, "原文件或目录不存在"); + } + // 文件直接添加单条记录 + if (sourceInfo.isFile()) { + addRecord(targetId, sourceInfo.getName(), sourceInfo.getSize(), sourceInfo.getMd5(), target); + return ; + } + + // 需要遍历的目录列表 + LinkedList t = new LinkedList<>(); + String root = source + "/" + name; + t.add(new PathIdPair(root, nodeService.getNodeIdByPath(uid, root).getId())); + + do { + // 更新目录列表 + PathIdPair pairInfo = t.getLast(); + t.removeLast(); + String newDirPath = target + "/" + name + "/" + pairInfo.path.substring(prefixLength); + pathBuilder.update(newDirPath); + PathIdPair newPathInfo; + try { + newPathInfo = new PathIdPair(newDirPath, mkdir(targetId, pathBuilder.getPath().getLast(), pathBuilder.range(-1))); + } catch (DuplicateKeyException e) { + newPathInfo = new PathIdPair(newDirPath, nodeService.getNodeIdByPath(targetId, newDirPath).getId()); + } + + + // 遍历一个目录并追加该目录下的子目录 + List t2 = fileDao.getFileListByNodeId(uid, pairInfo.nid); + for (FileInfo info : t2) { + if (info.isDir()) { + t.add(new PathIdPair(pairInfo.path + "/" + info.getName(), info.getMd5())); + } else { + if (fileDao.addRecord(targetId, info.getName(), info.getSize(), info.getMd5(), newPathInfo.nid) < 1 && overwrite) { + fileDao.updateRecord(targetId, info.getName(), newPathInfo.nid, info.getSize(), info.getMd5()); + log.debug("overwrite " + info.getName() + " at " + newPathInfo.nid); + } else if (!overwrite){ + log.error("addFile failed: " + info.getName() + " at " + newPathInfo.nid); + } else { + log.debug("addFile: " + info.getName() + " at " + newPathInfo.nid); + } + } + } + } while (t.size() > 0); + } + /** * 操作数据库移动网盘文件或目录到指定目录下 * @param uid 用户ID @@ -119,11 +197,22 @@ public class FileRecordService { * @param uid 用户ID * @param name 文件夹名称 * @param path 所在路径 + * @throws DuplicateKeyException + * 当目标目录已存在时抛出 */ - public void mkdir(int uid, String name, String path) { + public String mkdir(int uid, String name, String path) { + log.debug("mkdir " + name + " at " + path); NodeInfo node = nodeService.getNodeIdByPath(uid, path); + if (node == null) { + throw new HasResultException("目标目录 \"" + path + "\" 不存在,目录 \"" + name + "\"创建失败"); + } String nodeId = nodeService.addNode(uid, name, node.getId()); + if (fileDao.addRecord(uid, name, -1L, nodeId, node.getId()) < 1) { + throw new DuplicateKeyException("目录已存在"); + } fileDao.addRecord(uid, name, -1L, nodeId, node.getId()); + log.debug("mkdir finish: " + nodeId); + return nodeId; } /** diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index 2a00e479..5cfc82b4 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.dao.FileDao; import com.xiaotao.saltedfishcloud.exception.HasResultException; +import com.xiaotao.saltedfishcloud.helper.PathBuilder; import com.xiaotao.saltedfishcloud.po.NodeInfo; import com.xiaotao.saltedfishcloud.po.file.BasicFileInfo; import com.xiaotao.saltedfishcloud.po.file.FileDCInfo; @@ -13,6 +14,7 @@ import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.node.NodeService; import com.xiaotao.saltedfishcloud.utils.FileUtils; import com.xiaotao.saltedfishcloud.utils.JwtUtils; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.ResponseEntity; @@ -33,6 +35,7 @@ import java.util.List; import java.util.Objects; @Service("fileService") +@Slf4j public class FileService { @javax.annotation.Resource FileDao fileDao; @@ -43,13 +46,30 @@ public class FileService { @javax.annotation.Resource NodeService nodeService; + /** + * + * @param uid 原资源的用户ID + * @param source 要操作的文件所在的网盘目录 + * @param target 复制到的目的地目录 + * @param targetUid 目的地用户ID + * @param name 文件或目录名 + * @param overwrite 是否覆盖 + */ + public void copy(int uid, String source, String target, int targetUid, String name, Boolean overwrite) throws IOException { + if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target))) { + throw new IllegalArgumentException("无法原地复制"); + } + fileRecordService.copy(uid, source, target, targetUid, name, overwrite); + log.debug("Finish DB data copy"); + storeService.copy(uid, source, target, targetUid, name, overwrite); + } + /** * 移动网盘中的文件或目录到指定目录下 * @param uid 用户ID * @param source 要被移动的网盘文件或目录所在目录 * @param target 要移动到的目标目录 * @param name 文件名 - * @throws IOException 文件移动出错 */ public void move(int uid, String source, String target, String name) { try { @@ -70,7 +90,7 @@ public class FileService { * @param path 网盘路径 * @return 一个List数组,数组下标0为目录,1为文件,或null */ - public Collection[] getUserFileList(int uid, String path) { + public List[] getUserFileList(int uid, String path) { NodeInfo nodeId = nodeService.getNodeIdByPath(uid, path); return getUserFileListByNodeId(uid, nodeId.getId()); } @@ -82,7 +102,7 @@ public class FileService { * @return 一个List数组,数组下标0为目录,1为文件,或null */ @SuppressWarnings("unchecked") - public Collection[] getUserFileListByNodeId(int uid, String nodeId) { + public List[] getUserFileListByNodeId(int uid, String nodeId) { List fileList = fileDao.getFileListByNodeId(uid, nodeId); List dirs = new LinkedList<>(), files = new LinkedList<>(); fileList.forEach(file -> { diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java index b8158faa..1cec69bd 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java @@ -4,9 +4,11 @@ import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.config.StoreType; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.file.BasicFileInfo; +import com.xiaotao.saltedfishcloud.po.file.DirCollection; import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.file.path.PathHandler; import com.xiaotao.saltedfishcloud.service.file.path.RawPathHandler; +import com.xiaotao.saltedfishcloud.utils.FileUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -27,6 +29,67 @@ import static com.xiaotao.saltedfishcloud.utils.FileUtils.writeFile; @Slf4j public class StoreService { + /** + * 在本地存储中复制用户网盘文件 + * @param uid 用户ID + * @param source 所在网盘路径 + * @param target 目的地网盘路径 + * @param name 文件名 + * @param overwrite 是否覆盖,若非true,则跳过该文件 + */ + public void copy(int uid, String source, String target, int targetId, String name, Boolean overwrite) throws IOException { + if (DiskConfig.STORE_TYPE == StoreType.UNIQUE ) { + return ; + } + BasicFileInfo fileInfo = new BasicFileInfo(name, null); + String localSource = DiskConfig.getPathHandler().getStorePath(uid, source, fileInfo); + String localTarget = DiskConfig.getPathHandler().getStorePath(targetId, target, null); + + fileInfo = FileInfo.getLocal(localSource); + Path sourcePath = Paths.get(localSource); + + // 判断源与目标是否存在 + if (!Files.exists(sourcePath)) { + throw new IOException("资源 \"" + source + "/" + name + "\" 不存在"); + } + if (!Files.exists(Paths.get(localTarget))) { + throw new IOException("目标目录 " + target + " 不存在"); + } + + CopyOption[] option; + if (overwrite) { + option = new CopyOption[]{StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING}; + } else { + option = new CopyOption[]{StandardCopyOption.COPY_ATTRIBUTES}; + } + + if (fileInfo.isFile()) { + Files.copy(sourcePath, Paths.get(localTarget + "/" + name), option); + } + + if (fileInfo.isDir()) { + DirCollection dirCollection = FileUtils.scanDir(localSource); + Path targetDir = Paths.get(localTarget + "/" + name); + if (!Files.exists(targetDir)) { + Files.createDirectory(targetDir); + } + // 先创建文件夹 + for(File dir: dirCollection.getDirList()) { + String sour = dir.getPath().substring(localSource.length()); + String dest = targetDir + "/" + sour; + try { Files.createDirectory(Paths.get(dest)); } catch (FileAlreadyExistsException ignored) {} + } + + // 复制文件 + for(File file: dirCollection.getFileList()) { + String sour = file.getPath().substring(localSource.length()); + String dest = localTarget + "/" + name + sour; + try { Files.copy(Paths.get(file.getPath()), Paths.get(dest), option); } + catch (FileAlreadyExistsException ignored) {} + } + } + } + /** * 向用户网盘目录中保存一个文件 * @param uid 用户ID 0表示公共 @@ -41,7 +104,13 @@ public class StoreService { return writeFile(input, new File(target)); } - + /** + * 在本地存储中移动用户网盘文件 + * @param uid 用户ID + * @param source 所在网盘路径 + * @param target 目的地网盘路径 + * @param name 文件名 + */ public void move(int uid, String source, String target, String name) throws IOException { if (DiskConfig.STORE_TYPE == StoreType.UNIQUE) { return; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/PathHandler.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/PathHandler.java index 10faf8ba..169cf557 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/PathHandler.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/PathHandler.java @@ -1,5 +1,6 @@ package com.xiaotao.saltedfishcloud.service.file.path; +import com.sun.istack.Nullable; import com.xiaotao.saltedfishcloud.po.file.BasicFileInfo; public interface PathHandler { @@ -7,8 +8,8 @@ public interface PathHandler { * 获取文件在本地文件系统中的完整存储路径 * @param uid 用户ID 0表示公共 * @param targetDir 请求的目标目录(是相对用户网盘根目录的目录) - * @param fileInfo 目标文件信息 + * @param fileInfo 目标文件信息,若为null,则表示目标目录本身的本地存储路径 * @return 路径 */ - String getStorePath(int uid, String targetDir, BasicFileInfo fileInfo); + String getStorePath(int uid, String targetDir,@Nullable BasicFileInfo fileInfo); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/RawPathHandler.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/RawPathHandler.java index c60ab742..1f98dc1c 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/RawPathHandler.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/path/RawPathHandler.java @@ -1,5 +1,6 @@ package com.xiaotao.saltedfishcloud.service.file.path; +import com.sun.istack.Nullable; import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.po.file.BasicFileInfo; import com.xiaotao.saltedfishcloud.helper.PathBuilder; @@ -10,12 +11,23 @@ import org.springframework.stereotype.Component; */ @Component public class RawPathHandler implements PathHandler{ + /** + * + * @param uid 用户ID 0表示公共 + * @param targetDir 请求的目标目录(是相对用户网盘根目录的目录) + * @param fileInfo 目标文件信息 + * @return 目标文件在本地的存储路径 + */ @Override - public String getStorePath(int uid, String targetDir, BasicFileInfo fileInfo) { + public String getStorePath(int uid, String targetDir,@Nullable BasicFileInfo fileInfo) { PathBuilder pathBuilder = new PathBuilder(); - pathBuilder.append(DiskConfig.getRawFileStoreRootPath(uid)) - .append(targetDir) - .append(fileInfo.getName()); + if (fileInfo != null) { + pathBuilder.append(DiskConfig.getRawFileStoreRootPath(uid)) + .append(targetDir) + .append(fileInfo.getName()); + } else { + pathBuilder.append(DiskConfig.getRawFileStoreRootPath(uid)).append(targetDir); + } return pathBuilder.toString(); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java index fc49e9a9..881c71bb 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java @@ -5,6 +5,7 @@ import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.NodeInfo; import com.xiaotao.saltedfishcloud.helper.PathBuilder; import com.xiaotao.saltedfishcloud.utils.SecureUtils; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +16,7 @@ import java.util.LinkedList; import java.util.List; @Service +@Slf4j public class NodeService { @Resource NodeDao nodeDao; @@ -53,6 +55,13 @@ public class NodeService { info.setId("root"); link.add(info); } + if (log.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(); + for (NodeInfo nodeInfo : link) { + sb.append("/").append(nodeInfo.getName()).append('[').append(nodeInfo.getId()).append(']'); + } + log.debug(sb.toString()); + } return link; } @@ -137,9 +146,7 @@ public class NodeService { throw new HasResultException(404, "无效的nodeId"); } StringBuilder stringBuilder = new StringBuilder(); - link.forEach(name -> { - stringBuilder.append("/").append(name); - }); + link.forEach(name -> stringBuilder.append("/").append(name)); return stringBuilder.toString(); } diff --git a/src/test/java/com/xiaotao/saltedfishcloud/helper/PathBuilderTest.java b/src/test/java/com/xiaotao/saltedfishcloud/helper/PathBuilderTest.java new file mode 100644 index 00000000..9103eaca --- /dev/null +++ b/src/test/java/com/xiaotao/saltedfishcloud/helper/PathBuilderTest.java @@ -0,0 +1,31 @@ +package com.xiaotao.saltedfishcloud.helper; + +import com.xiaotao.saltedfishcloud.utils.OSInfo; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.jupiter.api.Test; + +@Slf4j +class PathBuilderTest { + + @Test() + void range() { + PathBuilder pb = new PathBuilder(); + pb.append("////1/\\//\\2\\\\\\\\3/4/5/6/../"); + if (OSInfo.isWindows()) { + Assert.assertEquals("1/2/3", pb.range(-2)); + Assert.assertEquals("1/2", pb.range(2)); + Assert.assertEquals("/", pb.range(0)); + Assert.assertEquals("5", pb.range(1,-1)); + Assert.assertEquals("4/5", pb.range(2,-2)); + } else { + Assert.assertEquals("/1/2/3", pb.range(-2)); + Assert.assertEquals("/1/2", pb.range(2)); + Assert.assertEquals("/", pb.range(0)); + Assert.assertEquals("/5", pb.range(1,-1)); + Assert.assertEquals("/4/5", pb.range(2,-2)); + } + log.info("测试通过!"); + + } +} diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java new file mode 100644 index 00000000..7e957905 --- /dev/null +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java @@ -0,0 +1,27 @@ +package com.xiaotao.saltedfishcloud.service.file; + +import com.xiaotao.saltedfishcloud.dao.UserDao; +import org.junit.Rule; +import org.junit.jupiter.api.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +@RunWith(SpringRunner.class) +@SpringBootTest +class FileRecordServiceTest { + @Resource + private FileRecordService fileRecordService; + @Resource + private UserDao userDao; + @Test + void copy() { + int uid = userDao.getUserByUser("xiaotao").getId(); + try { + fileRecordService.copy(uid, "/f1", "/new", uid, "233", true); + } catch (UnsupportedOperationException ignore) {} + } +} diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java index dbe40e46..ba5807fe 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java @@ -9,6 +9,8 @@ import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import java.io.IOException; + @RunWith(SpringRunner.class) @SpringBootTest public class FileServiceTest { @@ -30,4 +32,14 @@ public class FileServiceTest { e.printStackTrace(); } } + + @Test + public void copy() { + int uid = userDao.getUserByUser("xiaotao").getId(); + try { + fileService.copy(uid, "/f1", "/114514", uid, "233", true); + } catch (IOException e) { + e.printStackTrace(); + } + } } -- Gitee From 89af45dfdc490a0ebe4a551ee1f6fa18c82b2c7e Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 1 May 2021 17:58:01 +0800 Subject: [PATCH 02/13] =?UTF-8?q?test=20postman=E6=B5=8B=E8=AF=95=E9=9B=86?= =?UTF-8?q?=E4=B8=AD=E5=A2=9E=E5=8A=A0=E5=A4=8D=E5=88=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95=E6=A0=B7=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...3\206\345\220\210.postman_collection.json" | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" index fa3d54b4..663398b4 100644 --- "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" +++ "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" @@ -958,6 +958,69 @@ "description": "将文件上传到网盘/api/fileList/{用户id}/{请求的路径}\r\n注意:请求的路径必须是存在的路径,若不存在需要手动提前创建,否则文件会保存失败" }, "response": [] + }, + { + "name": "移动文件", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{addr}}/api/move/", + "host": [ + "{{addr}}" + ], + "path": [ + "api", + "move", + "" + ] + } + }, + "response": [] + }, + { + "name": "复制文件或目录", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "name", + "value": "233", + "description": "要复制的文件或目录名", + "type": "text" + }, + { + "key": "target", + "value": "/new", + "description": "复制到的目标目录名", + "type": "text" + }, + { + "key": "overwrite", + "value": "true", + "description": "是否覆盖原文件(可选,默认true)", + "type": "text" + } + ] + }, + "url": { + "raw": "{{addr}}/api/copy/42/f1", + "host": [ + "{{addr}}" + ], + "path": [ + "api", + "copy", + "42", + "f1" + ] + }, + "description": "### 复制文件或目录\r\n- API:`/api/copy/{用户ID}/{文件所在路径}` \r\n\r\n#### 参数列表\r\n- name: 要复制的文件或目录名\r\n- target: 复制到的目标目录名\r\n- overwrite:是否覆盖原文件(可选参数,可选值为false或true,默认true)\r\n\r\n#### 响应data\r\n- 下载码,用于'使用下载码下载文件'接口" + }, + "response": [] } ] } @@ -1002,4 +1065,4 @@ } } ] -} +} \ No newline at end of file -- Gitee From cd8c3aae59034f1ee0954dcace21c239416d2f7e Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 1 May 2021 18:08:45 +0800 Subject: [PATCH 03/13] =?UTF-8?q?docs=20=E7=A7=BB=E9=99=A4=E5=B7=B2?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=9A=84TODO=E6=A0=87=E7=AD=BE=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=96=B0=E9=9C=80=E6=B1=82=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saltedfishcloud/controller/file/BrowseController.java | 5 ++--- .../xiaotao/saltedfishcloud/service/file/FileService.java | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java index 0db0cb5f..ee42905e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java @@ -27,9 +27,8 @@ public class BrowseController { /** * 取网盘中某个目录的文件列表 - * @param request * @param uid 目标用户资源的ID - * @return + * @TODO 统一文件资源操作getList,copy,delete,download,mkdir,move,upload的API路由前缀 */ @GetMapping("/api/fileList/{uid}/**") @ResponseBody @@ -42,7 +41,7 @@ public class BrowseController { String baseLocalPath = DiskConfig.getRawFileStoreRootPath(uid); - Collection[] fileList = null; + Collection[] fileList; File root = new File(baseLocalPath); if (!root.exists()) { root.mkdir(); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index 5cfc82b4..9a38cae2 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -249,8 +249,7 @@ public class FileService { } /** - * 移动文件 - * @TODO 待完成,用于移动或重命名文件/文件夹 + * 重命名文件或目录 * @param uid 用户ID 0表示公共 * @param path 文件所在路径(相对用户网盘目录) * @param name 被操作的文件名或文件夹名 -- Gitee From 18c51f742105aa341715308d1452132627621df9 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sun, 2 May 2021 15:31:02 +0800 Subject: [PATCH 04/13] =?UTF-8?q?feat=20=E5=A4=8D=E5=88=B6=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=88=96=E7=9B=AE=E5=BD=95=E6=94=AF=E6=8C=81=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E7=9B=AE=E6=A0=87=E7=9B=AE=E6=A0=87=E4=B8=8B=E7=9A=84?= =?UTF-8?q?=E6=96=B0=E6=96=87=E4=BB=B6=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/advice/ControllerAdvice.java | 3 +- .../controller/file/BrowseController.java | 3 +- .../controller/file/CopyController.java | 4 +- .../controller/file/DeleteController.java | 3 +- .../controller/file/MkdirController.java | 4 +- .../controller/file/MoveController.java | 5 +- .../init/FileDBSynchronizer.java | 6 +- .../service/file/FileRecordService.java | 74 +++++++++---------- .../service/file/FileService.java | 46 ++++++++---- .../service/file/StoreService.java | 24 +++--- .../service/node/NodeService.java | 23 ++++-- .../xiaotao/saltedfishcloud/SpringTest.java | 3 +- .../service/file/FileRecordServiceTest.java | 5 +- .../service/file/FileServiceTest.java | 2 +- .../service/file/StoreServiceTest.java | 29 ++++++++ .../service/node/NodeServiceTest.java | 9 ++- 16 files changed, 154 insertions(+), 89 deletions(-) create mode 100644 src/test/java/com/xiaotao/saltedfishcloud/service/file/StoreServiceTest.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java index 7a0b7a42..5756721a 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.io.FileNotFoundException; +import java.nio.file.NoSuchFileException; /** * 全局异常处理,捕获进入控制器的异常并进行处理 @@ -26,7 +27,7 @@ public class ControllerAdvice { return JsonResult.getInstance(403, null, e.getMessage()); } - @ExceptionHandler(FileNotFoundException.class) + @ExceptionHandler({FileNotFoundException.class, NoSuchFileException.class}) public JsonResult handle(FileNotFoundException e) { return JsonResult.getInstance(404, null, "文件或资源不存在"); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java index ee42905e..eb072e29 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.io.File; +import java.nio.file.NoSuchFileException; import java.util.Collection; /** @@ -33,7 +34,7 @@ public class BrowseController { @GetMapping("/api/fileList/{uid}/**") @ResponseBody public JsonResult getFileList(HttpServletRequest request, - @PathVariable int uid) { + @PathVariable int uid) throws NoSuchFileException { // 解析URL UIDValidator.validate(uid); String prefix = "/api/fileList/" + uid; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java index 80abb3bb..b700da46 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java @@ -22,6 +22,7 @@ public class CopyController { * @param uid 用户ID * @param name 文件名 * @param target 目标目录 + * @param targetName 源目标被复制后的名称 * @param overwrite 覆盖同名文件 * @TODO 使用数组传入需要复制的文件名以替代并发请求接口实现多文件粘贴的方式 */ @@ -29,11 +30,12 @@ public class CopyController { public JsonResult copy(@PathVariable("uid") int uid, @RequestParam("name") String name, @RequestParam("target") String target, + @RequestParam("targetName") String targetName, @RequestParam(value = "overwrite", defaultValue = "true") Boolean overwrite, HttpServletRequest request) throws IOException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/copy/" + uid, request); - fileService.copy(uid, source, target, uid, name, overwrite); + fileService.copy(uid, source, target, uid, name, targetName, overwrite); return JsonResult.getInstance(); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java index c245bb3a..1f0a5397 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.nio.file.NoSuchFileException; /** * 删除资源相关控制器 @@ -26,7 +27,7 @@ public class DeleteController { public JsonResult delete(HttpServletRequest request, @PathVariable int uid, @RequestBody FileNameList fileName, - @RequestAttribute User user) { + @RequestAttribute User user) throws NoSuchFileException { UIDValidator.validate(uid, true); String path = URLUtils.getRequestFilePath("/api/resource/" + uid, request); long res = fileService.deleteFile(uid, path, fileName.getFileName()); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java index b2fce6c5..a622d55c 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java @@ -16,6 +16,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.nio.file.NoSuchFileException; + /** * 创建目录控制器 */ @@ -29,7 +31,7 @@ public class MkdirController { @Transactional(rollbackFor = Exception.class) public JsonResult mkdir(@PathVariable int uid, HttpServletRequest request, - @RequestParam("name") String name) throws HasResultException { + @RequestParam("name") String name) throws HasResultException, NoSuchFileException { UIDValidator.validate(uid, true); String prefix = "/api/mkdir/" + uid; String requestPath = URLUtils.getRequestFilePath(prefix, request); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java index 2af42fe9..5dd5fd75 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RestController; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.nio.file.NoSuchFileException; /** * 移动/重命名资源控制器 @@ -33,7 +34,7 @@ public class MoveController { public JsonResult rename(HttpServletRequest request, @PathVariable int uid, @RequestParam("oldName") String oldName, - @RequestParam("newName") String newName) throws HasResultException { + @RequestParam("newName") String newName) throws HasResultException, NoSuchFileException { UIDValidator.validate(uid, true); String from = URLUtils.getRequestFilePath("/api/rename/" + uid, request); if (newName.length() < 1) { @@ -54,7 +55,7 @@ public class MoveController { public JsonResult move(HttpServletRequest request, @PathVariable("uid") int uid, @RequestParam("name") String name, - @RequestParam("target") String target) throws UnsupportedEncodingException { + @RequestParam("target") String target) throws UnsupportedEncodingException, NoSuchFileException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/move/" + uid, request); target = URLDecoder.decode(target, "UTF-8"); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/init/FileDBSynchronizer.java b/src/main/java/com/xiaotao/saltedfishcloud/init/FileDBSynchronizer.java index ac6b12a3..4de8f310 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/init/FileDBSynchronizer.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/init/FileDBSynchronizer.java @@ -90,7 +90,7 @@ public class FileDBSynchronizer implements ApplicationRunner, Runnable { LinkedList localLostDir = new LinkedList<>(SetUtils.diff(dbFile.keySet(), localDir)); Set hasDelete = new HashSet<>(); localLostDir.sort( Comparator.comparingInt(o -> o.split("/").length) ); - localLostDir.forEach(p -> { + for(String p : localLostDir ){ int index = p.lastIndexOf('/'); String path = p.substring(0, index); String name = p.substring(index + 1); @@ -102,7 +102,7 @@ public class FileDBSynchronizer implements ApplicationRunner, Runnable { } fileRecordService.deleteRecords(0, path, Collections.singleton(name)); hasDelete.add(p); - }); + }; // 数据库中的所有文件, key为文件路径,value为文件信息 HashMap dbFiles = new HashMap<>(); @@ -153,7 +153,7 @@ public class FileDBSynchronizer implements ApplicationRunner, Runnable { fileDao.updateRecord( 0, fileInfo.getName(), - nodeService.getNodeIdByPath(0, fileInfo.getPath()).getId(), + nodeService.getLastNodeInfoByPath(0, fileInfo.getPath()).getId(), fileInfo.getSize(), fileInfo.getMd5() ); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java index f3f5b188..7f97b28a 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java @@ -6,22 +6,21 @@ import com.xiaotao.saltedfishcloud.dao.NodeDao; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.helper.PathBuilder; import com.xiaotao.saltedfishcloud.po.NodeInfo; -import com.xiaotao.saltedfishcloud.po.PathInfo; import com.xiaotao.saltedfishcloud.po.file.DirCollection; import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.node.NodeService; import com.xiaotao.saltedfishcloud.utils.FileUtils; import com.xiaotao.saltedfishcloud.utils.PathUtils; import com.xiaotao.saltedfishcloud.utils.StringUtils; -import lombok.AllArgsConstructor; -import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import java.io.File; import java.io.IOException; +import java.nio.file.NoSuchFileException; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; @@ -54,11 +53,11 @@ public class FileRecordService { * @param source 要复制的文件或目录所在目录 * @param target 复制到的目标目录 * @param targetId 复制到的目标目录所属用户ID - * @param name 要复制的文件或目录名 + * @param sourceName 要复制的文件或目录名 * @param overwrite 是否覆盖已存在的文件 */ @Transactional(rollbackFor = Exception.class) - public void copy(int uid, String source, String target, int targetId, String name, boolean overwrite) { + public void copy(int uid, String source, String target, int targetId, String sourceName, String targetName, boolean overwrite) throws NoSuchFileException { class PathIdPair { public String path; public String nid; @@ -66,34 +65,31 @@ public class FileRecordService { } PathBuilder pathBuilder = new PathBuilder(); pathBuilder.setForcePrefix(true); - int prefixLength = source.length() + 1 + name.length(); + int prefixLength = source.length() + 1 + sourceName.length(); - FileInfo sourceInfo = fileDao.getFileInfo(uid, name, nodeService.getNodeIdByPath(uid, source).getId()); - if (sourceInfo == null) { - throw new HasResultException(404, "原文件或目录不存在"); - } + FileInfo sourceInfo = fileDao.getFileInfo(uid, sourceName, nodeService.getLastNodeInfoByPath(uid, source).getId()); // 文件直接添加单条记录 if (sourceInfo.isFile()) { - addRecord(targetId, sourceInfo.getName(), sourceInfo.getSize(), sourceInfo.getMd5(), target); + addRecord(targetId, targetName, sourceInfo.getSize(), sourceInfo.getMd5(), target); return ; } // 需要遍历的目录列表 LinkedList t = new LinkedList<>(); - String root = source + "/" + name; - t.add(new PathIdPair(root, nodeService.getNodeIdByPath(uid, root).getId())); + String sourceRoot = source + "/" + sourceName; + t.add(new PathIdPair(sourceRoot, nodeService.getLastNodeInfoByPath(uid, sourceRoot).getId())); do { // 更新目录列表 PathIdPair pairInfo = t.getLast(); t.removeLast(); - String newDirPath = target + "/" + name + "/" + pairInfo.path.substring(prefixLength); + String newDirPath = target + "/" + targetName + "/" + pairInfo.path.substring(prefixLength); pathBuilder.update(newDirPath); PathIdPair newPathInfo; try { newPathInfo = new PathIdPair(newDirPath, mkdir(targetId, pathBuilder.getPath().getLast(), pathBuilder.range(-1))); } catch (DuplicateKeyException e) { - newPathInfo = new PathIdPair(newDirPath, nodeService.getNodeIdByPath(targetId, newDirPath).getId()); + newPathInfo = new PathIdPair(newDirPath, nodeService.getLastNodeInfoByPath(targetId, newDirPath).getId()); } @@ -122,11 +118,12 @@ public class FileRecordService { * @param source 网盘文件或目录所在目录 * @param target 网盘目标目录 * @param name 文件名 + * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 * @return 受影响行数 */ - public int move(int uid, String source, String target, String name) { - String nid = nodeService.getNodeIdByPath(uid, source).getId(); - String targetNodeId = nodeService.getNodeIdByPath(uid, target).getId(); + public int move(int uid, String source, String target, String name) throws NoSuchFileException { + String nid = nodeService.getLastNodeInfoByPath(uid, source).getId(); + String targetNodeId = nodeService.getLastNodeInfoByPath(uid, target).getId(); FileInfo fileInfo = fileDao.getFileInfo(uid, name, nid); if (fileInfo.isDir()) { nodeDao.move(uid, fileInfo.getMd5(), targetNodeId); @@ -143,8 +140,8 @@ public class FileRecordService { * @param path 文件所在路径 * @return 添加数量 */ - public int addRecord(int uid, String name, Long size, String md5, String path) { - NodeInfo node = nodeService.getNodeIdByPath(uid, path); + public int addRecord(int uid, String name, Long size, String md5, String path) throws NoSuchFileException { + NodeInfo node = nodeService.getLastNodeInfoByPath(uid, path); return fileDao.addRecord(uid, name, size, md5, node.getId()); } @@ -157,8 +154,8 @@ public class FileRecordService { * @param newMd5 新的文件MD5 * @return 影响行数 */ - int updateFileRecord(int uid, String name, String path, Long newSize, String newMd5) { - NodeInfo node = nodeService.getNodeIdByPath(uid, path); + int updateFileRecord(int uid, String name, String path, Long newSize, String newMd5) throws NoSuchFileException { + NodeInfo node = nodeService.getLastNodeInfoByPath(uid, path); return fileDao.updateRecord(uid, name, node.getId(), newSize, newMd5); } @@ -169,8 +166,8 @@ public class FileRecordService { * @param name 文件名列表 * @return 删除的文件个数 */ - public int deleteRecords(int uid, String path, Collection name) { - NodeInfo node = nodeService.getNodeIdByPath(uid, path); + public int deleteRecords(int uid, String path, Collection name) throws NoSuchFileException { + NodeInfo node = nodeService.getLastNodeInfoByPath(uid, path); List infos = fileDao.getFilesInfo(uid, name, node.getId()); // 先将文件和文件夹分开并单独提取其文件名 @@ -199,13 +196,12 @@ public class FileRecordService { * @param path 所在路径 * @throws DuplicateKeyException * 当目标目录已存在时抛出 + * @throws NoSuchFileException + * 当父级目录不存在时抛出 */ - public String mkdir(int uid, String name, String path) { + public String mkdir(int uid, String name, String path) throws NoSuchFileException { log.debug("mkdir " + name + " at " + path); - NodeInfo node = nodeService.getNodeIdByPath(uid, path); - if (node == null) { - throw new HasResultException("目标目录 \"" + path + "\" 不存在,目录 \"" + name + "\"创建失败"); - } + NodeInfo node = nodeService.getLastNodeInfoByPath(uid, path); String nodeId = nodeService.addNode(uid, name, node.getId()); if (fileDao.addRecord(uid, name, -1L, nodeId, node.getId()) < 1) { throw new DuplicateKeyException("目录已存在"); @@ -222,9 +218,9 @@ public class FileRecordService { * @param oldName 旧文件名 * @param newName 新文件名 */ - public void rename(int uid, String path, String oldName, String newName) { - NodeInfo pathNodeInfo = nodeService.getNodeIdByPath(uid, path); - NodeInfo nodeInfo = nodeService.getNodeIdByPath(uid, path + "/" + oldName); + public void rename(int uid, String path, String oldName, String newName) throws NoSuchFileException { + NodeInfo pathNodeInfo = nodeService.getLastNodeInfoByPath(uid, path); + NodeInfo nodeInfo = nodeService.getLastNodeInfoByPath(uid, path + "/" + oldName); FileInfo fileInfo = fileDao.getFileInfo(uid, oldName, pathNodeInfo.getId()); if (fileInfo.isDir()) { nodeDao.changeName(uid, nodeInfo.getId(), newName); @@ -243,19 +239,19 @@ public class FileRecordService { // 先创建目录 long finalTotal = total; - dirCollection.getDirList().forEach(file -> { - String path = PathUtils.getRelativePath(0, file.getParent()); + for (File file1 : dirCollection.getDirList()) { + String path = PathUtils.getRelativePath(0, file1.getParent()); String proc = StringUtils.getProcStr(atomicLong.get(), finalTotal, 10); - log.info("mkdir" + proc + " " + file.getName() + " at " + path); - mkdir(0, file.getName(), path); + log.info("mkdir" + proc + " " + file1.getName() + " at " + path); + mkdir(0, file1.getName(), path); atomicLong.incrementAndGet(); - }); + } // 再添加文件 atomicLong.set(0); total = dirCollection.getSize(); long finalTotal1 = total; - dirCollection.getFileList().forEach(file -> { + for (File file : dirCollection.getFileList()) { String path = PathUtils.getRelativePath(0, file.getParent()); String proc = StringUtils.getProcStr(atomicLong.get(), finalTotal1, 10); log.info("addFile " + proc + " " + file.getName() + " at " + path); @@ -263,7 +259,7 @@ public class FileRecordService { fileInfo.updateMd5(); addRecord(0, file.getName(), file.length(), fileInfo.getMd5(), path); atomicLong.addAndGet(file.length()); - }); + } log.info("Finish"); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index 9a38cae2..cc396800 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -27,6 +27,7 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URLEncoder; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; @@ -47,21 +48,30 @@ public class FileService { NodeService nodeService; /** - * - * @param uid 原资源的用户ID - * @param source 要操作的文件所在的网盘目录 - * @param target 复制到的目的地目录 - * @param targetUid 目的地用户ID - * @param name 文件或目录名 - * @param overwrite 是否覆盖 + * 复制指定用户的文件或目录到指定用户的某个目录下 + * @param uid + * 原资源的用户ID + * @param source + * 要操作的文件所在的网盘目录 + * @param target + * 复制到的目的地目录 + * @param targetUid + * 目标用户ID + * @param sourceName + * 源文件或目录名 + * @param targetName + * 目标文件或目录名, + * @param overwrite + * 是否覆盖 */ - public void copy(int uid, String source, String target, int targetUid, String name, Boolean overwrite) throws IOException { + public void copy(int uid, String source, String target, int targetUid, String sourceName, String targetName, Boolean overwrite) throws IOException { if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target))) { throw new IllegalArgumentException("无法原地复制"); } - fileRecordService.copy(uid, source, target, targetUid, name, overwrite); + fileRecordService.copy(uid, source, target, targetUid, sourceName, targetName,overwrite); log.debug("Finish DB data copy"); - storeService.copy(uid, source, target, targetUid, name, overwrite); + storeService.copy(uid, source, target, targetUid, sourceName, targetName, overwrite); + log.debug("Finish local filesystem data copy"); } /** @@ -69,9 +79,10 @@ public class FileService { * @param uid 用户ID * @param source 要被移动的网盘文件或目录所在目录 * @param target 要移动到的目标目录 + * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 * @param name 文件名 */ - public void move(int uid, String source, String target, String name) { + public void move(int uid, String source, String target, String name) throws NoSuchFileException { try { storeService.move(uid, source, target, name); } catch (Exception e) { @@ -90,8 +101,8 @@ public class FileService { * @param path 网盘路径 * @return 一个List数组,数组下标0为目录,1为文件,或null */ - public List[] getUserFileList(int uid, String path) { - NodeInfo nodeId = nodeService.getNodeIdByPath(uid, path); + public List[] getUserFileList(int uid, String path) throws NoSuchFileException { + NodeInfo nodeId = nodeService.getLastNodeInfoByPath(uid, path); return getUserFileListByNodeId(uid, nodeId.getId()); } @@ -225,8 +236,9 @@ public class FileService { * @param uid 用户ID 0表示公共 * @param path 请求的路径 * @param name 文件夹名称 + * @throws NoSuchFileException 当目标目录不存在时抛出 */ - public void mkdir(int uid, String path, String name) throws HasResultException { + public void mkdir(int uid, String path, String name) throws HasResultException, NoSuchFileException { if ( !storeService.mkdir(uid, path, name) ) { throw new HasResultException("在" + path + "创建文件夹失败"); } @@ -238,9 +250,10 @@ public class FileService { * @param uid 用户ID 0表示公共 * @param path 请求路径 * @param name 文件名列表 + * @throws NoSuchFileException 当目标路径不存在时抛出 * @return 删除的数量 */ - public long deleteFile(int uid, String path, List name) { + public long deleteFile(int uid, String path, List name) throws NoSuchFileException { // 计数删除数 long res = 0L; fileRecordService.deleteRecords(uid, path, name); @@ -253,9 +266,10 @@ public class FileService { * @param uid 用户ID 0表示公共 * @param path 文件所在路径(相对用户网盘目录) * @param name 被操作的文件名或文件夹名 + * @throws NoSuchFileException 当目标路径不存在时抛出 * @param newName 新文件名 */ - public void rename(int uid, String path, String name, String newName) throws HasResultException { + public void rename(int uid, String path, String name, String newName) throws HasResultException, NoSuchFileException { fileRecordService.rename(uid, path, name, newName); storeService.rename(uid, path, name, newName); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java index 1cec69bd..13c0e5fa 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java @@ -34,14 +34,14 @@ public class StoreService { * @param uid 用户ID * @param source 所在网盘路径 * @param target 目的地网盘路径 - * @param name 文件名 + * @param sourceName 文件名 * @param overwrite 是否覆盖,若非true,则跳过该文件 */ - public void copy(int uid, String source, String target, int targetId, String name, Boolean overwrite) throws IOException { + public void copy(int uid, String source, String target, int targetId, String sourceName, String targetName, Boolean overwrite) throws IOException { if (DiskConfig.STORE_TYPE == StoreType.UNIQUE ) { return ; } - BasicFileInfo fileInfo = new BasicFileInfo(name, null); + BasicFileInfo fileInfo = new BasicFileInfo(sourceName, null); String localSource = DiskConfig.getPathHandler().getStorePath(uid, source, fileInfo); String localTarget = DiskConfig.getPathHandler().getStorePath(targetId, target, null); @@ -50,10 +50,10 @@ public class StoreService { // 判断源与目标是否存在 if (!Files.exists(sourcePath)) { - throw new IOException("资源 \"" + source + "/" + name + "\" 不存在"); + throw new NoSuchFileException("资源 \"" + source + "/" + sourceName + "\" 不存在"); } if (!Files.exists(Paths.get(localTarget))) { - throw new IOException("目标目录 " + target + " 不存在"); + throw new NoSuchFileException("目标目录 " + target + " 不存在"); } CopyOption[] option; @@ -64,26 +64,28 @@ public class StoreService { } if (fileInfo.isFile()) { - Files.copy(sourcePath, Paths.get(localTarget + "/" + name), option); + Files.copy(sourcePath, Paths.get(localTarget + "/" + targetName), option); } if (fileInfo.isDir()) { DirCollection dirCollection = FileUtils.scanDir(localSource); - Path targetDir = Paths.get(localTarget + "/" + name); + Path targetDir = Paths.get(localTarget + "/" + targetName); if (!Files.exists(targetDir)) { Files.createDirectory(targetDir); } // 先创建文件夹 for(File dir: dirCollection.getDirList()) { - String sour = dir.getPath().substring(localSource.length()); - String dest = targetDir + "/" + sour; + String src = dir.getPath().substring(localSource.length()); + String dest = targetDir + "/" + src; + log.debug("local filesystem mkdir: " + dest); try { Files.createDirectory(Paths.get(dest)); } catch (FileAlreadyExistsException ignored) {} } // 复制文件 for(File file: dirCollection.getFileList()) { - String sour = file.getPath().substring(localSource.length()); - String dest = localTarget + "/" + name + sour; + String src = file.getPath().substring(localSource.length()); + String dest = localTarget + "/" + targetName + src; + log.debug("local filesystem copy: " + file + " ==> " + dest); try { Files.copy(Paths.get(file.getPath()), Paths.get(dest), option); } catch (FileAlreadyExistsException ignored) {} } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java index 881c71bb..9c3ab11b 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import java.nio.file.NoSuchFileException; import java.util.Collection; import java.util.Date; import java.util.LinkedList; @@ -22,12 +23,13 @@ public class NodeService { NodeDao nodeDao; /** - * 使用路径取节点ID + * 使用路径取最后一个节点的信息 * @TODO 使用缓存加快查询速度 * @param path 完整路径 + * @throws NoSuchFileException 请求的路径不存在时抛出此异常 * @return 路径ID */ - public NodeInfo getNodeIdByPath(int uid, String path) { + public NodeInfo getLastNodeInfoByPath(int uid, String path) throws NoSuchFileException { return getPathNodeByPath(uid, path).getLast(); } @@ -35,18 +37,24 @@ public class NodeService { * 获取某个路径中途径的节点信息 * @param uid 用户ID * @param path 路径 + * @throws NoSuchFileException 请求的路径不存在时抛出此异常 * @return 节点信息列表 */ - public LinkedList getPathNodeByPath(int uid, String path) { + public LinkedList getPathNodeByPath(int uid, String path) throws NoSuchFileException { + log.debug("search path" + uid + ": " + path); LinkedList link = new LinkedList<>(); PathBuilder pb = new PathBuilder(); pb.append(path); Collection paths = pb.getPath(); try { - paths.forEach(node -> { + for (String node : paths) { String parent = link.isEmpty() ? "root" : link.getLast().getId(); - link.add(nodeDao.getNodeByParentId(uid, parent, node)); - }); + NodeInfo info = nodeDao.getNodeByParentId(uid, parent, node); + if (info == null) { + throw new NoSuchFileException("路径 " + path + " 不存在"); + } + link.add(info); + } if (link.isEmpty()) { throw new NullPointerException(); } @@ -58,7 +66,8 @@ public class NodeService { if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); for (NodeInfo nodeInfo : link) { - sb.append("/").append(nodeInfo.getName()).append('[').append(nodeInfo.getId()).append(']'); + log.debug("nodeInfo:" + nodeInfo); + sb.append("/").append(nodeInfo.getName() == null ? nodeInfo.getName() : "").append('[').append(nodeInfo.getId()).append(']'); } log.debug(sb.toString()); } diff --git a/src/test/java/com/xiaotao/saltedfishcloud/SpringTest.java b/src/test/java/com/xiaotao/saltedfishcloud/SpringTest.java index 7b60f8ae..9bdc1b59 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/SpringTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/SpringTest.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import java.nio.file.NoSuchFileException; import java.util.LinkedList; @SpringBootTest @@ -17,7 +18,7 @@ public class SpringTest { private NodeService nodeService; @Test - public void doTest() { + public void doTest() throws NoSuchFileException { LinkedList pathNodeByPath = nodeService.getPathNodeByPath(0, "/"); for (NodeInfo nodeInfo : pathNodeByPath) { System.out.println(nodeInfo); diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java index 7e957905..58e73a46 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileRecordServiceTest.java @@ -9,6 +9,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; +import java.nio.file.NoSuchFileException; @RunWith(SpringRunner.class) @SpringBootTest @@ -18,10 +19,10 @@ class FileRecordServiceTest { @Resource private UserDao userDao; @Test - void copy() { + void copy() throws NoSuchFileException { int uid = userDao.getUserByUser("xiaotao").getId(); try { - fileRecordService.copy(uid, "/f1", "/new", uid, "233", true); + fileRecordService.copy(uid, "/", "/", uid, "f1", "234", true); } catch (UnsupportedOperationException ignore) {} } } diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java index ba5807fe..a78971cf 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java @@ -37,7 +37,7 @@ public class FileServiceTest { public void copy() { int uid = userDao.getUserByUser("xiaotao").getId(); try { - fileService.copy(uid, "/f1", "/114514", uid, "233", true); + fileService.copy(uid, "/", "/", uid, "f1", "234", true); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/StoreServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/StoreServiceTest.java new file mode 100644 index 00000000..ad34428f --- /dev/null +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/StoreServiceTest.java @@ -0,0 +1,29 @@ +package com.xiaotao.saltedfishcloud.service.file; + +import com.xiaotao.saltedfishcloud.dao.UserDao; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@RunWith(SpringRunner.class) +class StoreServiceTest { + @Resource + private StoreService storeService; + @Resource + private UserDao userDao; + + + @Test + void copy() throws IOException { + int uid = userDao.getUserByUser("xiaotao").getId(); + storeService.copy(uid, "/f1", "/", uid, "233", "f2", true); + } +} diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/node/NodeServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/node/NodeServiceTest.java index 92ca21b1..3e141df0 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/node/NodeServiceTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/node/NodeServiceTest.java @@ -1,6 +1,7 @@ package com.xiaotao.saltedfishcloud.service.node; import com.xiaotao.saltedfishcloud.po.NodeInfo; +import com.xiaotao.saltedfishcloud.service.user.UserService; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; @@ -8,6 +9,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; +import java.nio.file.NoSuchFileException; @RunWith(SpringRunner.class) @SpringBootTest @@ -15,10 +17,13 @@ import javax.annotation.Resource; class NodeServiceTest { @Resource private NodeService nodeService; + @Resource + private UserService userService; @Test - void getNodeIdByPath() { - NodeInfo node = nodeService.getNodeIdByPath(0, "/"); + void getLastNodeInfoByPath() throws NoSuchFileException { + int nid = userService.getUserByUser("xiaotao").getId(); + NodeInfo node = nodeService.getLastNodeInfoByPath(nid, "/f1"); log.info(node.toString()); } } -- Gitee From 972df26eea975a53359d369f58bb072a8b2ec5e9 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sun, 2 May 2021 22:30:48 +0800 Subject: [PATCH 05/13] =?UTF-8?q?feat=20=E5=AF=B9=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=E5=90=8D=E8=BF=9B=E8=A1=8C=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E9=98=B2=E6=AD=A2=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=E6=97=B6=E9=80=A0=E6=88=90=E8=B7=A8=E7=BA=A7=E8=AE=BF?= =?UTF-8?q?=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/file/BrowseController.java | 2 +- .../controller/file/CopyController.java | 2 +- .../controller/file/DeleteController.java | 2 +- .../controller/file/DownloadController.java | 2 +- .../controller/file/MkdirController.java | 2 +- .../controller/file/MoveController.java | 2 +- .../controller/file/ResourcesController.java | 2 +- .../controller/file/UploadController.java | 2 +- .../service/file/FileService.java | 9 ++++++++- .../validator/FileNameValidator.java | 19 +++++++++++++++++++ .../{utils => validator}/UIDValidator.java | 3 ++- .../saltedfishcloud/utils/RegexTest.java | 19 +++++++++++++++++++ 12 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java rename src/main/java/com/xiaotao/saltedfishcloud/{utils => validator}/UIDValidator.java (92%) create mode 100644 src/test/java/com/xiaotao/saltedfishcloud/utils/RegexTest.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java index eb072e29..3a2a300b 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/BrowseController.java @@ -4,7 +4,7 @@ import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java index b700da46..31d0ad61 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java @@ -2,7 +2,7 @@ package com.xiaotao.saltedfishcloud.controller.file; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java index 1f0a5397..188cb27f 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java @@ -4,7 +4,7 @@ import com.xiaotao.saltedfishcloud.po.file.FileNameList; import com.xiaotao.saltedfishcloud.po.User; import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.po.JsonResult; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DownloadController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DownloadController.java index 5051feaf..bbe2c24c 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DownloadController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DownloadController.java @@ -2,7 +2,7 @@ package com.xiaotao.saltedfishcloud.controller.file; import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java index a622d55c..cc7ab565 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java @@ -6,7 +6,7 @@ import javax.servlet.http.HttpServletRequest; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java index 5dd5fd75..cc27c8e4 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java @@ -6,7 +6,7 @@ import javax.servlet.http.HttpServletRequest; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java index 3db65eda..f7075cd2 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java @@ -16,7 +16,7 @@ import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.file.FileRecordService; import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.service.node.NodeService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java index 1d7c0fda..59d12384 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.utils.UIDValidator; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index cc396800..d947ccfc 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -14,12 +14,14 @@ import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.node.NodeService; import com.xiaotao.saltedfishcloud.utils.FileUtils; import com.xiaotao.saltedfishcloud.utils.JwtUtils; +import com.xiaotao.saltedfishcloud.validator.FileNameValidator; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -65,7 +67,8 @@ public class FileService { * 是否覆盖 */ public void copy(int uid, String source, String target, int targetUid, String sourceName, String targetName, Boolean overwrite) throws IOException { - if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target))) { + FileNameValidator.valid(targetName, sourceName); + if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target)) && sourceName.equals(targetName)) { throw new IllegalArgumentException("无法原地复制"); } fileRecordService.copy(uid, source, target, targetUid, sourceName, targetName,overwrite); @@ -83,6 +86,7 @@ public class FileService { * @param name 文件名 */ public void move(int uid, String source, String target, String name) throws NoSuchFileException { + FileNameValidator.valid(name); try { storeService.move(uid, source, target, name); } catch (Exception e) { @@ -239,6 +243,7 @@ public class FileService { * @throws NoSuchFileException 当目标目录不存在时抛出 */ public void mkdir(int uid, String path, String name) throws HasResultException, NoSuchFileException { + FileNameValidator.valid(name); if ( !storeService.mkdir(uid, path, name) ) { throw new HasResultException("在" + path + "创建文件夹失败"); } @@ -254,6 +259,7 @@ public class FileService { * @return 删除的数量 */ public long deleteFile(int uid, String path, List name) throws NoSuchFileException { + name.forEach(FileNameValidator::valid); // 计数删除数 long res = 0L; fileRecordService.deleteRecords(uid, path, name); @@ -270,6 +276,7 @@ public class FileService { * @param newName 新文件名 */ public void rename(int uid, String path, String name, String newName) throws HasResultException, NoSuchFileException { + FileNameValidator.valid(name, newName); fileRecordService.rename(uid, path, name, newName); storeService.rename(uid, path, name, newName); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java new file mode 100644 index 00000000..3492bbf0 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java @@ -0,0 +1,19 @@ +package com.xiaotao.saltedfishcloud.validator; + +import java.util.regex.Pattern; + +public class FileNameValidator { + private final static Pattern pattern = Pattern.compile("(^\\.+$)|(\\\\)|(/)", Pattern.MULTILINE); + + /** + * 验证是否为合法的文件名(不允许出现 + * @param fileName 文件名 + */ + public static void valid(CharSequence ...fileName) { + for (CharSequence name : fileName) { + if (pattern.matcher(name).find() || name.length() > 255) { + throw new IllegalArgumentException("不合法的文件名"); + } + } + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/utils/UIDValidator.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/UIDValidator.java similarity index 92% rename from src/main/java/com/xiaotao/saltedfishcloud/utils/UIDValidator.java rename to src/main/java/com/xiaotao/saltedfishcloud/validator/UIDValidator.java index 4567768d..c80ff898 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/utils/UIDValidator.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/UIDValidator.java @@ -1,7 +1,8 @@ -package com.xiaotao.saltedfishcloud.utils; +package com.xiaotao.saltedfishcloud.validator; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.User; +import com.xiaotao.saltedfishcloud.utils.SecureUtils; public class UIDValidator { /** diff --git a/src/test/java/com/xiaotao/saltedfishcloud/utils/RegexTest.java b/src/test/java/com/xiaotao/saltedfishcloud/utils/RegexTest.java new file mode 100644 index 00000000..f26b1955 --- /dev/null +++ b/src/test/java/com/xiaotao/saltedfishcloud/utils/RegexTest.java @@ -0,0 +1,19 @@ +package com.xiaotao.saltedfishcloud.utils; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexTest { + @Test + void test() { + Pattern pattern = Pattern.compile("(^\\.+$)|(\\\\)|(/)", Pattern.MULTILINE); + Assert.assertTrue(pattern.matcher("..").find()); + Assert.assertTrue(pattern.matcher("asd..asdasd/asd").find()); + Assert.assertTrue(pattern.matcher("asd..asdasd\\asd").find()); + Assert.assertTrue(pattern.matcher("asd..as/d/asd\\asd").find()); + Assert.assertFalse(pattern.matcher("asd..asdasdasd").find()); + } +} -- Gitee From 56a8a1328a2d160fc2cd4ee3b11b9e3eff875784 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Mon, 3 May 2021 23:28:09 +0800 Subject: [PATCH 06/13] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8DFileNotFound?= =?UTF-8?q?=E5=92=8CNoSuchFile=E5=BC=82=E5=B8=B8=E6=97=B6=EF=BC=8CControll?= =?UTF-8?q?erAdvice=E6=97=A0=E6=B3=95=E8=BD=AC=E6=8D=A2=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AF=BC=E8=87=B4=E7=9A=84=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8500=E8=80=8C=E6=97=A0=E6=B3=95=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E9=A2=84=E6=9C=9F=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saltedfishcloud/controller/advice/ControllerAdvice.java | 6 +++--- .../saltedfishcloud/service/file/FileRecordService.java | 1 + .../saltedfishcloud/service/file/FileServiceTest.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java index 5756721a..8b3c7641 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java @@ -28,12 +28,12 @@ public class ControllerAdvice { } @ExceptionHandler({FileNotFoundException.class, NoSuchFileException.class}) - public JsonResult handle(FileNotFoundException e) { - return JsonResult.getInstance(404, null, "文件或资源不存在"); + public JsonResult handle(Exception e) { + return JsonResult.getInstance(404, null, e.getMessage()); } @ExceptionHandler(Exception.class) - public JsonResult handle(Exception e) { + public JsonResult defaultHandle(Exception e) { log.error("异常", e); return JsonResult.getInstance(500, e.getClass().getCanonicalName(), e.getMessage()); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java index 7f97b28a..95ee118f 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java @@ -68,6 +68,7 @@ public class FileRecordService { int prefixLength = source.length() + 1 + sourceName.length(); FileInfo sourceInfo = fileDao.getFileInfo(uid, sourceName, nodeService.getLastNodeInfoByPath(uid, source).getId()); + if (sourceInfo == null) throw new NoSuchFileException("文件 " + source + "/" + sourceName + " 不存在"); // 文件直接添加单条记录 if (sourceInfo.isFile()) { addRecord(targetId, targetName, sourceInfo.getSize(), sourceInfo.getMd5(), target); diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java index a78971cf..7e7495c1 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java @@ -37,7 +37,7 @@ public class FileServiceTest { public void copy() { int uid = userDao.getUserByUser("xiaotao").getId(); try { - fileService.copy(uid, "/", "/", uid, "f1", "234", true); + fileService.copy(uid, "/", "/", uid, "f1", "f2", true); } catch (IOException e) { e.printStackTrace(); } -- Gitee From 43f3284c62e765800c9f4ed6a776ed2a9d40f757 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Mon, 3 May 2021 23:30:46 +0800 Subject: [PATCH 07/13] =?UTF-8?q?test=20=E6=9B=B4=E6=96=B0postman=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E9=9B=86=E5=90=88=E5=A4=8D=E5=88=B6=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0=E8=AF=B4=E6=98=8E=E5=92=8C=E6=A0=B7?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\233\206\345\220\210.postman_collection.json" | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" index 663398b4..262a06c1 100644 --- "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" +++ "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" @@ -988,13 +988,13 @@ "formdata": [ { "key": "name", - "value": "233", + "value": "f1", "description": "要复制的文件或目录名", "type": "text" }, { "key": "target", - "value": "/new", + "value": "/2312", "description": "复制到的目标目录名", "type": "text" }, @@ -1003,11 +1003,17 @@ "value": "true", "description": "是否覆盖原文件(可选,默认true)", "type": "text" + }, + { + "key": "targetName", + "value": "newf1", + "description": "复制后的文件或目录名", + "type": "text" } ] }, "url": { - "raw": "{{addr}}/api/copy/42/f1", + "raw": "{{addr}}/api/copy/42/", "host": [ "{{addr}}" ], @@ -1015,10 +1021,10 @@ "api", "copy", "42", - "f1" + "" ] }, - "description": "### 复制文件或目录\r\n- API:`/api/copy/{用户ID}/{文件所在路径}` \r\n\r\n#### 参数列表\r\n- name: 要复制的文件或目录名\r\n- target: 复制到的目标目录名\r\n- overwrite:是否覆盖原文件(可选参数,可选值为false或true,默认true)\r\n\r\n#### 响应data\r\n- 下载码,用于'使用下载码下载文件'接口" + "description": "### 复制文件或目录\r\n- API:`/api/copy/{用户ID}/{文件所在路径}` \r\n\r\n#### 参数列表\r\n- name: 要复制的文件或目录名\r\n- target: 复制到的目标目录名\r\n- targetName: 复制后的文件或目录名,可利用该参数实现目标文件重命名\r\n- overwrite: 是否覆盖原文件(可选参数,可选值为false或true,默认true)\r\n\r\n#### 响应data\r\n- 下载码,用于'使用下载码下载文件'接口" }, "response": [] } -- Gitee From 14488ddbfdd293ae4970138eee49234cca7198f1 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Tue, 4 May 2021 00:48:43 +0800 Subject: [PATCH 08/13] =?UTF-8?q?feat=20=E6=96=B0=E5=A2=9E=E4=BA=86?= =?UTF-8?q?=E5=87=A0=E4=B8=AAbug=EF=BC=8CTODO=E5=92=8CFIXME?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/file/MoveController.java | 6 ++++-- .../service/file/FileRecordService.java | 5 +++-- .../saltedfishcloud/service/file/FileService.java | 14 ++++++++++---- .../saltedfishcloud/service/file/StoreService.java | 9 +++++++-- .../service/file/FileServiceTest.java | 4 ++-- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java index cc27c8e4..ed953e7e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java @@ -55,14 +55,16 @@ public class MoveController { public JsonResult move(HttpServletRequest request, @PathVariable("uid") int uid, @RequestParam("name") String name, - @RequestParam("target") String target) throws UnsupportedEncodingException, NoSuchFileException { + @RequestParam("target") String target, + @RequestParam(value = "overwrite", defaultValue = "true") Boolean overwrite) + throws UnsupportedEncodingException, NoSuchFileException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/move/" + uid, request); target = URLDecoder.decode(target, "UTF-8"); if (source.equals(target)) { throw new HasResultException(400, "不能原处移动"); } - fileService.move(uid, source, target, name); + fileService.move(uid, source, target, name, overwrite); return JsonResult.getInstance(source); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java index 95ee118f..10646baa 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java @@ -3,7 +3,6 @@ package com.xiaotao.saltedfishcloud.service.file; import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.dao.FileDao; import com.xiaotao.saltedfishcloud.dao.NodeDao; -import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.helper.PathBuilder; import com.xiaotao.saltedfishcloud.po.NodeInfo; import com.xiaotao.saltedfishcloud.po.file.DirCollection; @@ -119,10 +118,12 @@ public class FileRecordService { * @param source 网盘文件或目录所在目录 * @param target 网盘目标目录 * @param name 文件名 + * @param overwrite 是否覆盖原文件信息 * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 * @return 受影响行数 + * @TODO 实现overwrite参数的效果 */ - public int move(int uid, String source, String target, String name) throws NoSuchFileException { + public int move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { String nid = nodeService.getLastNodeInfoByPath(uid, source).getId(); String targetNodeId = nodeService.getLastNodeInfoByPath(uid, target).getId(); FileInfo fileInfo = fileDao.getFileInfo(uid, name, nid); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index d947ccfc..1289a610 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -20,6 +20,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.File; @@ -66,6 +67,7 @@ public class FileService { * @param overwrite * 是否覆盖 */ + @Transactional(rollbackFor = Exception.class) public void copy(int uid, String source, String target, int targetUid, String sourceName, String targetName, Boolean overwrite) throws IOException { FileNameValidator.valid(targetName, sourceName); if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target)) && sourceName.equals(targetName)) { @@ -82,18 +84,22 @@ public class FileService { * @param uid 用户ID * @param source 要被移动的网盘文件或目录所在目录 * @param target 要移动到的目标目录 - * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 * @param name 文件名 + * @param overwrite 是否覆盖原文件 + * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 + * @TODO 解决下面的FIXME + * @// FIXME: 2021/5/4 文件名带空格时会导致数据库与本地文件失去同步 */ - public void move(int uid, String source, String target, String name) throws NoSuchFileException { + @Transactional(rollbackFor = Exception.class) + public void move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { FileNameValidator.valid(name); try { - storeService.move(uid, source, target, name); + storeService.move(uid, source, target, name, overwrite); } catch (Exception e) { e.printStackTrace(); throw new HasResultException(404, "资源不存在"); } - fileRecordService.move(uid, source, target, name); + fileRecordService.move(uid, source, target, name, overwrite); } /** diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java index 13c0e5fa..52b0195e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java @@ -112,8 +112,9 @@ public class StoreService { * @param source 所在网盘路径 * @param target 目的地网盘路径 * @param name 文件名 + * @param overwrite 是否覆盖原文件 */ - public void move(int uid, String source, String target, String name) throws IOException { + public void move(int uid, String source, String target, String name, boolean overwrite) throws IOException { if (DiskConfig.STORE_TYPE == StoreType.UNIQUE) { return; } @@ -121,7 +122,11 @@ public class StoreService { BasicFileInfo fileInfo = new BasicFileInfo(name, null); Path sourcePath = Paths.get(pathHandler.getStorePath(uid, source, fileInfo)); Path targetPath = Paths.get(pathHandler.getStorePath(uid, target, fileInfo)); - Files.move(sourcePath, targetPath); + if (overwrite) { + Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } else if (Files.exists(targetPath)) { + Files.move(sourcePath, targetPath); + } } /** diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java index 7e7495c1..a594bded 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/file/FileServiceTest.java @@ -26,8 +26,8 @@ public class FileServiceTest { int uid = userDao.getUserByUser("xiaotao").getId(); fileService.mkdir(uid, "/", "test"); fileService.mkdir(uid, "/", "test2"); - fileService.move(uid, "/", "/test", "test2"); - fileService.move(uid, "/", "/test/test2", "ml.exe"); + fileService.move(uid, "/", "/test", "test2", true); + fileService.move(uid, "/", "/test/test2", "ml.exe", true); } catch (Exception e) { e.printStackTrace(); } -- Gitee From 843ce465313709c9b6e7a513771bd859c7ca814a Mon Sep 17 00:00:00 2001 From: xiaotao Date: Tue, 4 May 2021 21:59:42 +0800 Subject: [PATCH 09/13] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8D=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E5=90=84=E6=A0=B7=E7=9A=84=E5=A4=8D=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E5=89=AA=E5=88=87=E5=92=8C=E9=87=8D=E5=91=BD=E5=90=8D=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiaotao/saltedfishcloud/dao/FileDao.java | 8 +-- .../xiaotao/saltedfishcloud/dao/NodeDao.java | 2 +- .../service/file/FileRecordService.java | 50 +++++++++++++++---- .../service/file/FileService.java | 9 ++-- .../service/file/StoreService.java | 19 +++++-- .../service/node/NodeService.java | 4 +- .../saltedfishcloud/utils/FileUtils.java | 46 +++++++++++++++++ 7 files changed, 113 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/dao/FileDao.java b/src/main/java/com/xiaotao/saltedfishcloud/dao/FileDao.java index f0413b59..8a44c685 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/dao/FileDao.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/dao/FileDao.java @@ -11,14 +11,14 @@ public interface FileDao { /** * 移动资源到指定目录下 * @param uid 用户ID - * @param nid 资源所属节点ID + * @param nid 原文件资源所属节点ID * @param targetNodeId 目标节点ID * @param name 文件名 * @return 受影响的行数 */ @Update("UPDATE file_table SET node=#{targetNodeId} WHERE uid=#{uid} AND node=#{nid} AND name=#{name}") - int move(@Param("uid") Integer uid, - @Param("nid") String nid, + int move(@Param("uid") Integer uid, + @Param("nid") String nid, @Param("targetNodeId") String targetNodeId, @Param("name") String name); @@ -117,7 +117,7 @@ public interface FileDao { * @param nodeId 文件所在节点ID * @return 文件信息 */ - @Select("SELECT name, size, md5 FROM file_table WHERE uid=#{uid} AND node=#{nodeId} AND name=#{name}") + @Select("SELECT name, size, md5, node FROM file_table WHERE uid=#{uid} AND node=#{nodeId} AND name=#{name}") FileInfo getFileInfo(@Param("uid") Integer uid, @Param("name") String name, @Param("nodeId") String nodeId); /** diff --git a/src/main/java/com/xiaotao/saltedfishcloud/dao/NodeDao.java b/src/main/java/com/xiaotao/saltedfishcloud/dao/NodeDao.java index e49446ca..e9e2a620 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/dao/NodeDao.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/dao/NodeDao.java @@ -66,7 +66,7 @@ public interface NodeDao { * @param name 目标节点名称 * @return 节点信息 */ - @Select("SELECT name, id, parent, uid parent FROM node_list WHERE uid = #{uid} AND parent = #{pid} AND name = #{name}") + @Select("SELECT name, id, parent, uid, parent FROM node_list WHERE uid = #{uid} AND parent = #{pid} AND name = #{name}") NodeInfo getNodeByParentId(@Param("uid") Integer uid, @Param("pid") String pid, @Param("name") String name); @Delete({ diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java index 10646baa..16ba4640 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java @@ -120,17 +120,48 @@ public class FileRecordService { * @param name 文件名 * @param overwrite 是否覆盖原文件信息 * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 - * @return 受影响行数 * @TODO 实现overwrite参数的效果 */ - public int move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { - String nid = nodeService.getLastNodeInfoByPath(uid, source).getId(); - String targetNodeId = nodeService.getLastNodeInfoByPath(uid, target).getId(); - FileInfo fileInfo = fileDao.getFileInfo(uid, name, nid); - if (fileInfo.isDir()) { - nodeDao.move(uid, fileInfo.getMd5(), targetNodeId); + public void move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { + NodeInfo sourceInfo = nodeService.getLastNodeInfoByPath(uid, source); + NodeInfo targetInfo = nodeService.getLastNodeInfoByPath(uid, target); + FileInfo sourceFileInfo = fileDao.getFileInfo(uid, name, sourceInfo.getId()); + FileInfo targetFileInfo = fileDao.getFileInfo(uid, name, targetInfo.getId()); + + if (sourceFileInfo.isDir()) { + if (targetFileInfo != null) { + // 当移动目录时存在同名文件或目录 + if (targetFileInfo.isDir()) { + // 目录 -> 目录 同名的是目录,则根据overwrite规则合并 + copy(uid, source, target, uid, name, name, overwrite); + deleteRecords(uid, source, Collections.singleton(name)); + } else { + // 目录 -> 文件 同名的是文件,不支持的操作,需要手动解决 + throw new UnsupportedOperationException("目标位置存在同名文件\"" + name + "\",无法移动"); + } + } else { + // 不存在同名目录,直接修改节点ID + fileDao.move(uid, sourceInfo.getId(), targetInfo.getId(), name); + nodeDao.move(uid, sourceFileInfo.getNode(), targetInfo.getId()); + } + } else { + if (targetFileInfo != null) { + // 当移动文件时存在同名文件或目录 + if (targetFileInfo.isFile()) { + // 文件 -> 文件,覆盖/删除 + if (overwrite) { + fileDao.updateRecord(uid, name, targetFileInfo.getNode(), sourceFileInfo.getSize(), sourceFileInfo.getMd5()); + } + fileDao.deleteRecord(uid, sourceFileInfo.getNode(), Collections.singletonList(name)); + } else if (targetFileInfo.isDir()){ + // 文件 -> 目录 不支持的操作,需要手动解决 + throw new UnsupportedOperationException("目标位置存在同名目录\"" + name + "\",无法移动"); + } + } else { + // 不存在同名文件,直接修改文件所属节点ID + fileDao.move(uid, sourceFileInfo.getNode(), targetInfo.getId(), name); + } } - return fileDao.move(uid, nid, targetNodeId, name); } /** @@ -222,10 +253,9 @@ public class FileRecordService { */ public void rename(int uid, String path, String oldName, String newName) throws NoSuchFileException { NodeInfo pathNodeInfo = nodeService.getLastNodeInfoByPath(uid, path); - NodeInfo nodeInfo = nodeService.getLastNodeInfoByPath(uid, path + "/" + oldName); FileInfo fileInfo = fileDao.getFileInfo(uid, oldName, pathNodeInfo.getId()); if (fileInfo.isDir()) { - nodeDao.changeName(uid, nodeInfo.getId(), newName); + nodeDao.changeName(uid, nodeDao.getNodeByParentId(uid, pathNodeInfo.getId(), oldName).getId(), newName); } fileDao.rename(uid, pathNodeInfo.getId(), oldName, newName); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index 1289a610..fa599afb 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -18,6 +18,7 @@ import com.xiaotao.saltedfishcloud.validator.FileNameValidator; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.dao.DuplicateKeyException; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,7 +68,6 @@ public class FileService { * @param overwrite * 是否覆盖 */ - @Transactional(rollbackFor = Exception.class) public void copy(int uid, String source, String target, int targetUid, String sourceName, String targetName, Boolean overwrite) throws IOException { FileNameValidator.valid(targetName, sourceName); if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target)) && sourceName.equals(targetName)) { @@ -87,19 +87,18 @@ public class FileService { * @param name 文件名 * @param overwrite 是否覆盖原文件 * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 - * @TODO 解决下面的FIXME - * @// FIXME: 2021/5/4 文件名带空格时会导致数据库与本地文件失去同步 */ - @Transactional(rollbackFor = Exception.class) public void move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { FileNameValidator.valid(name); try { + fileRecordService.move(uid, source, target, name, overwrite); storeService.move(uid, source, target, name, overwrite); + } catch (DuplicateKeyException e) { + throw new HasResultException(409, "目标目录下已存在 " + name + " 暂不支持目录合并或移动覆盖"); } catch (Exception e) { e.printStackTrace(); throw new HasResultException(404, "资源不存在"); } - fileRecordService.move(uid, source, target, name, overwrite); } /** diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java index 52b0195e..11db59cf 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java @@ -122,10 +122,23 @@ public class StoreService { BasicFileInfo fileInfo = new BasicFileInfo(name, null); Path sourcePath = Paths.get(pathHandler.getStorePath(uid, source, fileInfo)); Path targetPath = Paths.get(pathHandler.getStorePath(uid, target, fileInfo)); - if (overwrite) { + if (Files.exists(targetPath)) { + if (Files.isDirectory(sourcePath) != Files.isDirectory(targetPath)) { + throw new UnsupportedOperationException("文件类型不一致,无法移动"); + } + + if (Files.isDirectory(sourcePath)) { + // 目录则合并 + FileUtils.mergeDir(sourcePath.toString(), targetPath.toString(), overwrite); + } else if (overwrite){ + // 文件则替换移动(仅当overwrite为true时) + Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } else { + // 为了与数据库记录保持一致,原文件还是要删滴 + Files.delete(sourcePath); + } + } else { Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); - } else if (Files.exists(targetPath)) { - Files.move(sourcePath, targetPath); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java index 9c3ab11b..c1392d94 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/node/NodeService.java @@ -51,7 +51,7 @@ public class NodeService { String parent = link.isEmpty() ? "root" : link.getLast().getId(); NodeInfo info = nodeDao.getNodeByParentId(uid, parent, node); if (info == null) { - throw new NoSuchFileException("路径 " + path + " 不存在"); + throw new NoSuchFileException("路径 " + path + " 不存在,或目标节点信息已丢失"); } link.add(info); } @@ -67,7 +67,7 @@ public class NodeService { StringBuilder sb = new StringBuilder(); for (NodeInfo nodeInfo : link) { log.debug("nodeInfo:" + nodeInfo); - sb.append("/").append(nodeInfo.getName() == null ? nodeInfo.getName() : "").append('[').append(nodeInfo.getId()).append(']'); + sb.append("/").append(nodeInfo.getName() == null ? "" : nodeInfo.getName()).append('[').append(nodeInfo.getId()).append(']'); } log.debug(sb.toString()); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/utils/FileUtils.java b/src/main/java/com/xiaotao/saltedfishcloud/utils/FileUtils.java index b336a553..03f2879a 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/utils/FileUtils.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/utils/FileUtils.java @@ -2,6 +2,7 @@ package com.xiaotao.saltedfishcloud.utils; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.file.DirCollection; +import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.FileOutputStream; @@ -9,8 +10,11 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; import java.util.HashMap; +import java.util.function.Consumer; +@Slf4j public class FileUtils { private static final HashMap map = new HashMap<>(); static { @@ -122,4 +126,46 @@ public class FileUtils { } return res; } + + /** + * 合并两个本地目录的内容(包括子目录和文件,文件同名将被覆盖) + * @param source 源目录,合并完成后将被删除 + * @param target 被合并到的位置 + * @param overwrite 是否覆盖已有文件(若为false,源文件和目录将仍被删除) + */ + public static void mergeDir(String source, String target, boolean overwrite) throws IOException { + DirCollection sourceCollection = scanDir(source); + if (!Files.exists(Paths.get(target))) { + throw new NoSuchFileException(target); + } + + // 检查子目录和文件同名情况下类型是否一致 + Consumer consumer = file -> { + Path p = Paths.get(target + "/" + StringUtils.removePrefix(source, file.getPath())); + if (Files.exists(p)) { + if (file.isDirectory() != Files.isDirectory(p)) { + throw new UnsupportedOperationException("已存在文件与被移动文件类型不一致: " + file.getName()); + } + } + }; + sourceCollection.getFileList().forEach(consumer); + sourceCollection.getDirList().forEach(consumer); + + for (File file : sourceCollection.getDirList()) { + Path p = Paths.get(target + "/" + StringUtils.removePrefix(source, file.getPath())); + if (!Files.exists(p)) Files.createDirectory(p); + } + + for (File file : sourceCollection.getFileList()) { + Path p = Paths.get(target + "/" + StringUtils.removePrefix(source, file.getPath())); + if (overwrite) Files.move(Paths.get(file.getPath()), p, StandardCopyOption.REPLACE_EXISTING); + else file.delete(); + log.debug("move " + file.getPath() + " -> " + p); + } + + // 删除源文件夹 + Collections.reverse(sourceCollection.getDirList()); + sourceCollection.getDirList().forEach(File::delete); + Files.delete(Paths.get(source)); + } } -- Gitee From e130cfe6a925434cd9441a9345b28aa59c5c59a9 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Fri, 7 May 2021 20:29:55 +0800 Subject: [PATCH 10/13] =?UTF-8?q?feat=20=E4=BD=BF=E7=94=A8=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E8=BF=9B=E8=A1=8C=E8=A1=A8=E5=8D=95=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E6=81=B6=E6=84=8F=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++++ .../controller/advice/ControllerAdvice.java | 17 +++++++++ .../controller/demo/Hello.java | 14 -------- .../controller/file/CopyController.java | 25 +++++++------ .../controller/file/DeleteController.java | 7 ++-- .../controller/file/MkdirController.java | 6 +++- .../controller/file/MoveController.java | 13 ++++--- .../controller/file/ResourcesController.java | 7 ++-- .../controller/file/UploadController.java | 2 ++ .../po/param/FileCopyOrMoveInfo.java | 22 ++++++++++++ .../po/{file => param}/FileNameList.java | 8 ++++- .../saltedfishcloud/po/param/NamePair.java | 19 ++++++++++ .../service/file/FileService.java | 7 ---- .../validator/FileNameValidator.java | 19 ---------- .../validator/custom/FileName.java | 17 +++++++++ .../validator/custom/FileNameValidator.java | 35 +++++++++++++++++++ .../validator/custom/RejectRegex.java | 6 ++++ .../validator/custom/ValidPath.java | 23 ++++++++++++ .../validator/custom/ValidPathValidator.java | 27 ++++++++++++++ 19 files changed, 215 insertions(+), 65 deletions(-) delete mode 100644 src/main/java/com/xiaotao/saltedfishcloud/controller/demo/Hello.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java rename src/main/java/com/xiaotao/saltedfishcloud/po/{file => param}/FileNameList.java (38%) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/po/param/NamePair.java delete mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileName.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileNameValidator.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/custom/RejectRegex.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPath.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPathValidator.java diff --git a/pom.xml b/pom.xml index c6f4347c..4210095b 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,12 @@ jjwt 0.9.0 + + org.springframework.boot + spring-boot-starter-validation + 2.4.5 + pom + diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java index 8b3c7641..865bb733 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/advice/ControllerAdvice.java @@ -5,9 +5,12 @@ import com.xiaotao.saltedfishcloud.po.JsonResult; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import javax.validation.ConstraintViolationException; import java.io.FileNotFoundException; import java.nio.file.NoSuchFileException; @@ -17,6 +20,20 @@ import java.nio.file.NoSuchFileException; @Slf4j @RestControllerAdvice public class ControllerAdvice { + @ExceptionHandler(MethodArgumentNotValidException.class) + public JsonResult validFormError(MethodArgumentNotValidException e) { + BindingResult bindingResult = e.getBindingResult(); + StringBuilder sb = new StringBuilder(); + bindingResult.getFieldErrors().forEach(error -> sb.append(error.getDefaultMessage()).append(";")); + return JsonResult.getInstance(422, null, sb.toString()); + } + + @ExceptionHandler(ConstraintViolationException.class) + public JsonResult validFieldError(ConstraintViolationException e) { + return JsonResult.getInstance(422, null, e.getMessage()); + } + + @ExceptionHandler(HasResultException.class) public JsonResult handle(HasResultException e) { return e.getJsonResult(); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/demo/Hello.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/demo/Hello.java deleted file mode 100644 index f12ba74e..00000000 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/demo/Hello.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.xiaotao.saltedfishcloud.controller.demo; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class Hello { - @GetMapping("/hello") - public String hello(@RequestParam("a") Integer a - , @RequestParam("b") Integer b) { - return a+b+""; - } -} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java index 31d0ad61..196b161e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java @@ -1,9 +1,12 @@ package com.xiaotao.saltedfishcloud.controller.file; import com.xiaotao.saltedfishcloud.po.JsonResult; +import com.xiaotao.saltedfishcloud.po.param.FileCopyOrMoveInfo; +import com.xiaotao.saltedfishcloud.po.param.NamePair; import com.xiaotao.saltedfishcloud.service.file.FileService; -import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; +import com.xiaotao.saltedfishcloud.validator.UIDValidator; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -19,23 +22,19 @@ public class CopyController { /** * 复制文件或目录到指定目录下 - * @param uid 用户ID - * @param name 文件名 - * @param target 目标目录 - * @param targetName 源目标被复制后的名称 - * @param overwrite 覆盖同名文件 * @TODO 使用数组传入需要复制的文件名以替代并发请求接口实现多文件粘贴的方式 */ @PostMapping("/copy/{uid}/**") - public JsonResult copy(@PathVariable("uid") int uid, - @RequestParam("name") String name, - @RequestParam("target") String target, - @RequestParam("targetName") String targetName, - @RequestParam(value = "overwrite", defaultValue = "true") Boolean overwrite, - HttpServletRequest request) throws IOException { + public JsonResult copy( @PathVariable("uid") int uid, + @RequestBody @Validated FileCopyOrMoveInfo info, + HttpServletRequest request) throws IOException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/copy/" + uid, request); - fileService.copy(uid, source, target, uid, name, targetName, overwrite); + boolean overwrite = info.isOverwrite(); + String target = info.getTarget(); + for (NamePair file : info.getFiles()) { + fileService.copy(uid, source, target, uid, file.getSource(), file.getTarget(), overwrite); + } return JsonResult.getInstance(); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java index 188cb27f..65bc6422 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/DeleteController.java @@ -1,12 +1,12 @@ package com.xiaotao.saltedfishcloud.controller.file; -import com.xiaotao.saltedfishcloud.po.file.FileNameList; -import com.xiaotao.saltedfishcloud.po.User; +import com.xiaotao.saltedfishcloud.po.param.FileNameList; import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -26,8 +26,7 @@ public class DeleteController { @Transactional(rollbackFor = Exception.class) public JsonResult delete(HttpServletRequest request, @PathVariable int uid, - @RequestBody FileNameList fileName, - @RequestAttribute User user) throws NoSuchFileException { + @RequestBody @Validated FileNameList fileName) throws NoSuchFileException { UIDValidator.validate(uid, true); String path = URLUtils.getRequestFilePath("/api/resource/" + uid, request); long res = fileService.deleteFile(uid, path, fileName.getFileName()); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java index cc7ab565..af3215ef 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MkdirController.java @@ -2,6 +2,7 @@ package com.xiaotao.saltedfishcloud.controller.file; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; @@ -9,7 +10,9 @@ import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; +import com.xiaotao.saltedfishcloud.validator.custom.FileName; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,6 +26,7 @@ import java.nio.file.NoSuchFileException; */ @RestController @RequestMapping("/api") +@Validated public class MkdirController { @Resource FileService fileService; @@ -31,7 +35,7 @@ public class MkdirController { @Transactional(rollbackFor = Exception.class) public JsonResult mkdir(@PathVariable int uid, HttpServletRequest request, - @RequestParam("name") String name) throws HasResultException, NoSuchFileException { + @RequestParam("name") @Valid @FileName String name) throws HasResultException, NoSuchFileException { UIDValidator.validate(uid, true); String prefix = "/api/mkdir/" + uid; String requestPath = URLUtils.getRequestFilePath(prefix, request); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java index ed953e7e..622feb60 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java @@ -2,6 +2,7 @@ package com.xiaotao.saltedfishcloud.controller.file; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; @@ -9,7 +10,10 @@ import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; +import com.xiaotao.saltedfishcloud.validator.custom.FileName; +import com.xiaotao.saltedfishcloud.validator.custom.ValidPath; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,6 +29,7 @@ import java.nio.file.NoSuchFileException; */ @RestController @RequestMapping("/api") +@Validated public class MoveController { @Resource private FileService fileService; @@ -33,8 +38,8 @@ public class MoveController { @Transactional(rollbackFor = Exception.class) public JsonResult rename(HttpServletRequest request, @PathVariable int uid, - @RequestParam("oldName") String oldName, - @RequestParam("newName") String newName) throws HasResultException, NoSuchFileException { + @RequestParam("oldName") @Valid @FileName String oldName, + @RequestParam("newName") @Valid @FileName String newName) throws HasResultException, NoSuchFileException { UIDValidator.validate(uid, true); String from = URLUtils.getRequestFilePath("/api/rename/" + uid, request); if (newName.length() < 1) { @@ -54,8 +59,8 @@ public class MoveController { @PostMapping("/move/{uid}/**") public JsonResult move(HttpServletRequest request, @PathVariable("uid") int uid, - @RequestParam("name") String name, - @RequestParam("target") String target, + @RequestParam("name") @Valid @FileName String name, + @RequestParam("target") @Valid @ValidPath String target, @RequestParam(value = "overwrite", defaultValue = "true") Boolean overwrite) throws UnsupportedEncodingException, NoSuchFileException { UIDValidator.validate(uid); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java index f7075cd2..86f6862c 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/ResourcesController.java @@ -6,6 +6,7 @@ import java.util.List; import javax.annotation.Resource; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; import com.fasterxml.jackson.core.JsonProcessingException; import com.github.pagehelper.PageHelper; @@ -19,6 +20,8 @@ import com.xiaotao.saltedfishcloud.service.node.NodeService; import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; +import com.xiaotao.saltedfishcloud.validator.custom.FileName; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -31,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/api/resource") +@Validated public class ResourcesController { @Resource FileService fileService; @@ -85,12 +89,11 @@ public class ResourcesController { public JsonResult getDC(@PathVariable int uid, HttpServletRequest request, @RequestParam("md5") String md5, - @RequestParam("name") String name, + @RequestParam("name") @Valid @FileName String name, @RequestParam(value = "expr", defaultValue = "1") int expr) throws JsonProcessingException { UIDValidator.validate(uid); String filePath = URLUtils.getRequestFilePath("/api/resource/getFDC/" + uid, request); BasicFileInfo fileInfo = new BasicFileInfo(name, md5); - System.out.println(expr); String dc = fileService.getFileDC(uid, filePath, fileInfo, expr); return JsonResult.getInstance(dc); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java index 59d12384..f10b2685 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/UploadController.java @@ -12,6 +12,7 @@ import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -24,6 +25,7 @@ import org.springframework.web.multipart.MultipartFile; */ @RestController @RequestMapping(value = "/api", method = RequestMethod.PUT) +@Validated public class UploadController { @Resource FileService fileService; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java new file mode 100644 index 00000000..0249400d --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java @@ -0,0 +1,22 @@ +package com.xiaotao.saltedfishcloud.po.param; + +import com.xiaotao.saltedfishcloud.validator.custom.ValidPath; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.util.List; + + +@Data +public class FileCopyOrMoveInfo { + + @NotEmpty(message = "缺少参数target") + @ValidPath + private String target; + private boolean overwrite; + + @Valid + @NotEmpty(message = "缺少参数files") + private List files; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/po/file/FileNameList.java b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileNameList.java similarity index 38% rename from src/main/java/com/xiaotao/saltedfishcloud/po/file/FileNameList.java rename to src/main/java/com/xiaotao/saltedfishcloud/po/param/FileNameList.java index 6ba0446b..d235d89e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/po/file/FileNameList.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileNameList.java @@ -1,10 +1,16 @@ -package com.xiaotao.saltedfishcloud.po.file; +package com.xiaotao.saltedfishcloud.po.param; +import com.xiaotao.saltedfishcloud.validator.custom.FileName; import lombok.Data; import java.util.List; @Data public class FileNameList { + @FileName private List fileName; + + public int length() { + return fileName.size(); + } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/po/param/NamePair.java b/src/main/java/com/xiaotao/saltedfishcloud/po/param/NamePair.java new file mode 100644 index 00000000..d6323413 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/po/param/NamePair.java @@ -0,0 +1,19 @@ +package com.xiaotao.saltedfishcloud.po.param; + +import com.xiaotao.saltedfishcloud.validator.custom.FileName; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; + +@Data +public class NamePair { + + @NotEmpty(message = "files[].source不得为空") + @FileName + private String source; + + @NotEmpty(message = "files[].target不得为空") + @FileName + private String target; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index fa599afb..e20ebe24 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -14,14 +14,12 @@ import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.service.node.NodeService; import com.xiaotao.saltedfishcloud.utils.FileUtils; import com.xiaotao.saltedfishcloud.utils.JwtUtils; -import com.xiaotao.saltedfishcloud.validator.FileNameValidator; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.dao.DuplicateKeyException; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.File; @@ -69,7 +67,6 @@ public class FileService { * 是否覆盖 */ public void copy(int uid, String source, String target, int targetUid, String sourceName, String targetName, Boolean overwrite) throws IOException { - FileNameValidator.valid(targetName, sourceName); if (PathBuilder.formatPath(source).equals(PathBuilder.formatPath(target)) && sourceName.equals(targetName)) { throw new IllegalArgumentException("无法原地复制"); } @@ -89,7 +86,6 @@ public class FileService { * @throws NoSuchFileException 当原目录或目标目录不存在时抛出 */ public void move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { - FileNameValidator.valid(name); try { fileRecordService.move(uid, source, target, name, overwrite); storeService.move(uid, source, target, name, overwrite); @@ -248,7 +244,6 @@ public class FileService { * @throws NoSuchFileException 当目标目录不存在时抛出 */ public void mkdir(int uid, String path, String name) throws HasResultException, NoSuchFileException { - FileNameValidator.valid(name); if ( !storeService.mkdir(uid, path, name) ) { throw new HasResultException("在" + path + "创建文件夹失败"); } @@ -264,7 +259,6 @@ public class FileService { * @return 删除的数量 */ public long deleteFile(int uid, String path, List name) throws NoSuchFileException { - name.forEach(FileNameValidator::valid); // 计数删除数 long res = 0L; fileRecordService.deleteRecords(uid, path, name); @@ -281,7 +275,6 @@ public class FileService { * @param newName 新文件名 */ public void rename(int uid, String path, String name, String newName) throws HasResultException, NoSuchFileException { - FileNameValidator.valid(name, newName); fileRecordService.rename(uid, path, name, newName); storeService.rename(uid, path, name, newName); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java deleted file mode 100644 index 3492bbf0..00000000 --- a/src/main/java/com/xiaotao/saltedfishcloud/validator/FileNameValidator.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.xiaotao.saltedfishcloud.validator; - -import java.util.regex.Pattern; - -public class FileNameValidator { - private final static Pattern pattern = Pattern.compile("(^\\.+$)|(\\\\)|(/)", Pattern.MULTILINE); - - /** - * 验证是否为合法的文件名(不允许出现 - * @param fileName 文件名 - */ - public static void valid(CharSequence ...fileName) { - for (CharSequence name : fileName) { - if (pattern.matcher(name).find() || name.length() > 255) { - throw new IllegalArgumentException("不合法的文件名"); - } - } - } -} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileName.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileName.java new file mode 100644 index 00000000..05bb9b09 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileName.java @@ -0,0 +1,17 @@ +package com.xiaotao.saltedfishcloud.validator.custom; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Documented +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = FileNameValidator.class) +public @interface FileName { + String message() default "非法文件名,不可包含/\\<>?|:或文件名为.."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileNameValidator.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileNameValidator.java new file mode 100644 index 00000000..7c88d291 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/FileNameValidator.java @@ -0,0 +1,35 @@ +package com.xiaotao.saltedfishcloud.validator.custom; + +import lombok.extern.slf4j.Slf4j; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; +import java.util.regex.Pattern; + +@Slf4j +public class FileNameValidator implements ConstraintValidator { + private final Pattern pattern = Pattern.compile(RejectRegex.FILE_NAME); + @Override + public void initialize(FileName constraintAnnotation) { + } + + @Override + @SuppressWarnings("unchecked") + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (value instanceof List && ((List)value).get(0) instanceof CharSequence ) { + for(CharSequence name : (List)value) { + if (!valid(name)) { + return false; + } + } + } else if (value instanceof CharSequence) { + return valid((CharSequence)value); + } + return true; + } + + public boolean valid(CharSequence input) { + return !pattern.matcher(input).find(); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/RejectRegex.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/RejectRegex.java new file mode 100644 index 00000000..b3ff505e --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/RejectRegex.java @@ -0,0 +1,6 @@ +package com.xiaotao.saltedfishcloud.validator.custom; + +public class RejectRegex { + public final static String FILE_NAME = "[\\\\/:*\"<>|?]|(^\\.{1,2}$)"; + public final static String PATH = "(\\\\|/)\\.\\.(\\\\|/)|(\\\\|/)\\.\\.$|^\\.\\.(\\\\|/)"; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPath.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPath.java new file mode 100644 index 00000000..bc4ee2a6 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPath.java @@ -0,0 +1,23 @@ +package com.xiaotao.saltedfishcloud.validator.custom; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + + + +/** + * 验证字段是否是一个合法的文件路径 + * 包含/../或以/..结尾或以../开头的字符串会认作不合法 + */ +@Documented +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = ValidPathValidator.class) +public @interface ValidPath { + String message() default "非法的路径,不得包含/../或使用/..结尾"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPathValidator.java b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPathValidator.java new file mode 100644 index 00000000..294c9b3a --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/validator/custom/ValidPathValidator.java @@ -0,0 +1,27 @@ +package com.xiaotao.saltedfishcloud.validator.custom; + +import com.sun.xml.txw2.IllegalAnnotationException; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Pattern; + +public class ValidPathValidator implements ConstraintValidator { + private final static Pattern pattern = Pattern.compile(RejectRegex.PATH); + public boolean valid(CharSequence input) { + return !pattern.matcher(input).find(); + } + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (value instanceof CharSequence) { + return valid((CharSequence)value); + } else { + throw new IllegalAnnotationException("该验证注解只能使用在CharSequence的实现类字段上,错误的字段类型:" + value.getClass().getName()); + } + } + + @Override + public void initialize(ValidPath constraintAnnotation) { + + } +} -- Gitee From 67f095b1361e0523c996cc5e6c8d0fdf9d3c4d49 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Fri, 7 May 2021 20:45:36 +0800 Subject: [PATCH 11/13] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8D=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=8D=E5=88=B6=E6=8E=A5=E5=8F=A3=E6=9C=AA=E8=BF=9B?= =?UTF-8?q?=E8=A1=8CURL=E7=BC=96=E7=A0=81=E8=A7=A3=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saltedfishcloud/controller/file/CopyController.java | 3 +-- .../saltedfishcloud/controller/file/MoveController.java | 3 --- .../saltedfishcloud/po/param/FileCopyOrMoveInfo.java | 6 ++++++ .../xiaotao/saltedfishcloud/service/file/FileService.java | 7 +++++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java index 196b161e..a3ba3df7 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java @@ -30,10 +30,9 @@ public class CopyController { HttpServletRequest request) throws IOException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/copy/" + uid, request); - boolean overwrite = info.isOverwrite(); String target = info.getTarget(); for (NamePair file : info.getFiles()) { - fileService.copy(uid, source, target, uid, file.getSource(), file.getTarget(), overwrite); + fileService.copy(uid, source, target, uid, file.getSource(), file.getTarget(), info.isOverwrite()); } return JsonResult.getInstance(); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java index 622feb60..107ff8d2 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java @@ -66,9 +66,6 @@ public class MoveController { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/move/" + uid, request); target = URLDecoder.decode(target, "UTF-8"); - if (source.equals(target)) { - throw new HasResultException(400, "不能原处移动"); - } fileService.move(uid, source, target, name, overwrite); return JsonResult.getInstance(source); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java index 0249400d..4795118a 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java @@ -5,6 +5,8 @@ import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.util.List; @@ -19,4 +21,8 @@ public class FileCopyOrMoveInfo { @Valid @NotEmpty(message = "缺少参数files") private List files; + + public String getTarget() throws UnsupportedEncodingException { + return URLDecoder.decode(target, "UTF-8"); + } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index e20ebe24..bd549a2f 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -27,6 +27,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; +import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -87,10 +88,16 @@ public class FileService { */ public void move(int uid, String source, String target, String name, boolean overwrite) throws NoSuchFileException { try { + target = URLDecoder.decode(target, "UTF-8"); + if (PathBuilder.formatPath(target).equals(PathBuilder.formatPath(source))) { + throw new IllegalArgumentException("无法原地移动"); + } fileRecordService.move(uid, source, target, name, overwrite); storeService.move(uid, source, target, name, overwrite); } catch (DuplicateKeyException e) { throw new HasResultException(409, "目标目录下已存在 " + name + " 暂不支持目录合并或移动覆盖"); + } catch (UnsupportedEncodingException e) { + throw new HasResultException(400, "不支持的编码(请使用UTF-8)"); } catch (Exception e) { e.printStackTrace(); throw new HasResultException(404, "资源不存在"); -- Gitee From 322bb10b6389e59a9e83e0513e49be7c46c7031d Mon Sep 17 00:00:00 2001 From: xiaotao Date: Fri, 7 May 2021 21:06:23 +0800 Subject: [PATCH 12/13] =?UTF-8?q?feat=20=E7=BB=9F=E4=B8=80=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E4=B8=8E=E7=A7=BB=E5=8A=A8=E6=8E=A5=E5=8F=A3=E7=9A=84?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E4=BD=93=E5=8F=82=E6=95=B0=E5=B9=B6=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=B8=80=E6=AC=A1=E6=93=8D=E4=BD=9C=E5=90=8C=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E4=B8=8B=E7=9A=84=E5=A4=9A=E4=B8=AA=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/file/CopyController.java | 3 +- .../controller/file/MoveController.java | 23 ++++----- .../po/param/FileCopyOrMoveInfo.java | 6 --- .../service/file/FileService.java | 2 + ...3\206\345\220\210.postman_collection.json" | 49 ++++++++----------- 5 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java index a3ba3df7..6f29bd62 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/CopyController.java @@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.net.URLDecoder; @RestController @RequestMapping("/api") @@ -30,7 +31,7 @@ public class CopyController { HttpServletRequest request) throws IOException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/copy/" + uid, request); - String target = info.getTarget(); + String target = URLDecoder.decode(info.getTarget(), "UTF-8"); for (NamePair file : info.getFiles()) { fileService.copy(uid, source, target, uid, file.getSource(), file.getTarget(), info.isOverwrite()); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java index 107ff8d2..5a9963a2 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/file/MoveController.java @@ -6,6 +6,8 @@ import javax.validation.Valid; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; +import com.xiaotao.saltedfishcloud.po.param.FileCopyOrMoveInfo; +import com.xiaotao.saltedfishcloud.po.param.NamePair; import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.validator.UIDValidator; import com.xiaotao.saltedfishcloud.utils.URLUtils; @@ -14,15 +16,12 @@ import com.xiaotao.saltedfishcloud.validator.custom.FileName; import com.xiaotao.saltedfishcloud.validator.custom.ValidPath; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.file.NoSuchFileException; +import java.util.List; /** * 移动/重命名资源控制器 @@ -52,21 +51,19 @@ public class MoveController { /** * 移动文件或目录到指定目录下 * @param uid 用户ID - * @param name 文件名 - * @param target 目标目录 * @TODO 使用数组传入需要移动的文件名以替代并发请求接口实现多文件粘贴的方式 */ @PostMapping("/move/{uid}/**") public JsonResult move(HttpServletRequest request, @PathVariable("uid") int uid, - @RequestParam("name") @Valid @FileName String name, - @RequestParam("target") @Valid @ValidPath String target, - @RequestParam(value = "overwrite", defaultValue = "true") Boolean overwrite) + @RequestBody @Valid FileCopyOrMoveInfo info) throws UnsupportedEncodingException, NoSuchFileException { UIDValidator.validate(uid); String source = URLUtils.getRequestFilePath("/api/move/" + uid, request); - target = URLDecoder.decode(target, "UTF-8"); - fileService.move(uid, source, target, name, overwrite); - return JsonResult.getInstance(source); + String target = URLDecoder.decode(info.getTarget(), "UTF-8"); + for (NamePair file : info.getFiles()) { + fileService.move(uid, source, target, file.getSource(), info.isOverwrite()); + } + return JsonResult.getInstance(); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java index 4795118a..0249400d 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/po/param/FileCopyOrMoveInfo.java @@ -5,8 +5,6 @@ import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.util.List; @@ -21,8 +19,4 @@ public class FileCopyOrMoveInfo { @Valid @NotEmpty(message = "缺少参数files") private List files; - - public String getTarget() throws UnsupportedEncodingException { - return URLDecoder.decode(target, "UTF-8"); - } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java index bd549a2f..9af9b8ae 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileService.java @@ -98,6 +98,8 @@ public class FileService { throw new HasResultException(409, "目标目录下已存在 " + name + " 暂不支持目录合并或移动覆盖"); } catch (UnsupportedEncodingException e) { throw new HasResultException(400, "不支持的编码(请使用UTF-8)"); + } catch (IllegalArgumentException e) { + throw new HasResultException(422, e.getMessage()); } catch (Exception e) { e.printStackTrace(); throw new HasResultException(404, "资源不存在"); diff --git "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" index 262a06c1..e6690f86 100644 --- "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" +++ "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" @@ -964,17 +964,28 @@ "request": { "method": "POST", "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"target\": \"/\",\r\n \"files\":[\r\n {\r\n \"source\": \"123\",\r\n \"target\": \"f1233\"\r\n },\r\n {\r\n \"source\": \"123\",\r\n \"target\": \"newf233\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{addr}}/api/move/", + "raw": "{{addr}}/api/move/42/", "host": [ "{{addr}}" ], "path": [ "api", "move", + "42", "" ] - } + }, + "description": "### 复制文件或目录\r\n- API:`/api/move/{用户ID}/{原文件所在路径}` \r\n\r\n此API使用JSON作为请求体传参\r\n\r\n#### 请求体结构\r\n```json\r\n{\r\n \"target\":\"/abcd/\", // 要移动的文件名\r\n \"files\":[ // 暂时无用\r\n {\r\n \"source\":\"文件名\", \r\n \"target\":\"文件名\" \r\n },\r\n {\r\n \"source\":\"file1\",\r\n \"target\":\"file1\"\r\n }\r\n ]\r\n}\r\n```" }, "response": [] }, @@ -984,33 +995,13 @@ "method": "POST", "header": [], "body": { - "mode": "formdata", - "formdata": [ - { - "key": "name", - "value": "f1", - "description": "要复制的文件或目录名", - "type": "text" - }, - { - "key": "target", - "value": "/2312", - "description": "复制到的目标目录名", - "type": "text" - }, - { - "key": "overwrite", - "value": "true", - "description": "是否覆盖原文件(可选,默认true)", - "type": "text" - }, - { - "key": "targetName", - "value": "newf1", - "description": "复制后的文件或目录名", - "type": "text" + "mode": "raw", + "raw": "{\r\n \"target\": \"/././\",\r\n \"files\":[\r\n {\r\n \"source\": \"f1\",\r\n \"target\": \"f1233\"\r\n },\r\n {\r\n \"source\": \"newf1\",\r\n \"target\": \"newf233\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" } - ] + } }, "url": { "raw": "{{addr}}/api/copy/42/", @@ -1024,7 +1015,7 @@ "" ] }, - "description": "### 复制文件或目录\r\n- API:`/api/copy/{用户ID}/{文件所在路径}` \r\n\r\n#### 参数列表\r\n- name: 要复制的文件或目录名\r\n- target: 复制到的目标目录名\r\n- targetName: 复制后的文件或目录名,可利用该参数实现目标文件重命名\r\n- overwrite: 是否覆盖原文件(可选参数,可选值为false或true,默认true)\r\n\r\n#### 响应data\r\n- 下载码,用于'使用下载码下载文件'接口" + "description": "### 复制文件或目录\r\n- API:`/api/copy/{用户ID}/{原文件所在路径}` \r\n\r\n此API使用JSON作为请求体传参\r\n\r\n#### 请求体结构\r\n```json\r\n{\r\n \"target\":\"/abcd/\", // 粘贴目录\r\n \"files\":[ // 要操作的文件列表\r\n {\r\n \"source\":\"旧文件名\", // 被复制的文件名\r\n \"target\":\"新文件名\" // 粘贴后的文件名(可重命名)\r\n },\r\n {\r\n \"source\":\"file1\",\r\n \"target\":\"file2\"\r\n }\r\n ]\r\n}\r\n```" }, "response": [] } -- Gitee From 1723ec90c4e7098910158a2f6408fcb3de0b2d03 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 8 May 2021 16:22:01 +0800 Subject: [PATCH 13/13] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8D=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=96=87=E4=BB=B6=E5=A4=B9=E5=90=8E=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=E8=8A=82=E7=82=B9=E4=BF=A1=E6=81=AF=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=E7=9A=84=E8=87=B4=E5=91=BDbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiaotao/saltedfishcloud/service/file/FileRecordService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java index 16ba4640..b3efbd3f 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/FileRecordService.java @@ -142,7 +142,7 @@ public class FileRecordService { } else { // 不存在同名目录,直接修改节点ID fileDao.move(uid, sourceInfo.getId(), targetInfo.getId(), name); - nodeDao.move(uid, sourceFileInfo.getNode(), targetInfo.getId()); + nodeDao.move(uid, sourceFileInfo.getMd5(), targetInfo.getId()); } } else { if (targetFileInfo != null) { -- Gitee