이미지 파일을 사용자로 부터 입력 받아 처리하기 까지 매우 복잡하여 정리를 해본다.
<input type="file" name="files" id="files" multiple="multiple" />
그러면 다음과 같이 input 요소가 생긴다
formData
로 보낸다처음에 이미지를 JSON 형태로 고칠려고 했으나 불가능.
JSON을 formData객체에 함께 담아서 보내줘야 한다.
methods: {
sendArticle() {
if ((this.content_text != "") & (this.content_title != "")) {
// 폼 데이터로 보내줘야 하므로 객체 생성
let formData = new FormData();
// json형태로 바로 보내던 데이터를 일단 변수에 저장
let data = {
id: this.userInfo.userId,
title: this.content_title,
content: this.content_text,
};
// 그냥 files까지만 보내면 배열이 갈 줄 알았으나 배열 모양을 한 딕셔너리더라...{0:{이름:ㅇㅇ},1:...
// 얕은 복사(Array.from)을 통해 배열로 데이터를 바꾸어 저장해준다
Array.from(document.querySelector("#files").files).forEach((file) => {
formData.append("files", file); // files로 같은 이름에 데이터를 append 하면 배열로 들어간다
console.log(file);
});
formData.append(
"key",
new Blob([JSON.stringify(data)], { type: "application/json" })
);
// => 위의 과정을 거치면 formData에는 'files'이름의 이미지 배열과
// 'key'이름의 기존json데이터(이메일,제목 등)담김
// 아래는 POST 요청 부분
http
.post("/article/board/new", formData, {
headers: {
"Content-Type": "multipart/form-data", //기존의 json 대신 formData 설정
},
transformRequest: [
function () {
return formData;
},
],
})
.then(({ status }) => {
if (status == 200) {
this.$router.push({ name: "board" });
}
});
} else {
alert("정보를 입력해 주세요");
}
},
},
Blob
: Binary Large Object. → javascript에서 이미지, 음성 등 대용량 데이터 다루는 객체@PostMapping("/board/new")
int postBoard(@RequestPart(value = "key") Article article, @RequestPart(value = "files", required = false) MultipartFile[] files) throws Exception {
// formData는 RequestPart 어노테이션을 통해 MultipartFile 클래스로 데이터를 받는다. 여기서 required = false 를 안해주니 에러가 발생하였다.
System.out.println("article : " + article + ", files : " + files);
// 프린트 : article : Article [articleNo=0, title=asdf, id=ssafy, content=asdf, regTime=null], files : [Lorg.springframework.web.multipart.MultipartFile;@4049d099
String realPath = "/Users/hvvany/Desktop/OISO_BE/last_pjt/trip/src/main/resources/static/imgs"; // 스프링 부트에서 파일 저장 시 상대경로로 하면 경로 못찾음
String today = new SimpleDateFormat("yyMMdd").format(new Date());
File folder = new File(realPath);
if (!folder.exists()) {
folder.mkdirs();
}
List<FileInfo> fileInfos = new ArrayList<FileInfo>();
for (MultipartFile mfile : files) {
FileInfo fileInfo = new FileInfo();
String originalFileName = mfile.getOriginalFilename();
// 파일 경로 없으면 폴더 생성
if (!originalFileName.isEmpty()) {
String saveFileName = UUID.randomUUID().toString() // UUID는 이미지 이름 중복 방지 위해 랜덤하게 생성된 고유값
+ originalFileName.substring(originalFileName.lastIndexOf('.'));
fileInfo.setSaveFolder(today);
fileInfo.setOriginFile(originalFileName);
fileInfo.setSaveFile(saveFileName);
mfile.transferTo(new File(folder,saveFileName));
// FileCopyUtils.copy(mfile.getInputStream(), new FileOutputStream(realPath + Paths.get(saveFileName).toFile()));
}
fileInfos.add(fileInfo);
}
// article 객체에 이미지 파일 정보도 저장해준다 (배열 형태)
article.setFileInfos(fileInfos);
// Service 단으로 데이터 전송하고 성공 여부는 int로 받는다.
int cnt = service.postBoard(article);
return cnt;
}
‘files’를 찾지 못한다
⇒ @RequestPart(value = "files", required = false) required = false
를 추가해주니 해결
상대 경로 문제
java.io.IOException: java.io.FileNotFoundException: /private/var/folders/c3/hxgrbq710ndfsnm459mczt1c0000gn/T/tomcat.80.2385627806756500432/work/Tomcat/localhost/ROOT/src/main/resources/static/imgs/a14d2d56-c734-40f0-9c06-6d30cf39b48b.png (No such file or directory)
스프링 부트 내부의 폴더에 저장하기 위해 상대 경로로 static 폴더에 접근해보려 했으나 경로를 찾지 못하고 에러가 발생했다.
찾아보니 프로젝트 외부에 절대 경로를 사용하여 경로를 지정해주어야 한다고 한다…
위의 경로 문제로 알게 된 다른 사실
mfile.transferTo(new File(folder,saveFileName));
FileCopyUtils.copy(mfile.getInputStream(), new FileOutputStream(Paths.get(saveFileName).toFile()));
둘 다 된다
public class Article {
private int articleNo;
private String title;
private String id;
private String content;
private String regTime;
private List<FileInfo> fileInfos; // 파일 저장 위해서 기존 필드에 추가된 부분
public Article() {
super();
}
public Article(int articleNo, String title, String id, String content, String regTime, List<FileInfo> fileInfos) {
super();
this.articleNo = articleNo;
this.title = title;
this.id = id;
this.content = content;
this.regTime = regTime;
this.fileInfos = fileInfos; // 파일 저장 위해서 기존 필드에 추가된 부분
}
...
int postBoard(Article article) throws Exception;
...
...
@Override
@Transactional
public int postBoard(Article article) throws Exception {
List<FileInfo> file = article.getFileInfos();
if (file != null && !file.isEmpty()) {
for (int i = 0; i < file.size(); i++) {
String fileName = file.get(i).getOriginFile();
System.out.println("Uploaded file name: " + fileName);
// 프린트 : 스크린샷 2023-05-17 오후 10.00.26.png b8c1a584-72fa-4bac-b0d7-be92b2194432.png
}
// 게시글 작성하는 매퍼 and 이미지 저장하는 매퍼 동시에 article을 보내주어 처리한다. 결과물은 and 연산으로 둘 다 0 아니면 성공 처리하게 구현했다.
return articleMapper.postBoard(article) & articleMapper.fileRegister(article);
}
return 0;
}
...
...
<insert id="postBoard">
insert into board (id, title, content)
values (#{id}, #{title}, #{content})
<selectKey resultType="int" keyProperty="articleNo" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
<insert id="fileRegister" parameterType="Article">
insert into file_info (article_no, save_folder, original_file, save_file)
values
<foreach collection="fileInfos" item="fileinfo" separator=" , ">
(#{articleNo}, #{fileinfo.saveFolder}, #{fileinfo.originFile}, #{fileinfo.saveFile})
</foreach>
</insert>
...
SELECT LAST_INSERT_ID()
: 게시글 등록하면서 자동 생성된 auto increment 값을 받아와서 이미지 저장할 때 article_No에 값을 저장한다.
위의 게시글 명령 후 아직 커밋? 이 안된 상태이므로 정보를 가져와서 파일 업로드에 사용할 수 있다.
use enjoytrips;
drop table `file_info`;
CREATE TABLE `file_info` (
`article_no` int NOT NULL,
`save_folder` varchar(100) DEFAULT NULL,
`original_file` varchar(100) DEFAULT NULL,
`save_file` varchar(100) DEFAULT NULL
);
이미지 저장을 위한 테이블 따로 생성