vue + jpa project (13) - 게시판 첨부파일 처리(이미지) 본문
vue + jpa project (13) - 게시판 첨부파일 처리(이미지)
- 2023. 10. 31. 11:38
게시판에서 첨부를 할 수 있는 기능을 적용시켜 보겠다.
사실 실제 프로젝트에서는 멀티로 다양하게 첨부할 수 있는 기능을 만들겠지만
여기서는 맛보기?로 간단하게 처리할 수 있게만 만들려고 한다.
이것을 가지고 보다 다양한 방식으로 확장해나가면 좋을 듯 한다. (사실 많이 바꾸기도 해야한다 ^^;;)
1. 우선 application.yml에 첨부와 관련된 내용을 아래와 같이 추가한다.
그리고 파일 경로 폴더가 없다며 미리 생성을 시키도록 한다. (백엔드에서 폴더를 자동생성이 아닌경우)
spring: # 이 줄은 위에 이미 정의되어 있어서 추가시에는 생략해도 된다.
servlet:
multipart:
max-file-size: 8MB # 첨부가능한 파일사이즈
max-request-size: 8MB # request로 요청가능한 사이즈
file: # file 앞에 공백이 없어야 함
upload-path: c:/temp/upload/ # 임의 정의한 것으로 파일 업로드 경로로 사용
2. 그리고 BoardDto.java 파일에 MultipartFile picture 항목을 추가한다.
private Long boardNo;
private String title;
private String writer;
private String content;
private String pictureUrl;
private MultipartFile picture; // 첨부파일용 항목
private String regDate;
private String updDate;
3. BoardController.java 파일에 첨부파일 관련한 부분을 추가한다.
첨부파일을 업로드 받으면서 저장까지 처리를 해야하므로 별도 메소드를 추가해서
기존의 post와 patch처리를 하나로 처리하도록 하겠다.
하나씩 BoardController에 추가를 하면, 우선 로그를 찍기 위해서 @Slf4j 어노테이션을 추가하고, 상단에 첨부파일
올라가는 경로를 @Value 어노테이션을 이용하여 값을 읽어와서 저장시킨다.
@Slf4j
@RequiredArgsConstructor
@CrossOrigin
@RestController
public class BoardController {
@Value("${file.upload-path}")
private String file_upload_path;
delete 메소드 다음에 아래 메소드를 추가하고 기존 @PostMapping과 @PatchMapping 의 URI를 다른 것으로 바꾼다.
나의 경우에는 "board_old" 로 둘 다 바꾸었다. 첨부파일에 해당하는 Content-type이 multipart/form-data 경우에는
@PatchMapping을 사용하지 못하고 @PostMapping 만 사용가능하다.
그리고 데이터를 받을 때 @ModelAttribute로 받아야 multipart/form-data 를 받을 수 있게 된다.
나중에 아래서 프론트 단에서 넘겨주는 데이터는 기존의 boardDto에 각각 들어가고
위에서 BoardDto 에 추가한 picture 항목은 파일데이터를 받게된다.
중간에 파일 처리는 파일이 있는 경우에는 uploadFile 메소드를 통해서 별도 파일을 처리하고
그 이름만 넘겨받아서 테이블에 저장하도록 한다.
또한 이 메소드 하나로 등록과 수정을 동시에 처리해야하므로 boardNo 값이 없으면 저장, 있으면 수정하도록 하였다.
/* 이미지 첨부 및 저장/수정 */
@PostMapping("/board")
public BoardEntity register(@ModelAttribute BoardDto boardDto) throws Exception {
MultipartFile pictureFile = boardDto.getPicture();
if(pictureFile != null) {
log.info("register pictureFile != null " + pictureFile.getOriginalFilename());
String createdPictureFilename = uploadFile(pictureFile.getOriginalFilename(), pictureFile.getBytes());
boardDto.setPictureUrl(createdPictureFilename);
}
else {
log.info("register pictureFile == null ");
}
if(boardDto.getBoardNo() == null) {
return boardService.create(boardDto);
} else {
return boardService.update(boardDto);
}
}
위의 uploadFile 메소드를 아래와 같이 추가한다. 원래의 파일명 앞에 UUID를 붙혀서 폴더에 저장시킨다.
UUID를 붙히는 이유는 중복파일을 업로드 할 수 있기 때문이다.
(실제로는 이렇게 저장하지 않고 파일명자체를 UUID로 바꾸고 별도 첨부파일 테이블에서 관리한다)
private String uploadFile(String originalName, byte[] fileData) throws Exception {
UUID uid = UUID.randomUUID();
String createdFileName = uid.toString() + "_" + originalName;
File target = new File(file_upload_path, createdFileName);
FileCopyUtils.copy(fileData, target);
return createdFileName;
}
그리고 프론트에서 저장된 이미지를 조회하여 보여주는 부분도 필요하기 때문에 아래의 메소드를 추가한다.
추후에 /board/picture/ 라는 URI에 Get파라미터로 boardNo값을 수신하여 저장된 정보를 가져와서
파일을 리턴해주는 메소드이고, 미디어타입이 일단 jpg, gif, png만 처리되도록 한다.
@GetMapping("/board/picture")
public Resource displayFile(@RequestParam("boardNo") Long boardNo) throws Exception {
String pictureUrl = boardService.getPictureUrl(boardNo);
try {
String formatName = pictureUrl.substring(pictureUrl.lastIndexOf(".") + 1);
MediaType mediaType = getMediaType(formatName);
if(mediaType != null) {
return new UrlResource("file:" + file_upload_path + pictureUrl);
} else {
return null;
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
private MediaType getMediaType(String formatName){
if(formatName != null) {
if(formatName.equalsIgnoreCase("JPG")) {
return MediaType.IMAGE_JPEG;
}
if(formatName.equalsIgnoreCase("GIF")) {
return MediaType.IMAGE_GIF;
}
if(formatName.equalsIgnoreCase("PNG")) {
return MediaType.IMAGE_PNG;
}
}
return null;
}
4. 프론트의 BoardWrite.vue 파일을 아래와 같이 수정한다.
boardWrite.vue
... 중략
<div class="board-content">
<textarea id="" cols="30" rows="10" v-model="content" class="w3-input w3-border" style="resize: none;">
</textarea>
</div>
<!-- 아래 div 추가 -->
<div class="board-content">
<input type="file" @change="handleFileChange($event)" >
</div>
... 중략
<script>
export default {
data() {
return {
requestBody : this.$route.query,
boardNo : this.$route.query.boardNo,
title : '',
content : '',
writer : '',
picture : '', // 첨부파일 항목 추가
reg_date : '',
}
},
... 중략
fnSave() {
if(!confirm('저장하시겠습니까?')) return
let apiUrl = this.$serverUrl + '/board'
this.form = {
"boardNo": this.boardNo,
"title": this.title,
"writer": this.writer,
"content": this.content,
"picture": (this.picture === '')?null:this.picture, // 첨부파일 항목 추가
}
if (this.boardNo === undefined) {
//INSERT
this.$axios.post(apiUrl, this.form, {
headers: {'Content-type': 'multipart/form-data'} // 첨부파일 헤더추가
})
.then((res) => {
alert('정상적으로 저장되었습니다.')
this.fnView(res.data.board_no);
})
.catch((err) => {
if (err.message.indexOf('Network Error') > -1) {
alert('네트워크가 원활하지 않습니다.\n잠시 후 다시 시도해주세요.')
}
})
} else {
//UPDATE
this.$axios.post(apiUrl, this.form, { // 수정도 .post로 변경
headers: {'Content-type': 'multipart/form-data'} // 첨부파일 헤더추가
})
.then((res) => {
alert('정상적으로 저장되었습니다.')
this.fnView(res.data.board_no);
})
.catch((err) => {
if (err.message.indexOf('Network Error') > -1) {
alert('네트워크가 원활하지 않습니다.\n잠시 후 다시 시도해주세요.')
}
})
}
},
handleFileChange(evt) { // 첨부파일 처리 함수 추가
this.picture = evt.target.files[0]
}
... 중략
5. 프론트의 BoardDetail.vue 파일을 아래와 같이 수정한다.
boardDetail.vue
... 중략
<div class="board-content">
<span>{{ content }}</span>
</div>
<!-- 아래 div 추가 -->
<div class="board-content">
<img v-if="picture_url" :src="previewPicture(`${boardNo}`)" width="220" height="220">
</div>
... 중략
<script>
export default {
data() {
return {
requestBody : this.$route.query,
boardNo : this.$route.query.boardNo,
title : '',
content : '',
writer : '',
picture_url : '', // 첨부파일 항목 추가
reg_date : '',
}
},
... 중략
fnGetView() {
this.$axios.get(this.$serverUrl + '/board/' + this.boardNo, {
params : this.requestBody,
}).then((res) => {
this.title = res.data.title
this.writer = res.data.writer
this.content = res.data.content
this.picture_url = res.data.picture_url // 첨부파일 항목 추가
this.reg_date = res.data.reg_date
})
.catch((err) => {
if (err.message.indexOf('Network Error') > -1) {
alert('네트워크가 원활하지 않습니다.\n잠시 후 다시 시도해주세요.')
}
})
},
... 중략
previewPicture(boardNo) {
return 'http://localhost:8081/board/picture?boardNo=' + boardNo + '×tamp=' + new Date().getTime()
}
이렇게 수정하고 신규로 등록하여 첨부파일을 지정하고 저장, 수정을 진행해보자.
게시판에 대한 파일첨부를 할 수 있는 기능을 어느정도? 완료하였다. (완벽하진 않다는 뜻임)
게시판 관련한 기능을 이것으로써 마무리가 되었다.
다음 장에서는 로그인과 관련한 처리를 진행해보겠다.
'프로그램 > Vue.js' 카테고리의 다른 글
vue + jpa project (15) - 로그인 및 백엔드 환경 초기 구성 (0) | 2023.11.01 |
---|---|
vue + jpa project (14) - vuex설정 및 로그인 화면 생성 (0) | 2023.10.31 |
vue + jpa project (12) - 게시판 등록, 상세, 수정, 삭제 (0) | 2023.10.30 |
vue + jpa project (11) - 게시판 검색조건 프론트 처리 (0) | 2023.10.27 |
vue + jpa project (10) - 게시판 검색조건 백엔드 처리 (0) | 2023.10.27 |
RECENT COMMENT