前置准备
- 环境:JDK17
- SpringBoot:SpringBoot3
- SpringContent:3.0.9
依赖引入
注意:fs(本地文件系统)和S3的starter只能设置1个,否则启动时会报错!
引入fs的stater
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-fs-boot-starter</artifactId>
<version>3.0.9</version>
</dependency>
实现
开启SpringContent包扫描
启动类添加@EnableFilesystemStores注解,标记要扫描的包
@EnableFilesystemStores(basePackages = "org.evaltool.*")
定义文件实体对象
- @ContentId:存储文件时的唯一编号
- @ContentLength:文件的大小
import lombok.Data;
import org.springframework.content.commons.annotations.ContentId;
import org.springframework.content.commons.annotations.ContentLength;
/**
* spring content文件定义
*/
@Data
public class FileDocument {
@ContentId
private String id;
private String fileName;
@ContentLength
private long fileSize;
private String fileExt;
}
定义Store接口
自定义接口继承自ContentStore。
ContentStore 是 Spring Content 中的核心接口,用于定义内容存储和检索的标准操作。它提供了一套统一的 API 来处理各种类型的内容存储后端。
import org.evaltool.tool.domain.FileDocument;
import org.springframework.content.commons.store.ContentStore;
import org.springframework.stereotype.Repository;
/**
* 本地文件存储器
*/
@Repository
public interface LocalFileStore extends ContentStore<FileDocument, String> {
}
AppService中使用Store
import lombok.extern.slf4j.Slf4j;
import org.evaltool.evalengine.common.utils.NanoIdUtils;
import org.evaltool.tool.domain.FileDocument;
import org.evaltool.tool.store.LocalFileStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
@Service
public class FileStorageAppService {
@Autowired
private LocalFileStore fileStore;
/**
* 上传文件
*/
public String storeFile(MultipartFile file) {
String original = file.getOriginalFilename();
String ext = StringUtils.getFilenameExtension(original);
if (original == null || original.contains(".."))
throw new RuntimeException("非法文件名");
FileDocument doc = new FileDocument();
String nanoId = NanoIdUtils.random();
doc.setId(nanoId);
doc.setFileName(original);
doc.setFileExt(ext);
doc.setFileSize(file.getSize());
try (InputStream in = file.getInputStream()) {
fileStore.setContent(doc, in);
} catch (IOException e) {
throw new RuntimeException("存储文件失败", e);
}
return doc.getId() + "." + ext;
}
/**
* 下载文件
*/
public Resource loadFile(String fileName) {
String ext = StringUtils.getFilenameExtension(fileName);
ext = ext != null ? ext : "";
String fileId = StringUtils.replace(fileName, "." + ext, "");
FileDocument doc = new FileDocument();
doc.setId(fileId);
doc.setFileName(fileName);
doc.setFileExt(ext);
InputStream content = fileStore.getContent(doc);
if (content == null) {
throw new IllegalArgumentException("File not found");
}
return new InputStreamResource(content);
}
}
(可选操作)保存的文件携带后缀
SpringContent默认存储的文件名等于id,不带后缀。如果要携带后缀,需要修改配置,添加Converter来增加后缀。
注意:本地文件只能使用filesystemStorePlacementService
import org.apache.commons.lang3.StringUtils;
import org.evaltool.tool.domain.FileDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.ConverterRegistry;
@Configuration
public class ContentStoreConfig {
/**
* Spring Content默认使用id作为文件名不满足要求, 需要给文件添加扩展名
*/
@Autowired
public void configureConverter(@Qualifier("filesystemStorePlacementService") ConverterRegistry registry) {
registry.addConverter(FileDocument.class, String.class, doc -> {
String ext = doc.getFileExt();
return StringUtils.isEmpty(ext) ? doc.getId() : doc.getId() + "." + ext.toLowerCase();
});
}
}