File Upload
File Upload
2021/11/30
Multipart傳送檔案
首先,產生一個上傳的頁面 (src/ui/ImageUpload.js):
import React from 'react';
import {Box, Input} from '@mui/material';
import axios from 'axios';
export default function ImageUpload() {
const handleUpload = function(e){
console.log(e.target.files[0]);
}
return (
<Box>
<Input type="file" accept="image/x-png,image/jpeg" onChange={handleUpload}/>
</Box>
)
}
應該會在console裡看到類似這樣的內容
File {name: 'cat.jpeg', lastModified: 1576031351526, lastModifiedDate: Wed Dec 11 2019 10:29:11 GMT+0800 (台北標準時間), webkitRelativePath: '', size: 38095, …}
那要怎麼把檔案傳出去呢? 可以使用post。
import React from 'react';
import {Box, Input} from '@mui/material';
import axios from 'axios';
export default function ImageUpload() {
const handleUpload = async function(e){
console.log(e.target.files[0]);
try {
const result = await axios.post("/file",e.target.files[0]);
console.log(result);
}
catch(error){
console.log(error);
}
}
return (
<Box>
<Input type="file" accept="image/x-png,image/jpeg" onChange={handleUpload}/>
</Box>
)
}
在spring的部分,我們新增對應的rest controller,spring framework提供MultipartFile來接收上傳的檔案。
controller/FileUploadController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController {
@PostMapping("/file")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
System.out.println(file.getOriginalFilename());
//storageService.store(file);
return "OK";
}
}
但是,會在瀏覽器這邊得到:
Error: Request failed with status code 500
看一下spring這邊,會發現
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
原因是,因為我們要傳遞檔案,不能直接呼叫post,要將資料包在FormData裡。那我們來改一下上傳頁面(src/ui/ImageUpload.js):
import React from 'react';
import {Box, Input} from '@mui/material';
import axios from 'axios';
export default function ImageUpload() {
const handleUpload = async function(e){
console.log(e.target.files[0]);
try {
const formData = new FormData()
formData.append(
'file',e.target.files[0]
)
const result = await axios.post("/file",formData);
console.log(result);
}
catch(error){
console.log(error);
}
}
return (
<Box>
<Input type="file" accept="image/x-png,image/jpeg" onChange={handleUpload}/>
</Box>
)
}
在console.log可以看到
{data: 'OK', status: 200, statusText: 'OK', headers: {…}, config: {…}, …}
在伺服器端,可以看到上傳檔案的名稱。
Spring儲存檔案
收到檔案之後,接下來要將檔案儲存在伺服器上。習慣上,我們都不直接串連實作的類別,我們先產生一個StorageService,StorageService是個interface,真正的實作是FileSystemStorageService,
service/StorageService.java
package com.example.demo.service;
import org.springframework.web.multipart.MultipartFile;
public interface StorageService {
void store(MultipartFile file);
}
真正的實作,service/FileSystemStorageService.java,在這裡,利用了java內建的Path及Files,將收到的檔案放到指定的路徑(如果路徑是檔案名,就是以該名稱為檔案名)。
package com.example.demo.service;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@Service
public class FileSystemStorageService implements StorageService {
@Override
public void store(MultipartFile file) {
try {
Path p = Paths.get("test.jpg");
Files.copy(file.getInputStream(),p,StandardCopyOption.REPLACE_EXISTING );
} catch (IOException e) {
System.out.println(e);
}
}
}
把服務跟controller串連起來:
package com.example.demo.controller;
import com.example.demo.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController {
@Autowired
private StorageService storageService;
@PostMapping("/file")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
System.out.println(file.getOriginalFilename());
storageService.store(file);
return "OK";
}
}
這時候可以看到專案最上面那一層多了一個test.jpg了! 如果我們要把檔案儲存為原本的檔案名稱,我們就改一下,注意,因為我們的設定是REPLACE_EXISTING,所以,如果檔案名稱一樣,會被覆蓋:
Path p = Paths.get(file.getOriginalFilename());
Files.copy(file.getInputStream(),p,StandardCopyOption.REPLACE_EXISTING );