Browse Source

完善 yudao-spring-boot-starter-file 组件,支持 S3 对接云存储、local、ftp、sftp 等协议

YunaiV 3 years atrás
parent
commit
3d40fc81dd
15 changed files with 512 additions and 8 deletions
  1. 29 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java
  2. 12 0
      yudao-framework/yudao-spring-boot-starter-file/pom.xml
  3. 13 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java
  4. 73 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java
  5. 59 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java
  6. 52 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java
  7. 30 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java
  8. 11 3
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java
  9. 61 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java
  10. 52 0
      yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java
  11. 39 0
      yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java
  12. 27 0
      yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java
  13. 9 0
      yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java
  14. 37 0
      yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java
  15. 8 3
      yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

+ 29 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java

@@ -22,12 +22,39 @@ public class FileUtils {
      */
     @SneakyThrows
     public static File createTempFile(String data) {
+        File file = createTempFile();
+        // 写入内容
+        FileUtil.writeUtf8String(data, file);
+        return file;
+    }
+
+    /**
+     * 创建临时文件
+     * 该文件会在 JVM 退出时,进行删除
+     *
+     * @param data 文件内容
+     * @return 文件
+     */
+    @SneakyThrows
+    public static File createTempFile(byte[] data) {
+        File file = createTempFile();
+        // 写入内容
+        FileUtil.writeBytes(data, file);
+        return file;
+    }
+
+    /**
+     * 创建临时文件,无内容
+     * 该文件会在 JVM 退出时,进行删除
+     *
+     * @return 文件
+     */
+    @SneakyThrows
+    public static File createTempFile() {
         // 创建文件,通过 UUID 保证唯一
         File file = File.createTempFile(IdUtil.simpleUUID(), null);
         // 标记 JVM 退出时,自动删除
         file.deleteOnExit();
-        // 写入内容
-        FileUtil.writeUtf8String(data, file);
         return file;
     }
 

+ 12 - 0
yudao-framework/yudao-spring-boot-starter-file/pom.xml

@@ -51,6 +51,18 @@
             <artifactId>jackson-core</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>commons-net</groupId>
+            <artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
+            <version>3.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
+            <version>0.1.55</version>
+        </dependency>
+
+
         <!-- 三方云服务相关 -->
         <dependency>
             <groupId>software.amazon.awssdk</groupId>

+ 13 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/AbstractFileClient.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.file.core.client.impl;
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.file.core.client.FileClient;
 import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
 import lombok.extern.slf4j.Slf4j;
@@ -55,4 +56,16 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem
         return id;
     }
 
+    /**
+     * 格式化文件的 URL 访问地址
+     * 使用场景:local、ftp、db,通过 FileController 的 getFile 来获取文件内容
+     *
+     * @param domain 自定义域名
+     * @param path 文件路径
+     * @return URL 访问地址
+     */
+    protected String formatFileUrl(String domain, String path) {
+        return StrUtil.format("{}/system-api/{}/get/{}", domain, getId(), path);
+    }
+
 }

+ 73 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClient.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.framework.file.core.client.impl.ftp;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.ftp.Ftp;
+import cn.hutool.extra.ftp.FtpException;
+import cn.hutool.extra.ftp.FtpMode;
+import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+/**
+ * Ftp 文件客户端
+ *
+ * @author 芋道源码
+ */
+public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
+
+    private Ftp ftp;
+
+    public FtpFileClient(Long id, FtpFileClientConfig config) {
+        super(id, config);
+    }
+
+    @Override
+    protected void doInit() {
+        // 补全风格。例如说 Linux 是 /,Windows 是 \
+        if (!config.getBasePath().endsWith(File.separator)) {
+            config.setBasePath(config.getBasePath() + File.separator);
+        }
+        // 初始化 Ftp 对象
+        this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
+                CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
+    }
+
+    @Override
+    public String upload(byte[] content, String path) {
+        // 执行写入
+        String filePath = getFilePath(path);
+        String fileName = FileUtil.getName(filePath);
+        String dir = StrUtil.removeSuffix(filePath, fileName);
+        boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
+        if (!success) {
+            throw new FtpException(StrUtil.format("上海文件到目标目录 ({}) 失败", filePath));
+        }
+        // 拼接返回路径
+        return super.formatFileUrl(config.getDomain(), path);
+    }
+
+    @Override
+    public void delete(String path) {
+        String filePath = getFilePath(path);
+        ftp.delFile(filePath);
+    }
+
+    @Override
+    public byte[] getContent(String path) {
+        String filePath = getFilePath(path);
+        String fileName = FileUtil.getName(filePath);
+        String dir = StrUtil.removeSuffix(path, fileName);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ftp.download(dir, fileName, out);
+        return out.toByteArray();
+    }
+
+    private String getFilePath(String path) {
+        return config.getBasePath() + path;
+    }
+
+}

+ 59 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/ftp/FtpFileClientConfig.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.framework.file.core.client.impl.ftp;
+
+import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * Ftp 文件客户端的配置类
+ *
+ * @author 芋道源码
+ */
+@Data
+public class FtpFileClientConfig implements FileClientConfig {
+
+    /**
+     * 基础路径
+     */
+    @NotEmpty(message = "基础路径不能为空")
+    private String basePath;
+
+    /**
+     * 自定义域名
+     */
+    @NotEmpty(message = "domain 不能为空")
+    @URL(message = "domain 必须是 URL 格式")
+    private String domain;
+
+    /**
+     * 主机地址
+     */
+    @NotEmpty(message = "host 不能为空")
+    private String host;
+    /**
+     * 主机端口
+     */
+    @NotNull(message = "port 不能为空")
+    private Integer port;
+    /**
+     * 用户名
+     */
+    @NotEmpty(message = "用户名不能为空")
+    private String username;
+    /**
+     * 密码
+     */
+    @NotEmpty(message = "密码不能为空")
+    private String password;
+    /**
+     * 连接模式
+     *
+     * 使用 {@link  cn.hutool.extra.ftp.FtpMode} 对应的字符串
+     */
+    @NotEmpty(message = "连接模式不能为空")
+    private String mode;
+
+}

+ 52 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClient.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.framework.file.core.client.impl.local;
+
+import cn.hutool.core.io.FileUtil;
+import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient;
+
+import java.io.File;
+
+/**
+ * 本地文件客户端
+ *
+ * @author 芋道源码
+ */
+public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {
+
+    public LocalFileClient(Long id, LocalFileClientConfig config) {
+        super(id, config);
+    }
+
+    @Override
+    protected void doInit() {
+        // 补全风格。例如说 Linux 是 /,Windows 是 \
+        if (!config.getBasePath().endsWith(File.separator)) {
+            config.setBasePath(config.getBasePath() + File.separator);
+        }
+    }
+
+    @Override
+    public String upload(byte[] content, String path) {
+        // 执行写入
+        String filePath = getFilePath(path);
+        FileUtil.writeBytes(content, filePath);
+        // 拼接返回路径
+        return super.formatFileUrl(config.getDomain(), path);
+    }
+
+    @Override
+    public void delete(String path) {
+        String filePath = getFilePath(path);
+        FileUtil.del(filePath);
+    }
+
+    @Override
+    public byte[] getContent(String path) {
+        String filePath = getFilePath(path);
+        return FileUtil.readBytes(filePath);
+    }
+
+    private String getFilePath(String path) {
+        return config.getBasePath() + path;
+    }
+
+}

+ 30 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/local/LocalFileClientConfig.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.framework.file.core.client.impl.local;
+
+import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * 本地文件客户端的配置类
+ *
+ * @author 芋道源码
+ */
+@Data
+public class LocalFileClientConfig implements FileClientConfig {
+
+    /**
+     * 基础路径
+     */
+    @NotEmpty(message = "基础路径不能为空")
+    private String basePath;
+
+    /**
+     * 自定义域名
+     */
+    @NotEmpty(message = "domain 不能为空")
+    @URL(message = "domain 必须是 URL 格式")
+    private String domain;
+
+}

+ 11 - 3
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/s3/S3FileClient.java

@@ -7,6 +7,8 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
 import software.amazon.awssdk.core.sync.RequestBody;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
 import software.amazon.awssdk.services.s3.model.PutObjectRequest;
 
 import java.net.URI;
@@ -14,7 +16,7 @@ import java.net.URI;
 import static cn.iocoder.yudao.framework.file.core.client.impl.s3.S3FileClientConfig.ENDPOINT_QINIU;
 
 /**
- * 基于 S3 协议,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
+ * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
  *
  * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
  *
@@ -84,12 +86,18 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
 
     @Override
     public void delete(String path) {
-
+        DeleteObjectRequest.Builder request = DeleteObjectRequest.builder()
+                .bucket(config.getBucket()) // bucket 必须传递
+                .key(path); // 相对路径作为 key
+        client.deleteObject(request.build());
     }
 
     @Override
     public byte[] getContent(String path) {
-        return new byte[0];
+        GetObjectRequest.Builder request = GetObjectRequest.builder()
+                .bucket(config.getBucket()) // bucket 必须传递
+                .key(path); // 相对路径作为 key
+        return client.getObjectAsBytes(request.build()).asByteArray();
     }
 
 }

+ 61 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClient.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.framework.file.core.client.impl.sftp;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.extra.ssh.Sftp;
+import cn.iocoder.yudao.framework.common.util.io.FileUtils;
+import cn.iocoder.yudao.framework.file.core.client.impl.AbstractFileClient;
+
+import java.io.File;
+
+/**
+ * Sftp 文件客户端
+ *
+ * @author 芋道源码
+ */
+public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> {
+
+    private Sftp sftp;
+
+    public SftpFileClient(Long id, SftpFileClientConfig config) {
+        super(id, config);
+    }
+
+    @Override
+    protected void doInit() {
+        // 补全风格。例如说 Linux 是 /,Windows 是 \
+        if (!config.getBasePath().endsWith(File.separator)) {
+            config.setBasePath(config.getBasePath() + File.separator);
+        }
+        // 初始化 Ftp 对象
+        this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());
+    }
+
+    @Override
+    public String upload(byte[] content, String path) {
+        // 执行写入
+        String filePath = getFilePath(path);
+        File file = FileUtils.createTempFile(content);
+        sftp.upload(filePath, file);
+        // 拼接返回路径
+        return super.formatFileUrl(config.getDomain(), path);
+    }
+
+    @Override
+    public void delete(String path) {
+        String filePath = getFilePath(path);
+        sftp.delFile(filePath);
+    }
+
+    @Override
+    public byte[] getContent(String path) {
+        String filePath = getFilePath(path);
+        File destFile = FileUtils.createTempFile();
+        sftp.download(filePath, destFile);
+        return FileUtil.readBytes(destFile);
+    }
+
+    private String getFilePath(String path) {
+        return config.getBasePath() + path;
+    }
+
+}

+ 52 - 0
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/impl/sftp/SftpFileClientConfig.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.framework.file.core.client.impl.sftp;
+
+import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * Sftp 文件客户端的配置类
+ *
+ * @author 芋道源码
+ */
+@Data
+public class SftpFileClientConfig implements FileClientConfig {
+
+    /**
+     * 基础路径
+     */
+    @NotEmpty(message = "基础路径不能为空")
+    private String basePath;
+
+    /**
+     * 自定义域名
+     */
+    @NotEmpty(message = "domain 不能为空")
+    @URL(message = "domain 必须是 URL 格式")
+    private String domain;
+
+    /**
+     * 主机地址
+     */
+    @NotEmpty(message = "host 不能为空")
+    private String host;
+    /**
+     * 主机端口
+     */
+    @NotNull(message = "port 不能为空")
+    private Integer port;
+    /**
+     * 用户名
+     */
+    @NotEmpty(message = "用户名不能为空")
+    private String username;
+    /**
+     * 密码
+     */
+    @NotEmpty(message = "密码不能为空")
+    private String password;
+
+}

+ 39 - 0
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/ftp/FtpFileClientTest.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.framework.file.core.client.ftp;
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.extra.ftp.FtpMode;
+import cn.iocoder.yudao.framework.file.core.client.impl.ftp.FtpFileClient;
+import cn.iocoder.yudao.framework.file.core.client.impl.ftp.FtpFileClientConfig;
+import org.junit.jupiter.api.Test;
+
+public class FtpFileClientTest {
+
+    @Test
+    public void test() {
+        // 创建客户端
+        FtpFileClientConfig config = new FtpFileClientConfig();
+        config.setDomain("http://127.0.0.1:48080");
+        config.setBasePath("/home/ftp");
+        config.setHost("kanchai.club");
+        config.setPort(221);
+        config.setUsername("");
+        config.setPassword("");
+        config.setMode(FtpMode.Passive.name());
+        FtpFileClient client = new FtpFileClient(0L, config);
+        client.init();
+        // 上传文件
+        String path = IdUtil.fastSimpleUUID() + ".jpg";
+        byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
+        String fullPath = client.upload(content, path);
+        System.out.println("访问地址:" + fullPath);
+        if (false) {
+            byte[] bytes = client.getContent(path);
+            System.out.println("文件内容:" + bytes);
+        }
+        if (false) {
+            client.delete(path);
+        }
+    }
+
+}

+ 27 - 0
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/local/LocalFileClientTest.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.framework.file.core.client.local;
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.iocoder.yudao.framework.file.core.client.impl.local.LocalFileClient;
+import cn.iocoder.yudao.framework.file.core.client.impl.local.LocalFileClientConfig;
+import org.junit.jupiter.api.Test;
+
+public class LocalFileClientTest {
+
+    @Test
+    public void test() {
+        // 创建客户端
+        LocalFileClientConfig config = new LocalFileClientConfig();
+        config.setDomain("http://127.0.0.1:48080");
+        config.setBasePath("/Users/yunai/file_test");
+        LocalFileClient client = new LocalFileClient(0L, config);
+        client.init();
+        // 上传文件
+        String path = IdUtil.fastSimpleUUID() + ".jpg";
+        byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
+        String fullPath = client.upload(content, path);
+        System.out.println("访问地址:" + fullPath);
+        client.delete(path);
+    }
+
+}

+ 9 - 0
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClientTest.java

@@ -76,6 +76,15 @@ public class S3FileClientTest {
         byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
         String fullPath = client.upload(content, path);
         System.out.println("访问地址:" + fullPath);
+        // 读取文件
+        if (false) {
+            byte[] bytes = client.getContent(path);
+            System.out.println("文件内容:" + bytes);
+        }
+        // 删除文件
+        if (false) {
+            client.delete(path);
+        }
     }
 
 }

+ 37 - 0
yudao-framework/yudao-spring-boot-starter-file/src/test/java/cn/iocoder/yudao/framework/file/core/client/sftp/SftpFileClientTest.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.framework.file.core.client.sftp;
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.iocoder.yudao.framework.file.core.client.impl.sftp.SftpFileClient;
+import cn.iocoder.yudao.framework.file.core.client.impl.sftp.SftpFileClientConfig;
+import org.junit.jupiter.api.Test;
+
+public class SftpFileClientTest {
+
+    @Test
+    public void test() {
+        // 创建客户端
+        SftpFileClientConfig config = new SftpFileClientConfig();
+        config.setDomain("http://127.0.0.1:48080");
+        config.setBasePath("/home/ftp");
+        config.setHost("kanchai.club");
+        config.setPort(222);
+        config.setUsername("");
+        config.setPassword("");
+        SftpFileClient client = new SftpFileClient(0L, config);
+        client.init();
+        // 上传文件
+        String path = IdUtil.fastSimpleUUID() + ".jpg";
+        byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
+        String fullPath = client.upload(content, path);
+        System.out.println("访问地址:" + fullPath);
+        if (false) {
+            byte[] bytes = client.getContent(path);
+            System.out.println("文件内容:" + bytes);
+        }
+        if (false) {
+            client.delete(path);
+        }
+    }
+
+}

+ 8 - 3
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

@@ -57,10 +57,15 @@ public class FileController {
         return success(true);
     }
 
-    @GetMapping("/get/{path}")
+    @GetMapping("/{configId}/get/{path}")
     @ApiOperation("下载文件")
-    @ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class)
-    public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException {
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "configId", value = "配置编号",  required = true, dataTypeClass = String.class),
+            @ApiImplicitParam(name = "path", value = "文件路径", required = true, dataTypeClass = String.class)
+    })
+    public void getFile(HttpServletResponse response,
+                        @PathVariable("configId") String configId,
+                        @PathVariable("path") String path) throws IOException {
         FileDO file = fileService.getFile(path);
         if (file == null) {
             log.warn("[getFile][path({}) 文件不存在]", path);