register.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<jsp:include page="../layout/header.jsp"></jsp:include>
<jsp:include page="../layout/nav.jsp"></jsp:include>
<h2>글쓰기</h2>
<hr>
<div class="container-md">
// ... (기존 코드)
<!-- file 입력 라인 추가 -->
<div class="mb-3">
<input type="file" name="files" class="form-control" id="files" multiple="multiple" style="display: none"><br>
<!-- 파일 버튼 트리거 사용하기 위해서 주는 버튼 -->
<button type="button" class="btn btn-primary" id="trigger">파일 업로드</button>
</div>
<!-- 파일 목록 표시라인 -->
<div class="mb-3" id="fileZone">
</div>
<button type="submit" class="btn btn-primary" id="regBtn">등록</button>
<a href="/"><button type="button" class="btn btn-danger">취소</button></a>
</form>
</div>
<script src="/resources/js/boardFile.js"></script>
<jsp:include page="../layout/footer.jsp"></jsp:include>
boardFile.js
document.getElementById('trigger').addEventListener('click', ()=>{
document.getElementById('files').click();
});
const regExp = new RegExp("\.(.exe|sh|bat|dll|jar|msi)$");
const maxSize = 1024 * 1024 * 20;
function fileValidation(fileName, fileSize) {
if(regExp.test(fileName)) {
return 0;
} else if (fileSize > maxSize) {
return 0;
} else {
return 1;
}
}
document.addEventListener('change',(e)=>{
console.log(e.target);
if(e.target.id == 'files') {
const fileObj = document.getElementById('files').files;
console.log(fileObj);
document.getElementById('regBtn').disabled = false;
let div = document.getElementById('fileZone');
div.innerHTML = '';
let isOk = 1;
let ul = `<ul class="list-group list-group-flush">`;
for(let file of fileObj) {
let vaildResult = fileValidation(file.name, file.size);
isOk *= vaildResult;
ul += `<li class="list-group-item">`;
ul += `<div class="mb-3">`;
ul += `${vaildResult ? '<div class="fw-bold"> 업로드 가능</div>' : '<div class="fw-bold text-danger">} 업로드 불가능 </div>'}`;
ul += `${file.name}</div>`;
ul += `<span class="badge rounded-pill text-bg-${vaildResult ? 'success':'danger'}">${file.size}Byte</span>`;
ul += `</li>`;
}
ul += `</ul>`;
div.innerHTML = ul;
if(isOk == 0) {
document.getElementById('regBtn').disabled = true;
}
}
});
document.getElementById('files').files에서 .files는 사용자가 선택한 파일의 목록을 나타냄
const fileObj = document.getElementById('files').files;
document.getElementById('regBtn').disabled = false;는 비활성화 상태를 해제
document.getElementById('regBtn').disabled = false;
BoardDTO class를 domain package에 생성하고 작성한다.
BoardDTO.java
package com.basicWeb.www.domain;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
public class BoardDTO {
private BoardVO bvo;
private List<FileVO> flist;
}
BoardController.java
package com.basicWeb.www.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.basicWeb.www.domain.BoardDTO;
import com.basicWeb.www.domain.BoardVO;
import com.basicWeb.www.domain.FileVO;
import com.basicWeb.www.domain.PagingVO;
import com.basicWeb.www.handler.FileHandler;
import com.basicWeb.www.handler.PagingHandler;
import com.basicWeb.www.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequestMapping("/board/*")
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService bsv;
private final FileHandler fh;
// ... (기존 코드)
@PostMapping("/register")
public String register(BoardVO bvo, @RequestParam(name="files", required = false) MultipartFile[] files) {
List<FileVO> flist = null;
if(files[0].getSize() > 0) {
flist = fh.uploadFiles(files);
}
int isOk = bsv.register(new BoardDTO(bvo, flist));
return "index";
}
// ... (기존 코드)
}
@RequestParam는 client에서 전송된 HTTP 요청 parameter를, method에 사용
required = false는 필수적으로 요청에 포함되지 않아도 된다는 뜻이다.
MultipartFile[]는 여러 파일을 동시에 업로드할 수 있게 한다.
BoardService.interface
package com.basicWeb.www.service;
import java.util.List;
import com.basicWeb.www.domain.BoardDTO;
import com.basicWeb.www.domain.BoardVO;
import com.basicWeb.www.domain.PagingVO;
public interface BoardService {
int register(BoardDTO bdto);
List<BoardVO> getList(PagingVO pgvo);
BoardVO getDetail(long bno);
void modify(BoardVO bvo);
void remove(BoardVO bvo);
int totalCount(PagingVO pgvo);
}
BoardServiceImpl.java
package com.basicWeb.www.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.basicWeb.www.domain.BoardDTO;
import com.basicWeb.www.domain.BoardVO;
import com.basicWeb.www.domain.FileVO;
import com.basicWeb.www.domain.PagingVO;
import com.basicWeb.www.repository.BoardDAO;
import com.basicWeb.www.repository.CommentDAO;
import com.basicWeb.www.repository.FileDAO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@Service
public class BoardServiceImpl implements BoardService{
private final BoardDAO bdao;
private final CommentDAO cdao;
private final FileDAO fdao;
@Transactional
@Override
public int register(BoardDTO bdto) {
// TODO Auto-generated method stub
int isOk = bdao.register(bdto.getBvo());
if(bdto.getFlist() == null) {
return isOk;
}
if(isOk > 0 && bdto.getFlist().size() > 0) {
long bno = bdao.selectOneBno();
for(FileVO fvo : bdto.getFlist()) {
fvo.setBno(bno);
isOk += fdao.insertFile(fvo);
}
}
return isOk;
}
// ... (기존 코드)
}
@Transactional은 Method 내에서 데이터베이스 작업이 수행되고,
이 작업 중에 예외가 발생하면 해당 트랜잭션은 Rollback된다.
성공적으로 작업이 완료되면 commit이 된다.
BoardDAO.interface
package com.basicWeb.www.repository;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.basicWeb.www.domain.BoardVO;
import com.basicWeb.www.domain.PagingVO;
public interface BoardDAO {
int register(BoardVO bvo);
// ... (기존 코드)
long selectOneBno();
}
boardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.basicWeb.www.repository.BoardDAO">
<!-- ... (기존 코드) -->
<select id="selectOneBno" resultType="long">
SELECT MAX(bno) FROM board
</select>
<!-- ... (기존 코드) -->
</mapper>
게시물 등록을 할 땐 등록하고자 하는 게시물이 제일 최신 게시물이 되기 때문에 MAX(bno)를 SELECT 한다
FileDAO.interface
package com.basicWeb.www.repository;
import com.basicWeb.www.domain.FileVO;
public interface FileDAO {
int insertFile(FileVO fvo);
}
fileMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.basicWeb.www.repository.FileDAO">
<insert id="insertFile">
INSERT INTO file (uuid, save_dir, file_name, file_type, bno, file_size)
VALUES (#{uuid}, #{saveDir}, #{fileName}, #{fileType}, #{bno}, #{fileSize})
</insert>
</mapper>
[Spring] 21. 게시물 등록 - 파일 업로드 기능 추가
(다음 게시물 예고편)
[Spring] 22. 게시물 상세/수정 - 파일 업로드/삭제 기능 추가
얼렁뚱땅 주니어 개발자
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!