在后端使用spring-boot 2.0.4,在前端使用vue 2.5.16 / axios 0.18.0,我希望将PDF文件上传到后端db并从我的数据库中检索它们前端。
最初,我从这个示例的春天部分得到启发:https://grokonez.com/frontend/angular/angular-6/angular-6-client-upload-files-download-files-to-mysql-with-springboot-restapis
Axios部分的要点是: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
我的代码如下:
使用axios在Vue中上传文件(将this.file变量使用“ input type =“ file”“形式输入正确设置为我的文件,AxiosService()仅用于设置正确的baseUrl并包括具有令牌的授权标头):
createRecord() {
let formData = new FormData();
formData.append("file", this.file);
AxiosService()
.post("/commands/material/", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(response => {
console.log("File uploaded");
})
.catch(error => {
console.log(error);
});
处理上载的弹簧部分如下所示。在我的实体中,content字段定义为带@Lob注释的byte []。
@BasePathAwareController
@RequestMapping("/commands/material")
public class MaterialCommandHandler {
@Autowired
MaterialRepository MaterialRepository;
@RequestMapping(method=POST, path = "/")
public ResponseEntity create(@RequestParam("file") MultipartFile file){
MaterialEntity material = new MaterialEntity();
material.setName(file.getOriginalFilename());
material.setMimetype(file.getContentType());
try {
material.setContent(file.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
try {
MaterialRepository.save(material);
} catch (Exception e) {
if (e instanceof DataIntegrityViolationException) {
throw new InvalidCommandException("Data is invalid for creation.");
}
throw(e);
}
return ResponseEntity.status(HttpStatus.CREATED).body(material.getId());
}
使用此代码,该条目已在数据库中正确创建,mysql中的content字段为longblob类型。
定义为返回文件内容的方法:
@RequestMapping(method = GET, path = "/download/{fileId}")
public ResponseEntity<byte[]> getFile(@PathVariable Long fileId) {
Optional<MaterialEntity> fileOptional = materialRepository.findById(fileId);
if(fileOptional.isPresent()){
FrancaisMaterialEntity file = fileOptional.get();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachement; filename=\"" + file.getName() + "\"");
return ResponseEntity.ok()
.headers(headers)
.body(file.getContent());
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
最后,使用axios从前端发送的GET方法:
downloadFile() {
AxiosService()
.get(`/commands/material/download/${this.material.id}`, {
responseType: "blob",
})
.then(response => {
console.log(response);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "CalculRanking.pdf");
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
})
.catch(error => {
console.log(error);
this.errorMessage = error.response.data.message;
});
}
当尝试下载文件时,导航器中的弹出窗口会正确显示,但不幸的是,由于Chrome指出:“错误无法加载PDF文档”,因此下载的.pdf文件似乎已损坏。我无法打开它在预览中都不会。
我认为问题出在过程中的某个时刻,内容解释有误。我做了很多研究,但没有尝试过的解决方案使它起作用(我尝试过的一些事情:在值中添加值为“ application / pdf”的“ Accept”标头,并在get请求中设置“ responseType:arrayBuffer”)我决定在这里问这个问题。预先感谢您的帮助。
答案 0 :(得分:1)
您可以通过以下方式更改方法getFile
:
@GetMapping("/download/{fileId}")
@CrossOrigin
@ResponseBody
public ResponseEntity<InputStreamResource> getFile(@PathVariable(required = true, value = "fileId") Long fileId,
HttpServletRequest request) throws IOException {
Optional<MaterialEntity> fileOptional = materialRepository.findById(fileId);
if (ensemblesRegles.isPresent()) {
String fileName = "example.xlsx";
MediaType mediaType = MediaType.parseMediaType("application/vnd.ms-excel");
File file = new File(fileName); //the fileUtils is org.apache.commons.io.FileUtils;
FileUtils.writeByteArrayToFile(file, fileOptional.get()); // Hope that your get return a byte[] array
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
return ResponseEntity.ok()
// Content-Disposition
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
// Content-Type
.contentType(mediaType)
// Contet-Length
.contentLength(file.length()) //
.body(resource);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
答案 1 :(得分:1)
您为什么不看Spring Content。旨在完全按照您的意图进行操作,并将一个或多个内容对象与Spring数据实体相关联。
要将其添加到现有的Spring Boot项目中,请执行以下操作:-
pom.xml
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa-boot-starter</artifactId>
<version>0.4.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.4.0</version>
</dependency>
MaterialEntity.java
@Entity
public class MaterialEntity {
@Id
@GeneratedValue
private long id;
...other existing fields...
@ContentId
private String contentId;
@ContentLength
private long contentLength = 0L;
@MimeType
private String mimeType = "text/plain";
...
}
MaterialEntityContentStore.java
@StoreRestResource(path="materialEntityContents")
public interface MaterialEntityContentStore extends ContentStore<MaterialEntity, String> {
}
这是获取REST端点所需要做的全部工作,这些端点将使您可以存储和检索与每个MaterialEntity关联的内容。实际工作方式与Spring Data非常相似。当您的应用程序启动时,Spring Content将看到spring-content-jpa-boot-starter
依赖项,并且知道您要将内容存储在数据库中。然后,它将在数据库中创建一个架构来这样做,并注入MaterialEntityContentStore
接口的JPA实现。它还将看到spring-content-rest-boot-starter
并将注入与内容存储接口对话的REST端点。意味着您不必自己执行任何此操作。
例如,
curl -X POST /materialEntityContents/{materialEntityId} -F "file=@/path/to/image.jpg"
会将图像存储在数据库中,并将其与ID为materialEntityId
的材料实体相关联。
curl /materialEntityContents/{materialEntity}
将再次获取它,依此类推...实际上也完全支持完整的CRUD和视频流。
具体来说,有一个(非SpringBoot)MySQL示例here。
您还可以通过将spring-content-jpa-boot-starter
依赖项交换为适当的Spring Content Storage模块,来决定将内容存储在文件系统上或S3中的其他位置。每种存储类型的示例均为here。
不幸的是,没有前端vuejs示例,但是我们确实有angularjs 1.x示例here。这可能对前端有所帮助,因为它们是类似的技术(以我对两者的有限经验!)。