-
MultipartFile 파일업로드(with. react)Spring 2021. 9. 5. 20:49
spring에서 파일업로드 기능을 실행해보자.
파일을 저장하기 위해서는 spring프로젝트 내부에 파일저장 폴더가 위치해야 한다.
File Upload Utils
먼저 파일을 업로드하는 util코드를 작성해준다. saveFile을 이용해서 파일폴더가 존재하지 않으면 폴더를 만들어주고 파일을 저장한다. cleanDir는 프로필이미지를 저장하는 경우에 아이디를 이용한 폴더안에 이미 파일이 존재하면 기존파일을 삭제한 후에 새로 파일을 저장해준다.
import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; public class FileUploadUtils { public static void saveFile(String uploadDir, String fileName, MultipartFile multipartFile) throws IOException { Path uploadPath = Paths.get(uploadDir); if(!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); } try(InputStream inputStream = multipartFile.getInputStream()) { Path filePath = uploadPath.resolve(fileName); Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); }catch (IOException ex){ throw new IOException("Could not save file: " +fileName, ex); } } public static void cleanDir(String dir) { Path dirPath = Paths.get(dir); try { Files.list(dirPath).forEach(file->{ if(!Files.isDirectory(file)) { try { Files.delete(file); }catch(IOException ex) { System.out.println("Could not delete file : " + file); } } }); }catch(IOException ex2) { System.out.println("Could not list directory : " + dirPath); } } }
WebConfig 파일폴더위치 알려주기
파일폴더가 내부에 위치하는 곳을 알려주어야 파일을 읽어낼 수 있다. 업로드 폴더를 알아내는 부분이 중복되므로, 업로드 폴더 위치를 알려주는 함수를 다음과 같이 빼내서 정의해준다.
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // registry.addResourceHandler("/profileUploads/**") // .addResourceLocations("file:////"+ profileImagesFolder ); // String dirName = "profileUploads"; // // Path profilePhotosDir = Paths.get(dirName); // // String profilePhotosPath = profilePhotosDir.toFile().getAbsolutePath(); // // registry.addResourceHandler("/"+ dirName+"/**") // .addResourceLocations("file:" + profilePhotosPath+"/"); uploadFolder("profileUploads", registry); uploadFolder("messageUploads", registry); } private void uploadFolder(String dirName, ResourceHandlerRegistry registry){ Path photosDir = Paths.get(dirName); String photospath = photosDir.toFile().getAbsolutePath(); registry.addResourceHandler("/"+dirName+"/**") .addResourceLocations("file:" + photospath + "/"); }
Web Security Config
파일업로드와 같은 정적파일에 외부에서 접근 가능하도록 설정해준다.
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/profileUploads/**","/messageUploads/**", "/js/**","/webjars/**"); }
React
multipart파일을 서버로 보내보자. 메세지에서 이미지를 보내는 코드를 작성했다. 이미지를 입력받으면 thumbnail을 보여주도록 createObjectURL을 통해서 src를 설정해주고, submit 버튼을 누르면 파일을 bodyFormData에 formData형식으로 파일을 multipartFile의 이름으로 넣어준다.
const handleImage = async(e)=>{ const file = e.target.files[0] const err = checkImage(file) if(err) return window.alert(err) if(file){ var preview = document.getElementById('preview') preview.src = URL.createObjectURL(file) } setImageUrl(file) } const checkImage = (file) =>{ let err="" if(!file) return err="File does not exist." if(file.size>1024*1024){ err = "The largest image size is 1mb." } if(file.type !== 'image/jpeg' && file.type !== 'image/png'){ err = "Image format is incorrect." } return err } const handleImageDelete= () =>{ setImageUrl('') var preview = document.getElementById('preview') preview.src = '' } const handleSubmit= async(e) =>{ e.preventDefault() var imageURL = '' if(imageUrl!==''){ const bodyFormData = new FormData() bodyFormData.append('multipartFile', imageUrl) const result = await axios.post('/message/image', bodyFormData,{ headers : {Authorization : `Bearer ${auth.token}`} }) imageURL = result.data } ... } <div className="show_media" style={{display: imageUrl ? 'grid':'none'}}> <div id="file_media"> <img id="preview" src={''} alt="imageURL" /> <span onClick={handleImageDelete}>×</span> </div> </div> <form className="message-input" onSubmit={handleSubmit} > <input type="text" placeholder="Enter your Message" value={text} onChange={(e)=>setText(e.target.value)} /> <div className="file_upload"> <i className="fas fa-image text-danger"></i> <input type="file" name="file" id="file" accept="image/*" onChange={handleImage}/> </div> <button type="submit" className="material-icons" disabled={text || imageUrl? false : true}> near_me </button> </form>
Controller에서 multipartFile 받기
@RequestParam을 이용해서 multipartFile을 받아준다. 여기서 "multipartFile"은 위에서 bodyformData에 넣어준 이름과 동일해야한다.
@PostMapping("/message/image") public String saveImages(@RequestParam("multipartFile") MultipartFile multipartFile){ return service.saveImages(multipartFile); }
Service에서 파일 처리하기(저장)
디렉토리를 설정해주고 fileUploadUtil을 이용해서 파일을 저장해준다.
public String saveImages(MultipartFile multipartFile){ String fileName = System.currentTimeMillis()+ StringUtils.cleanPath(multipartFile.getOriginalFilename()); try{ String uploadDir = "messageUploads"; FileUploadUtils.saveFile(uploadDir, fileName, multipartFile); }catch (IOException e){ new IOException("Could not save file : " + multipartFile.getOriginalFilename()); } return "/messageUploads/"+ fileName; }
bodyFormData 형태로 여러데이터 받기
이번엔 multipartFile외에도 여러형태의 데이터를 bodyFormData로 받아서 한번에 처리해보자.
const handleImage = async(e)=>{ const file = e.target.files[0] const err = checkImage(file) if(err) return window.alert(err) if(file){ var preview = document.getElementById('preview') preview.src = URL.createObjectURL(file) } setImageURL(file) } const checkImage = (file) =>{ let err="" if(!file) return err="File does not exist." if(file.size>1024*1024){ err = "The largest image size is 1mb." } if(file.type !== 'image/jpeg' && file.type !== 'image/png'){ err = "Image format is incorrect." } return err } const handleSubmit = async(e) =>{ e.preventDefault(); const check = valid(name, email, password, confirmPassword, description, phoneNumber) if(check.errLength===0){ const res = await axios.post(`/user/check_email?email=${email}`,null) const companyres = await axios.post(`/company/check_name?name=${cName}`, null) if(res.data ==="Duplicated"){ window.alert('This email already exist.') }else if(companyres.data ==="Duplicated"){ window.alert('This Company name already exist.') }else{ const bodyFormData = new FormData() bodyFormData.append('multipartFile', imageURL) bodyFormData.append('name', name) bodyFormData.append('email', email) bodyFormData.append('password', password) bodyFormData.append('description', description) bodyFormData.append('phoneNumber', phoneNumber) bodyFormData.append('role', ceo? "CEO" : "Member") const userDTO = { name: name, email : email, password :password, description: description, phoneNumber : phoneNumber, role : ceo? "CEO" : "Member", } dispatch(register(bodyFormData)) //const res1 = await axios.post('/user/register',bodyFormData) //const res2 = await axios.post('authenticate',{username:email,password:password }) }else { setErr(check.err) } } <div className="form"> <form onSubmit={handleSubmit} encType="multipart/form-data"> <div className="form-name"> Register </div> ... <div className="form-group"> <label htmlFor="image">Image</label> <img id="preview" src={''} alt="imageURL" /> <input type="file" className="form-control" id="file_up" name="file" accept="image/*" onChange={handleImage} /> </div> <div className="form-group"> <label htmlFor="phoneNumber">Phone</label> <input type="text" className="form-control" id="phoneNumber" name="phoneNumber" onChange={e=>setPhoneNumber(e.target.value)} value={phoneNumber}/> </div> ... <div className="form-button"> <button type="submit" >Register</button> </div> </form> </div>
userController를 예시로 프로필 이미지를 입력받도록 하자. 여기서 bodyform형태로 모든 정보를 담아서 왔기 때문에 다음과 같이 requestParam을 이용해서 받아준다.
@PostMapping("/user/register") public ResponseEntity<UserDTO> register(@RequestParam("name")String name, @RequestParam("email")String email, @RequestParam("password")String password, @RequestParam("description")String description, @RequestParam("phoneNumber")String phoneNumber, @RequestParam("role")String role, @RequestParam("multipartFile")MultipartFile multipartFile) throws IOException { UserDTO userDTO = new UserDTO(name, email, password, description, phoneNumber, role); UserDTO savedUserDTO = service.save(userDTO, multipartFile); savedUserDTO.setPassword(""); return new ResponseEntity<UserDTO>(savedUserDTO, HttpStatus.OK); }
userService코드를 작성해준다. 여기서 프로파일은 "/profileUploads/아이디"폴더안에 각각 저장해주고, 하나의 아이디당 1개의 파일만을 가질 수 있도록 설정해주었다. 이미지를 프론트엔드에서 경로를 찾을 수 있도록 imageUrl에 저장해주었다. ("/"을 붙여서 폴더위치를 찾도록 설정해준다)
public UserDTO save(UserDTO userDTO, MultipartFile multipartFile) throws IOException { if(userDTO.getId()!=null){ User existingUser = repo.findById(userDTO.getId()).get(); existingUser.setName(userDTO.getName()); if(userDTO.getPassword()!=null){ existingUser.setPassword(userDTO.getPassword()); } if(multipartFile != null){ String fileName = StringUtils.cleanPath(multipartFile.getOriginalFilename()); try{ existingUser.setImageName(fileName); String uploadDir = "profileUploads/"+ existingUser.getId(); FileUploadUtils.cleanDir(uploadDir); FileUploadUtils.saveFile(uploadDir, fileName, multipartFile); existingUser.setImageUrl("/profileUploads/"+ existingUser.getId() + "/" + fileName); }catch (IOException e){ new IOException("Could not save file : " + multipartFile.getOriginalFilename()); } } existingUser.setDescription(userDTO.getDescription()); repo.save(existingUser); return new UserDTO(existingUser); }else{ User user = new User(); userDTO.setId(user.getId()); user.setName(userDTO.getName()); user.setEmail(userDTO.getEmail()); user.setPassword(passwordEncoder.encode(userDTO.getPassword())); user.setDescription(userDTO.getDescription()); if(userDTO.getRole()!=null){ Role role = roleRepository.findByName("CEO"); user.setRole(role); }else{ Role role = roleRepository.findByName("Member"); user.setRole(role); } repo.save(user); if(multipartFile != null){ String fileName = StringUtils.cleanPath(multipartFile.getOriginalFilename()); user.setImageName(fileName); String uploadDir = "profileUploads/"+ user.getId(); FileUploadUtils.saveFile(uploadDir, fileName, multipartFile); user.setImageUrl("/profileUploads/"+ user.getId() + "/" + fileName); } repo.save(user); return new UserDTO(user); } }
'Spring' 카테고리의 다른 글
No serializer found for class Exception 에러 해결방법 (0) 2021.09.05 @ManyToMany, @OneToMany, @ManyToOne관계 작성하기 (0) 2021.09.05 예외처리전략 (Exception Handler) (0) 2021.09.05 JWT 토큰을 이용해서 로그인 인증 구현하기 (9) 2021.09.05 WebSocket 사용해서 react와 함께 채팅구현하기 (Stomp사용하기) (1) 2021.09.05