Jmix 中 FileRef 的使用技巧

FileRef 使用分类

FileRef 使用分三种:

  1. 临时使用
  • url路过,比如你上传数据到第三方云存储,第三方返回url,你此时直接用就行了,无需在Jmix内再下载转换一次(官方和社区的文件存储插件都是还需再jmix框架过一遍的,很鸡肋!)。
  • 还有种就是使用本地资源文件,它既不是url也不属于Jmix文件机制内的文件。
  1. Jmix UI 使用还是上者,文件保存至第三方,但此时jmix框架需要使用,比如后台UI需要展示,这种属于常规使用。
  2. 存储数据使用常规使用

Jmix 文件存储机制

了解 FileRef 前先了解一下Jmix 文件存储机制

实现 io.jmix.core.FileStorage接口即可自定义一个文件存储器

public interface FileStorage {

    /**
     * Returns the name of this storage, which will be saved in {@link FileRef}s.
     * <p>
     * Each file storage in the application should have a unique name.
     */
    String getStorageName();

    /**
     * Saves an InputStream contents into the file storage.
     *
     * @param fileName    file name
     * @param inputStream input stream, must be closed in the calling code
     * @return file reference
     * @throws IllegalArgumentException if arguments are incorrect
     * @throws FileStorageException     if something goes wrong
     */
    FileRef saveStream(String fileName, InputStream inputStream);

    /**
     * Returns an input stream to load a file contents.
     *
     * @param reference file reference
     * @return input stream, must be closed after use
     * @throws IllegalArgumentException if arguments are incorrect
     * @throws FileStorageException     if something goes wrong
     */
    InputStream openStream(FileRef reference);

    /**
     * Removes a file from the file storage.
     *
     * @param reference file reference
     * @throws IllegalArgumentException if file reference is invalid
     */
    void removeFile(FileRef reference);

    /**
     * Tests whether the file denoted by this file reference exists.
     *
     * @param reference file reference
     * @return true if the file denoted by this file reference exists
     * @throws IllegalArgumentException if file reference is invalid
     */
    boolean fileExists(FileRef reference);
}

主要就

  • FileRef saveStream(String fileName, InputStream inputStream);
  • InputStream openStream(FileRef reference);

都是还是IO流操作,我们给转换好就可以了。

FileRef 简述

FileRef 就是描述了存储器、path和文件名,以及一些自定义的 parameters ,这个很关键

顺便看一下数据库里是怎么转换的吧

实现io.jmix.core.metamodel.datatype.Datatype<T> 即可自定义数据类型,自定义类型在数据库里最终都保存为String。

package io.jmix.core.metamodel.datatype.impl;

import io.jmix.core.FileRef;
import io.jmix.core.metamodel.annotation.DatatypeDef;
import io.jmix.core.metamodel.datatype.Datatype;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.text.ParseException;
import java.util.Locale;

@DatatypeDef(id = "fileRef", javaClass = FileRef.class, defaultForClass = true, value = "core_FileRefDatatype")
public class FileRefDatatype implements Datatype<FileRef> {

    private static final Logger log = LoggerFactory.getLogger(FileRefDatatype.class);

    @Override
    public String format(@Nullable Object value) {
        return value == null ? "" : value.toString();
    }

    @Override
    public String format(@Nullable Object value, Locale locale) {
        return format(value);
    }

    @Nullable
    @Override
    public FileRef parse(@Nullable String value) throws ParseException {
        try {
            return value == null ? null : FileRef.fromString(value);
        } catch (RuntimeException e) {
            log.warn("Cannot convert " + value + " to FileRef", e);
            return null;
        }
    }

    @Nullable
    @Override
    public FileRef parse(@Nullable String value, Locale locale) throws ParseException {
        return parse(value);
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

第三方存储:文件上传修改

上传至第三方服务时我们可以这样做

  1. 拿到返回的url
  2. 往FileRef里添加一个参数 addParameter ,自己定义好这个key URL_IDENTITY

这里改造的是社区的 腾讯COS 存储器 的代码

@Override
public FileRef saveStream(String fileName, InputStream inputStream) {
    String tenantId = TdParamUtil.getTenantId();
    String fileKey = tenantId + "/" + TdFileRefUtil.createFileKey(fileName);
    try {
        ...

        listAllParts(fileKey, uploadId);
        String fileUrl = completeMultipartUpload(partETags, fileKey, uploadId);
        FileRef fileRef = new FileRef(getStorageName(), fileKey, fileName);
        fileRef.addParameter(URL_IDENTITY, fileUrl.replace("http://", "https://"));

        return fileRef;
    } catch (IOException e) {
        String message = String.format("Could not save file %s.", fileName);
        throw new FileStorageException(FileStorageException.Type.IO_EXCEPTION, message);
    }
}

数据最终存储

cos://no_tenant/2023/03/27/f19c53e6-1a87-9556-d85a-90aa0cdb463a.gif?name=0D10ECA0E519008AA239A4740D322F2D.gif&file_url=https%3A%2F%2Ftdplat-1002987409.cos.ap-guangzhou.myqcloud.com%2Fno_tenant%2F2023%2F03%2F27%2Ff19c53e6-1a87-9656-d95a-90aa0cdb463a.gif

第三方存储:使用

直接使用

无需修改

获取第三方返回url

定义一个工具类,关键方法String url = fileRef.getParameters().get(UPaasConstant.URL_IDENTITY);与上面的修改呼应。

public static String restFullPath(FileRef fileRef) {

    if (fileRef != null) {
        String url = fileRef.getParameters().get(UPaasConstant.URL_IDENTITY);
        if (StrUtil.isNotBlank(url)) {
            return url;
        }
    }

    return TdContext.loadUPaasSettings().getUrl() + restPath(fileRef);
}

这就是开头的临时使用的一种场景

本地资源文件转 FileRef

临时使用的第二种场景,对于 Jmix 文件机制来说是无中生有了。我们简单修改即可实现

逻辑就是 FileStorage 里InputStream openStream(FileRef reference);返回 IO流即可。

同样的修改或创建一个专门读取本地资源的存储器,这里修改了一个自定义的本地存储器,定义的数据格式是 :

storageName://UP_STORAGE_RESOURCES:path?name=[parameters]

如果启一个新的存储器就可以定义一个专属的storageName就可以了,也不需要if (referencePath.startsWith(UP_STORAGE_RESOURCES))这个判断,直接取path读取文件返回流即可。

@Override
public InputStream openStream(FileRef reference) {
    // 支持资源文件直接读取
    String referencePath = reference.getPath();

    if (referencePath.startsWith(UP_STORAGE_RESOURCES)) {
        ClassPathResource classPathResource = new ClassPathResource(referencePath.replace(UP_STORAGE_RESOURCES,""));
        try {
            return classPathResource.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    ...
    
    return inputStream;
}

还有个文件判断需要处理一下if (path.startsWith(UP_STORAGE_RESOURCES)){ return true; }

@Override
public boolean fileExists(FileRef reference) {
    String path = reference.getPath();

    if (path.startsWith(UP_STORAGE_RESOURCES)) {
        // 获取本地资源文件
        return true;
    }

    String tenantId = TdParamUtil.getTenantId();
    Path relativePath = TdFileRefUtil.getRelativePath(path);

    // 只检查自己目录的文件
    Path filePath = storageRoots.get(tenantId).resolve(relativePath);
    return filePath.toFile().exists();
}

使用

public static FileRef getDefaultAvatar() {
    return new FileRef(UP_STORAGE_NAME, UP_STORAGE_RESOURCES + "/cn/aaa/bbb/ccc/avatar.png", "avatar.png");
}

数据最终存储

tdfs://resources:/cn/aaa/bbb/ccc/avatar.png?name=avatar.png
4 个赞