From 7a29eda7e93c461381a5b4844e883ff6d66fed3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=AE=81?= <64892242+Ning19230223@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:40:10 +0800 Subject: [PATCH] =?UTF-8?q?FileServer=E6=A8=A1=E5=9D=97=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E9=9D=A2=E5=90=91Java=E5=B1=82=E9=9D=A2=E7=9A=84CRUD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8F=8A=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 面向Java层文件CRUD功能,增加相应的单元测试 * 优化下载文件后,文件副本残留的问题 * 优化下载文件后,文件副本残留的问题 * 增加CRUD单元测试,更新原有http服务测试 --- .../muyun/test/fileserver/TestFileCRUD.java | 98 +++++++ .../muyun/test/fileserver/TestFileServer.java | 12 +- .../muyun/fileserver/FileInfoEntity.java | 26 +- .../ximatai/muyun/fileserver/FileService.java | 258 +++++++++--------- .../muyun/fileserver/IFileService.java | 13 + 5 files changed, 258 insertions(+), 149 deletions(-) create mode 100644 muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileCRUD.java create mode 100644 muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/IFileService.java diff --git a/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileCRUD.java b/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileCRUD.java new file mode 100644 index 00000000..e8fa1d4b --- /dev/null +++ b/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileCRUD.java @@ -0,0 +1,98 @@ +package net.ximatai.muyun.test.fileserver; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.vertx.core.json.JsonObject; +import jakarta.inject.Inject; +import net.ximatai.muyun.fileserver.FileInfoEntity; +import net.ximatai.muyun.fileserver.FileServerConfig; +import net.ximatai.muyun.fileserver.IFileService; +import net.ximatai.muyun.test.testcontainers.PostgresTestResource; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@QuarkusTest +@QuarkusTestResource(value = PostgresTestResource.class, restrictToAnnotatedClass = true) +public class TestFileCRUD { + + @Inject + IFileService service; + + @Inject + FileServerConfig config; + + @Test + void testCRUD() throws IOException, InterruptedException { + // save() + int fileNameInt = getRandomInt(); + String fileName = String.valueOf(fileNameInt) + ".txt"; + File tempFile = File.createTempFile(fileName, ".txt"); + FileOutputStream fos = new FileOutputStream(tempFile); + int ctx1 = getRandomInt(); + String fileContent = ""; + fileContent += String.valueOf(ctx1 + "\n"); + int ctx2 = getRandomInt(); + fileContent += String.valueOf(ctx2); + fos.write(fileContent.getBytes()); + fos.close(); + String id = service.save(tempFile, fileName); + String filePathWithN = config.uploadPath() + id + "-n"; + String filePathWithO = config.uploadPath() + id + "-o"; + Path pathN = Paths.get(filePathWithN); + Path pathO = Paths.get(filePathWithO); + Thread.sleep(1000); + assertTrue(Files.exists(pathN)); + assertTrue(Files.exists(pathO)); + String name = Files.readString(pathN); + String content = Files.readString(pathO); + assertEquals(fileName, name); + assertEquals(fileContent, content); + + // get() + File file = service.get(id); + if (file == null) System.out.println("file is null"); + assert file != null; + String newFileName = file.getName(); + assertEquals(fileName, newFileName); + String newFileContent = Files.readString(Paths.get(file.getPath())); + assertEquals(fileContent, newFileContent); + + // info + FileInfoEntity entity = service.info(id); + JsonObject jsonObject = entity.toJson(); + String infoName = jsonObject.getString("name"); + long infoSize = jsonObject.getLong("size"); + String infoSuffix = jsonObject.getString("suffix"); + assertEquals(fileName, infoName); + assertEquals(fileContent.length(), infoSize); + assertEquals("txt", infoSuffix); + + // delete() + boolean isDeleted = service.delete(id); + assertTrue(isDeleted); + boolean isDeleted2 = file.delete(); + assertTrue(isDeleted2); + } + + /** + * @return 返回一个随机的正数 + */ + int getRandomInt() { + Random rand = new Random(); + int num = rand.nextInt(); + while (num < 1000000000) { + num = rand.nextInt(); + } + return num; + } +} diff --git a/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileServer.java b/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileServer.java index 1d389b06..41fbe77a 100644 --- a/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileServer.java +++ b/muyun-boot/src/test/java/net/ximatai/muyun/test/fileserver/TestFileServer.java @@ -23,7 +23,7 @@ public class TestFileServer { // 文件名 String fileName; - String uid; + String id; // 文件内容 String fileContent = ""; // 临时文件 @@ -49,19 +49,19 @@ void testFileProcess() { Response response = given() .multiPart("file", tempFile) .when() - .post("/fileServer/form") + .post("/fileServer/upload") .then() .log().all() .statusCode(200) .extract() .response(); - uid = response.getBody().asString(); + id = response.getBody().asString(); // 下载文件 Response response2 = given() .when() - .get("/fileServer/download/" + uid) + .get("/fileServer/download/" + id) .then() .log().all() .statusCode(200) @@ -75,7 +75,7 @@ void testFileProcess() { // 读取文件info Response response3 = given() .when() - .get("/fileServer/info/" + uid) + .get("/fileServer/info/" + id) .then() .log().all() .statusCode(200) @@ -89,7 +89,7 @@ void testFileProcess() { @AfterEach public void tearDown() { - String deleteUrl = "/fileServer/delete/" + uid; + String deleteUrl = "/fileServer/delete/" + id; given() .when() .get(deleteUrl) diff --git a/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileInfoEntity.java b/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileInfoEntity.java index 632a2fe9..b7ffbbb2 100644 --- a/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileInfoEntity.java +++ b/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileInfoEntity.java @@ -1,20 +1,22 @@ package net.ximatai.muyun.fileserver; +import io.vertx.core.json.JsonObject; + public class FileInfoEntity { String name; long size; String suffix; - String uid; + String id; String time; public FileInfoEntity() { } - public FileInfoEntity(String name, long size, String suffix, String uid, String time) { + public FileInfoEntity(String name, long size, String suffix, String id, String time) { this.name = name; this.size = size; this.suffix = suffix; - this.uid = uid; + this.id = id; this.time = time; } @@ -42,12 +44,12 @@ public void setSuffix(String suffix) { this.suffix = suffix; } - public String getUid() { - return uid; + public String getId() { + return id; } - public void setUid(String uid) { - this.uid = uid; + public void setId(String id) { + this.id = id; } public String getTime() { @@ -57,4 +59,14 @@ public String getTime() { public void setTime(String time) { this.time = time; } + + public JsonObject toJson() { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("name", name) + .put("size", size) + .put("suffix", suffix) + .put("id", id) + .put("time", time); + return jsonObject; + } } diff --git a/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileService.java b/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileService.java index 2078b42e..58536a7f 100644 --- a/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileService.java +++ b/muyun-fileserver/src/main/java/net/ximatai/muyun/fileserver/FileService.java @@ -1,8 +1,9 @@ package net.ximatai.muyun.fileserver; +import io.vertx.core.Future; +import io.vertx.core.Promise; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; -import io.vertx.core.json.JsonObject; import io.vertx.ext.web.FileUpload; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; @@ -20,10 +21,11 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; -//TODO 抽象一个 IFileService 接口,对Java层暴露相关方法 @ApplicationScoped -public class FileService { +public class FileService implements IFileService { final Logger logger = LoggerFactory.getLogger(getClass()); @@ -35,77 +37,64 @@ public class FileService { @Inject Vertx vertx; - // TODO 代码不够优雅,尤其是最后一行 return private String getRootPath() { String rootPath = config.pagePath(); - rootPath = rootPath.startsWith("/") ? rootPath : "/" + rootPath; - return rootPath.endsWith("/") ? rootPath : rootPath + "/"; + if (!rootPath.startsWith("/")) { + rootPath = "/" + rootPath; + } + if (!rootPath.endsWith("/")) { + rootPath = rootPath + "/"; + } + return rootPath; } void init(@Observes Router router, Vertx vertx) { router.get(getRootPath() + "index").handler(this::indexFunc); - router.post(getRootPath() + "form").handler(this::form); - router.get(getRootPath() + "download/:fileUid").handler(this::download); - router.get(getRootPath() + "delete/:uid").handler(this::delete); - router.get(getRootPath() + "info/:uid").handler(this::info); + router.post(getRootPath() + "upload").handler(this::upload); + router.get(getRootPath() + "download/:id").handler(this::download); + router.get(getRootPath() + "delete/:id").handler(this::delete); + router.get(getRootPath() + "info/:id").handler(this::info); } - //TODO 改成 """ """ 的方式存放字符串 // @Route(path = "/fileServer/index", methods = Route.HttpMethod.GET) private void indexFunc(RoutingContext ctx) { ctx.response() .putHeader("content-type", "text/html") .end( - // "
" + """ + + """ ); } - // TODO 要知道这是对 前端开放的上传接口,所以叫 form 不合适,正常应该叫 upload // @Route(path = "/fileServer/form", methods = Route.HttpMethod.POST) - private void form(RoutingContext ctx) { - + private void upload(RoutingContext ctx) { // 支持分块传输编码 ctx.response().setChunked(true); for (FileUpload f : ctx.fileUploads()) { - // 原来之前面向http的处理逻辑 - /* - // 获取文件名、文件大小、uid - String uploadedFileName = f.uploadedFileName(); - originalFileName = f.fileName(); - long fileSize = f.size(); - String uid = "bsy-" + uploadedFileName.split("\\\\")[2]; - String fileNameUid = suffixFileNameWithN(uid); - String fileContextUid = suffixFileNameWithO(uid); - vertx.fileSystem().writeFile(config.uploadPath() + fileNameUid, Buffer.buffer(originalFileName)); - vertx.fileSystem().copy(uploadedFileName, config.uploadPath() + fileContextUid); - vertx.fileSystem().delete(uploadedFileName); - */ - - // 面向Java的文件上传逻辑 String uploadedFileName = f.uploadedFileName(); originalFileName = f.fileName(); File file = new File(uploadedFileName); - String uid = save(file); - ctx.response().write(uid); + String id = save(file, originalFileName); + ctx.response().write(id); } ctx.response().end(); } - // @Route(path = "/fileServer/download/:fileUid", methods = Route.HttpMethod.GET) + // @Route(path = "/fileServer/download/:id", methods = Route.HttpMethod.GET) private void download(RoutingContext ctx) { - String fileUid = ctx.pathParam("fileUid"); - File fileObtained = obtain(fileUid); + String id = ctx.pathParam("id"); + File fileObtained = get(id); // 发送文件到客户端 - String nameFile = suffixFileNameWithN(fileUid); + String nameFile = suffixFileNameWithN(id); String nameFilePath = config.uploadPath() + nameFile; vertx.fileSystem().readFile(nameFilePath, result -> { if (result.succeeded()) { @@ -115,19 +104,19 @@ private void download(RoutingContext ctx) { ctx.response() .putHeader("Content-Disposition", "attachment; filename=" + content) .sendFile(fileObtained.getPath()); + vertx.fileSystem().delete(fileObtained.getPath()); } } else { logger.error("Failed to read file name: " + result.cause()); ctx.fail(result.cause()); } }); - } - // @Route(path = "/fileServer/delete/:uid", methods = Route.HttpMethod.GET) + // @Route(path = "/fileServer/delete/:id", methods = Route.HttpMethod.GET) private void delete(RoutingContext ctx) { - String uid = ctx.pathParam("uid"); - boolean isDeleted = drop(uid); + String id = ctx.pathParam("id"); + boolean isDeleted = delete(id); if (isDeleted) { ctx.response().end("Successfully deleted."); } else { @@ -135,12 +124,26 @@ private void delete(RoutingContext ctx) { } } - // @Route(path = "/fileServer/info/:uid", methods = Route.HttpMethod.GET) private void info(RoutingContext ctx) { - String uid = ctx.pathParam("uid"); - String fileNamePath = suffixFileNameWithN(config.uploadPath() + uid); - String fileContentPath = suffixFileNameWithO(config.uploadPath() + uid); - File file = new File(fileNamePath); + String id = ctx.pathParam("id"); + asyncInfo(id) + .onSuccess(entity -> { + ctx.response() + .putHeader("Content-Type", "application/json") + .end(entity.toJson().toString()); + + }).onFailure(err -> { + logger.error("Failed to get file info: " + err); + ctx.fail(err); + }); + } + + // 异步得到文件信息 + public Future