티스토리 뷰
꽤나 길었던 기간동안 문제를 해결하지 못하고 있었다.
요구사항
- 자료실의 파일은 3개까지 올릴 수 있다.
- 자료실과 갤러리는 각자의 확장자를 받는다. => 자료실 : pdf, docs, hwp / 갤러리 : jpg, jpeg, png
- 자료실의 게시글을 수정할 때 자료가 3개이면 자료를 삭제 후 올릴 수 있고, 자료가 2개이면 1개의 자료만, 자료가 1개면 2개의 자료를 올릴 수 있도록 하자(핵심!)
마지막 3번째 요구사항에서 많은 버그를 겪었다. 쉽게 예시를 보자면



각 게시글은 각각 파일을 1, 2, 3개씩 갖고 있는 게시글이고 게시글당 파일은 3개씩 받기로 했으니 파일 수정은 동적으로 변해야 한다는 얘기이다.
(디자인은 못본척 ㅎㅎ)
HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/basic.html}">
<div layout:fragment="content">
<div class="row mt-3">
<div class="col">
<div class="card">
<div class="card-header">
수정
</div>
<div class="card-body view-card-body">
<form action="/attached/modify" method="post" enctype="multipart/form-data">
<input type="hidden" id="boardNo" name="boardNo" th:value="${dto.boardNo}">
<input type="hidden" id="categoryBoard" name="categoryBoard" th:value="${dto.categoryBoard}">
<input type="hidden" id="urlCategory" name="urlCategory" th:value="attached">
<div class="input-group mb-3">
<span class="input-group-text col-sm-1">제목</span>
<input type="text" class="form-control" name="boardTitle" th:value="${dto.boardTitle}">
</div>
<div class="input-group mb-3">
<span class="input-group-text col-sm-1">작성자</span>
<input type="text" class="form-control" name="boardWriter" th:value="${dto.boardWriter}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text col-sm-1">조회수</span>
<input type="text" class="form-control" th:value="${dto.boardView}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text col-sm-1">생성일자</span>
<input type="text" class="form-control" th:value="${#dates.format(dto.boardRegDate, 'yy.MM.dd HH:mm')}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text col-sm-1">수정일자</span>
<input type="text" class="form-control" th:value="${dto.boardModDate == null ? '-' : #dates.format(dto.boardModDate, 'yy.MM.dd HH:mm')}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text col-sm-1">내용</span>
<textarea class="form-control col-sm-5" name="boardContent" rows="5">[[ ${dto.boardContent} ]]</textarea>
</div>
//파일 수정 핵심
<div class="col">
<div class="input-group mb-3">
<div th:if="${response.attachedCount == 0}">
<div class="col-md-3">
<input class="form-control" id="file1" name="files" type="file">
</div>
<div class="col-md-3">
<input class="form-control" id="file2" name="files" type="file">
</div>
<div class="col-md-3">
<input class="form-control" id="file3" name="files" type="file">
</div>
</div>
<div th:if="${response.attachedCount == 1}" th:each="attached: ${response.attachedDto}">
<div class="card-text" th:text="${attached.attachedOriginalName}"></div>
<a th:href="@{/attached/delete/{attachedNo}(attachedNo=${attached.attachedNo})}"
class="btn btn-danger">파일삭제</a>
<div>
<input class="form-control" name="files" type="file">
<input class="form-control" name="files" type="file">
</div>
</div>
<div>
<div th:if="${response.attachedCount == 2}" th:each="attached: ${response.attachedDto}">
<div class="card-text" th:text="${attached.attachedOriginalName}"></div>
<a th:href="@{/attached/delete/{attachedNo}(attachedNo=${attached.attachedNo})}"
class="btn btn-danger">파일삭제</a>
</div>
<div th:if="${response.attachedCount == 2}">
<input class="form-control" name="files" type="file">
</div>
</div>
<div th:if="${response.attachedCount == 3}" th:each="attached: ${response.attachedDto}">
<div class="card-text" th:text="${attached.attachedOriginalName}"></div>
<a th:href="@{/attached/delete/{attachedNo}(attachedNo=${attached.attachedNo})}"
class="btn btn-danger">파일삭제</a>
</div>
</div>
</div>
//파일수정 마무리
<div class="my-4">
<div class="float-end">
<a th:href="@{/attached}" class="text-decoration-none">
<button type="button" class="btn btn-secondary">취소</button>
</a>
<button type="submit" class="btn btn-primary" id="btn-file-save">수정</button>
<button type="button" class="btn btn-danger" id="btn-delete">삭제</button>
</div>
</div>
</form>
</div><!--card body-->
</div><!--card-->
</div>
</div>
</div>
최종 결과물이다. 그간의 시행착오를 적고싶지만 너무 복잡하게 생각하였기 때문에 결과로 풀어보겠다.
핵심은 response의 attachedCount이다.
기존의 AttachedDTO에 attachedCount라는 첨부파일 개수를 추가하여 컨트롤러에서 전해준다.
그 후 각각 0개부터 3개까지의 경우에 따라 파일첨부하는 <div>태그를 추가해주었다.
개인적인 생각으론 이게 좋은 코드라고 생각하지 않는다.
만약 파일의 개수에 따라 코드를 추가한다면 훗날 자료실에 첨부파일이 5개~10개 늘어날때마다 위 코드를 추가해주어야한다.
(단지 첨부파일이 3개일 경우만 해도 40줄 가까운 코드가 나왔다.)
후에 컴포넌트화 시켜 해결할 방법을 생각해봐야할듯 하다.
왜 0개의 경우를 추가했을까? 한다면
파일 수정할때 전체 파일을 바꾸고 싶을 경우 게시글을 전체 삭제 하고 다시 올리는게 아닌 '파일'만을 삭제하고 다시 올리고 싶어 0개의 경우를 추가했다. (만약 0개의 경우가 없으면 에러가 날것이다.)
<div>
<div th:if="${response.attachedCount == 2}" th:each="attached: ${response.attachedDto}">
<div class="card-text" th:text="${attached.attachedOriginalName}"></div>
<a th:href="@{/attached/delete/{attachedNo}(attachedNo=${attached.attachedNo})}"
class="btn btn-danger">파일삭제</a>
</div>
<div th:if="${response.attachedCount == 2}">
<input class="form-control" name="files" type="file">
</div>
</div>
Controller
/**
* 자료실 수정
* @param boardDTO
* @param files
*/
@PostMapping("/attached/modify")
public String modifyAttachedArticle(BoardDTO boardDTO,
@RequestParam(required = false) MultipartFile[] files) throws IOException {
if (files == null) {
attachedService.modifyAttachedArticle(boardDTO);
} else {
attachedService.modifyAttachedArticle(boardDTO);
//파일 수정
attachedService.uploadAttached(boardDTO, files);
}
return "redirect:/attached";
}
required가 false가 없을 경우 : 자료실의 글만 바꾸고 files의 수정이 없을 경우 에러를 발생한다.
if (files == null) 은 required = false 를 걸러졌을때 게시글만 변경하는 경우니 service에서 "글"만 변경하도록 하였다.
else일 경우 정상적인 파일, 게시글을 수정한다.
Service
/**
* 다중 파일 업로드
* @param boardDTO
*/
@Transactional
public void uploadAttached(BoardDTO boardDTO, MultipartFile[] files) throws IOException {
for (MultipartFile file : files) {
//파일이 존재 하지 않을 경우 넘어감
if (file.isEmpty()) {
continue;
}
String extension = StringUtils.getFilenameExtension(file.getOriginalFilename());
//자료실 파일 확장자 구분
assert extension != null;
if (extension.equalsIgnoreCase("hwp") ||
extension.equalsIgnoreCase("pdf") ||
extension.equalsIgnoreCase("docs")) {
AttachedDTO attachedDTO = boardUtil.uploadFile(boardDTO, file);
file.transferTo(new File(attachedDTO.getAttachedPath()));
attachedRepository.postAttached(attachedDTO);
} else {
throw new IllegalArgumentException("맞지 않는 확장자 입니다. 확인 후 재시도 해주세요.");
}
}
}
요구사항에 맞는 확장자를 분리하였다.
사실 따지고 보자면 파일 수정이 아닌 "파일을 삭제 후 등록"이 맞는 표현같다.
그렇기에 내부적으로 에러를 처리해야 되는 부분이 보이기도 하고..
따로 다른 부분들을 참고하면서 진행하는 것이 아닌 스스로 전부 생각하면서 진행하니 하나만 막혀도 시간이 오래걸리고
애로사항이 많다고 생각한다.
하지만 정말 0부터 100까지 스스로 작성해보자고 생각한 이상 시간이 걸려도 마무리 짓도록 해야겠다.
https://github.com/Mo-Greene/SpringBoot_Admin_MPA
GitHub - Mo-Greene/SpringBoot_Admin_MPA: MPA 관리자 게시판
MPA 관리자 게시판. Contribute to Mo-Greene/SpringBoot_Admin_MPA development by creating an account on GitHub.
github.com
'Backend > SpringBoot' 카테고리의 다른 글
Swagger Opendoc API (0) | 2023.07.18 |
---|---|
Jwt + SpringSecurity + Mybatis 구현 (0) | 2023.07.06 |
SpringBoot 비동기 댓글 (0) | 2023.04.27 |
SpringBoot 파라미터 달고다니기 (0) | 2023.04.26 |
SpringSecurity 없이 SpringBoot 자동로그인 구현 (0) | 2023.04.24 |
- mybatis구현
- JWT
- 객체지향
- 함께모으기
- 리눅스마스터2급
- java 플레이그라운드
- 한권으로끝내기리눅스마스터2급
- for
- 정수형으로 변환
- springboot
- vuex
- 책리뷰
- pinia
- 스프링부트
- 토스페이먼츠
- SpringSecurity
- vue.js
- script setup
- 알고리즘
- Vue.js3
- it책 리뷰
- CompositionAPI
- 다음 큰 숫자
- 타임리프
- 짝지어제거하기
- 프로그래머스
- 객체 지도
- 맥 error
- LEVEL2
- 객체지향의 사실과 오해
- Total
- Today
- Yesterday