본문 바로가기

Spring 수업 정리

Spring 5일

이어서 댓글 더보기 버튼 구현

 

● 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