FileRef 使用分类
FileRef 使用分三种:
- 临时使用
- url路过,比如你上传数据到第三方云存储,第三方返回url,你此时直接用就行了,无需在Jmix内再下载转换一次(官方和社区的文件存储插件都是还需再jmix框架过一遍的,很鸡肋!)。
- 还有种就是使用本地资源文件,它既不是url也不属于Jmix文件机制内的文件。
- Jmix UI 使用还是上者,文件保存至第三方,但此时jmix框架需要使用,比如后台UI需要展示,这种属于常规使用。
- 存储数据使用常规使用
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();
}
}
第三方存储:文件上传修改
上传至第三方服务时我们可以这样做
- 拿到返回的url
- 往FileRef里添加一个参数
addParameter
,自己定义好这个keyURL_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