이어서 댓글 더보기 버튼 구현
● js에서 댓글을 뿌리는 메서드안에 화면에서 더보기버튼을 가져와서 result안의 pgvo 현재페이지가 result의 전체페이지보다 작으면 표시하도록 추가
let moreBtn = document.getElementById('moreBtn');
if(result.pgvo.pageNo < result.realEndPage){
// style.visibility = "hidden" : 숨김 / "visible" : 표시
moreBtn.style.visibility = 'visible'; // 버튼표시
moreBtn.dataset.page = page + 1 ; // 1페이지 증가
} else{
moreBtn.style.visibility = 'hidden';
}
● moreBtn을 누르면 타겟의 dataset을 가져오게 할 때 String으로 가져오기에 parseInt를 해줘서page를 가져오고
page를 추가해서 만든 메서드를 넣어줘서 댓글을 뿌리게함.
if(e.target.id == "moreBtn"){
let page = parseInt(e.target.dataset.page);
spreadCommentList(bnoVal, page);
}
파일 업로드를 구현
● pom.xml에 디펜던시 추가
- commons-fileupload 1.4 / /commons-io 2.11.0 / thumbnailator 0.4.14
- tika-core 2.4.1/ tika-parsers 2.4.1 : 첨부파일이 이미지파일인지 확인
● 파일을 모아둘 저장공간을 생성 나는 D드라이브에 폴더를 만들어서 생성함. 경로 : D:\\_myProject\\_java\\_fileUpload
● 파일 업로드 설정
파일경로,파일최대크기, 리퀘스트최대크기, 임시파일사이즈
@Override
protected void customizeRegistration(Dynamic registration) {
// 파일 업로드 설정 ( 위치 설정 )
String uploadLocation = "D:\\_myProject\\_java\\_fileUpload";
int maxFileSize = 1024*1024*20; // 20MB
int maxReqSize = maxFileSize * 3;
int fileSizeThreshold = maxFileSize;
MultipartConfigElement multipartConfig = new MultipartConfigElement(uploadLocation, maxFileSize, maxReqSize, fileSizeThreshold);
registration.setMultipartConfig(multipartConfig);
}
● 파일 업로드 경로 추가
registry.addResourceHandler("/upload/**").addResourceLocations("file:///D:\\_myProject\\_java\\_fileUpload\\");
● 파일 업로드 리졸버 추가
@Bean(name = "multipartResolver")
public MultipartResolver getMultipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
return multipartResolver;
}
Bean 이름이 multipartResolver여야 에러가 안남.
● 하나의 게시글에 여러개의 첨부파일이 가능하도록 설정하기위해서 파일 테이블을 만들고 VO를 생성.
create table file(
uuid varchar(256) not null,
save_dir varchar(256) not null,
file_name varchar(256) not null,
file_type tinyint(1) default 0,
bno bigint,
file_size bigint,
reg_date datetime default now(),
primary key(uuid));
public class FileVO {
private String uuid;
private String saveDir;
private String fileName;
private int fileType;
private long bno;
private long fileSize;
private String regDate;
}
● register에 파일 입력라인추가
<!-- 첨부파일 입력 라인 추가 -->
<div class="mb-3">
<label for="file" class="form-label"></label>
<input type="file" class="form-control" id="file" name="files" multiple="multiple" style="display:none">
<button type="button" class="btn btn-info" id="trigger">FileUpload</button>
</div>
mutiple을 이용해서 여러개의 파일을 첨부할 수 있도록하고 여러개를 첨부하면 파일 몇개 이렇게나오는데 그걸 파일이름으로 펼쳐서 뿌려줄 수 있도록 표시라인추가. 어처피 표시라인이 추가되니까 위에 label,input은 안보이게하고 버튼을 누르면 기능을 수행 할 수있도록 trigger을 넣어줌.
<!-- 첨부파일 표시 라인 추가 -->
<div class="mb-3" id="fileZone">
</div>
● 펼쳐서 표시할 수 있도록, 버튼 트리거를 작동할 수있도록 js처리를 하기위해 js 파일 생성 후 추가
<script type="text/javascript" src="/resources/js/boardRegister.js"></script>
트리거를 누르면 안보이는 파일타입의 인풋이 클릭되도록 설정.
document.getElementById('trigger').addEventListener('click', () => {
document.getElementById('file').click;
})
● 파일이 사이즈에 맞는 이미지파일인지 확인하는 검증메서드 생성
실행파일에 대한 정규표현식 작성 / 파일 최대 사이즈 20MB
const regExp = new RegExp("\.(exe|jar|msi|dll|sh|bat)$");
const maxSize = 1024*1024*20;
function fileValidation(fileName, fileSize){
if(regExp.test(fileName)){
return 0;
} else if (fileSize > maxSize){
return 0;
} else {
return 1;
}
}
● 파일을 업로드해서 변화가 생길 때마다 정보를 가져오도록 해야하고, 변화가 생겼다면 file타입의 저장된 file 정보를 가져오는 files를 가져와야함.
document.addEventListener('change', (e) => {
console.log(e.target);
if(e.target.id == 'file'){ // 파일창에 변화가 생겼다면..
// type="file" element에 저장된 file 정보를 가져오는 property files
const fileObj = document.getElementById('file').files;
console.log(fileObj);
}
● 파일 표시라인에 표시할건데 표시되는 첨부파일이 위에서만든 검증메서드를 통과해서 모든 파일이 1을 리턴했을 때만register 버튼이 활성화되게하고 하나의 파일이라도 0을 리턴하게 되면 register가 비활성화되고 0을 리턴한 파일에 업로드불가능을 표시해줌.
let div = document.getElementById('fileZone');
div.innerHTML = ""; // 새로 추가되는 목록이 있다면 삭제하고 처리
// 여러개의 첨부파일이 모두 검증을 통과해야만 register 버튼을 활성화
// isOk = 1 * 리턴이 0이 하나라도 있으면 => 0 (1부터 시작하는 누적 곱)
let isOk = 1;
let ul = ` <ul class="list-group list-group-flush">`
// 개별 파일에 대한 검증 / 결과 표시
for(let file of fileObj){
// 개별 파일에 대한 검증 결과 리턴 변수
let validResult = fileValidation(file.name, file.size)
isOk *= validResult;
ul += `<li class="list-group-item">`;
ul += `<div class="ms-2 me-auto">`;
ul += `${validResult ? `<div class="fw-bold text-success-emphasis"></div>` : `<div class="fw-bold text-danger-emphasis">업로드 불가능</div>`}`;
ul += `${file.name}</div>`;
ul += `<span class="badge text-bg-${validResult ? 'success' : 'danger'} rounded-pill">${file.size}Bytes</span>`
ul += `</li>`;
}
ul += `</ul>`
div.innerHTML += ul;
if(isOk == 0){
// 하나라도 검증을 통과하지 못한 파일이 있다면... 버튼 비활성화
document.getElementById('regBtn').disabled = true;
}
isOk는 1로두고 0을리턴하는 파일이 하나라도있으면 곱셈으로 0리턴 아니면 1을 리턴하도록 구현
● 한번 register가 비활성화되면 혼자 활성화가 될 수 없기때문에 다시 활성화 시켜줘야함.
// 한번 disabled 되면 혼자 false가 될 수 없기때문에 버튼을 원상복구.
document.getElementById('regBtn').disabled = false;
파일업로드를 해서 변화를 일으킬때 이 코드를 추가해줌.
● form 데이터에 파일형식으로 보내기위해 타입을 추가
enctype="multipart/form-data"
● 이제 컨트롤러에서 이미지파일도 같이받아서 insert해서 register 해줘야함. 그러기위해선 보드객체와 파일객체의 리스트를 가지고있는 DTO가 필요하기에 생성.
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class BoardDTO {
private BoardVO bvo;
private List<FileVO> flist;
}
● 파일객체의 리스트를 null로 임의로 생성해주고 파일객체의 정보가 전부 담겨있는 files를 받아서 file객체의 리스트로 변환해주는 핸들러를 추가해줘서 flist 안에 넣어주도록함.
private final FileHandler fh;
핸들러로 변환해준 flist와 bvo 객체를 받는 DTO를 생성해줌.
@PostMapping("/insert")
public String insert(BoardVO bvo, MultipartFile[] files) {
log.info(">>> insert bvo > {} ", bvo);
List<FileVO> flist = null;
if(files[0].getSize() > 0) {
// 파일의 내용이 있다면
flist = fh.uploadFiles(files);
}
BoardDTO bdto = new BoardDTO(bvo, flist); // bvo, flist
return "redirect:/";
}
핸들러는 생성자 주입으로 넣어줌 넣어주기위해 @Component로 사용자 클래스를 빈으로 등록
@Slf4j
@Component // 사용자 클래스를 빈으로 등록하는 어노테이션
public class FileHandler {
// 저장 경로
private final String UP_DIR = "D:\\_myProject\\_java\\_fileUpload";
// 첨부파일을 주고, FileVO의 리스트로 변환하여 리턴 + 저장
public List<FileVO> uploadFiles(MultipartFile[] files) {
List<FileVO> flist = new ArrayList<FileVO>();
// FileVO 생성 + 저장 + 썸네일 저장
// 날짜별로 폴더 생성하여 업로드 파일을 관리
LocalDate date = LocalDate.now(); // 오늘날짜 리턴 2024-11-01
log.info(">>> date > {} ",date.toString());
String today = date.toString();
today = today.replace("-", File.separator); // 2024\\ 11\\ 01 win(\) mac(/)
// D:\\_myProject\\_java\\_fileUpload\\2024\11\01
File folders = new File(UP_DIR, today);
//폴더 생성(있으면 생성안함.) : mkdir (1개만생성) mkdirs (하위폴더까지 생성)
if(!folders.exists()) {
folders.mkdirs();
}
return flist;
}
}
날짜를 받아와서 separator로 년 월 일별로 자르고 폴더가 존재하지않으면 생성하도록 해줌.
● for문을 돌려서 files를 가지고 flist를 생성
파일 정보 set => fvo 생성
- 파일 경로를 today에가져와서 set하고 사이즈도 getSize로 set해주기
- 파일이름을 OriginalFilename에서 가져와서 뒤에서 seperator 에서 +1 해 잘라가지고 저장해줘서 set해주기
- UUID를 이용해서 uuid에 정보를 랜덤생성해서 저장하고 toString으로 String으로 저장해서 set해주기
for(MultipartFile file : files) {
FileVO fvo = new FileVO();
fvo.setSaveDir(today); // 2024\\11\\01
fvo.setFileSize(file.getSize());
// 경로를 포함하는 이름이라면 ...
String fileName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(File.separator)+1);
log.info("fileName > {} " , fileName);
fvo.setFileName(fileName);
UUID uuid = UUID.randomUUID();
String uuidStr = uuid.toString();
fvo.setUuid(uuidStr);
// ------------ fvo 생성완료 > bno / file_type(File 객체로 전달해야 해서 저장 후 확인)
// 디스크에 저장 => 저장할 객체(File객체)를 생성 > 저장
tring fullFileName = uuidStr + "_" + fileName;
File storeFile = new File(folders, fullFileName);
try {
file.transferTo(storeFile); // 저장
// 썸네일 저장 => 이미지만 가능
// 이미지인지 확인
if(isImageFile(storeFile)) {
fvo.setFileType(1); // 이미지 파일만 1
// 썸네일 설정
File thumbNail = new File(folders, uuidStr + "_th_" + fileName );
Thumbnails.of(storeFile).size(100, 100).toFile(thumbNail);
}
} catch (Exception e) {
log.info("파일 저장 오류");
e.printStackTrace();
}
// for문 안에 있어야 함.
flist.add(fvo);
}
이미지 파일인지 확인할 수 있는 메서드를 생성해서 이미지파일이면 타입을 1로 set해주고
썸네일 크기와 이름을 정해서 저장해주도록함.
flist안에 add를 해줘서 저장 끝.
● 이미지 파일인지 확인할 수 있는 메서드를 생성
private boolean isImageFile(File storeFile) throws IOException {
String mimeType = new Tika().detect(storeFile); // file의 내부요소 type ="image/jpeg" 인지
return mimeType.startsWith("image") ? true : false;
}
file 내부요소의 타입을 인지하는 Tika를 이용해서 저장하고 startsWith을 이용해서 시작이 image면 ture 아니면 false 를 리턴.
● 컨트롤러에 bsv.insert에 bdto가 들어갈 수 있도록 수정해주고 bdto가 들어가는 서비스, 다오, 매퍼를 수정해줘야함.
컨트롤러
int isOk = bsv.insert(bdto);
log.info(" insert >> {} ", ( isOk >0? "성공" : "실패" ));
return "redirect:/";
서비스
@Transactional
@Override
public int insert(BoardDTO bdto) {
// bvo + file
// bvo 먼저 insert 하고 난 후 bno를 DB에서 빼와야 함. > fvo를 DB에 저장
int isOk = bdao.insert(bdto.getBvo());
if(bdto.getFlist() == null) { // 첨부파일이 없을 경우
return isOk;
}
// 첨부파일이 있는 케이스
if(isOk > 0 && bdto.getFlist().size() > 0) {
// bno setting
long bno = bdao.getOneBno(); // 가장 마지막에 저장된 bno
for(FileVO fvo : bdto.getFlist()) {
fvo.setBno(bno);
isOk *= fdao.insertFile(fvo);
}
}
return isOk;
}
insert를 하는동안은 락을 걸어서 다른 접근을 못하게 막기위해 Transactional을 사용해줘야함.
다오에 연결해서 bno를 가져올 수 있도록 매퍼작성하고 fdao랑 fileMapper를 만들어서 연결해가지고 flist의 FileVO 객체에 bno랑 파일의 정보를 가져와서 insert 해줘야함
FileDAO
public interface FileDAO {
int insertFile(FileVO fvo);
}
fileMapper
<mapper namespace="com.ezen.spring.dao.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>
이렇게하면 DB에도 파일의 정보가 insert됨.
● 이제 detail 페이지로 들어가면 저장된 이미지파일을 뿌려줄 수 있도록 해야하기에 컨트롤러에 detail을 수정
원래는 VO 객체를 받고있었지만 이제는 BoardVO객체 bvo와 파일객체의 리스트인 flist가 둘다있는 DTO 객체를 받아야함
@GetMapping({"/detail","/modify"})
public void detail(Model m, int bno, HttpServletRequest request) {
String path = request.getServletPath();
log.info(">>>>>path > {}" , path);
BoardDTO bdto = bsv.getDetail(bno);
if(path.equals("/board/detail")) {
int readCountOk = bsv.readCount(bno);
bdto = bsv.getDetail(bno);
}
m.addAttribute("bdto",bdto);
}
bvo를 bdto로 수정해준 내용.
● 컨트롤러를 수정했으므로 서비스 , 다오, 매퍼까지 이어서 전부 추가 /수정
서비스
@Transactional
@Override
public BoardDTO getDetail(int bno) {
BoardVO bvo = bdao.getDetail(bno);
List<FileVO> flist = fdao.getList(bno);
BoardDTO bdto = new BoardDTO(bvo, flist);
return bdto;
}
bvo객체에 bno을 넣어서 bvo 객체를 가져와주고, 파일다오에 연결해서 파일객체의 리스트인 flist를 가져와서
DTO 객체인 bdto를 생성해서 넣어주고 리턴해줌.
fileDAO와 fileMapper에 추가되는 내용
List<FileVO> getList(int bno); // fileDAO 추가
<select id="getList" resultType="com.ezen.spring.domain.FileVO">
select * from file where bno = #{bno}
</select> // fileMapper 추가
● 원래 나오던 화면이 bdto.bvo의 정보이므로 c:set으로 bvo로 설정해서 정상적으로 나오도록해주고
<c:set value="${bdto.bvo }" var="bvo"></c:set>
● detail.jsp에 파일 표시라인을 구성해주어야 함.
<!-- file upload 표시라인 -->
<c:set value="${bdto.flist }" var="flist"></c:set>
<div class="mb-3">
<ul class="list-group list-group-flush">
<!-- 파일의 개수만큼 li를 반복하여 파일 표시 타입이 1인경우만 그림을 표시 -->
<c:forEach items="${flist }" var="fvo">
<li class="list-group-item">
<c:choose>
<c:when test="${fvo.fileType > 0 }">
<div>
<img alt="" src="/upload/${fvo.saveDir }/${fvo.uuid}_${fvo.fileName}">
</div>
</c:when>
<c:otherwise>
<!-- 일반파일 : 아이콘 하나 가져와서 다운로드 가능하게 생성 -->
</c:otherwise>
</c:choose>
<div class="fw-bold">${fvo.fileName }</div>
<span class="badge text-bg-primary rounded-pill">${fvo.regDate } / ${fvo.fileSize }Bytes</span>
</li>
</c:forEach>
</ul>
</div>
여기서도 c:set을 이용해서 bdto.flist를 flist로 사용 할 수 있게 해주고 이미지 파일을 뿌려줘야 하니까 forEach를 사용
fileType이 0보다크면 이미지파일이므로 이미지를 띄워줄 수 있도록 만들어주고,
아니면 아이콘 하나를 가져와서 다운로드가 가능하게 생성 할 예정 이거는 다음에.
댓글로 이미지 파일을 업로드해서 띄울 수 있게 된 걸 확인할 수 있음.
'Spring 수업 정리' 카테고리의 다른 글
Spring 8일 (0) | 2024.11.06 |
---|---|
Spring 7일 (0) | 2024.11.05 |
Spring 6일 (3) | 2024.11.04 |
Spring 3일차. (0) | 2024.10.30 |
Spring 1일차. (0) | 2024.10.28 |