如何使用 Spring Data REST 将图像文件提供给前端?
How to serve image files to frontend with Spring Data REST?
我正在尝试创建一个电影数据库网络应用程序。每部电影都应该有一张海报图片。我不知道如何使用 Spring Data REST 正确地将图像提供给前端。
Movie.java
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.File;
import java.sql.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Data
@Entity
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String director;
private Date releaseDate;
private File posterFile;
@ManyToMany
@JoinTable(
name = "MOVIE_GENRES",
joinColumns = @JoinColumn(name = "MOVIE_ID"),
inverseJoinColumns = @JoinColumn(name = "GENRE_ID"))
private Set<Genre> genres = new HashSet<>();
@OneToMany
@MapKeyColumn(name = "ACTOR_ROLE")
private Map<String, Actor> cast = new HashMap<>();
public Movie(String title) {
this.title = title;
}
public void addActor(String role, Actor actor) {
cast.put(role, actor);
}
public void removeActor(String role) {
cast.remove(role);
}
public void addGenre(Genre genre) {
genres.add(genre);
}
public void removeGenre(Genre genre) {
genres.remove(genre);
}
}
我不能在电影 bean 中使用字节数组,因为它太大而无法保存在数据库中。我可以改为存储 File 对象或 Path 对象或包含路径的 String:
private File posterFile;
问题是,它将保存像 "C:\user\documents\project\backend\images\posterxyz.png"
这样的本地路径。
当我尝试在我的前端使用此路径作为 img-src 时,出现错误 "Not allowed to load local resource"。我的意思是,无论如何,这听起来像是一种愚蠢的做法。我只是不知道这样做的正确方法是什么。
这是电影资料库。
我在后端使用 Spring 数据 REST,以超媒体应用程序语言格式生成 JSON。
MovieRepository.java
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(collectionResourceRel = "movies", path = "movies")
public interface MovieRepository extends PagingAndSortingRepository<Movie, Long> {
}
我会:
一个
通过向字段添加 @JsonIgnore
注释来防止 posterFile
属性被序列化。
@JsonIgnore
private File posterFile;
您也可以通过 Jackson mix-in class 执行此操作,以避免 'polluting' 您使用 Json 处理指令的实体,但您需要自己进行研究。
两个
向资源表示添加自定义 link,这将允许客户端按需获取图像数据。例如/movies/21/poster
有关如何向资源添加自定义 link 的详细信息,请参阅此处:
并且专门用于为 Spring MVC 控制器创建 link:
三个
创建一个标准 Spring MVC 控制器绑定到您的自定义 link 指向的路径,它将读取文件数据并流式传输响应。
例如
@Controller
public MoviePosterController{
@GetMapping(path="/movies/{movieId}/poster")
//https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web for auto resolution of path var to domain Object
public @ResponseBody byte[] getPoster(@PathVariable("movieId") Movie movie, HttpServletResponse response){
File file = movie.getPosterFile();
//stream the bytes of the file
// see https://www.baeldung.com/spring-controller-return-image-file
// see https://www.baeldung.com/spring-mvc-image-media-data
}
}
这在 Spring Data/REST 中是不可能的,因为它专注于结构化数据;即大部分表格和关联。是的,如其他答案中所述,您可以跳过一些环节,但还有一个名为 Spring Content 的相关项目正好解决了这个问题域。
Spring内容提供与SpringData/REST相同的编程范式,只是针对非结构化数据;即图像、文档、电影等。因此,使用此项目,您可以将一个或多个 "content" 对象与 Spring 数据实体相关联,并通过 HTTP 管理它们,就像您的 Spring 数据实体一样也是。
添加到您的项目中非常简单,如下所示:
pom.xml (boot starters also available)
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa</artifactId>
<version>0.9.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>0.9.0</version>
</dependency>
Configuration
@Configuration
@EnableJpaStores
@Import("org.springframework.content.rest.config.RestConfiguration.class")
public class ContentConfig {
// schema management (assuming mysql)
//
@Value("/org/springframework/content/jpa/schema-drop-mysql.sql")
private Resource dropContentTables;
@Value("/org/springframework/content/jpa/schema-mysql.sql")
private Resource createContentTables;
@Bean
DataSourceInitializer datasourceInitializer() {
ResourceDatabasePopulator databasePopulator =
new ResourceDatabasePopulator();
databasePopulator.addScript(dropContentTables);
databasePopulator.addScript(createContentTables);
databasePopulator.setIgnoreFailedDrops(true);
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource());
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
要关联内容,请将 Spring 内容注释添加到您的电影实体。
Movie.java
@Entity
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.. existing fields...
// private File posterFile; no longer required
@ContentId
private String contentId;
@ContentLength
private long contentLength = 0L;
// if you have rest endpoints
@MimeType
private String mimeType = "text/plain";
}
创建 "store":
MoviePosterContentStore.java
@StoreRestResource(path="moviePosters")
public interface MoviePosterContentStore extends ContentStore<Movie, String> {
}
这就是创建 REST 端点所需的全部 @ /moviePosters
。当您的应用程序启动时,Spring Content 将查看您的依赖项,查看 Spring Content JPA,查看您的 MoviePosterContentStore
接口并为 JPA 注入该接口的实现。它还将看到 Spring 内容 REST 依赖项并注入一个 @Controller
实现,将 HTTP 请求转发到您的 MoviePosterContentStore。这使您不必自己实施任何这些,我认为这就是您所追求的。
所以...
使用注入的 REST 管理内容 API:
curl -X POST /moviePosters/{movieId}
-F 文件=@/path/to/poster.jpg
将图像存储在数据库中(作为 BLOB)并且将其与 id 为 movieId
.
的电影实体相关联
curl /moviePosters/{movieId} -H "Accept: image/jpeg"
将再次获取它等等...支持所有 CRUD 方法和视频流顺便说一句!
有几个入门指南 here. The reference guide for JPA is here. And there is a tutorial video here。编码位从大约 1/2 开始。
另外几点:
- 如果您使用 Spring Boot Starters,那么大部分情况下您不需要 @Configuration。
- 就像 Spring 数据是一种抽象一样,Spring 内容也是一种抽象,因此您不仅限于将海报图像作为 BLOB 存储在数据库中。您可以将它们存储在文件系统或云存储(如 S3)或 Spring 内容支持的任何其他存储中。
HTH
@RestController
// Becareful here, never use @RepositoyRestController.it will be got Error look like NoConverter for.....
@RequiredArgsConstructor
// becase you use Path rootLocation, don't use @AllArgsContructor
public class StorageController {
private final StorageRepository storageRepository;
@Value("${file.upload.path}")
private Path rootLocation;
@Bean
public RepresentationModelProcessor<EntityModel<Storage>> storageProcessor() {
return new RepresentationModelProcessor<EntityModel<Storage>>() {
@Override
public EntityModel<Storage> process(EntityModel<Storage> model) {
model.add(
linkTo(methodOn(StorageController.class).look(model.getContent().getId())).withRel("view")
);
return model;
}
};
}
@GetMapping(path = "storages/{id}/view")
public ResponseEntity<?> look(@PathVariable final Long id) {
Storage storage = storageRepository.findById(id).orElseThrow(RuntimeException::new);
Path path = rootLocation.resolve(storage.getPath());
Resource resource = null;
try {
resource = new UrlResource(path.toUri());
} catch (MalformedURLException e) {
throw new RuntimeException("Error!!!!");
}
if (resource.exists() || resource.isReadable()) {
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_TYPE, storage.getMime())
.body(resource);
} else {
throw new RuntimeException("Error!!!");
}
}
}
我正在尝试创建一个电影数据库网络应用程序。每部电影都应该有一张海报图片。我不知道如何使用 Spring Data REST 正确地将图像提供给前端。
Movie.java
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.File;
import java.sql.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Data
@Entity
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String director;
private Date releaseDate;
private File posterFile;
@ManyToMany
@JoinTable(
name = "MOVIE_GENRES",
joinColumns = @JoinColumn(name = "MOVIE_ID"),
inverseJoinColumns = @JoinColumn(name = "GENRE_ID"))
private Set<Genre> genres = new HashSet<>();
@OneToMany
@MapKeyColumn(name = "ACTOR_ROLE")
private Map<String, Actor> cast = new HashMap<>();
public Movie(String title) {
this.title = title;
}
public void addActor(String role, Actor actor) {
cast.put(role, actor);
}
public void removeActor(String role) {
cast.remove(role);
}
public void addGenre(Genre genre) {
genres.add(genre);
}
public void removeGenre(Genre genre) {
genres.remove(genre);
}
}
我不能在电影 bean 中使用字节数组,因为它太大而无法保存在数据库中。我可以改为存储 File 对象或 Path 对象或包含路径的 String:
private File posterFile;
问题是,它将保存像 "C:\user\documents\project\backend\images\posterxyz.png"
这样的本地路径。
当我尝试在我的前端使用此路径作为 img-src 时,出现错误 "Not allowed to load local resource"。我的意思是,无论如何,这听起来像是一种愚蠢的做法。我只是不知道这样做的正确方法是什么。
这是电影资料库。 我在后端使用 Spring 数据 REST,以超媒体应用程序语言格式生成 JSON。
MovieRepository.java
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(collectionResourceRel = "movies", path = "movies")
public interface MovieRepository extends PagingAndSortingRepository<Movie, Long> {
}
我会:
一个
通过向字段添加 @JsonIgnore
注释来防止 posterFile
属性被序列化。
@JsonIgnore
private File posterFile;
您也可以通过 Jackson mix-in class 执行此操作,以避免 'polluting' 您使用 Json 处理指令的实体,但您需要自己进行研究。
两个
向资源表示添加自定义 link,这将允许客户端按需获取图像数据。例如/movies/21/poster
有关如何向资源添加自定义 link 的详细信息,请参阅此处:
并且专门用于为 Spring MVC 控制器创建 link:
三个
创建一个标准 Spring MVC 控制器绑定到您的自定义 link 指向的路径,它将读取文件数据并流式传输响应。
例如
@Controller
public MoviePosterController{
@GetMapping(path="/movies/{movieId}/poster")
//https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web for auto resolution of path var to domain Object
public @ResponseBody byte[] getPoster(@PathVariable("movieId") Movie movie, HttpServletResponse response){
File file = movie.getPosterFile();
//stream the bytes of the file
// see https://www.baeldung.com/spring-controller-return-image-file
// see https://www.baeldung.com/spring-mvc-image-media-data
}
}
这在 Spring Data/REST 中是不可能的,因为它专注于结构化数据;即大部分表格和关联。是的,如其他答案中所述,您可以跳过一些环节,但还有一个名为 Spring Content 的相关项目正好解决了这个问题域。
Spring内容提供与SpringData/REST相同的编程范式,只是针对非结构化数据;即图像、文档、电影等。因此,使用此项目,您可以将一个或多个 "content" 对象与 Spring 数据实体相关联,并通过 HTTP 管理它们,就像您的 Spring 数据实体一样也是。
添加到您的项目中非常简单,如下所示:
pom.xml (boot starters also available)
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa</artifactId>
<version>0.9.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>0.9.0</version>
</dependency>
Configuration
@Configuration
@EnableJpaStores
@Import("org.springframework.content.rest.config.RestConfiguration.class")
public class ContentConfig {
// schema management (assuming mysql)
//
@Value("/org/springframework/content/jpa/schema-drop-mysql.sql")
private Resource dropContentTables;
@Value("/org/springframework/content/jpa/schema-mysql.sql")
private Resource createContentTables;
@Bean
DataSourceInitializer datasourceInitializer() {
ResourceDatabasePopulator databasePopulator =
new ResourceDatabasePopulator();
databasePopulator.addScript(dropContentTables);
databasePopulator.addScript(createContentTables);
databasePopulator.setIgnoreFailedDrops(true);
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource());
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
要关联内容,请将 Spring 内容注释添加到您的电影实体。
Movie.java
@Entity
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.. existing fields...
// private File posterFile; no longer required
@ContentId
private String contentId;
@ContentLength
private long contentLength = 0L;
// if you have rest endpoints
@MimeType
private String mimeType = "text/plain";
}
创建 "store":
MoviePosterContentStore.java
@StoreRestResource(path="moviePosters")
public interface MoviePosterContentStore extends ContentStore<Movie, String> {
}
这就是创建 REST 端点所需的全部 @ /moviePosters
。当您的应用程序启动时,Spring Content 将查看您的依赖项,查看 Spring Content JPA,查看您的 MoviePosterContentStore
接口并为 JPA 注入该接口的实现。它还将看到 Spring 内容 REST 依赖项并注入一个 @Controller
实现,将 HTTP 请求转发到您的 MoviePosterContentStore。这使您不必自己实施任何这些,我认为这就是您所追求的。
所以...
使用注入的 REST 管理内容 API:
curl -X POST /moviePosters/{movieId}
-F 文件=@/path/to/poster.jpg
将图像存储在数据库中(作为 BLOB)并且将其与 id 为 movieId
.
curl /moviePosters/{movieId} -H "Accept: image/jpeg"
将再次获取它等等...支持所有 CRUD 方法和视频流顺便说一句!
有几个入门指南 here. The reference guide for JPA is here. And there is a tutorial video here。编码位从大约 1/2 开始。
另外几点:
- 如果您使用 Spring Boot Starters,那么大部分情况下您不需要 @Configuration。
- 就像 Spring 数据是一种抽象一样,Spring 内容也是一种抽象,因此您不仅限于将海报图像作为 BLOB 存储在数据库中。您可以将它们存储在文件系统或云存储(如 S3)或 Spring 内容支持的任何其他存储中。
HTH
@RestController
// Becareful here, never use @RepositoyRestController.it will be got Error look like NoConverter for.....
@RequiredArgsConstructor
// becase you use Path rootLocation, don't use @AllArgsContructor
public class StorageController {
private final StorageRepository storageRepository;
@Value("${file.upload.path}")
private Path rootLocation;
@Bean
public RepresentationModelProcessor<EntityModel<Storage>> storageProcessor() {
return new RepresentationModelProcessor<EntityModel<Storage>>() {
@Override
public EntityModel<Storage> process(EntityModel<Storage> model) {
model.add(
linkTo(methodOn(StorageController.class).look(model.getContent().getId())).withRel("view")
);
return model;
}
};
}
@GetMapping(path = "storages/{id}/view")
public ResponseEntity<?> look(@PathVariable final Long id) {
Storage storage = storageRepository.findById(id).orElseThrow(RuntimeException::new);
Path path = rootLocation.resolve(storage.getPath());
Resource resource = null;
try {
resource = new UrlResource(path.toUri());
} catch (MalformedURLException e) {
throw new RuntimeException("Error!!!!");
}
if (resource.exists() || resource.isReadable()) {
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_TYPE, storage.getMime())
.body(resource);
} else {
throw new RuntimeException("Error!!!");
}
}
}