使用文件存储

文件存储是一种抽象,提供文件存储方式和位置的不同实现,并提供统一的 接口 用于访问文件,或者从数据模型实体引用。

Jmix 带有文件存储的两个实现:本地文件存储AWS 文件存储。在 Studio 中创建一个新项目时,默认包括本地文件存储实现。

文件存储中可以存储任意大小的文件,因为文件与文件存储之间的传输是通过在输入和输出流之间复制小块数据来完成的,因此永远不会完全将文件加载到内存中。

示例

UI 中使用文件

本章节我们演示如何使用 UI 组件处理文件存储中的文件。

首先,在实体中创建 FileRef 类型的属性,示例:

@JmixEntity
@Entity
@Table(name = "ATTACHMENT")
public class Attachment {
    // ...

    @Column(name = "FILE_")
    private FileRef file;

    public FileRef getFile() {
        return file;
    }

    public void setFile(FileRef file) {
        this.file = file;
    }

当运行应用程序时,Studio 会生成一个数据库迁移脚本,用于创建字符串类型的相应列。因为 FileRef 是 URI 格式的字符串表现形式。

如需从用户界面上传文件,请使用 FileStorageUploadField 文件存储上传 组件绑定至实体属性:

<data>
    <instance id="attachmentDc"
              class="files.ex1.entity.Attachment">
        <fetchPlan extends="_base"/>
        <loader/>
    </instance>
</data>
<layout spacing="true">
    <form dataContainer="attachmentDc">
        <column>
            <fileStorageUpload id="fileField" property="file"/>

如需下载文件,在浏览界面的表格中添加一个自定义列:

<groupTable id="attachmentsTable"
            width="100%"
            dataContainer="attachmentsDc">
    <columns>
        <column id="fileName" caption="File"/>

并定义列生成器:

@Autowired
private UiComponents uiComponents;
@Autowired
private Downloader downloader; (1)

@Install(to = "attachmentsTable.fileName", subject = "columnGenerator")
private Component attachmentsTableFileColumnGenerator(Attachment attachment) {
    if (attachment.getFile() != null) {
        LinkButton linkButton = uiComponents.create(LinkButton.class);
        linkButton.setAction(new BaseAction("download")
                .withCaption(attachment.getFile().getFileName())
                .withHandler(actionPerformedEvent ->
                        downloader.download(attachment.getFile()) (2)
                )
        );
        return linkButton;
    } else {
        return new Table.PlainTextCell("<empty>");
    }
}
1 Downloader bean 下载文件。
2 download() 方法接收 FileRef 对象值,并该值指定的文件存储中获取文件。文件的名称和类型也包含在 FileRef 值中,因此 Web 浏览器可以正确选择是下载还是显示文件。

使用 FileStorage 接口

下面示例展示如何直接使用 FileStorage 接口。

第一个方法从一个 web 服务中获取文件并保存至文件存储。第二个方法从文件存储加载文件并保存至本地文件系统。

示例中使用了与之相同的 Attachment 实体。

@Autowired
private FileStorageLocator fileStorageLocator; (1)
@Autowired
private DataManager dataManager;

private void getAndSaveImage() {
    try {
        (2)
        URLConnection connection = new URL("https://picsum.photos/300").openConnection();
        try (InputStream responseStream = connection.getInputStream()) {
            (3)
            FileStorage fileStorage = fileStorageLocator.getDefault();
            FileRef fileRef = fileStorage.saveStream("photo.jpg", responseStream);
            (4)
            Attachment attachment = dataManager.create(Attachment.class);
            attachment.setFile(fileRef);
            dataManager.save(attachment);
        }
    } catch (IOException e) {
        throw new RuntimeException("Error getting image", e);
    }
}

private void saveToLocalFile(Attachment attachment, Path path) {
    FileStorage fileStorage = fileStorageLocator.getDefault();
    FileRef fileRef = attachment.getFile();
    (5)
    InputStream inputStream = fileStorage.openStream(fileRef);
    try {
        (6)
        Files.copy(inputStream, path.resolve(fileRef.getFileName()),
                StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
        throw new RuntimeException("Error saving image", e);
    }
}
1 如果在项目中定义了 多个文件存储,则 FileStorageLocator 支持使用特定的文件存储。如果只有单一文件存储(默认情况),则可以直接注入 FileStorage 接口。
2 获取 web 资源的输入流。除了 URLConnection 类,也可以使用使用 Java 11 中的 HttpClient 或 Apache HttpClient 等第三方库。
3 将资源内容保存至文件存储。返回的 FileRef 对象是文件存储中文件的引用。
4 将引用保存至实体属性。
5 获取从文件存储加载文件的输入流。
6 将文件保存至本地文件系统。

本地文件存储

本地文件存储支持将文件保存在应用程序服务器的本地文件系统或任何网络附属存储(NAS)中。

如需在应用程序中使用本地文件存储,请确保 build.gradle 文件的 dependencies 中包含下列内容:

implementation 'io.jmix.localfs:jmix-localfs-starter'

文件存储在由文件存储维护的特殊目录结构中。默认情况下,根目录是 ${user.dir}/.jmix/work/filestorage,其中 ${user.dir} 是用户工作目录(JVM 启动目录)。可以通过 jmix.core.work-dir 应用程序属性指定一个工作目录或者通过 jmix.localfs.storage-dir 属性指定一个绝对路径。示例:

jmix.localfs.storage-dir = /opt/file-storage

AWS 文件存储

AWS 文件存储支持将文件存储在 Amazon S3

如需在应用程序中使用 AWS 文件存储,请按照 扩展组件 部分的描述从组件市场安装,或手动在 build.gradledependencies 中添加:

implementation 'io.jmix.awsfs:jmix-awsfs-starter'

如果打算只使用 AWS 文件存储,需要从 build.gradle 中删除本地文件存储的依赖(即包含 io.jmix.localfs:jmix-localfs-starter 的那一行)。如果想使用多个文件存储,参阅 下一节 内容了解如何配置多个文件存储。

定义应用程序熟悉你:

jmix.awsfs.access-key = <access key ID>
jmix.awsfs.secret-access-key = <secret access key>
jmix.awsfs.region = <AWS region, for example eu-north-1>
jmix.awsfs.bucket = <S3 bucket name>
jmix.awsfs.chunk-size = <optional upload chunk size in KB, default is 8192>
jmix.awsfs.endpoint-url = <optional endpoint URL for S3-compatible cloud storages>

在 AWS 选择 region 后,需要先创建该地区的 S3 bucket。

使用多个文件存储

如需在应用程序中使用多个文件存储,在应用程序属性中指定一个默认的存储:

jmix.core.default-file-storage = fs

本地文件存储的名称为 fs,AWS 文件存储的名称为 s3

如果需要使用相同类型的多个文件存储,可以使用唯一名称定义额外的存储。例如,定义根目录位于 /var/tmp/myfs 的额外本地文件存储,将下列代码添加至主应用程序类:

@Bean
FileStorage myFileStorage() {
    return new LocalFileStorage("myfs", "/var/tmp/myfs");
}

如需以编程的方式处理不同的文件存储,可使用 FileStorageLocator bean。支持通过名称获取文件存储。

FileStorageUploadField 文件存储上传fileStorage 属性可以指定文件存储名称。如未指定,则使用默认文件存储。