Browse Source

优化文件存储

zhou-hao 4 years ago
parent
commit
160507302f

+ 12 - 2
hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileServiceConfiguration.java

@@ -1,5 +1,7 @@
 package org.hswebframework.web.file;
 
+import org.hswebframework.web.file.service.FileStorageService;
+import org.hswebframework.web.file.service.LocalFileStorageService;
 import org.hswebframework.web.file.web.ReactiveFileController;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@@ -15,11 +17,19 @@ public class FileServiceConfiguration {
     @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
     static class ReactiveConfiguration {
 
+        @Bean
+        @ConditionalOnMissingBean(FileStorageService.class)
+        public FileStorageService fileStorageService(FileUploadProperties properties) {
+            return new LocalFileStorageService(properties);
+        }
+
         @Bean
         @ConditionalOnMissingBean(name = "reactiveFileController")
-        private ReactiveFileController reactiveFileController() {
-            return new ReactiveFileController();
+        public ReactiveFileController reactiveFileController(FileUploadProperties properties,
+                                                             FileStorageService storageService) {
+            return new ReactiveFileController(properties, storageService);
         }
+
     }
 
 }

+ 32 - 0
hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/FileStorageService.java

@@ -0,0 +1,32 @@
+package org.hswebframework.web.file.service;
+
+import org.springframework.http.codec.multipart.FilePart;
+import reactor.core.publisher.Mono;
+
+import java.io.InputStream;
+
+/**
+ * 文件存储服务,用于保存文件信息到服务器
+ *
+ * @author zhouhao
+ * @since 4.0.9
+ */
+public interface FileStorageService {
+
+    /**
+     * 保存文件,通常用来文件上传接口
+     *
+     * @param filePart FilePart
+     * @return 文件访问地址
+     */
+    Mono<String> saveFile(FilePart filePart);
+
+    /**
+     * 使用文件流保存文件,并返回文件地址
+     *
+     * @param inputStream 文件输入流
+     * @param fileType    文件类型,如: png,jpg
+     * @return 文件访问地址
+     */
+    Mono<String> saveFile(InputStream inputStream, String fileType);
+}

+ 60 - 0
hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/service/LocalFileStorageService.java

@@ -0,0 +1,60 @@
+package org.hswebframework.web.file.service;
+
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.hswebframework.web.file.FileUploadProperties;
+import org.springframework.http.codec.multipart.FilePart;
+import reactor.core.publisher.Mono;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.OpenOption;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+
+@AllArgsConstructor
+public class LocalFileStorageService implements FileStorageService {
+
+    private final FileUploadProperties properties;
+
+    @Override
+    public Mono<String> saveFile(FilePart filePart) {
+        FileUploadProperties.StaticFileInfo info = properties.createStaticSavePath(filePart.filename());
+        return (filePart)
+                .transferTo(new File(info.getSavePath()))
+                .thenReturn(info.getLocation());
+    }
+
+    private static final OpenOption[] FILE_CHANNEL_OPTIONS = {
+            StandardOpenOption.CREATE,
+            StandardOpenOption.TRUNCATE_EXISTING,
+            StandardOpenOption.WRITE};
+
+    @Override
+    @SneakyThrows
+    public Mono<String> saveFile(InputStream inputStream, String fileType) {
+        String fileName = "_temp" + (fileType.startsWith(".") ? fileType : "." + fileType);
+
+        FileUploadProperties.StaticFileInfo info = properties.createStaticSavePath(fileName);
+
+        return Mono
+                .fromCallable(() -> {
+                    try (ReadableByteChannel input = Channels.newChannel(inputStream);
+                         FileChannel output = FileChannel.open(Paths.get(info.getSavePath()), FILE_CHANNEL_OPTIONS)) {
+                        long size = (input instanceof FileChannel ? ((FileChannel) input).size() : Long.MAX_VALUE);
+                        long totalWritten = 0;
+                        while (totalWritten < size) {
+                            long written = output.transferFrom(input, totalWritten, size - totalWritten);
+                            if (written <= 0) {
+                                break;
+                            }
+                            totalWritten += written;
+                        }
+                        return info.getLocation();
+                    }
+                });
+    }
+}

+ 10 - 7
hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/web/ReactiveFileController.java

@@ -10,6 +10,7 @@ import org.hswebframework.web.authorization.annotation.Resource;
 import org.hswebframework.web.authorization.annotation.ResourceAction;
 import org.hswebframework.web.authorization.exception.AccessDenyException;
 import org.hswebframework.web.file.FileUploadProperties;
+import org.hswebframework.web.file.service.FileStorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.codec.multipart.FilePart;
 import org.springframework.http.codec.multipart.Part;
@@ -28,8 +29,14 @@ import java.io.File;
 @Tag(name = "文件上传")
 public class ReactiveFileController {
 
-    @Autowired
-    private FileUploadProperties properties;
+    private final FileUploadProperties properties;
+
+    private final FileStorageService fileStorageService;
+
+    public ReactiveFileController(FileUploadProperties properties, FileStorageService fileStorageService) {
+        this.properties = properties;
+        this.fileStorageService = fileStorageService;
+    }
 
     @PostMapping("/static")
     @SneakyThrows
@@ -37,16 +44,12 @@ public class ReactiveFileController {
     @Operation(summary = "上传静态文件")
     public Mono<String> uploadStatic(@RequestPart("file")
                                      @Parameter(name = "file", description = "文件", style = ParameterStyle.FORM) Part part) {
-        FileUploadProperties.StaticFileInfo name;
         if (part instanceof FilePart) {
             FilePart filePart = ((FilePart) part);
             if (properties.denied(filePart.filename(), filePart.headers().getContentType())) {
                 throw new AccessDenyException();
             }
-            name = properties.createStaticSavePath(filePart.filename());
-            return ((FilePart) part)
-                    .transferTo(new File(name.getSavePath()))
-                    .thenReturn(name.getLocation());
+            return fileStorageService.saveFile(filePart);
         } else {
             return Mono.error(() -> new IllegalArgumentException("[file] part is not a file"));
         }

+ 38 - 0
hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/service/LocalFileStorageServiceTest.java

@@ -0,0 +1,38 @@
+package org.hswebframework.web.file.service;
+
+import lombok.SneakyThrows;
+import org.hswebframework.web.file.FileUploadProperties;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.util.StreamUtils;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.io.FileInputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.*;
+
+public class LocalFileStorageServiceTest {
+
+    @Test
+    @SneakyThrows
+    public void test() {
+        FileUploadProperties properties = new FileUploadProperties();
+        properties.setStaticFilePath("./target/upload");
+        properties.setStaticLocation("./");
+        LocalFileStorageService storageService = new LocalFileStorageService(properties);
+
+        String text = StreamUtils.copyToString(new ClassPathResource("test.json").getInputStream(), StandardCharsets.UTF_8);
+
+        storageService
+                .saveFile(new ClassPathResource("test.json").getInputStream(), "json")
+                .flatMap(str -> Mono
+                        .fromCallable(() -> StreamUtils
+                                .copyToString(new FileInputStream("./target/upload/" + str), StandardCharsets.UTF_8)))
+                .as(StepVerifier::create)
+                .expectNext(text)
+                .verifyComplete();
+
+    }
+}