1.概述
在构建某种内容管理解决方案时,我们需要解决两个问题。我们需要一个地方来存储文件本身,并且需要某种数据库来对它们进行索引。
可以将文件的内容存储在数据库本身中,或者我们可以将内容存储在其他位置并用数据库建立索引。
在本文中,我们将通过基本的Image Archive Application演示这两种方法。我们还将实现用于上传和下载的REST API。
2.用例
我们的图像存档应用程序将允许我们上传和下载JPEG图像。
当我们上传图像时,应用程序将为其创建唯一的标识符。然后,我们可以使用此标识符下载它。
我们将使用带有Spring Data JPA和Hibernate的关系数据库。
3.数据库存储
让我们从我们的数据库开始。
3.1 图像实体
首先,让我们创建Image
实体:
@Entity
class Image {
@Id
@GeneratedValue
Long id;
@Lob
byte[] content;
String name;
// Getters and Setters
}
id
字段用@GeneratedValue
注释。这意味着数据库将为我们添加的每条记录创建一个唯一的标识符。通过使用这些值索引图像,我们无需担心同一图像的多次上传会相互冲突。
其次,我们有Hibernate @Lob
批注。这就是我们告诉JPA我们打算存储潜在的大型二进制文件的方式。
3.2 Image Repository
接下来,我们需要一个存储库以连接到数据库。
我们将使用spring JpaRepository
:
@Repository
interface ImageDbRepository extends JpaRepository<Image, Long> {}
现在我们准备保存图像。我们只需要一种将它们上传到我们的应用程序中的方法。
4. REST控制器
我们将使用MultipartFile
上传图像。上载将返回imageId
我们以后可以使用它来下载图像。
4.1 图片上传
让我们开始创建我们的ImageController
以支持上传:
@RestController
class ImageController {
@Autowired
ImageDbRepository imageDbRepository;
@PostMapping
Long uploadImage(@RequestParam MultipartFile multipartImage) throws Exception {
Image dbImage = new Image();
dbImage.setName(multipartImage.getName());
dbImage.setContent(multipartImage.getBytes());
return imageDbRepository.save(dbImage)
.getId();
}
}
MultipartFile
对象包含文件的内容和原始名称。我们使用它来构造Image
像以存储在数据库中。
该控制器返回生成的id作为其响应的主体。
4.2 图片下载
现在,让我们添加一个下载路径:
@GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE)
Resource downloadImage(@PathVariable Long imageId) {
byte[] image = imageRepository.findById(imageId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND))
.getContent();
return new ByteArrayResource(image);
}
imageId
路径变量包含在上载时生成的ID。如果提供的ID无效,那么我们将使用ResponseStatusException
返回HTTP响应代码404(未找到)。否则,我们会将存储的文件字节包装在ByteArrayResource
,以便下载它们。
5. 数据库映像存档测试
现在,我们可以测试图像存档了。
首先,让我们构建应用程序:
mvn package
其次,让我们开始吧:
java -jar target/image-archive-0.0.1-SNAPSHOT.jar
5.1 图片上传测试
应用程序运行后,我们将使用curl
命令行工具上传图像:
curl -H "Content-Type: multipart/form-data" \
-F "[email protected]" http://localhost:8080/image
由于上传服务的响应为imageId
,
这是我们的第一个请求,因此输出为:
1
5.2 图片下载测试
然后,我们可以下载图像:
curl -v http://localhost:8080/image/1 -o image.jpeg
-o image.jpeg
选项将创建一个名为image.jpeg
的文件,并将响应内容存储在其中:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /image/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< Accept-Ranges: bytes
< Content-Type: image/jpeg
< Content-Length: 9291
我们获得了HTTP / 1.1 200,这意味着我们的下载成功。
我们也可以尝试通过点击http://localhost:8080/image/1
在浏览器中下载图像。
6.内容和位置分开
到目前为止,我们已经能够在数据库中上载和下载图像。
另一个不错的选择是将文件内容上传到其他位置。然后,我们仅将其文件系统location
**保存**在DB中。
为此,我们需要向Image
实体添加一个新字段:
String location;
这将包含某些外部存储中文件的逻辑路径。在我们的例子中,它将是服务器文件系统上的路径。
但是,我们可以将此想法同样应用到不同的商店。例如,我们可以使用云存储– Google Cloud Storage或Amazon S3。该位置也可以使用URI格式,例如s3://somebucket/path/to/file
。
我们的上载服务不是将文件的字节写入数据库,而是将文件存储在适当的服务(在本例中为文件系统)中,然后将文件的位置放入数据库中。
7.文件系统存储
让我们将在文件系统中存储图像的功能添加到我们的解决方案中。
7.1 保存在文件系统中
首先,我们需要将图像保存到文件系统中:
@Repository
class FileSystemRepository {
String RESOURCES_DIR = FileSystemRepository.class.getResource("/")
.getPath();
String save(byte[] content, String imageName) throws Exception {
Path newFile = Paths.get(RESOURCES_DIR + new Date().getTime() + "-" + imageName);
Files.createDirectories(newFile.getParent());
Files.write(newFile, content);
return newFile.toAbsolutePath()
.toString();
}
}
一个重要的注意事项–我们需要确保我们的每个图像在上传时都在服务器端定义了唯一的location
。否则,我们的上传内容可能会相互覆盖。
相同的规则适用于任何云存储,我们应该在其中创建唯一的密钥。在此示例中,我们将以毫秒格式将当前日期添加到图像名称:
/workspace/archive-achive/target/classes/1602949218879-baeldung.jpeg
7.2 从文件系统检索
现在,让我们实现代码以从文件系统中获取图像:
FileSystemResource findInFileSystem(String location) {
try {
return new FileSystemResource(Paths.get(location));
} catch (Exception e) {
// Handle access or file not found problems.
throw new RuntimeException();
}
}
在这里,我们**使用其location
**查找图像。然后,我们返回FileSystemResource
。
另外,我们正在捕获读取文件时可能发生的任何异常。我们可能还希望抛出具有特定HTTP状态的异常。
7.3 数据流和Spring的资源
我们的findInFileSystem
方法返回FileSystemResource
,这是Spring的Resource
接口的实现。
仅当我们使用文件时,它才会开始读取文件。在我们的例子中,将是通过RestController
将其发送到客户端的时候。同样,它将把文件内容从文件系统流传输到用户,从而避免了将所有字节加载到内存中的麻烦。
这种方法是将文件流式传输到客户端的良好通用解决方案。如果使用的是云存储而不是文件系统,则可以将FileSystemResource
替换为另一个资源的实现,例如InputStreamResource
或ByteArrayResource
。
8.连接文件内容和位置
现在我们有了FileSystemRepository,
我们需要将其链接到ImageDbRepository.
8.1。保存在数据库和文件系统中
让我们创建一个FileLocationService
,从保存流程开始:
@Service
class FileLocationService {
@Autowired
FileSystemRepository fileSystemRepository;
@Autowired
ImageDbRepository imageDbRepository;
Long save(byte[] bytes, String imageName) throws Exception {
String location = fileSystemRepository.save(bytes, imageName);
return imageDbRepository.save(new Image(imageName, location))
.getId();
}
}
首先,我们将图像保存在文件系统中。然后,将包含其location
的记录保存在数据库中。
8.2 从数据库和文件系统检索
现在,让我们创建一个使用其id
查找图片的方法:
FileSystemResource find(Long imageId) {
Image image = imageDbRepository.findById(imageId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return fileSystemRepository.findInFileSystem(image.getLocation());
}
首先,我们在数据库中查找图像。然后我们获取它的位置并从文件系统中获取它。
如果imageId
,则使用ResponseStatusException返回HTTP Not Found响应.
9.文件系统上传和下载
最后,让我们创建FileSystemImageController:
@RestController
@RequestMapping("file-system")
class FileSystemImageController {
@Autowired
FileLocationService fileLocationService;
@PostMapping("/image")
Long uploadImage(@RequestParam MultipartFile image) throws Exception {
return fileLocationService.save(image.getBytes(), image.getOriginalFilename());
}
@GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE)
FileSystemResource downloadImage(@PathVariable Long imageId) throws Exception {
return fileLocationService.find(imageId);
}
}
首先,我们使新路径以“ / file-system
”开头。
然后,我们创建了与ImageController
类似的上载路由,但没有dbImage
对象。
最后,我们有下载路径,该路径使用FileLocationService
查找图像并返回FileSystemResource
作为HTTP响应。
10.文件系统映像存档测试
现在,我们可以像使用数据库版本一样测试文件系统版本,尽管路径现在以“ file-system
”开头:
curl -H "Content-Type: multipart/form-data" \
-F "[email protected]" http://localhost:8080/file-system/image
1
然后我们下载:
curl -v http://localhost:8080/file-system/image/1 -o image.jpeg
11.结论
在本文中,我们学习了如何将文件信息保存在数据库中,文件内容位于同一行或位于外部位置。
我们还使用分段上传来构建和测试REST API,并使用Resource
提供了下载功能,以允许将文件流式传输到调用方。
0 评论