파일업로드를 포함한 게시물 (비동기 방식)

Let's Just Go·2022년 8월 13일
0

Spring

목록 보기
24/26

Spring

Board

  • 페이지 이동 없이 진행하기 위해 비동기 방식으로 진행
  • 파일 데이터를 포함한 글을 등록할 때 사용

글 등록

  • 글 등록
    • form태그 없이 데이터를 전송

    • FormData 객체를 통해 파일의 정보를 append함수를 통해 데이터를 넣어줌

    • 비동기로 파일을 넘길 때 contentType를 반드시 false로 지정해야 multipart/form-data로 선언

      $('#uploadBtn').click(function(){
      			regist();
      			// 등록 버튼을 클릭하면 regist() 실행  
      		}); 
      		
      		// 등록 담당 함수 
      		function regist(){
      			// 세션에서 현재 로그인 중인 사용자 정보(아이디)를 얻어와야함 
      			const user_id = '${sessionScope.login.userId}';
      			// sessionScope를 통해서 login이라는 이름을 가진 세션의 userId 가져옴 
      			
      			// js 파일 확장자 체크 검색 
      			let file = $('#file').val();
      			// 아이디가 file이라는 곳의 value를 가져옴
      			// console.log(user_id);
      			// console.log(file);
      			
      			// .을 제거한 확장자만 얻어낸 후 그것을 소문자로 일괄 변경 
      			file = file.slice(file.indexOf('.') + 1).toLowerCase();
      			// .이 있는 곳의 인덱스 번호를 찾아서 +1하고 소문자로 변환 
      			// 사용자가 업로드한 파일의 확장자만 추출할 수 있음
      			// console.log(file);
      			
      			if (file !== 'jpg' && file !== 'png' && file !== 'jpeg' && file !== 'bmp'){
      				alert("jpg, png, jpeg, bmp 확장자만 등록하실 수 있습니다.");
      				$('#file').val('');
      				// 사용자가 등록한 file의 value를 지움 
      				return;
      			}
      			else if (user_id === ''){
      				// 로그인을 안했다면 (세션 데이터가 존재하지 않음 )
      				alert('로그인이 필요한 서비스 입니다.');
      				return;
      			}
      			else{
      				// 개발자가 원하는 파일 형식을 만족했으므로 비동기 방식으로 업로드 진행 
      				// ajax 폼 전송의 핵심인 FormData 객체
      				const formData = new FormData();
      				const data = $('#file');
      				
      				console.log('폼 데이터 : ' + formData);
      				console.log('data : ' + data );
      				console.log(data[0]); 
      				// 첫번째 데이터를 지정 (현재는 id로 지정했기 때문에 0번 인덱스만 존재)
      				// <input type="file" name="file" id="file">이 출력 
      				console.log(data[0].files);
      				console.log(data[0].files[0]);
      				// 하나의 파일 태그에 여러개의 파일이 들어가게 된다면 files[인덱스번호]를 통해서 각 파일을 가져올 수 있음
      				// 사용자가 등록한 최종 파일 정보(현재는 한개의 파일만 등록했기 때문)
      				
      				/*
      					data[index] -> 파일 업로드 버튼이 여러개 존재할 경우 요소의 인덱스를 지목해서 가져오는 법 
      					요소를 id로 취득했기 때문에 하나만 찍히지만 ,class이름 같은 것으로 지정하면 여러개가 취득이 될 수 있음 
      					data[index].files : 파일 태그에 담긴 파일 정보를 확인할 수 있는 키값.
      					가져온 파일의 정보를 FormData객체에 넣어서 보내주기 위해 파일의 정보를 인덱스를 통해 가져옴 
      				*/
      				
      				formData.append('file', data[0].files[0]);
      				// 객체에 사용자가 업로드한 파일의 정보가 들어있는 객체 전달 
      				// 만약 여러개의 파일을 등록했다면 파라미터 이름과 인덱스 번호를 다르게 해서 FormData객체에 저장
      				
      				// 글 내용 (content)
      				const content = $('#content').val();
      				formData.append('content', content);
      				// 글 내용을 가져와서 FormData객체에 넣어줌 
      				
      				// 비동기 방식으로 파일 업로드 및 게시글 등록 
      				$.ajax({
      					url:'<c:url value="/snsBoard/upload" />',
      					// 서버에 요청 
      					type : 'post',
      					data : formData,
      					// 위에서 만든 폼 데이터 객체를 넘김 
      					contentType:false,
      					// ajax 방식에서 파일을 넘길 때 반드시 false로 처리 -> multipart/form-data로 처리가 됨  
      					processData:false,
      					// 폼 데이터를 &변수=값&변수=값... 형식으로 변경되는 것을 막음
      					
      					success: function(result){
      						// 서버와 통신을 성공했다면 서버가 다시 주는 데이터  
      						if (result === 'Success'){
      							$('#file').val('');
      							// 파일 선택지 비우기
      							
      							$('#content').val('');
      							// 글 영역 비우기 
      							
      							$('.fileDiv').css('display','none'); 
      							// 미리보기를 감추기
      							getList(1, true);
      							// 글 목록 함수 호출 
      						}
      						else{
      							alert("업로드에 실패했습니다. 관리자에게 문의해주세요.");
      						}
      					},
      					error : function(){
      						// 서버와 통신을 실패했다면
      						alert("업로드에 실패했습니다. 관리자에게 문의해주세요.");
      						
      					}
      				});// end ajax
      				
      				
      			} // end regist()

  • Controller
    • 글 등록에 관련된 Controller

    • 실제 로컬에 사용자가 올린 파일을 저장하기 위해 File 객체 사용

    • DB에는 로컬에 저장한 파일의 경로를 저장

      	@PostMapping("/upload")
      	@ResponseBody
      	public String upload(@RequestParam("file") MultipartFile file, 
      						 @RequestParam("content") String content, HttpSession session) {
      		// FormData 객체가 값을 보내는 것을 받음 
      		System.out.println("snsBoard/upload : POST");
      		String writer = ((UserVO) session.getAttribute("login")).getUserId();
      		// session에 저장된 id를 가져옴 
      		
      		// 날짜별로 폴더를 생성해서 파일을 관리 
      		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
      		// 원하는 형태로 지정 
      		Date date = new Date();
      		String filelocation = sdf.format(date);
      		
      		// 저장할 폴더 경로 
      		String uploadPath = "Users\\user\\Desktop\\upload" + filelocation;
      		
      		// 폴더가 없으면 생성하도록 진행 
      		File folder = new File(uploadPath);
      		if(!folder.exists()) {
      			// 폴더가 존재하지 않는다면 
      			folder.mkdirs();
      			// 상위 폴더까지 모두 생성 
      		}
      		
      		// 파일 명을 고유한 랜덤 문자로 생성
      		UUID uuid = UUID.randomUUID();
      		String uuids = uuid.toString().replaceAll("-", "");
      		
      		// 확장자 추출 로직  
      		String fileRealName = file.getOriginalFilename();
      		// 원본 파일 명
      		
      		String fileExtention = fileRealName.substring(fileRealName.indexOf("."), fileRealName.length());
      		// 원본 파일의 확장자 추출
      		System.out.println("저장할 폴더 경로 : " + uploadPath);
      		System.out.println("실제 파일 명 : " + fileRealName);
      		System.out.println("폴더 명  : " + filelocation);
      		System.out.println("확장자 : " + fileExtention);
      		System.out.println("고유 랜덤 문자 : " + uuids);
      		
      		String fileName = uuids + fileExtention;
      		
      		// 업로드한 파일을 서버 컴퓨터 내에 지정한 경로에 실제 저장 
      		File saveFile = new File(uploadPath + "\\" + fileName);
      		
      		// 예외처리가 발생함으로 try catch 사용 
      		try {
      			file.transferTo(saveFile);
      			
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      		
      		// db에 insert 작업 진행 
      		SnsBoardVO snsBoard = new SnsBoardVO(0, writer, uploadPath, filelocation, fileName, fileRealName, content, null);
      		// null을 줘도 mybatis-config에서 null을 다른 값으로 바꿔줬기 때문에 상관 x 
      		service.insert(snsBoard);
      		// db에 값을 넣음
      				
      		return "Success";
      	}

글 목록

  • 글 목록
    • 글 목록 요청을 통해 전체 글 출력

    • getJSON함수를 통해 비동기 방식으로 진행

    • 전체 글 목록을 가져와서 반복문으로 글 목록을 화면에 보여줌

    • ``을 이용해서 추가할 html영역에 서버로부터 받은 데이터들을 넣어줌

      // 리스트 작업 
      		let str ='';
      		let page = 1;
      		getList(1, true);
      		
      		function getList(page, reset){
      			if (reset === true){
      				// 화면 reset여부가 true라면 str 초기화
      				str = '';
      			}
      			$.getJSON(
      				'<c:url value="/snsBoard/getList?pageNum=' + page + '" />',
      				function(boardList){
      					// 서버가 전달해준 글 목록 데이터 
      					console.log(boardList);
      					
      					for (let i=0; i<boardList.length; i++){
      						str += 
      						`<div class="title-inner">
      							<!--제목영역-->
      						<div class="profile">
      								<img src="<c:url value='/img/profile.png'/>">
      							</div>
      							<div class="title">
      								<p>` + boardList[i].writer + `</p>
      								<small>` + timeStamp(boardList[i].regdate) + `</small> &nbsp; &nbsp;
      								<a href=''>이미지 다운로드</a>
      							</div>
      						</div>
      						<div class="content-inner">
      							<!--내용영역-->
      							
      							<p>`+ (boardList[i].content === null ? '' : boardList[i].content) + `</p>
      							<!-- 삼항 연산자로 content가 null일 때 대처방안 작성  -->
      						</div>
      						<div class="image-inner">
      							<!-- 이미지영역 -->
      							<img src="<c:url value='/snsBoard/display?fileLocation=` + boardList[i].fileloca + `&fileName=` + boardList[i].filename + `'/>">
      							<!-- 요청 url을 작성해서 거기서 로컬에 있는 파일을 불러옴 
      							 서버에 요청을 보내줌 -->
      						</div>
      						<div class="like-inner">
      							<!--좋아요-->
      							<img src="../resources/img/icon.jpg"> <span>522</span>
      						</div>
      						<div class="link-inner">
      							<a href="##"><i class="glyphicon glyphicon-thumbs-up"></i>좋아요</a>
      							<a href="##"><i class="glyphicon glyphicon-comment"></i>댓글달기</a> 
      							<a href="##"><i class="glyphicon glyphicon-remove"></i>삭제하기</a>
      						</div>`;
      						$('#contentDiv').html(str);
      						// 실제 dom에 위에서 작성한 내용을 넣음 
      					} // end for 
      				} 
      			); // end getJSON
      		} // end getList

  • Controller
    • 글 목록과 관련된 Controller

    • ResponseEntity<>(응답 객체에 담을 내용, 헤더에 담을 내용, 상태 메세지)

      • 응답에 관련된 여러 정보를 담아서 보낼 수 있음
    • 이미지 태그로 서버에 요청을 보내 로컬에 저장되어 있는 이미지를 가져오는 로직 구현

      • File객체를 활용해서 요청과 함께 온 데이터를 매개값을 통해 로컬에 저장되어 있는 파일을 불러옴 ****
    • Code

      	// 비동기 통신 후 가져올 글 목록 
      	@GetMapping("/getList")
      	@ResponseBody
      	public List<SnsBoardVO> getList(PageVO paging){
      		paging.setCpp(5);
      		System.out.println("/snsBoard/getList : GET");
      		// 한페이지당 보여줄 게시물 개수 
      		return service.getList(paging);
      	}
      	
      	// 게시글의 이미지 파일 전송 요청
      	// img 태그에 의해 요청이 들어오고 있음 
      	// snsList.jsp페이지가 로딩되면서 글 목록을 가져오고 있고 js를 이용해서 화면을 꾸밀 때 
      	// img 태그의 src에 작성된 요청 url을 통해 자동으로 요청이 들어옴 
      	// 요청을 받아서 경로에 지정된 파일을 보낼 예정
      	@GetMapping("/display")
      	public ResponseEntity<byte[]> getFile(String fileLocation, String fileName){
      		System.out.println("/snsBoard/display : GET");
      		System.out.println("fileName : " + fileName + "fileLocation : " + fileLocation);
      		
      		// 요청과 함께 온 데이터를 매개값으로 받아서 해당 값들을 통해 로컬에 저장되어 있는 파일을 불러옴
      		File file = new File("C:\\Users\\user\\Desktop\\upload\\" + fileLocation + "\\" + fileName);
      		System.out.println(file);
      		
      		ResponseEntity<byte[]> result = null;
      		HttpHeaders headers = new HttpHeaders();
      		// 응답 헤더 파일에 여러가지 정보를 담아서 전송하는 것도 가능 
      		try {
      			// 응답 헤더에 값을 넣음 
      			// probeContentType : 파라미터로 전달받은 파일의 타입을 문자열로 변환해주는 메서드
      			// 사용자에게 보여주고자 하는 데이터가 어떤 파일인지를 검사해서 응답상태 코드를 다르게 리턴할 수 있음
      			headers.add("Content-Type", Files.probeContentType(file.toPath()));
      			
      			// ResponseEntity<>(응답 객체에 담을 내용, 헤더에 담을 내용, 상태 메세지)
      			// FileCopyUtils : 파일 및 스트림 데이터 복사를 위한 간단한 유틸리티 메서드 집합체 
      			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), headers, HttpStatus.OK);
      			// FileCopyUtils.copyToByteArray(경로) : 경로를 byte로 변환 
      			// ResponseEntity에 보내고자 하는 값들을 전달 
      			// ResponseEntity는 응답에 관련된 여러 정보를 담아서 보낼 수 있음 
      			
      			
      		} catch (IOException e) {
      			// TODO Auto-generated catch block
      			e.printStackTrace();
      		}
      		
      		return result;

글 상세보기

  • 글 상세보기
    • 사용자가 이미지를 클릭했을 때 새로운 모달 창 구현

    • getJSON함수를 통해 비동기 방식으로 진행

    • 이벤트 전파

      • 이벤트가 발생해야 하는 곳은 반복문을 통해 만들어진 곳이므로 이벤트가 먹히지 않을 수 있기 때문에 실제 요소에 이벤트를 걸어 이벤트 전파 방식 이용
    • 서버에 요청하여 데이터를 받아와 비동기 방식으로 데이터들을 화면에 각각 배치

      // 상세보기 처리 (모달창 열어줌)
      		// 실제 이벤트가 발생하는 곳은 반복문에 의해 생성되는 곳임으로 이벤트가 먹히지 않을 수 있음 
      		// 그렇기 때문에 실제 요소가 있는 곳에 이벤트를 걸어 이벤트 전파방식으로 진행 
      		$('#contentDiv').on('click', '.image-inner a', function(event){
      			// image-inner안의 a태그에 클릭이 발생하면 함수 발동 
      			event.preventDefault();
      			// a태그의 고유기능 중지
      			
      			// 글번호 얻어오기 
      			const bno = $(this).attr('href');
      			// 이벤트가 발생한 곳의 href 속성의 값을 가져옴 
      			console.log(bno);
      			
      			$.getJSON(
      				'<c:url value="/snsBoard/getDetail/" />' + bno,
      				function(data){
      					console.log(data);
      					// 서버에서 준 데이터 
      					
      					// 미리 준비한 모달창에 뿌림  
      					// 값을 위치에 뿌려주고 모달을 열어줌 
      					$('#snsModal').modal('show');
      					// 모달 열어줌 
      					
      					// console.log(data.writer);
      					// 값 입력
      					$('#snsWriter').html(data.writer);
      					$('#snsRegdate').html(timeStamp(data.regdate));
      					if (data.content !== null){
      						$('#snsContent').text(data.content);							
      					}
      					else {
      						$('#snsContent').text('');
      					}
      					const src ='<c:url value="/snsBoard/display?fileLocation=' + data.fileloca + '&fileName=' + data.filename + '"/>';
      					// const src = "<c:url value='/snsBoard/display?fileLocation=` + data.fileloca + `&fileName=` + data.filename + `'/>";
      					$('#snsImg').attr("src", src);
      					// 로컬에 저장되어 있는 이미지를 불러옴 
      
      				}
      			);
      			
      		}); // 상세보기 처리 끝

  • Controller
    • 비동기 방식으로 글 상세보기 처리

      // 비동기 통신 
      	@GetMapping("getDetail/{bno}")
      	@ResponseBody
      	public SnsBoardVO detail(@PathVariable int bno) {
      		System.out.println("/snsBoard/getDetail : GET");
      		System.out.println("detail 파라미터 가져오는지 확인 : " + bno);
      		
      		SnsBoardVO board = service.getDetail(bno);
      		return board;
      	}

글 삭제

  • 글 삭제
    • 이벤트 전파 방식을 활용하여 삭제 로직 수행

      //삭제 처리
       			$('#contentDiv').on('click', '.link-inner a', function(event){
       				// 이벤트가 발생한 곳을 지정해서 삭제 
      			event.preventDefault();
       			 	const bno = $(this).attr('href');
       			 	// 글번호를 묻혀놓았으니 그것을 가져옴 
       			 	console.log(bno);
      
       			 	$.ajax({
      			 		type: 'post',
      			 		url : '<c:url value="/snsBoard/delete/" />' + bno,
      			 		data : bno,
      			 		dataType:'text',
      			 		contentType:'application/json',
      			 		success:function(result){
      			 			if (result === 'Success') {
      			 				alert("게시글이 정상적으로 삭제 완료되었습니다.");
      			 				getList(1, true);
      			 				// 삭제가 되었으니 값을 다시 불러옴
      			 			}
      			 			else if (result==='noAuth') {
      			 				alert("권한이 없습니다.");
      			 			}
      			 			else{
      			 				alert("파일 삭제에 실패했습니다.");
      			 			}
      			 		}, 
      			 		error:function(){
      			 			alert("삭제에 실패했습니다. 관리자에게 연락해주세요.");
      			 		}
      			 		}); // end ajax
       			}); // 삭제 처리 끝

  • Controller
    • 삭제 권한 유무를 session정보와 게시물 번호로 DB에서 유저의 정보를 가져와서 대조하며 권한 유무 검증

    • 저장된 파일도 File객체를 이용해서 삭제

      	@PostMapping("delete/{bno}")
      	@ResponseBody
      	public String delete(@PathVariable int bno, HttpSession session) {
      		System.out.println("/snsBoard/delete : POST");
      		System.out.println("delete 파라미터 가져오는지 확인 : " + bno);
      		
      		SnsBoardVO board = service.getDetail(bno);
      		UserVO user = (UserVO) session.getAttribute("login");
      		
      		if (user == null || !user.getUserId().equals(board.getWriter())) {
      			// 사용자가 다르면 삭제 못하게 막음 
      			
      			return "noAuth";			
      		}
      		
      		// 세션에 저장된 사용자와 현재 사용자가 같은지 즉, 같은 사용자가 삭제를 수행할려고 하는지 검증하는 로직 
      		service.delete(bno);
      		
      		// 글이 삭제되었다면 로컬에 저장된 파일도 삭제 
      		File file = new File(board.getUploadpath() + "\\" + board.getFilename());
      		// 지워야하는 파일의 로컬 경로 
      		
      		return file.delete() ? "Success" : "fail";
      		// 파일 삭제 메서드
      		
      	}

참고 (무한 스크롤)

  • 무한 스크롤 페이징
    • 비동기 방식으로 무한 스크롤 진행

    • 스크롤을 내릴때 마다 계속 업데이트하면서 게시글을 보여주는 로직

    • 문서 높이 - 브라우저 창 높이 === 스크롤 창의 끝 높이

      // 무한 스크롤
       			$(window).scroll(function() {
       				// device의 높이와 현재 스크롤 위치 값을 더한 뒤, 문서(content)의 높이와 같다면 
       				// 로직을 수행 
       				// 문서 높이 - 브라우저 창 높이 == 스크롤  창의 끝 높이와 같다면 -> 새로운 내용을 불러오자
       				
       				if (Math.round($(window).scrollTop()) === $(document).height() - $(window).height()){
       					// 스크롤이 끝까지 내려왔다면 
       					// 사용자의 스크롤이 바닥에 닿았을 때 페이지 변수의 값을 하나 올리고 reset여부는 false를 주어서 누적해서 계속 불러오면 됨 
       					// 게시글을 몇개씩 불러올지는 페이징 알로기즘에서 정해주면 됨
       					getList(++page, false);
       					// 값을 새롭게 불러줌 
       					// reset하면 안됨 (누적해서 불러와야 하기 때문)
       				}
       				
       				
       			}); // 무한 스크롤 끝
profile
안녕하세요! 공부한 내용을 기록하는 공간입니다.

0개의 댓글