728x90
게시글 작성에서 파일 첨부하는 방법을 기록한다. (본 글은 이미지 첨부 방법을 다룸)
1. Mysql - file 테이블
CREATE TABLE `tb_file` (
`id` int NOT NULL AUTO_INCREMENT,
`board_id` int NOT NULL,
`original_file_name` varchar(100) NOT NULL,
`size` int NOT NULL,
`path` varchar(100) NOT NULL,
`stored_file_name` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
KEY `board_id` (`board_id`),
CONSTRAINT `tb_file_ibfk_1` FOREIGN KEY (`board_id`) REFERENCES `tb_board` (`id`) ON DELETE CASCADE
)
- orginal_file_name : 사용자가 저장한 원본 파일이름
- size : KB단위의 파일의 용량
- path : 파일이 저장된 위치
- stred_file_name : 서버에 저장된 uuid로 중복을 제거한 이름
2. FileDTO
@Data
public class FileDTO {
private Long id;
private Long boardId;
private String originalFileName;
private Long size;
private String path;
private String storedFileName;
public FileDTO(Long boardId, String originalFileName, String storedFileName, Long size, String path) {
this.boardId = boardId;
this.originalFileName = originalFileName;
this.storedFileName = storedFileName;
this.size = size;
this.path = path;
}
}
3. 게시글 작성 Controller
// 게시글 작성 & 수정
@PostMapping("/form")
public String boardSubmit(@Valid Board board, BindingResult bindingResult, Principal principal,
@RequestParam(value = "files", required = false) List<MultipartFile> files, Long id) throws IOException, SQLException {
if (bindingResult.hasErrors() || files.size() > 7) {
return "board/form";
}
String loginUsername = principal.getName();
Long newBoardId = 0l;
if (id == null) { // 새 글 작성
newBoardId = boardService.save(board, loginUsername); // Insert
// 첨부파일 있을 때
if(!files.get(0).getOriginalFilename().isEmpty()) {
for(int i = 0; i < files.size(); i ++) {
if(files.get(i).getContentType().contains("image/")) {
fileService.saveFile(files.get(i), newBoardId);
} else {
System.out.println("이미지 타입이 아닙니다");
}
}
}
} else { // 기존 글 수정
boardService.update(board, id); // Update
}
return "redirect:/board/list";
}
- @Valid : 게시글 작성 중에 제목과 내용의 제약조건(제목 2글자 이상 등)을 지키지 않으면 첫 줄의 if문을 통해 게시글 작성 홈페이지로 다시 돌아간다.
- !files.get(0).getOriginalFilename().isEmpty() : MultipartFile 타입의 List의 첫 값을 통해 첨부파일의 존재 여부를 확인
- getContentType을 통해 첨부파일의 형태가 IMAGE인지 확인 후 저장 로직 실행
- @RequestParam(value = "files", required = false) : 값이 필수가 아닐 때 required = false 작성. (안적어주면 파일 첨부하지 않을 때 오류 Bad request 400 오류 발생)
4. Service
@Service
public class FileService {
private final FileMapper fileRepository;
@Autowired
public FileService(FileMapper fileMapper) {
this.fileRepository = fileMapper;
}
// 게시글 작성 & 수정에서 첨부파일 추가
public void saveFile(MultipartFile file, Long boardId) throws IOException, SQLException {
String uuid = UUID.randomUUID().toString(); // 파일명 중복 제거를 위한 uuid
String originName = file.getOriginalFilename(); // 파일 원본명
Long fileSize = file.getSize() / 1024; // kb
String path = "C:/Temp/";
String newName = "";
if (originName.lastIndexOf(".") < 0) {
newName = uuid + originName; // 확장자명이 없을 때
} else {
newName = uuid + StringUtils.substring(originName, originName.lastIndexOf(".")); //확장자명 포함
}
FileDTO fileDTO = new FileDTO(boardId, originName, newName, fileSize, path + newName);
int result = fileRepository.insertFile(fileDTO);
if (result > 0 && !file.getOriginalFilename().isEmpty()) {
file.transferTo(new File(path + newName));
}
}
}
- UUID를 이용해서 서버에 저장되는 파일명의 중복을 막는다.
- StringUtils.substring을 이용해 확장자를 포함시켜 저장
- result값을 받아서 insert가 바르게 진행 됐는지 확인 (바르게 저장 시 1 리턴)
- MultipartFile.transferTo : if문 조건(result값과 MultipartFile의 null 체크)을 만족하면 서버로 첨부파일(이미지) 저장
5. View
<main class="flex-shrink-0">
<div class="container mt-3">
<h2>게시판</h2>
<form action="#" th:action="@{/board/form}" th:object="${board}" method="post"
enctype="multipart/form-data">
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{writerId}">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control"
th:classappend="${#fields.hasErrors('title')} ? 'is-invalid'" id="title" th:field="*{title}">
<div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" id="validationServer03Feedback"
class="invalid-feedback">
Title Error
</div>
</div>
<div class="mb-3">
<label for="content" class="form-label">content</label>
<textarea class="form-control" id="content" rows="13"
th:classappend="${#fields.hasErrors('content')} ? 'is-invalid'"
th:field="*{content}"></textarea>
<div th:if="${#fields.hasErrors('content')}" th:errors="*{content}" id="validationServer03Feedback"
class="invalid-feedback">
Content Error
</div>
</div>
<div id="imageThumbnail">
</div>
<!-- button -->
<div id="uploadForm" th:if="${param.boardId == null}">
<div id="uploadElement">
<input id="uploadInput" type="file" class="btn btn-outline-primary" name="files" accept="image/*"
onchange="setThumbnail(event);" multiple /> <span style="font-size: small;"> * jpeg / png 타입의
이미지를
7개까지
등록해주세요.</span>
</div>
<a id="reset" class="mt-3 btn btn-danger" onclick="resetImg()">Reset</a>
</div>
<div class="nav justify-content-end mb-5">
<button id="submit" type="submit" class="me-2 btn btn-primary">write</button>
<a type="button" class="btn btn-primary" th:href="@{/board/list}">exit</a>
</div>
</form>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
- th:if를 이용해서 새로운 글 작성 할 때만 이미지 첨부를 가능하게 제약조건 추가 (글 수정으로 글 작성 form으로 진입하는 경우 첨부파일 버튼을 html에서 제거)
- <form> : enctype 속성을 "multipart/form-data" 로 설정해줘야 파일을 컨트롤러로 전달 가능
- <div> : id="imageThumbnail" <-- 여기에 첨부된 파일의 미리보기를 불러올 예정 (썸네일)
- <input type="file" accept="image/*" multiple/> : accept로 첨부파일 선택 시 모든파일 -> image타입의 파일을 선택하도록 유도. multiple을 사용해야 다중 파일 선택 가능
6. script
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
document.getElementById('submit').addEventListener('click', imageCounting)
// 첨부 이미지 리셋 버튼
function resetImg() {
var fileUpload = document.getElementById('uploadInput')
var img = document.querySelectorAll('img')
fileUpload.value = null
$('#imageThumbnail').children().remove()
}
// 이미지 개수 제한
function imageCounting() {
var fileUpload = $("input[type='file']")
if (parseInt(fileUpload.get(0).files.length) > 7) {
alert("이미지는 최대 7개까지 등록 가능합니다.")
}
}
// 첨부 이미지 미리보기
function setThumbnail(event) {
for (var image of event.target.files) {
var reader = new FileReader()
reader.onload = function (event) {
var img = document.createElement("img")
img.className = "img-thumbnail mb-3 me-3"
img.name = "thumbnail"
img.style = "inline"
img.width = 200
img.height = 200
img.setAttribute("src", event.target.result)
document.querySelector("div#imageThumbnail").appendChild(img)
}
reader.readAsDataURL(image)
}
}
</script>
- resetImg (첨부 이미지 리셋, 첨부파일 리셋)
- fileUpload.value = null 을 통해 지금까지 불러온 첨부파일 값을 null로 설정
- $('#imageThumbnail').children().remove() : imageThumbnail 아이디의 태그 자식태그를 제거
- imgCounting (첨부 개수제한)
- $("input[type='file']") 를 이용해서 타입이 file인 input 태그를 가져와 저장
- setThumbnail
- FileReader를 이용해서 이미지 미리보기(썸네일) 정보 셋팅
7. 결과
* 왜인지 모르겠으나 녹화할 때 파일 선택창이 안나와서 추가!
8. 게시글 작성, 수정 (일부 코드 수정) : https://black-mint.tistory.com/70
6번 - setThumbnail 참고 : https://sinna94.tistory.com/entry/JavaScript-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0
'Web > SpringBoot' 카테고리의 다른 글
[Spring Boot] 게시글 작성, 수정 (form 값 검증) (0) | 2022.01.18 |
---|---|
[Spring Boot] 첨부 이미지 보여주기 (0) | 2022.01.11 |
[Spring Boot] Caused by: java.lang.NumberFormatException: For input string: "컬럼 데이터" (오류 해결) (0) | 2022.01.10 |
[Spring Boot] Mybatis insert, update 시 PK값 얻기 (0) | 2022.01.09 |
[Spring Boot] ajax 리스트 값 서버로 보내기 (휴지통 복원, 삭제) (0) | 2022.01.06 |