반응형

오랜만에 포스팅이다..

 

요즘 프로젝트 및 해외상황 및 기타 등등 잡무로 너무나 바쁜 하루를 보내고 있다.

주말에 매일 써야지 써야지 하면서 넘긴 하루들을 반성하며 

오늘은 꼭 써야지라고 하고 드디어 내 블로그를 켰다. 

 

저번에 만들었던 상세보기 모달까지 만들었으며

이번에는 삭제까지 만들려고 한다. 

 

우선 js를 작성해주고 ajax를 통하여 서버와 통신한다. 

 


Js 작성 

 

      $('#contentDiv').on('click', '.link-inner a', function(e) {
         e.preventDefault();
         
         const bno = $(this).attr('href');
         
         $.ajax({
            type: "post",
            url: "<c:url value='/snsBoard/delete' />",
            data: bno,
            contentType: 'application/json',
            success: function(result) {
               if(result === 'noAuth') {
                  alert('권한이 없습니다.');
               } else if(result === 'fail') {
                  alert('삭제에 실패했습니다. 관리자에게 문의하세요.');
               } else {
                  alert('게시물이 정상적으로 삭제되었습니다.');
                  getList(1, true); //삭제가 반영된 글 목록을 새롭게 보여줘야 하기 때문에 str을 초기화. 
               }
            },
            error: function() {
               alert('삭제에 실패했습니다. 다시 시도하세요.');
            }
         });
         
      });

 

이제 통신할 컨트롤러를 만들어 준다. 

 

	@PostMapping("/delete")
	@ResponseBody
	public String delete(@RequestBody int bno, HttpSession session) {
		System.out.println("삭제 글 번호 : " + bno);
		SnsBoardVO vo = service.getDetail(bno);
		UserVO user = (UserVO) session.getAttribute("login");
		
		//로그인을 하지 않았거나, 작성자와 현재 로그인한 id가 일치하지 않는다면
		if(user == null || !user.getUserId().equals(vo.getWriter()) ) {
			return "noAuth";
		}
		service.delete(bno);

 

 

우선 삭제할 글번호와 로그인한 세션의 정보를 가져온다. 

그 후 vo 객체에서 get으로 불러 준 뒤 

userVo의 세션 정보와 매핑시킨디ㅏ. 

그 후 조건문으로 로그인을 하지않은 경우 에러코드를 ajax쪽으로 보낸다,

 

그후 

		//파일 객체를 생성해서 지워지고 있는 게시물의 파일을 지목
		File file = new File(vo.getUploadpath()+"\\"+vo.getFilename());
		System.out.println("파일 삭제완료");
		return file.delete() ? "Success" : "fail"; // 파일 삭제 메소드

파일객체를 생성자로 불러와 지워 지고 있는 게시물의 파일을 지목한다., 

그후 리턴값으로 fail or success를 넣는다. 

 

그 뒤 Service문을 작성한다. 

 

게시물의 정보를 가져올 GetDetail() 과 

삭제를 담당하게 될 getDelete를  작성한다. 

 

		//파일 객체를 생성해서 지워지고 있는 게시물의 파일을 지목
		File file = new File(vo.getUploadpath()+"\\"+vo.getFilename());
		System.out.println("파일 삭제완료");
		return file.delete() ? "Success" : "fail"; // 파일 삭제 메소드
        
        	@Override
	public void delete(int bno) {
		mapper.delete(bno);

	}

 

mapper.xml을 작성한다. 

 

	<delete id="delete">
		delete from snsboard
		where bno = #{bno}
	</delete>
      //삭제 처리
      //삭제하기 링크를 클릭했을 때 이벤트를 발생 시켜서
      //비동기 방식으로 삭제를 진행해 주세요. (삭제 버튼은 한 화면에 여러개 겠죠?)
      //서버쪽에서 권한을 확인 해 주세요. (작성자와 로그인 중인 사용자의 id를 비교해서)
      //일치하지 않으면 문자열 "noAuth" 리턴, 성공하면 "Success" 리턴.
      // url: /snsBoard/delete, method: post

삭제처리를 총 종합해보면 위와 같다. 

 

 

그 후 작동 테스트를 해보면 

 

로그인 하지 않을 경우 권한없음 메시지 출력

삭제는.. 로그인 부분이 고장난거같다 추 후 확인을 해보아야 할거같다.

Otl...

반응형
반응형

저번에 리스트 만들기 에서 좀 막힌 부분은 좀 더 공부가 필요해 보인다. 

이번에는 상세보기 모달을 작성해 보려고 한다. 

 


1. 모달창 JSP 제작

.modal-body {
      padding: 0px;
   }
   
   .modal-content>.row {
      margin: 0px;
   }
   
   .modal-body>.modal-img {
      padding: 0px;
   }
   
   .modal-body>.modal-con {
      padding: 15px;
   }
   
   .modal-inner {
      position: relative;
   }
   
   .modal-inner .profile {
      position: absolute; /*부모기준으로 위치지정 릴레이티브*/
      top: 0px;
      left: 0px;
   }
   
   .modal-inner .title {
      padding-left: 50px;
   }
   
   .modal-inner p {
      margin: 0px;
   }

 

   <div class="modal fade" id="snsModal" role="dialog">
         <div class="modal-dialog modal-lg">
         <div class="modal-content">
            <div class="modal-body row">
               <div class="modal-img col-sm-8 col-xs-6" >
                  <img src="../resources/img/img_ready.png" id="snsImg" width="100%">
               </div>
               <div class="modal-con col-sm-4 col-xs-6">
                  <div class="modal-inner">
                  <div class="profile">
                     <img src="../resources/img/profile.png">
                  </div>
                  <div class="title">
                     <p id="snsWriter">테스트</p>
                     <small id="snsRegdate">21시간전</small>
                  </div>
                  <div class="content-inner">
                     <p id="snsContent">다람쥐 헌 쳇바퀴에 타고파. sns Content 내용입니다.</p>
                  </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-share-alt"></i>공유하기</a>
                  </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>

클릭했을때 작동 될 모달창

 

리스트에 그림을 클릭했을때 모달을 열어준다!

 

 

2. JS 작성

 

$('#contentDiv').on('click', '.image-inner a', function(e) {
         e.preventDefault();
         
         //글 번호 얻어오기
         const bno = $(this).attr('href');
         
         $.getJSON(
            "<c:url value='/snsBoard/getDetail/' />" + bno,
            function(data) {
               console.log(data);
               
               const img = 'display?fileLoca=' + data.fileloca + '&fileName=' + data.filename;
               $('#snsImg').attr('src', img); //이미지 경로 처리
               $('#snsWriter').html(data.writer); //작성자 처리
               $('#snsRegdate').html(timeStamp(data.regdate)); //날짜 처리
               $('#snsContent').html(data.content); //내용 처리
               $('#snsModal').modal('show'); //모달 열기
            }
         );
         
      });

클릭이벤트함수를 설정한뒤

E의 이벤트를 preventDefault()함수로 기능을 상실시킨다. 

 

그뒤 번 번호를 가져온 후 

 

getJson 방식으로 컨트롤러와 통신하여 DB의 데이터를 가져온다. 

 

 

컨트롤러 제작

 

@GetMapping("/getDetail/{bno}")
	@ResponseBody
	public SnsBoardVO getDetail(@PathVariable int bno) {
		return service.getDetail(bno);
	}

getJson에서 요청된 파라미터의 값을

@pathVariable의 값으로 받는다 .

이때 변수의 값이 동일하다면 pathVariable의 annotation을 생략해도 된다. 

방식은 모든 정보를 담고 있는 객체인 VO 객체로 리턴한다. 

 

 

서비스 및 매퍼 작성

 

 

	@Override
	public SnsBoardVO getDetail(int bno) {
		// TODO Auto-generated method stub
		return mapper.getDetail(bno);
	}

 

리턴한 매퍼를 작성해주자!

 

	<select id="getDetail" resultType="com.spring.myweb.command.SnsBoardVO">
		select * from snsboard
		where bno = #{bno}
	</select>

 

이제 getJson에서 요청된 정보를 컨트롤러에서 파라미터를 받아 서비스로 리턴 후 매퍼에서 주어 

매퍼에서 쿼리문을 작성해 가져온 데이터를 js 모달에 뿌려주면 된다.

 

         $.getJSON(
            "<c:url value='/snsBoard/getDetail/' />" + bno,
            function(data) {
               console.log(data);
               
               const img = 'display?fileLoca=' + data.fileloca + '&fileName=' + data.filename;
               $('#snsImg').attr('src', img); //이미지 경로 처리
               $('#snsWriter').html(data.writer); //작성자 처리
               $('#snsRegdate').html(timeStamp(data.regdate)); //날짜 처리
               $('#snsContent').html(data.content); //내용 처리
               $('#snsModal').modal('show'); //모달 열기
            }
         );
         
      });

이런식으로 Jqurey .html 및 attr 함수로 추가해주었다. 

 


동작확인

 

모달창이 아주 잘 뜨는걸 확인 할 수 있다. 

 

 

주인장 GIt : https://github.com/MoonSeokHyun

반응형
반응형

Insert까지 진행했엇다. 

이제는 리스트를 불러오도록 해보자!

 

 


Js 작업

 

       let str = '';
      let page = 1;
      getList(1, true);
 
 function getList(page, reset) {
         if(reset === true) {
            str = ''; //화면 리셋 여부가 true라면 str변수를 초기화.
         }
         
         $.getJSON(
            '<c:url value="/snsBoard/getList?pageNum=' + page + '" />',
            function(list) {
               console.log(list);
               
               for(let i=0; i<list.length; i++) {
                  str += "<div class='title-inner'>";
                        str += "<div class='profile'>";
                        str += "<img src='../resources/img/profile.png' >";    
                        str += "</div>";
                        str += "<div class='title'>";
                        str += "<p>"+ list[i].writer +"</p>";
                        str += "<small>"+ timeStamp(list[i].regdate) +"</small> &nbsp;&nbsp;";
                        
                        //파일다운로드
                        str += "<a href='download?fileLoca=" + list[i].fileloca +  "&fileName=" + list[i].filename + "'>이미지 다운로드</a>";
                        //파일다운로드끝
                        
                        str += "</div>";
                        str += "<div class='content-inner'>"
                        str += "<p>"+ (list[i].content === null ? '' : list[i].content) +"</p>";
                        str += "</div>";
                        /* 이미지 영역 */
                        str += "<div class='image-inner'>";
                        str += "<a href='" + list[i].bno + "'>"; ///////추가
                        str += "<img src='display?fileLoca=" + list[i].fileloca + "&fileName=" + list[i].filename +"'>";
                        str += "</a>" ////////추가
                        str += "</div>";
                        str += "<div class='like-inner'>";                   
                        str += "<img src='../resources/img/like.png'><span>522</span>";                  
                        str += "</div>";
                        str += "<div class='link-inner'>";                     
                        str += "<a href='##'><i class='glyphicon glyphicon-thumbs-up'></i>좋아요</a>";
                        str += "<a href='##'><i class='glyphicon glyphicon-comment'></i>댓글달기</a>";
                        str += "<a href='" + list[i].bno + "'><i class='glyphicon glyphicon-remove'></i>삭제하기</a>";
                        str += "</div>";
                        $('#contentDiv').html(str);
               }
               
            }
               
         ); //end getJSON
         
      } //end getList()

우선 변수 str과 page를 만들어준다. 

나는 무한스크롤을 진행을 하려고 페이지는 1로 두었다. 

 

함수를 만들어 reset을 변수로 둔다.

reset의 역활은 화면 여부가 리셋되는 것을 의미 한다. 

false가 오면 실패 true가 오면 성공이다. 

 

true값이 오면 

str을 초기화 해주는 역활을 한다. 

 

그다음 

 

(1) $.getJSON(url,callback)

1) 자바에서 static 메서드와 유사--> jQuery에서는 전역메서드라 불린다.

2) 첫번째 매개변수로 JSON 파일을 로드한다. 

3) 두번째 매개변수(콜백함수)에서 JSON 파일을 이용하여 로드된 데이터를 처리한다. 콜백함수는 로드된 데이터를 인자로 넘겨받는다.(JSON 데이터를 참조하기 위해 data 변수를 사용하고 있다.)

 

참고하자

을 활용 하여 

컨트롤러와 통신을 한다. url을 넣어주고

콜백으로 

for문을 돌려 str에 html을 add 시켜준다. 

그 후 컨트롤러와 통신을 하여 불러온 list의 값으로 

작성자 파일이름, 파일경로 등등을 불러와 준다.

그 후 str이 완료가 되었으면

 

J-Qurry 함수 html()로 전달을 해준다. 

 

그 후 reset 값이 true로 바뀌면 str의 값은 ''으로 초기화가 된다.

 

 

VO 제작

@Getter
@Setter
public class PageVO {
	
	//사용자가 선택한 페이지 정보를 담을 변수.
	private int pageNum;
	private int countPerPage;
	
	//검색에 필요한 데이터를 변수로 선언.
	private String keyword;
	private String condition;
	
	public PageVO() {
		this.pageNum = 1;
		this.countPerPage = 10;
	}

}

 

컨트롤러 제작

	@GetMapping("/getList")
	@ResponseBody
	public List<SnsBoardVO> getList(PageVO paging) {
		paging.setCountPerPage(3);
		return service.getList(paging);
	}

 

rest방식으로 통신을 진행한다. 

리스트타입으로 snsVO 타입으로 받고 

페이지의 정보를 리턴해야 하기 때문에 paging을 변수로 설정한다. 

 

 

 

서비스 제작

	@Override
	public List<SnsBoardVO> getList(PageVO paging) {
		return mapper.getList(paging);
	}

 

매퍼 제작

 

   <select id="getList" resultType="com.spring.myweb.command.SnsBoardVO">
          SELECT * FROM snsboard
            ORDER BY bno DESC
    
      
   </select>

js 추가 작성

 

 $.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(request, status, error) {
               console.log('code: ' + request + '\n' + 'message: ' + request.responseText + '\n' + 'error: ' + error);
               alert('업로드에 실패했습니다. 관리자에게 문의해 주세요.');
            }
            
         }); //end ajax

저번에 insert 에서 만들어 두었는데 

마지막 부분에 getList()를 추가해주어

등록하기 버튼을 눌렀을 때 getList가 작동될 수 있게 해준다.

 

그 후 

 

  for(let i=0; i<list.length; i++) {
                  str += "<div class='title-inner'>";
                        str += "<div class='profile'>";
                        str += "<img src='../resources/img/profile.png' >";    
                        str += "</div>";
                        str += "<div class='title'>";
                        str += "<p>"+ list[i].writer +"</p>";
                        str += "<small>"+ timeStamp(list[i].regdate) +"</small> &nbsp;&nbsp;";
                        
                        //파일다운로드
                        str += "<a href='download?fileLoca=" + list[i].fileloca +  "&fileName=" + list[i].filename + "'>이미지 다운로드</a>";
                        //파일다운로드끝
                        
                        str += "</div>";
                        str += "<div class='content-inner'>"
                        str += "<p>"+ (list[i].content === null ? '' : list[i].content) +"</p>";
                        str += "</div>";
                        /* 이미지 영역 */
                        str += "<div class='image-inner'>";
                        str += "<a href='" + list[i].bno + "'>"; ///////추가
                        str += "<img src='display?fileLoca=" + list[i].fileloca + "&fileName=" + list[i].filename +"'>";
                        str += "</a>" ////////추가
                        str += "</div>";
                        str += "<div class='like-inner'>";                   
                        str += "<img src='../resources/img/like.png'><span>522</span>";                  
                        str += "</div>";
                        str += "<div class='link-inner'>";                     
                        str += "<a href='##'><i class='glyphicon glyphicon-thumbs-up'></i>좋아요</a>";
                        str += "<a href='##'><i class='glyphicon glyphicon-comment'></i>댓글달기</a>";
                        str += "<a href='" + list[i].bno + "'><i class='glyphicon glyphicon-remove'></i>삭제하기</a>";
                        str += "</div>";
                        $('#contentDiv').html(str);
               }

 

그 후 snsList에서 등록된  이미지를 저장 후 불러 와줄 컨트롤러를 작성한다.

 

str += "<img src='display?fileLoca=" + list[i].fileloca + "&fileName=" + list[i].filename +"'>";

이부분에서 컨트롤러를 부른다. 

 

fileLoca = 파일경로

fileName = 파일이름 

	//게시글의 이미지 파일 전송 요청
	//ResponseEntity: 응답으로 변환될 정보를 모두 담은 요소들을 객체로 만들어서 반환해 줍니다.
	@GetMapping("/display")
	public ResponseEntity<byte[]> getFile(String fileLoca, String fileName) {
		
		System.out.println("fileName: " + fileName);
		System.out.println("fileLoca: " + fileLoca);
		
		File file = new File("C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca + "\\" + fileName);
		System.out.println(file);
		
		ResponseEntity<byte[]> result = null;
		
		try {
			
			HttpHeaders headers = new HttpHeaders();
			//probeContentType: 파라미터로 전달받은 파일의 타입을 문자열로 변환해 주는 메서드.
			//사용자에게 보여주고자 하는 데이터가 어떤 파일인지를 검사해서 응답 상태 코드를 다르게 리턴할 수도 있습니다.
			headers.add("Content-Type", Files.probeContentType(file.toPath()));
//			headers.add("merong", "hello");
			
			//ResponseEntity<>(응답 객체에 담을 내용, 헤더에 담을 내용, 상태 메세지);
			//FileCopyUtils: 파일 및 스트림 데이터 복사를 위한 간단한 유틸리티 메서드의 집합체.
			//file객체 안에 있는 내용을 복사해서 byte배열로 변환해서 바디에 담아 화면에 전달.
			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), headers, HttpStatus.OK);

		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
		return result;
		
	}

이부분은 좀더 공부가 필요해 보인다 . 

 

3편에서 계속...

반응형
반응형


(1)에서는 기본적인 프로젝트의 기초틀을 만들었다. 

이번에는 insert를 만들어 보려고한다! 

 

자 만들어 보자 

 


1. Jsp 검증 로직 구현

  <c:when test="${login != null}">
<p>
<img src="../resources/img/profile.png">&nbsp;&nbsp;${login.userName}님
 </p>
    <ul>
        <li>내정보</li>
        <li>내쿠폰</li>
        <li>내 좋아요 게시물</li>
	 </ul>
</c:when>
<c:otherwise>

 

   $(function() {
      
      //등록하기 버튼 클릭 이벤트
      $('#uploadBtn').click(function() {
         regist();
      });
      
      //등록을 담당하는 함수
      function regist() {
         //세션에서 현재 로그인 중인 사용자 정보(아이디)를 얻어오자
         const user_id = '${sessionScope.login.userId}';
         //자바스크립트의 파일 확장자 체크 검색.
         let file = $('#file').val();
         
         console.log(user_id);
         console.log(file);
         //.을 제거한 확장자만 얻어낸 후 그것을 소문자로 일괄 변경
         file = file.slice(file.indexOf('.') + 1).toLowerCase();
         console.log(file);
         if(file !== 'jpg' && file !== 'png' && file !== 'jpeg' && file !== 'bmp') {
            alert('이미지 파일(jpg, png, jpeg, bmp)만 등록이 가능합니다.');
            $('#file').val('');
            return;
         } else if(user_id === '') { //세션 데이터가 없다 -> 로그인 x
            alert('로그인이 필요한 서비스입니다.');
            return;
         }

세션데이터가 없을경우 글 등록을 하지 못하게 하였다.

그리고 얻어온 세션 아이디로 글을 올린 사람에 대한 정보를 쿼리문으로 넘겨 줄 것이다.

 

우선 file이라는 변수로 file의 값을 가져온다. 

그 후 파일 업로드가 끝나면 slice 함수로 .png 등 확장자를 잘라내어 

tolowerCase로 소문자로 변경해준다.

 

그 뒤 이미지 파일만 올릴 수 있게

if문 으로 검증 로직을 구현 해준다. 

 

즉 로그인 안됨 = 파일을 올릴 수없음

로그인 함 = 이미지 파일만 올릴 수있음

이 두가지 관문을 거쳐야 파일을 올릴 수있게 처리를 하였다.

 

 

미리보기 기능

 

      function readURL(input) {
           if (input.files && input.files[0]) {
              
               var reader = new FileReader(); //비동기처리를 위한 파읽을 읽는 자바스크립트 객체
               //readAsDataURL 메서드는 컨텐츠를 특정 Blob 이나 File에서 읽어 오는 역할 (MDN참조)
              reader.readAsDataURL(input.files[0]); 
               //파일업로드시 화면에 숨겨져있는 클래스fileDiv를 보이게한다
               $(".fileDiv").css("display", "block");
               
               reader.onload = function(event) { //읽기 동작이 성공적으로 완료 되었을 때 실행되는 익명함수
                   $('#fileImg').attr("src", event.target.result); 
                   console.log(event.target)//event.target은 이벤트로 선택된 요소를 의미
              }
           }
       }
      $("#file").change(function() {
           readURL(this); //this는 #file자신 태그를 의미
           
       });

1) 이미지가 1개인 경우

미리보기 이미지가 표시될 이미지 태그를 생성하고, input file 태그를 생성하고, 자바스크립트의 FileReader()를 통해 이미지가 로딩되면 이미지 태그의 src 속성이 교체되도록 합니다.

 

 

 

 

2. 컨트롤러 제작

 

	@GetMapping("/snsList")
	public void snsList() {}
	
	@PostMapping("/upload")
	@ResponseBody
	public String upload(MultipartFile file, String content, 
						 HttpSession session) {
		
		try {
			
			String writer = ((UserVO)session.getAttribute("login")).getUserId();
			
			//날짜별로 폴더를 생성해서 파일을 관리
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
			Date date = new Date();
			String fileLoca = sdf.format(date);
			
			//저장할 폴더 경로
			String uploadPath = "C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca;
			
			File folder = new File(uploadPath);
			if(!folder.exists()) {
				folder.mkdir(); //폴더가 존재하지 않는다면 생성해라.
			}
			
			String fileRealName = file.getOriginalFilename();
			
			//파일명을 고유한 랜덤 문자로 생성.
			UUID uuid = UUID.randomUUID();
			String uuids = uuid.toString().replaceAll("-", "");
			
			//확장자를 추출합니다.
			String fileExtension = fileRealName.substring(fileRealName.indexOf("."), fileRealName.length());
			
			System.out.println("저장할 폴더 경로: " + uploadPath);
			System.out.println("실제 파일명: " + fileRealName);
			System.out.println("폴더명: " + fileLoca);
			System.out.println("확장자: " + fileExtension);
			System.out.println("고유랜덤문자: " + uuids);
			
			String fileName = uuids + fileExtension;
			System.out.println("변경해서 저장할 파일명: " + fileName);
			
			//업로드한 파일을 서버 컴퓨터의 지정한 경로 내에 실제로 저장.
			File saveFile = new File(uploadPath + "\\" + fileName);
			file.transferTo(saveFile);
			
			//DB에 insert 작업을 진행.
			SnsBoardVO snsVO = new SnsBoardVO(0, writer, uploadPath, fileLoca, fileName, fileRealName, content, null);
			service.insert(snsVO);
			
			
			return "success";
			
		} catch (Exception e) {
			System.out.println("업로드 중 에러 발생: " + e.getMessage());
			return "fail"; //에러가 났을 시에는 실패 키워드를 반환.
		}
		
	}

 

@GetMapping("/snsList") void 처리로 페이지 이동을 할 수 있게 처리 

 

로그인 세션을 가져와  로그인한 아이디의 값을 가져온다. 

 

날자별로 폴더를 생성해서 관리를 하려고한다.

자바 객체인 SimpleDateFormat을 활용하여

날자를 생성해주고

Date date = new Date(); 하면 오늘 날자가 생성된다.

fileloca라는 변수에 저장해 둔다 (20220215) 이런식으로 나옴

 

저장할 폴더의 경로를 선택해준다.

 

String uploadPath = 실제사용할경로 + fileloca

 

그 후 자바 FIle객체를 사용하여 

 

          exist() - File 객체가 참조하는 파일 또는 디렉토리가 실제로 존재하면 true를, 그렇지

                            않으면 false를 리턴한다.

 

메소드를 활용하여 파일이 한번 생성 된 뒤 중복 생성을 방지한다.


String fileRealName = file.getOriginalFilename();로 파일 고유의 이름을 추출

 

UUID uuid = UUID.randomUUID();
String uuids = uuid.toString().replaceAll("-", "");

로 랜덤한 이름을 추출 생성 해준뒤 

 

자바스크립트와 같이 확장자를 추출한다.(substring 활용)

String fileExtension = fileRealName.substring(fileRealName.indexOf("."), fileRealName.length());

 

그 후 이름을 변경한 파일의 이름을 변수 명으로 담는다.

String fileName = uuids + fileExtension;

 

업로드한 파일을 서버 컴퓨터의 지정한 경로 내에 실제로 저장.
File saveFile = new File(uploadPath + "\\" + fileName);
file.transferTo(saveFile);

MultipartFile 의 transferTo() 라는 메소드를 사용해서 원하는 위치에 저장해줍니다

//DB에 insert 작업을 진행.
SnsBoardVO snsVO = new SnsBoardVO(0, writer, uploadPath, fileLoca, fileName, fileRealName, content, null);
service.insert(snsVO);

 

 

 

2-1 서비스제작

 

	@Override
	public void insert(SnsBoardVO vo) {
		mapper.insert(vo);
	}

VO값을 컨트롤러로 보내준다.

 

2-2 Mapper 제작

 

 

	<!-- 등록하기 -->
	<insert id="insert">
		INSERT INTO snsboard
		(bno, writer, uploadpath, fileloca, filename, filerealname, content)
		VALUES
		(snsboard_seq.NEXTVAL, #{writer}, #{uploadpath}, #{fileloca}, #{filename}, #{filerealname}, #{content})
	</insert>

원래 나중에 resultmap을 써야하지만 나는 변수명과 컬럼값을 다 맞춰 벼렸다.. 

다음부터는 원칙을 지키자

sql은 snake_case!!

jaca CamelCase!!

 

3. 비동기 통신(ajax)

 

const formData = new FormData();
         const data = $('#file');
         
         console.log('폼 데이터: ' + formData);
         console.log('data: ' + data);
         console.log(data[0]);
         console.log(data[0].files); //파일 태그에 담긴 파일 정보를 확인하는 키값.
         console.log(data[0].files[0]);
         
         //data[index] -> 파일 업로드 버튼이 여러 개 존재할 경우 요소의 인덱스를 지목해서 가져오는 법.
         //우리는 요소를 id로 취득했기 때문에 하나만 찍히지만, class이름 같은거로 지목하면 여러개가 취득되겠죠?
         //files[index] -> 파일이 여러개 전송되는 경우, 몇 번째 파일인지를 지목.
         //우리는 multiple 속성을 주지 않았기 때문에 0번 인덱스 밖에 없는 겁니다.
         
         //FormData 객체에 사용자가 업로드한 파일의 정보들이 들어있는 객체를 전달.
         formData.append('file', data[0].files[0]);
         //content(글 내용) 값을 얻어와서 폼 데이터에 추가
         const content = $('#content').val();
         formData.append('content', content);
         
         //비동기 방식으로 파일 업로드 및 게시글 등록을 진행.
         $.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(request, status, error) {
               console.log('code: ' + request + '\n' + 'message: ' + request.responseText + '\n' + 'error: ' + error);
               alert('업로드에 실패했습니다. 관리자에게 문의해 주세요.');
            }
            
         }); //end ajax
               
      } //end regist()

폼데이터를 활용하여 컨트롤러에게 비동기 통신으로 전달한다.

 

FromData란 ajax로 폼 전송을 가능하게 해주는 FormData 객체입니다.

보통은 Ajax로 폼(form 태그) 전송을 할 일이 거의 없습니다.

주로 JSON 구조로 "KEY-VALUE" (키와 값) 구조로 데이터를 전송합니다.

 

하지만,

form전송이 필요한 경우가 있는데, 이미지를 ajax로 업로드할 때 필요합니다.

이미지는 base64, buffer, 2진 data 형식으로 서버로 전송해도 됩니다.

 

하지만 추천 드리는 방법은 input[type=file]을 사용해 form(폼)을 통해서 업로드를 하는 것 입니다.

보통, form을 제출하면 action 속성에 의해 지정한 페이지로 이동하면서 데이터를 전송합니다.

ajax 반대로 제출 버튼을 누르면 기본 폼 동작은 e.preventDefault()  로 멈추고, 페이지 전환 없이 데이터를 전송합니다.

 

페이지 전환 없이 폼 데이터를 제출 하고 싶을 때 바로 FormData 객체를 사용합니다.

기존에 스캔 설정이랑은 해서 바로 테스트를 해보면 될 듯하다. 

 

 


4. 동작 확인

로그인 세션값 불러오기 및 화면
이미지 미리보기 구현
컨트롤러 정상 동작 확인

 

sql 업로드 확인

학원에서 배운내용을 정리를 하는거지만 정말 어렵다.

나중에 다시 코드리뷰를 해보아야겠다. 

 

 

주인장 Git : https://github.com/MoonSeokHyun

반응형
반응형

페이스북이나 인스타그램 같은 간단한 Ajax와 파일 업로드 기능을 추가하여 

간단한 게시판을 만들어 보자 😁😁

 

순서

1. 테이블제작

2. VO객체 제작

3. 인터페이스 제작

4. 서비스 제작

5. 매퍼제작 

6. 마이바티스 스캔설정

7.mapper.xml 제작

8. 컨트롤러 제작

9. jsp 만들기

 

위 순서는 바뀌어도 된다. 내가 편하기 때문에 이순서를 지양한다.


1. 테이블 제작

 

우선 오라클을 사용하기 때문에 sqld로 테이블을 제작해준다.

 

CREATE TABLE snsboard(
    bno NUMBER(10,0) PRIMARY KEY,
    writer VARCHAR2(50) NOT NULL,
    uploadpath VARCHAR2(100) NOT NULL,
    fileloca VARCHAR2(100) NOT NULL,
    filename VARCHAR2(50) NOT NULL,
    filerealname VARCHAR2(50) NOT NULL,
    content VARCHAR2(2000),
    regdate DATE DEFAULT sysdate
);

CREATE SEQUENCE snsboard_seq
    START WITH 1
    INCREMENT BY 1
    MAXVALUE 1000
    NOCYCLE
    NOCACHE;

bno은 게시물의 번호로로 시퀀스 처리를 해준다. 

 


VO(모델) 제작

 

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class SnsBoardVO {

	private int bno;
	private String writer;
	private String uploadpath;
	private String fileloca;
	private String filename;
	private String filerealname;
	private String content;
	private Timestamp regdate;
	
}

lombok을 이용하여 간단하게 구현한다.

 

@NoArgsConstructor 파라미터가 없는 기본 생성자를 생성
@AllArgsConstructor 모든 필드 값을 파라미터로 받는 생성자를 만듦

🤓 참고해 주자 🤓

 

인터페이스 제작(기능)

 

package com.spring.myweb.snsboard.service;

import java.util.List;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.util.PageVO;

public interface ISnsBoardService {

	//등록
	void insert(SnsBoardVO vo);
	
	//목록
	List<SnsBoardVO> getList(PageVO paging);
	
	//상세
	SnsBoardVO getDetail(int bno);
	
	//삭제
	void delete(int bno);

	
}

 

간단하게 등록 목록 상세 삭제 정도로 만들어 주려고한다.

이제 인터페이스를 활용하게 기능이 들어간 클래스를 만들어 준다.

 

서비스 제작(기능)

 

package com.spring.myweb.snsboard.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.snsboard.mapper.ISnsBoardMapper;
import com.spring.myweb.util.PageVO;

@Service
public class SnsBoardService implements ISnsBoardService {

	@Autowired
	private ISnsBoardMapper mapper;
	
	@Override
	public void insert(SnsBoardVO vo) {
		mapper.insert(vo);
	}

	@Override
	public List<SnsBoardVO> getList(PageVO paging) {
		return mapper.getList(paging);
	}

	@Override
	public SnsBoardVO getDetail(int bno) {
		// TODO Auto-generated method stub
		return mapper.getDetail(bno);
	}

	@Override
	public void delete(int bno) {
		mapper.delete(bno);

	}

}

이렇게 리턴 값들과 나중에 Mapper에게 줄 값들을 정리해준다.

이때 아노테이션을 빼먹으면 오류가 나니 확인 해주자

 

 

Mapper 제작 (쿼리문)

 

 

package com.spring.myweb.snsboard.mapper;

import java.util.List;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.util.PageVO;

public interface ISnsBoardMapper {
	
	//등록
	void insert(SnsBoardVO vo);
	
	//목록
	List<SnsBoardVO> getList(PageVO paging);
	
	//상세
	SnsBoardVO getDetail(int bno);
	
	//삭제
	void delete(int bno);
	
}

인터페이스와 같은 형식으로 진행해 주어도 무리 없을듯 하다. 

 

Mapper.xml  (쿼리문)

 

매퍼를 만들기 전에 꼭 마이바티스 스캔 설정을 해주어야한다. 

 

<!-- 위의 구현체 xml을 빈으로 등록하기 위해서는 타입이 필요하니까
		인터페이스를 자동으로 스캔해서 빈으로 등록하기 위한 설정.
	 -->
	<mybatis-spring:scan base-package="com.spring.myweb.freeboard.mapper"/>
	<mybatis-spring:scan base-package="com.spring.myweb.reply.mapper"/> 
	<mybatis-spring:scan base-package="com.spring.myweb.user.mapper"/>
	<mybatis-spring:scan base-package="com.spring.myweb.snsboard.mapper"/>

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.spring.myweb.snsboard.mapper.ISnsBoardMapper">

	<!-- 등록하기 -->
	<insert id="insert">
		INSERT INTO snsboard
		(bno, writer, uploadpath, fileloca, filename, filerealname, content)
		VALUES
		(snsboard_seq.NEXTVAL, #{writer}, #{uploadpath}, #{fileloca}, #{filename}, #{filerealname}, #{content})
	</insert>
	
	<!-- 전체 목록 

	<select id="getList" resultType="com.spring.myweb.command.SnsBoardVO">
		SELECT * FROM snsboard
		ORDER BY bno DESC
	</select>
	 -->
	
	<!-- 상세보기 -->
	<select id="getDetail" resultType="com.spring.myweb.command.SnsBoardVO">
		select * from snsboard
		where bno = #{bno}
	</select>
	
	<!-- 삭제 -->
	<delete id="delete">
		delete from snsboard
		where bno = #{bno}
	</delete>
	
	<!-- 페이징 전체목록 -->
<!-- 전체 목록 -->
   <select id="getList" resultType="com.spring.myweb.command.SnsBoardVO">
      
      SELECT * FROM
         (
         SELECT ROWNUM AS rn, tbl.* FROM
            (
            SELECT * FROM snsboard
            ORDER BY bno DESC
            ) tbl
         )
      <![CDATA[
      WHERE rn > (#{pageNum} - 1) * #{countPerPage}
      AND rn <= #{pageNum} * #{countPerPage}
      ]]>
      
   </select>
</mapper>

원래 만들고 junit으로 테스트를 해주어야하지만 생략하려고 한다.

 

 

컨트롤러 제작

 

 

package com.spring.myweb.controller;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.command.UserVO;
import com.spring.myweb.snsboard.service.ISnsBoardService;
import com.spring.myweb.util.PageVO;

@Controller
@RequestMapping("/snsBoard")
public class SnsBoardController {
	
	@Autowired
	private ISnsBoardService service;
	
	@GetMapping("/snsList")
	public void snsList() {}
	
	@PostMapping("/upload")
	@ResponseBody
	public String upload(MultipartFile file, String content, 
						 HttpSession session) {
		
		try {
			
			String writer = ((UserVO)session.getAttribute("login")).getUserId();
			
			//날짜별로 폴더를 생성해서 파일을 관리
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
			Date date = new Date();
			String fileLoca = sdf.format(date);
			
			//저장할 폴더 경로
			String uploadPath = "C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca;
			
			File folder = new File(uploadPath);
			if(!folder.exists()) {
				folder.mkdir(); //폴더가 존재하지 않는다면 생성해라.
			}
			
			String fileRealName = file.getOriginalFilename();
			
			//파일명을 고유한 랜덤 문자로 생성.
			UUID uuid = UUID.randomUUID();
			String uuids = uuid.toString().replaceAll("-", "");
			
			//확장자를 추출합니다.
			String fileExtension = fileRealName.substring(fileRealName.indexOf("."), fileRealName.length());
			
			System.out.println("저장할 폴더 경로: " + uploadPath);
			System.out.println("실제 파일명: " + fileRealName);
			System.out.println("폴더명: " + fileLoca);
			System.out.println("확장자: " + fileExtension);
			System.out.println("고유랜덤문자: " + uuids);
			
			String fileName = uuids + fileExtension;
			System.out.println("변경해서 저장할 파일명: " + fileName);
			
			//업로드한 파일을 서버 컴퓨터의 지정한 경로 내에 실제로 저장.
			File saveFile = new File(uploadPath + "\\" + fileName);
			file.transferTo(saveFile);
			
			//DB에 insert 작업을 진행.
			SnsBoardVO snsVO = new SnsBoardVO(0, writer, uploadPath, fileLoca, fileName, fileRealName, content, null);
			service.insert(snsVO);
			
			
			return "success";
			
		} catch (Exception e) {
			System.out.println("업로드 중 에러 발생: " + e.getMessage());
			return "fail"; //에러가 났을 시에는 실패 키워드를 반환.
		}
		
	}
	
	//비동기 통신 후 가져올 목록
	@GetMapping("/getList")
	@ResponseBody
	public List<SnsBoardVO> getList(PageVO paging) {
		paging.setCountPerPage(3);
		return service.getList(paging);
	}
	
	//게시글의 이미지 파일 전송 요청
	//ResponseEntity: 응답으로 변환될 정보를 모두 담은 요소들을 객체로 만들어서 반환해 줍니다.
	@GetMapping("/display")
	public ResponseEntity<byte[]> getFile(String fileLoca, String fileName) {
		
		System.out.println("fileName: " + fileName);
		System.out.println("fileLoca: " + fileLoca);
		
		File file = new File("C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca + "\\" + fileName);
		System.out.println(file);
		
		ResponseEntity<byte[]> result = null;
		
		try {
			
			HttpHeaders headers = new HttpHeaders();
			//probeContentType: 파라미터로 전달받은 파일의 타입을 문자열로 변환해 주는 메서드.
			//사용자에게 보여주고자 하는 데이터가 어떤 파일인지를 검사해서 응답 상태 코드를 다르게 리턴할 수도 있습니다.
			headers.add("Content-Type", Files.probeContentType(file.toPath()));
//			headers.add("merong", "hello");
			
			//ResponseEntity<>(응답 객체에 담을 내용, 헤더에 담을 내용, 상태 메세지);
			//FileCopyUtils: 파일 및 스트림 데이터 복사를 위한 간단한 유틸리티 메서드의 집합체.
			//file객체 안에 있는 내용을 복사해서 byte배열로 변환해서 바디에 담아 화면에 전달.
			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), headers, HttpStatus.OK);

		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
		return result;
		
	}
	// 상세보기 처리
	@GetMapping("/getDetail/{bno}")
	@ResponseBody
	public SnsBoardVO getDetail(@PathVariable int bno) {
		return service.getDetail(bno);
	}

	//삭제처리
	@PostMapping("/delete")
	@ResponseBody
	public String delete(@RequestBody int bno, HttpSession session) {
		System.out.println("삭제 글 번호 : " + bno);
		SnsBoardVO vo = service.getDetail(bno);
		UserVO user = (UserVO) session.getAttribute("login");
		
		//로그인을 하지 않았거나, 작성자와 현재 로그인한 id가 일치하지 않는다면
		if(user == null || !user.getUserId().equals(vo.getWriter()) ) {
			return "noAuth";
		}
		service.delete(bno);
		
		//파일 객체를 생성해서 지워지고 있는 게시물의 파일을 지목
		File file = new File(vo.getUploadpath()+"\\"+vo.getFilename());
		System.out.println("파일 삭제완료");
		return file.delete() ? "Success" : "fail"; // 파일 삭제 메소드
	}
	
	//다운로드 비동기처리(화면에서 a태그를 클릭시download요청이 들어올 수 있도록 처리)
	@GetMapping("/download")
	@ResponseBody
	public ResponseEntity<byte[]> download(String fileLoca, String fileName ){
		System.out.println("fileName: " + fileName);
		System.out.println("fileLoca: " + fileLoca);
		File file = new File("C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca + "\\" + fileName);
		
		
		ResponseEntity<byte[]> result = null;
		try {
			// 응답하는 본문을 브라우저가 어떻게 표시해야 할 지 알려주는 헤더 정보를 추가합니다.
			// inline이라느 헤더 정보를 줄 경우 웹 페이지 화면에 포시가 되고, attachment인 경우 다운로드를 제공한다. 
			
			//request객체의 getHeader("User-Agent") -> 단어를 뽑아서 확인
	         //ie: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko  
	         //chrome: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
			
			//파일명한글처리(Chrome browser) 크롬
	         //header.add("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1") );
	         //파일명한글처리(Edge) 엣지 
	         //header.add("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
	         //파일명한글처리(Trident) IE
	         //Header.add("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", " "));
			
			HttpHeaders header = new HttpHeaders();
			header.add("content-Disposition", "attachment; filename="+ fileName);
			
			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), header,HttpStatus.OK);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
		
	}
	
	
}

스프링에서는 아노테이션이 중요하다. 

@Controller
@RequestMapping("/snsBoard")

컨트롤러라는 것을 등록 해주고 

기본 url을 매핑해준다.

 

 

@Autowired
private ISnsBoardService service;

서비스를 참조한다고 변수를 등록해준뒤 빈등록을 해준다. 

 

 


JSP

</head>
<body>
   <%@ include file="../include/header.jsp" %>
   <section>
      <div class="container">
         <div class="row">
            <aside class="col-sm-2">
               <div class="aside-inner">
                  <div class="menu1">
                     <c:choose>
                        <c:when test="${login != null}">
                           <p>
                              <img src="../resources/img/profile.png">&nbsp;&nbsp;${login.userName}님
                           </p>
                           <ul>
                              <li>내정보</li>
                              <li>내쿠폰</li>
                              <li>내 좋아요 게시물</li>
                           </ul>
                        </c:when>
                        <c:otherwise>
                           <button type="button" class="btn btn-info" onclick="location.href='<c:url value="/user/userLogin" />'">로그인</button>
                        </c:otherwise>
                     </c:choose>
                  </div>
                  <div class="menu2">
                     <p>둘러보기</p>
                     <ul>
                        <li>기부 캠페인</li>
                        <li>페이지</li>
                        <li>그룹</li>
                        <li>이벤트</li>
                        <li>친구리스트</li>
                     </ul>
                  </div>
               </div>
            </aside>
            <div class="col-xs-12 col-sm-8 section-inner">
               <h4>게시물 만들기</h4>
               <!-- 파일 업로드 폼입니다 -->
               <div class="fileDiv">
                  <img id="fileImg" src="<c:url value='/img/img_ready.png' />" />
               </div>
               <div class="reply-content">
                  <textarea class="form-control" rows="3" name="content"
                     id="content" placeholder="무슨 생각을 하고 계신가요?"></textarea>
                  <div class="reply-group">
                     <div class="filebox pull-left">
                        <label for="file">이미지업로드</label> 
                        <input type="file" name="file" id="file">
                     </div>
                     <button type="button" class="right btn btn-info" id="uploadBtn">등록하기</button>
                  </div>
               </div>


               <!-- 파일 업로드 폼 끝 -->
               
               <div id="contentDiv">
               <div class="title-inner">
                  <!--제목영역-->
                  <div class="profile">
                     <img src="../resources/img/profile.png">
                  </div>
                  <div class="title">
                     <p>테스트</p>
                     <small>21시간</small>
                  </div>
               </div>
               <div class="content-inner">
                  <!--내용영역-->
                  <p>삶이 우리를 끝없이 시험하기에 고어텍스는 한계를 테스트합니다</p>
               </div>
               <div class="image-inner">
                  <!-- 이미지영역 -->
                  <img src="../resources/img/facebook.jpg">
                  
               </div>
               <div class="like-inner">
                  <!--좋아요-->
                  <img src="../resources/img/like.png"> <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>
               </div>
            </div>
            <!--우측 어사이드-->
            <aside class="col-sm-2">
               <div class="aside-inner">
                  <div class="menu1">
                     <p>목록</p>
                     <ul>
                        <li>목록1</li>
                        <li>목록2</li>
                        <li>목록3</li>
                        <li>목록4</li>
                        <li>목록5</li>
                     </ul>
                  </div>
               </div>
            </aside>
         </div>
      </div>
   </section>
   <%@ include file="../include/footer.jsp" %>
   
   <!-- 모달 -->
   <div class="modal fade" id="snsModal" role="dialog">
         <div class="modal-dialog modal-lg">
         <div class="modal-content">
            <div class="modal-body row">
               <div class="modal-img col-sm-8 col-xs-6" >
                  <img src="../resources/img/img_ready.png" id="snsImg" width="100%">
               </div>
               <div class="modal-con col-sm-4 col-xs-6">
                  <div class="modal-inner">
                  <div class="profile">
                     <img src="../resources/img/profile.png">
                  </div>
                  <div class="title">
                     <p id="snsWriter">테스트</p>
                     <small id="snsRegdate">21시간전</small>
                  </div>
                  <div class="content-inner">
                     <p id="snsContent">다람쥐 헌 쳇바퀴에 타고파. sns Content 내용입니다.</p>
                  </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-share-alt"></i>공유하기</a>
                  </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>

html을 만들어 준뒤 이제 본격적으로 기능구현을 시작하면 된다. 

 

😋

 

주인장 git : https://github.com/MoonSeokHyun

반응형
반응형

오늘은 홈페이지에서 필수적인 파일 업로드이다

 

파일업로드는 커뮤니티 쇼핑몰.. 등등 모든 사이트에 다 들어가는 거같다.

안들어가는 사이트 있으면 알려주실뿐~~?? 😎

 

그래서 스프링이나 다른 언어를 공부할때 많은 라이브러리를 사용을 하는데

메이픈 레포지토리에서 다운 받을 수 있는 

Apache Commons FileUpload 

Apache Commons IO

 

이 2가지를 이용하여 파일 업로드를 구현을 해보자

 

시작🤩

 


 

1. 준비물 세팅

 

https://mvnrepository.com/

위 사이트에서 

 

Apache Commons FileUpload 

Apache Commons IO

 

2 가지를 코드를 카피한다.

 

참고로 

 

Apache Commons FileUpload 는 서블릿 3.0이상 부터 사용이 가능하다.

 

 

		<!-- 서블릿 3.0이상 사용 가능한 파일 업로드 api-->
		<dependency>
		    <groupId>commons-fileupload</groupId>
		    <artifactId>commons-fileupload</artifactId>
		    <version>1.3.1</version>
		</dependency>
		
		<!-- commons-io -->
		<dependency>
		    <groupId>commons-io</groupId>
		    <artifactId>commons-io</artifactId>
		    <version>2.4</version>
		</dependency>

 

레거시 프로젝트 pom.xml에서 넣어주고 꼭 메이븐 업데이트(alt + F5)

를 눌러서 꼬옥! 업데이트를 해주자

 

1-2 객체 등록 및 빈(Bean) 등록

 

Web-INF  > config(Spring) > Servlet-config(이름다를 수 있음!)

위 경로에 객체 및 빈 등록을 해주자 

 

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
      
      <beans:property name="maxUploadSize" value="10485760" />
      <beans:property name="defaultEncoding" value="utf-8" />
   
   </beans:bean>

이렇게 빈등록을 해주자 

 

반드시 id를 multipartResolver로 선언

 

 

 최대 업로드 가능한 바이트 크기(바이트 단위), -1은 제한이 없음을 의미 -
      <beans:property name="maxUploadSize" value="10485760" />
      
       업로드 요청을 변환할 때 사용할 문자 인코딩 방식 
      <beans:property name="defaultEncoding" value="utf-8" />

 

이 두가지는 꼭 기억해주자 

 


2. 본격적인 코딩😋

 

2-1 JSP 

 

	<!-- 파일 업로드에서는 enctype(인코딩타입)을 multipart/form-data로 반드시 설정 -->
	<form action="upload_ok" method="post" enctype="multipart/form-data">
		파일 선택 : <input type="file" name="file">
		<input type="submit" value="전송">
	</form>
	
	<br><hr><br>
	
	<!-- 파일 두개이상 붙히는거 -->
	<form action="upload_ok2" method="post" enctype="multipart/form-data">
		파일 선택 : <input type="file" multiple="multiple" name="files"> 
		<input type="submit" value="전송">
	</form>

연습이기 떄문에 디자인은 생략한다

 

이때

🤓

중요한게 form태그안에 

enctype="multipart/form-data를 입력 해주어야한다.

 

참조 코딩의 시작 TCP School

 

2-1 Class VO 등 제작


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UploadVO {
	private String nmae;
	private MultipartFile file;
}

 

@Getter
@Setter
@ToString
public class MultiUploadVO {
	private List<UploadVO> list;
}

 

파일을 올릴때 한가지면 VO만 작성해도 되지만

2개 이상일 시는 LIst 타입으로 받아준다.

 

2-2 ConTroller 제작

 

//업로드로 가는 메소드
	@GetMapping("/upload")
	public void form() {}
	
	@PostMapping("/upload_ok")
	public String upload(@RequestParam("file") MultipartFile file) {
		String fileRealName = file.getOriginalFilename(); //파일명을 얻어낼 수 있는 메서드!
		long size = file.getSize(); //파일 사이즈
		
		System.out.println("파일명 : "  + fileRealName);
		System.out.println("용량크기(byte) : " + size);
		//서버에 저장할 파일이름 fileextension으로 .jsp이런식의  확장자 명을 구함
		String fileExtension = fileRealName.substring(fileRealName.lastIndexOf("."),fileRealName.length());
		String uploadFolder = "C:\\test\\upload";
		
		
		/*
		  파일 업로드시 파일명이 동일한 파일이 이미 존재할 수도 있고 사용자가 
		  업로드 하는 파일명이 언어 이외의 언어로 되어있을 수 있습니다. 
		  타인어를 지원하지 않는 환경에서는 정산 동작이 되지 않습니다.(리눅스가 대표적인 예시)
		  고유한 랜던 문자를 통해 db와 서버에 저장할 파일명을 새롭게 만들어 준다.
		 */
		
		UUID uuid = UUID.randomUUID();
		System.out.println(uuid.toString());
		String[] uuids = uuid.toString().split("-");
		
		String uniqueName = uuids[0];
		System.out.println("생성된 고유문자열" + uniqueName);
		System.out.println("확장자명" + fileExtension);
		
		
		
		// File saveFile = new File(uploadFolder+"\\"+fileRealName); uuid 적용 전
		
		File saveFile = new File(uploadFolder+"\\"+uniqueName + fileExtension);  // 적용 후
		try {
			file.transferTo(saveFile); // 실제 파일 저장메서드(filewriter 작업을 손쉽게 한방에 처리해준다.)
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "fileupload/upload_ok";
	}

우선 Jsp에서 파일의 name = file로 지정해주었기 떄문에 

@RequestParam("file")로 가져온다. 

 

다음에 getOriginalFilename()로 파일을 업로드하면서 업로드한 파일명을 얻어낸다.

그 후 size를 알고 싶으면 file size를 붙혀 알아낸다. 

여기서 int를 붙히지않는다 용량이 커질 수도 있기 떄문에 

int값을 못 받을 수 있다.

 

물론 int로 받고 빈등록에서 용량을 조절해도 된다 

 

이때 나는 지정한 fileRealName(가져온 파일의 이름)에 확장자를 얻고 싶다.

fileRealName에서 subString을 사용하여  파일명을 조각낸다.

그 후 indexof로 (.)을 지정한 뒤 fileRealName.length()로 하면

예를 들면 이미지 파일일 경우

xxx.jpg 의 jpg를 따오게 된다. 

그래서 fileExtension에는 확장자가 들어가 있다. 

 

String uploadFolde= "C:\\test\\upload";을 지정해 주고 

위에 경로와 동일한 폴더를 생성해준다. 

이곳으로 업로드한 파일을 보낼것이다

 

 

		UUID uuid = UUID.randomUUID();
		System.out.println(uuid.toString());
		String[] uuids = uuid.toString().split("-");
		
		String uniqueName = uuids[0];
		System.out.println("생성된 고유문자열" + uniqueName);
		System.out.println("확장자명" + fileExtension);

그리고 컨트롤러에 왠 UUID 일 수가 있는데

컴퓨터는 동일한 파일명을 저장할 수 없다. 

이렇게 UUID를 생성하여 랜덤한 파일명의 이름으로 저장해준다.

 

File saveFile = new File(uploadFolder+"\\"+uniqueName + fileExtension);  
		try {
			file.transferTo(saveFile); // 실제 파일 저장메서드(filewriter 작업을 손쉽게 한방에 처리해준다.)
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "fileupload/upload_ok";
	}

그리고 saveFile의 이름으로 아까 생성한 경로에 파일이 들어갈 수 있도록 생성해 준뒤

file.transferTo(saveFile);로 파일을 전송해준다. 

 

이때 파일전달에 예외를 던지고 있어 예외처리를 해준다. 

 


파일이 2개 이상 업로드 될 시🤡 

위의 경우는 1가지일 때이다.

 

 

VO는 동일하다.

 

 

 

Jsp 파일이 달라지게 되는데.

 

	<form action="upload_ok2" method="post" enctype="multipart/form-data">
		파일 선택 : <input type="file" multiple="multiple" name="files"> 
		<input type="submit" value="전송">
	</form>

 

 

multiple="multiple"을 써주게 되면

 

이렇게 바뀌게 된다🤓 

 

 

컨트롤러에서 추가된 부분을 찾아보자!

 

@PostMapping("/upload_ok2")
	public String upload2(MultipartHttpServletRequest files) {
		

		//서버에서 저장 할 경로
		String uploadFolder = "C:\\test\\upload";
		List<MultipartFile> list = files.getFiles("files");
		for(int i = 0; i<list.size(); i++) {
			String fileRealName = list.get(i).getOriginalFilename();
			long size = list.get(i).getSize();
			
			System.out.println("파일명 :" + fileRealName);
			System.out.println("사이즈" + size);
			
			File saveFile = new File(uploadFolder + "\\" + fileRealName);
			try {
				list.get(i).transferTo(saveFile);
			} catch (IllegalStateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	
		return "fileupload/upload_ok";
	}

기본적인 형식을 똑같으나.

MultipartHttpServletRequest : 다중파일을 받아 올 수 있다. 

 먼저 와 같이 서버에 저장할 경로를 지정해 준뒤 

List로 파일을 받아오자

List<MultipartFile> list = files.getFiles("files") 파일 여러개라 리스트로받고
반복문을 돌린다. 하나씩 뺴줘야하니까.

 

그 후 반복문 중간에 UUID를 넣어 중복을 막아주면 된다(선택사항)

 

 


3.동작확인

 

파일 등록 후 전송

 

잉 오류 떳다.... ㅇ ㅏ 뭐지 하다가

곰곰히 영문을 보니 아까 빈으로 등록했던 파일의 용량보다 커서 예외처리가 되었다.

그러면 적은 파일을 넣어보자.

 

 

드디어 성공 

자 이제 아까 확인용 println을 보자.

 

아주 잘 가져오는것을 확인 할 수 있다.

uuid로 임의로 변경된 이름으로 저장이 되었다.

 

성공 !!!!

 

 

이렇게 오늘은 파일 업로드를 알아 보았는데

나중에 현업으로 가면 많이 쓰일 것 같다

생각보다 어렵지 않아서 좋당

 

코딩을 배우시는분들 화이팅!!

 

 

 

주인장 Git : https://github.com/MoonSeokHyun

 

MoonSeokHyun - Overview

http://mls0000.dothome.co.kr/. MoonSeokHyun has 7 repositories available. Follow their code on GitHub.

github.com

그럼 이만 !

 

반응형
반응형

요즘사이트에서 회원가입을 하다보면 꼭!  각종 인증들 이있다.

사용자 입장에서는 굳이?? 이걸 해야하나? 싶기도 하겠지만 

사업자 입장에서보면 각종 Black Consumer 외 다른 부분에 대해서 

보안을 강조한다. 그렇기 때문에 휴대폰인증 이메일인증은 꼭 필요하다고 말하고 싶다. 

 

why 이메일인증?? 😞

 

JUST 

 

비싸니까..

 

이유는 단순하다 그냥 프로젝트에 대해서 돈이들기 떄문이다.스프링에서 기본으로 제공하는 API로 충분한 이메일 인증이 가능하기 떄문이다!

 

자 이제 시작해보자!


 

1. API 다운 

 

https://mvnrepository.com/

 

https://mvnrepository.com/에서 javax.mail과 spring-context-support을 다운받아 준다. 

 

<!-- https://mvnrepository.com/artifact/javax.mail/mail -->
<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.9</version>
</dependency>

여러분의 시간은 소중하니까. 이걸 pom.xml 라이브러리에 추가해준다.

 

👻  나름의 꿀팁  👻 

<version>${org.springframework-version}</version> 이거 써주면 알아서 버전 맞춰준다! 

 

 


2. JSP 수정(HTML , JS 영역)

 

<div class="form-group email-form">
	 <label for="email">이메일</label>
	 <div class="input-group">
	<input type="text" class="form-control" name="userEmail1" id="userEmail1" placeholder="이메일" >
	<select class="form-control" name="userEmail2" id="userEmail2" >
	<option>@naver.com</option>
	<option>@daum.net</option>
	<option>@gmail.com</option>
	<option>@hanmail.com</option>
	 <option>@yahoo.co.kr</option>
	</select>
	/div>   
<div class="input-group-addon">
	<button type="button" class="btn btn-primary" id="mail-Check-Btn">본인인증</button>
</div>
	<div class="mail-check-box">
<input class="form-control mail-check-input" placeholder="인증번호 6자리를 입력해주세요!" disabled="disabled" maxlength="6">
</div>
	<span id="mail-check-warn"></span>
</div>

기존의 Jsp에서 본인인증 버튼을 추가하고 인증번호를 받을 자리를 남겨 두었다. 😁

 

2-1 JS 작성

 

$('#mail-Check-Btn').click(function() {
		const eamil = $('#userEmail1').val() + $('#userEmail2').val(); // 이메일 주소값 얻어오기!
		console.log('완성된 이메일 : ' + eamil); // 이메일 오는지 확인
		const checkInput = $('.mail-check-input') // 인증번호 입력하는곳 
		
		$.ajax({
			type : 'get',
			url : '<c:url value ="/user/mailCheck?email="/>'+eamil, // GET방식이라 Url 뒤에 email을 뭍힐수있다.
			success : function (data) {
				console.log("data : " +  data);
				checkInput.attr('disabled',false);
				code =data;
				alert('인증번호가 전송되었습니다.')
			}			
		}); // end ajax
	}); // end send eamil

우선 회원가입 도중에 진행되는 부분이라 비동기통신인 ajax를 진행한다.

GET방식으로 진행하며 기존에 있던 name 파라미터 값을 그대로 컨트롤러로 옮기려고한다. 

 


3. SPRING 부분 

 

 

//이메일 인증
	@GetMapping("/mailCheck")
	@ResponseBody
	public String mailCheck(String email) {
		System.out.println("이메일 인증 요청이 들어옴!");
		System.out.println("이메일 인증 이메일 : " + email);
	}

 

Get방식으로 가져오며 이메일을 확인 할 수있게 간단하게 프린트로 찍어준다. 

이때 변수의 이름이 동일하면 @붙는 아노테이션을 지정 안할수 있다.

 

굳 아주 잘 들어오는 것을 알 수있다.

 

컨트롤러에서 다 작성해주어도 되지만 컨트롤러가 너무 비대해지기 때문에 따로 MailSendService라는

CLASS를 하나 만들어서 진행하려고 한다. 

 

클래스는 만들었다면 빈등록을 해주어야한다. 

 

Spring 폴더에 있는 경로로 빈 xml 파일을 만들어 주어 설정값을 해주었다.

 

XML 빈등록

 

   <context:property-placeholder location="classpath:/db-config/email.properties" />

   <!-- 이메일 인증 관련 빈 등록 -->
   <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
      <property name="host" value="smtp.gmail.com" />
      <property name="port" value="587" />
      <property name="username" value="${email.account}" />
      <property name="password" value="${email.password}" />
      <property name="javaMailProperties">
         <props>
                <prop key="mail.transport.protocol">smtp</prop>
                <prop key="mail.smtp.auth">true</prop>
                <!-- gmail의 경우 보안문제 업데이트로 인해 SSLSocketFactory를 추가해야 smtp 사용 가능. -->
                <prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
                <prop key="mail.smtp.starttls.enable">true</prop>
                <prop key="mail.debug">true</prop>
                <prop key="mail.smtp.ssl.trust">smtp.gmail.com</prop>
				<prop key="mail.smtp.ssl.protocols">TLSv1.2</prop>
            </props>
      </property>
      
   </bean>

각 property의 값

host : 이메일 보낼 url을 입력

port : 포트 번호 입력

userName : 이메일을 보낼 아이디 입력

Password : 이메일 비번

 

아래 키값은 보안 업데이트로 따로 추가 해 주어야한다. 

 

아이디 비밀번호가 노출되는게 싫은 경우 file을 따로 만들어서 빈 주입후 따로 사용이 가능하다. 

 

다시 돌아와서 

 

MailSendService.class 제작🥰

 

@Component
public class MailSendService {
	@Autowired
	private JavaMailSenderImpl mailSender;
	private int authNumber; 
	// 난수 발생(여러분들 맘대러)
	
		public void makeRandomNumber() {
			// 난수의 범위 111111 ~ 999999 (6자리 난수)
			Random r = new Random();
			int checkNum = r.nextInt(888888) + 111111;
			System.out.println("인증번호 : " + checkNum);
			authNumber = checkNum;
		}
		
		
				//이메일 보낼 양식! 
		public String joinEmail(String email) {
			makeRandomNumber();
			String setFrom = ".com"; // email-config에 설정한 자신의 이메일 주소를 입력 
			String toMail = email;
			String title = "회원 가입 인증 이메일 입니다."; // 이메일 제목 
			String content = 
					"홈페이지를 방문해주셔서 감사합니다." + 	//html 형식으로 작성 ! 
	                "<br><br>" + 
				    "인증 번호는 " + authNumber + "입니다." + 
				    "<br>" + 
				    "해당 인증번호를 인증번호 확인란에 기입하여 주세요."; //이메일 내용 삽입
			mailSend(setFrom, toMail, title, content);
			return Integer.toString(authNumber);
		}
		
		//이메일 전송 메소드
		public void mailSend(String setFrom, String toMail, String title, String content) { 
			MimeMessage message = mailSender.createMimeMessage();
			// true 매개값을 전달하면 multipart 형식의 메세지 전달이 가능.문자 인코딩 설정도 가능하다.
			try {
				MimeMessageHelper helper = new MimeMessageHelper(message,true,"utf-8");
				helper.setFrom(setFrom);
				helper.setTo(toMail);
				helper.setSubject(title);
				// true 전달 > html 형식으로 전송 , 작성하지 않으면 단순 텍스트로 전달.
				helper.setText(content,true);
				mailSender.send(message);
			} catch (MessagingException e) {
				e.printStackTrace();
			}
		}
		
	
}

🥰

makeRandomNumber()

이메일을 보낼때 인증번호로 111111~999999까지의 난수를 발생시킨다.

 

public String joinEmail(String email)

컨트롤러에서 아이디가 넘어오면서 붙을 스트링값

메일 샌드로 보내줄 준비를 마친다.

 

public void mailSend(String setFrom, String toMail, String title, String content) {

 

 

MimeMessage message = mailSender.createMimeMessage(); 객체를 생성해준다.

이것이 스프링에서 제공하는 메일 API이다.

 

여기서 중요한 점은 빈 자동등록을 위해 @component를 해준다. 

 

 

컨트롤러 마무리😊

 

	
	@Autowired
	private IUserService service;
	@Autowired
	private MailSendService mailService;
	


	
	//회원가입 페이지 이동
	@GetMapping("/userJoin")
	public void userJoin() {}
	
	//이메일 인증
	@GetMapping("/mailCheck")
	@ResponseBody
	public String mailCheck(String email) {
		System.out.println("이메일 인증 요청이 들어옴!");
		System.out.println("이메일 인증 이메일 : " + email);
		return mailService.joinEmail(email);
		
			
	}

우선 아까 만들어둔 mailsendService 부분을 등록해준다. 

AutoWired로 자동 빈등록을 해준다. 

 

이제 Jsp파일 > 컨트롤러로 이메일값을 전달해주면 리턴값으로 Email을 전달하면서 

아까 만들어둔 메일을 발송해준다.

 


🤪 JS 마무리!🤪 

아까 만들어 둔 Jsp파일에 Js부분을 마무리해준다. 

 

$('#mail-Check-Btn').click(function() {
		const eamil = $('#userEmail1').val() + $('#userEmail2').val(); // 이메일 주소값 얻어오기!
		console.log('완성된 이메일 : ' + eamil); // 이메일 오는지 확인
		const checkInput = $('.mail-check-input') // 인증번호 입력하는곳 
		
		$.ajax({
			type : 'get',
			url : '<c:url value ="/user/mailCheck?email="/>'+eamil, // GET방식이라 Url 뒤에 email을 뭍힐수있다.
			success : function (data) {
				console.log("data : " +  data);
				checkInput.attr('disabled',false);
				code =data;
				alert('인증번호가 전송되었습니다.')
			}			
		}); // end ajax
	}); // end send eamil
	
	// 인증번호 비교 
	// blur -> focus가 벗어나는 경우 발생
	$('.mail-check-input').blur(function () {
		const inputCode = $(this).val();
		const $resultMsg = $('#mail-check-warn');
		
		if(inputCode === code){
			$resultMsg.html('인증번호가 일치합니다.');
			$resultMsg.css('color','green');
			$('#mail-Check-Btn').attr('disabled',true);
			$('#userEamil1').attr('readonly',true);
			$('#userEamil2').attr('readonly',true);
			$('#userEmail2').attr('onFocus', 'this.initialSelect = this.selectedIndex');
	         $('#userEmail2').attr('onChange', 'this.selectedIndex = this.initialSelect');
		}else{
			$resultMsg.html('인증번호가 불일치 합니다. 다시 확인해주세요!.');
			$resultMsg.css('color','red');
		}
	});

Ajax부분을 우선 완성해준다! 

 

error는 없다 왜냐하면 완벽하다! 

제발..

 

우선 funtion(data)값으로 아까 보내둔 인증번호가 왔을 것이다. 

이제 이것을 비교해 주면서 회원가입을 처리해주면 될것이다. 

 

 

인증번호 어서오고

이제 받은 data값으로 인증번호를 비교 하면 된다 .

위의 코드를 참조 바란다. 

 


🤩동작 확인🤩

 

우선 이메일을 보낼 주소를 입력해주고

 

alert창이 나를 반긴다.
!?
오오 .. 인증번호는 376236이라고 한다. 이제 입력해보자!
우선 거짓말을 해보았다. 역시 거짓말은 안통한다.
콤퓨타가 준 코드를 넣었다. 이제 반응한다. 쨔식... 

 

 

이거 좀 어려워서 한두번 정도 더 반복을 해보아야겠다. 

 

항상 지적은 환영! 

 

위의 코드를 쓴 깃 주소이다 . 한번쯤 방문해보자!

 

https://github.com/MoonSeokHyun/spring/tree/master/MyWeb/src

반응형
반응형

저번에 포스팅 했던 회원가입 및 로그인 검증은 우선 끝낫다. 

이제 저번에 만들었던 게시판을 한번 만들어 보고자 한다! 

 

사용 기술 Tools
Java 11 jdk STS3
Oracle 18g SQLDev

 

 

 

1. SQL 테이블 생성 😎 

 

 -- 게시판 테이블 생성
CREATE TABLE mvc_board (
    board_no NUMBER PRIMARY KEY,
    title VARCHAR2(100) NOT NULL,
    content VARCHAR2(300) NOT NULL,
    writer VARCHAR2(50) NOT NULL,
    reg_date DATE DEFAULT sysdate,
    view_cnt NUMBER DEFAULT 0
);

-- board_no에 대한 시퀀스 설정
CREATE SEQUENCE board_seq
    START WITH 1
    INCREMENT BY 1
    MAXVALUE 1000
    NOCYCLE
    NOCACHE;

우선 게시판을 만들 수있는 뼈대 즉 테이블 부터 생성해준다! 

 

 


2. BoardVO 생성 🤪

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class BoardVO {
	
	private int boardNo;
	private String title;
	private String content;
	private String writer;
	private Timestamp regDate;
	private int viewCnt;
	
	//new 마크 부착 여부 논리타입 필드
	private boolean newMark;
	

}

 

우선 Getter or Setter를 좀 더 쉽게 하기 위해 lombok을 사용하였다. 


3.  HTML 제작

 

	<div class="container">
		<div class="row">
			<div class="col-lg-2">
			</div>
			<div class="col-lg-8">
				<div class="panel-body">
				<h2 class="page-header"><span style="color: #643691;">Spring</span> 자유 게시판
					<span id="count-per-page" style="float: right;">
	                     <input class="btn btn-cpp" type="button" value="10">  
	                     <input class="btn btn-cpp" type="button" value="20">   
	                     <input class="btn btn-cpp" type="button" value="30">
                     </span>
					
				</h2>
					<table class="table table-bordered table-hover">
						<thead>
							<tr style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">
								<th>#번호</th>
								<th>작성자</th>
								<th>제목</th>
								<th>작성일</th>
								<th>조회수</th>
							</tr>
						</thead>

						<!-- 게시물이 들어갈 공간 -->
						<c:forEach var="b" items="${articles}">
							<tr style="color: #643691;">
								<td>${b.boardNo}</td>
								<td>${b.writer}</td>

								<td><a style="margin-top: 0; height: 40px; color: orange;"
								 href="<c:url value="/board/content/${b.boardNo}${pc.makeUri(pc.paging.page)}"/>">
										${b.title} 
									</a>
									&nbsp;
									<c:if test="${b.newMark}">
										<img alt="newmark" src="C:\Users\mls00\OneDrive\바탕 화면\SpringWebMvcProject\src\main\webapp\resources\img\icon_new.gif">
									</c:if>
								</td>

								<td> <fmt:formatDate pattern="yyyy-MM-dd hh:mm:ss" value="${b.regDate}"/></td>
								<td>${b.viewCnt}</td>
							</tr>
						</c:forEach>
						
						
					</table>
					
					<!-- 페이징 처리 부분  -->
						<ul class="pagination justify-content-center">
					<c:if test="${pc.prev}">
                       	<li class="page-item">
							<a class="page-link" href="/board/list/?${pc.makeUri(pc.beginPage-1)}"
							style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">이전</a>
						</li></c:if>
				
						<c:forEach var="num" begin="${pc.beginPage}" end="${pc.endPage}" >
							<li class="page-item">
						   <a href="/board/list${pc.makeUri(num)}" 
						   class="page-link  ${pc.paging.page == num ? 'page-active': '' }" style="margin-top: 0; height: 40px; color: pink; border: 1px solid #643691;">${num}</a>
						</li>
					   </c:forEach>
					
					 <c:if test="${pc.next}">
					     <li class="page-item">
					      <a class="page-link" href='<c:url value='/board/list${pc.makeUri(pc.endPage+1)}}'/>' 
					      style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">다음</a>
					    </li>
				    </ul></c:if>
					
					<!-- 페이징 처리 끝 -->
					</div>
				</div>
			</div>
					<!-- 검색 버튼 -->
					<div class="row">
						<div class="col-sm-2"></div>
	                    <div class="form-group col-sm-2">
	                        <select id="condition" class="form-control" name="condition">                            	
	                            <option value="title" ${param.condition == 'title' ? 'selected' : '' }>제목</option>
	                            <option value="content"${param.condition == 'content' ? 'selected' : '' }>내용</option>
	                            <option value="writer"${param.condition == 'writer' ? 'selected' : '' }>작성자</option>
	                            <option value="titleContent"${param.condition == 'titleContent' ? 'selected' : '' }>제목+내용</option>
	                        </select>
	                    </div>
	                    <div class="form-group col-sm-4">
	                        <div class="input-group">
	                            <input type="text" class="form-control" name="keyword" id="keywordInput" placeholder="검색어" value="${param.keyword}">
	                            <span class="input-group-btn">
	                                <input type="button" value="검색" class="btn btn-cpp btn-flat" id="searchBtn">                                       
	                            </span>
	                        </div>
	                    </div>
	                    <div class="col-sm-2">
							<a href="<c:url value ="/board/write"/>" class="btn btn-cpp float-right">글쓰기</a>
						</div>
						<div class="col-sm-2"></div>
					</div>
		
	</div>

 

기본 뼈대의 Html이다 CSS는 BOOTSTRAP을 사용하였다. 😎😎😎

 


4. 본격적인 Spring 시작!😁

 

4-1 Controller 제작

 

 

@Controller
@RequestMapping("/board")
public class BoardController {

   @GetMapping("/list")
   public String list(Model model) {
      System.out.println("/board/list: GET");
      model.addAttribute("articles", service.getArticleList());
      
      return "board/list";
   	}
   }

 

 

4-2 인터페이스 제작

package com.spring.mvc.board.service;

import java.util.List;

import com.spring.mvc.board.commons.SearchVO;
import com.spring.mvc.board.commons.pageVO;
import com.spring.mvc.board.model.BoardVO;

public interface IBoardService {

	//게시글 등록 기능
	void insert(BoardVO article);

	//게시글 전체 목록 기능
	List<BoardVO> getArticleList(SearchVO search);

	//게시글 상세 조회 기능
	BoardVO getArticle(int boardNo);

	//게시글 수정 기능
	void update(BoardVO article);

	//게시글 삭제 기능
	void delete(int boardNo);

	//게시물 수 조회 기능 
	int countArticles(SearchVO search);
	
	//날자 한글로 변경
	
}

 

IboardService를 만든다. 기능은 단순하게 세팅해준다.

 

그 다음은 IBoardMapper를 생성해준다.

 

package com.spring.mvc.board.repository;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Param;

import com.spring.mvc.board.commons.SearchVO;
import com.spring.mvc.board.commons.pageVO;
import com.spring.mvc.board.model.BoardVO;

public interface IBoardMapper {

	//게시글 등록 기능
	void insert(BoardVO article);
	
	//검색 결과와 페이지 정보까지 가지고 있는 하나의 객체를 매개값으로 받는 방식
	List<BoardVO> getArticleList(SearchVO search);
	//게시글 상세 조회 기능
	BoardVO getArticle(int boardNo);
	
	//게시글 수정 기능
	void update(BoardVO article);
	
	//게시글 삭제 기능
	void delete(int boardNo);
	
	//게시물 수 조회 기능
	int countArticles(SearchVO search);


	
	
}

 여기서 주의 할 점!

 

 MYBatis로 db연동을 진행할 떄 파라미터 값이 2개 이상이라면 
 1. @param으로 작성하는법

 2. map으로 포장해서 보내는법 

 3. 객체하나를 매개값으로 보내는법 들을 적절하게 상황에 맞게 선택하면 됨

 

 

주로 Map을 이용한 방법을 많이 쓴다. 

 

 


5. Service 제작 👏

package com.spring.mvc.board.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.mvc.board.commons.SearchVO;
import com.spring.mvc.board.commons.pageVO;
import com.spring.mvc.board.model.BoardVO;
import com.spring.mvc.board.repository.IBoardMapper;

@Service
public class BoardService implements IBoardService {
	
	@Autowired
	private IBoardMapper mapper;

	@Override
	public void insert(BoardVO article) {
		mapper.insert(article);
	}


	@Override
	public List<BoardVO> getArticleList(SearchVO search) {
		//mapper에게 전달할 맵 데이터를 생성
//		Map<String, Object> datas = new HashMap<>();
//		datas.put("paging", paging);
//		datas.put("keyword", keyword);
//		datas.put("condition", condition);
		
		List<BoardVO> list = mapper.getArticleList(search);
		for(BoardVO article : list) {
			long now = System.currentTimeMillis();
			long regTime = article.getRegDate().getTime();
			if(now - regTime < 60 * 60*24 *1000) {
				article.setNewMark(true);
			}
		}
		return list;
	}

	@Override
	public BoardVO getArticle(int boardNo) {
		return mapper.getArticle(boardNo);
	}

	@Override
	public void update(BoardVO article) {
		mapper.update(article);
	}

	@Override
	public void delete(int boardNo) {
		mapper.delete(boardNo);
	}
	
	@Override
	public int countArticles(SearchVO search){
		return mapper.countArticles(search);
	}
	
}

 

IServiceBoard를 Add하여 생성해준다.

 

 

 


6. Mapper 제작🤞

 

Mapper 를 제작해준다. 

이때 꿀팁

<mybatis-spring:scan base-package="com.spring.mvc.board.repository"/>
<mybatis-spring:scan base-package="com.spring.mvc.user.repository"/>

Context-root.xml 파일일에 Mybatis.xml파일을 bean으로 등록하기 위한 스캔 설정을 해줍니다. 

<interceptors>
		<interceptor>
			<!-- <mapping path="/board/**"/>  -->	
			<mapping path="/board/write"/>
			<mapping path="/board/content/**"/> <!-- **은 글번호  -->
			<beans:bean class="com.spring.mvc.board.commons.intercertor.BoardInterceptor" />
		</interceptor>

이때 servlet-config.xml에서 이렇게 설정을 해주면 별도로 xml 파일을 만들때 빈등록을 안해줘도 됩니다. 

👏👏👏👏즉 위 지정된 경로만 지정해주면 된다! 👏👏👏👏

 

위에 지정한 대로 파일을 이렇게 설정 하였다!

	<select id="getArticleList" resultMap="BoardMap">

		SELECT * FROM mvc_board
		
		ORDER BY board_no DESC
		

	</select>

리스트를 불러올 수 있도록 Select 값을 넣어준다!

	<insert id="insert">
		INSERT INTO mvc_board
		(board_no, title, content, writer)
		VALUES(board_seq.NEXTVAL,#{title},#{content},#{writer})
	</insert>

그리고 현재 insert 관련하여 컨트롤러나 이런부분을 만들지 않았다 하지만 게시물이 보이는

리스트를 만들려면 insert를 하여 볼 데이터가 필요했다. 

sqldev로 해도 되지만 plsql하기 귀찮아 Junit으로 테스트를 한다. 

 


7. Junit Test

 

package com.spring.mvc.board;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.spring.mvc.board.model.BoardVO;
import com.spring.mvc.board.repository.IBoardMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/mvc-config.xml"})
public class BoardMapperTest {

	@Autowired
	private IBoardMapper mapper;
	
	//게시글 등록 단위 테스트
	@Test
	public void insertTest() {
		for(int i=1; i<=300; i++) {
			BoardVO article = new BoardVO();
			article.setTitle("테스트 제목입니다." + i);
			article.setWriter("김테스트" + i);
			article.setContent("테스트 중이니까 조용히 하세요!" + i);
			mapper.insert(article);
		}
	}

 

우선 300개만 가볍게 넣어보자

 

데이터가 잘 들어갔다면 이제 화면에 뿌려주자!

 


8. 화면 작업

<c:forEach var="b" items="${articles}">
		<tr style="color: #643691;">
		<td>${b.boardNo}</td>
		<td>${b.writer}</td>

		<td><a style="margin-top: 0; height: 40px; color: orange;"
		href="<c:url value="/board/content/${b.boardNo}${pc.makeUri(pc.paging.page)}"/>">
		${b.title} 
		</a>
		&nbsp;
		<c:if test="${b.newMark}">
		<img alt="newmark" src="C:\Users\mls00\OneDrive\바탕 화면\SpringWebMvcProject\src\main\webapp\resources\img\icon_new.gif">
		</c:if>
		</td>

		<td> <fmt:formatDate pattern="yyyy-MM-dd hh:mm:ss" value="${b.regDate}"/></td>
		<td>${b.viewCnt}</td>
		</tr>
		</c:forEach>
						
						
		</table>

 

Jstl 을 이용하여 받아온 파라미터 값을 뿌려준다.

😎<c:forEach var="b" items="${articles}">😎

😎변수는 b로 하고 뿌려준다!😎


😎

9. 동작 확인🙏

 

 

테스트로 넣은 게시글이 잘 보이는걸 알 수 있다!

 

또 오류 나는 줄 알고 십년감수했다.☠️

 

 

 

주인장 Github 

 

https://github.com/MoonSeokHyun

반응형
반응형

저번에 포스팅 했던 회원가입 진행 및 아이디 중복 체크에 404에러 뜨던것 해결 했다. 

 

나는 바보였다.  학원 강사님께 문의 하니 경로가 잘 못되었엇다. 

 

Spring

🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏

기도 메타가 부족했나 이번에는 🙏 *100으로  도전 한다. 

 


1. 문제의 원인🕵️‍♂️

 

<Context docBase="MyWeb" path="/myweb" reloadable="true" 
source="org.eclipse.jst.jee.server:MyWeb"/><Context docBase="SpringWebMvcProject"
path="/mvc" 
reloadable="true" source="org.eclipse.jst.jee.server:SpringWebMvcProject"/></Host>
    </Engine>

톰캣 서버 내의 context root의 path가 /인줄 알았는데 알고보니 /mvc 더라..

 


 

2. 문제의 해결🕵️‍♂️

 

$.ajax({
         		 type :'post', // 서버에 전송하는 http방식
         		 url :'/mvc/user/checkId', // 서버 요청 url
         		 headers : {
         			 'Content-Type' : 'application/json'
         		 },
        		 data : id, // 서버로 전송할 데이터 // 위에서 지정한 const id 
        		 success : function(result) { // 매개변수에 통신성공시 데이터가 저장된다.
					//서버와 통신성공시 실행할 내용 작성.
					console.log('통신 성공!' + result);
        		 	if(result === 'available'){
        		 		 $('#user_id').css('background-color', 'aqua');
        		 		 $('#idChk').html('<b style="font-size: 14px; color: green">[아이디 사용이 가능하다.]</b>');
        		 		 chk1 = true;
        		 	}else{
        		 		 $('#user_id').css('background-color', 'pink');
        		 		 $('#idChk').html('<b style="font-size: 14px; color: red">[아이디 중복!.]</b>');
        		 		 chk1 = false;
        		 	}
				},
				error : function (status, error) { //통신에 실패했을때
					console.log('통신실패');
					console.log(status, error)
				}
          	}); // end ajax(아이디 중복 확인)
         }

url 부분은 기존에 /mvc/가 빠졋었다. 

/mvc를 넣었더니 정상 작동을 하였다. 

 

 

이것과 별개로 

 

url : "<c:url value ='/user/checkId'/>

 

<c:url value ='url값'/>을 쓰는것도 방법이다. 다음부터는 이방법 위주로 쓰려고 한다.,😻😻😻

 

 


3. 정상 동작 확인💪

드디어 정상적으로 확인!!

 

드디어 올 파랭이를 볼수 있음!!!

 

👏👏👏👏👏👏👏👏

 

 

문제 해결 !👏

 

 

주인장  git 주소 : https://github.com/MoonSeokHyun

반응형
반응형
	@Override
	public void keepLogin(String session, Date limitTime, String account) {
		Map<String, Object> datas = new HashMap<String, Object>();
		datas.put("sessionId", session);
		datas.put("limitTime", limitTime);
		datas.put("account", account);
		
		mapper.keepLogin(datas);
	}
    
    	@Override
	public UserVO selectOne(String account) {
		return mapper.selectOne(account);
	}

로그인 처리를 위한 html 코드 작성. 🤩

 

부트스트랩으로 작성되었음!!

<div class="modal-body">
<table style="cellpadding: 0; cellspacing: 0; margin: 0 auto; width: 100%">
<tr>
	<td style="text-align: left">
		<p><strong>아이디를 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="idCheck"></span></p>
	</td>
</tr>
<tr>
<td><input type="text" name="userId" id="signInId"
	class="form-control tooltipstered" maxlength="10"
	required="required" aria-required="true"
	style="margin-bottom: 25px; width: 100%; height: 40px; border: 1px solid #d9d9de"
	placeholder="최대 10자"></td>
	</tr>
	<tr>
<td style="text-align: left">
<p><strong>비밀번호를 입력해주세요.</strong>&nbsp;&nbsp;&nbsp;<span id="pwCheck"></span></p>
</td>
/tr>
	<tr>
	<td><input type="password" size="17" maxlength="20" id="signInPw"
	name="userPw" class="form-control tooltipstered" 
	maxlength="20" required="required" aria-required="true"
	style="ime-mode: inactive; margin-bottom: 25px; height: 40px; border: 1px solid #d9d9de"
								placeholder="최소 8자"></td>
		</tr>
		<!--  자동로그인 체크박스 -->
		<tr>
		<td><input type="checkbox" id="auto-login" name="autoLogin"> 자동 로그인</td>
			</tr>
			<tr>
		td style="padding-top: 10px; text-align: center">
		<p><strong>로그인하셔서 더 많은 서비스를 이용하세요~</strong></p>
			</td>
			</tr>
		tr>
		td style="width: 100%; text-align: center; colspan: 2;"><input
		type="button" value="로그인" class="btn form-control tooltipstered" id="signIn-btn"
		style="background-color: #643691; margin-top: 0; height: 40px; color: white; border: 0px solid #f78f24; opacity: 0.8">
			</td>
			</tr>
				<tr>
				<td
style="width: 100%; text-align: center; colspan: 2; margin-top: 24px; padding-top: 12px; border-top: 1px solid #ececec">

<a class="btn form-control tooltipstered" data-toggle="modal"
href="#sign-up"
style="cursor: pointer; margin-top: 0; height: 40px; color: white; background-color: orange; border: 0px solid #388E3C; opacity: 0.8">
			회원가입</a>
			</td>
			</tr>

		</table>
			
			</div>
		</div>
	</div>
</div>

2. 로그인 처리를 위한 JQuery문 🤩

  - 아이디 검증

 

 //각 입력값들의 유효성 검증을 위한 정규표현식을 변수로 선언.
         const getIdCheck = RegExp(/^[a-zA-Z0-9]{4,14}$/);
         const getPwCheck = RegExp(/([a-zA-Z0-9].*[!,@,#,$,%,^,&,*,?,_,~])|([!,@,#,$,%,^,&,*,?,_,~].*[a-zA-Z0-9])/);
         const getNameCheck = RegExp(/^[가-힣]+$/);
// ID 입력값 검증 (공백확인 , 정규표현식 어긋나는지 확인)
		$('#signInId').keyup(function() {
			if($(this).val() === ''){
				$(this).css('background-color', 'pink');
			 	$('#idCheck').html('<b style="font-size: 14px; color: red">[아이디는 필수 값입니다.]</b>');
			 	chk1 = false;
			}else if(!getIdCheck.test($(this).val())){
				$(this).css('background-color', 'pink');
			 	$('#idCheck').html('<b style="font-size: 14px; color: red">[아이디가 형식에 어긋납니다!]</b>');
			 	chk1 = false;
			}else{
				$(this).css('background-color', 'aqua');
			 	$('#idCheck').html('<b style="font-size: 14px; color: red">[아이디 입력 성공!]</b>');
			 	chk1 = true;
			}
/ 비밀번호 입력값 검증 (공백확인 , 정규표현식 어긋나는지 확인)
		$('#signInPw').keyup(function() {
			if($(this).val() === ''){
				$(this).css('background-color', 'pink');
			 	$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호는 필수 값입니다.]</b>');
			 	chk2 = false;
			}else if(!getPwCheck.test($(this).val())){
				$(this).css('background-color', 'pink');
			 	$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호가 형식에 어긋납니다.]</b>');
			 	chk2 = false;
			}else{
				$(this).css('background-color', 'aqua');
			 	$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호 입력 성공!]</b>');
			 	chk2 = true;
			}
		}); // 비밀번호 검증 끝

 

 

정규식 표현식으로 아이디가 어긋나는지 or 빈칸인지 둘다 만족 된다면 

else값으로 이동하여 OK표기!

 

 

$('#signIn-btn').click(function() {
			if(chk1 && chk2){
				// ajax를 이용한 비동기 방식으로 로그인을 처리할 예정이다. 
		        /*
		        아이디, 비밀번호를 가져오셔서 객체로 포장하세요.
		        비동기 통신을 진행하여 서버로 객체를 json형태로 전송하세요.
		        그리고, console.log()로 서버가 보내온 데이터를 확인하여
		        아이디가 없습니다, 비밀번호가 틀렸습니다, 로그인 성공이라는
		        메세지를 브라우저의 console창에서 확인하세요.
		        서버에서 클라이언트로 데이터 전송은 text로 이루어 질 것이며
		        idFail, pwFail, loginSuccess라는 문자열을 리턴할 것입니다.
		        전송방식: POST, url: /user/loginCheck
		        */
		        
				const id = $('#signInId').val();
				const pw = $('#signInPw').val();
				
				//자동로그인 체크박스가 체크가 되었는지의 여부 
				//제이쿼리 is 상태여부를 확인할 수 있는 함수 논리값을 판단하여 논리값을 리턴
				const autoLogin = $('#auto-login').is(':checked');
				
				console.log("id" + id);
				console.log("pw" + pw);
				const userInfo ={
						"account" : id,
						"password" : pw,
						
						//오토로그인 추가 체크의 여부를 확인하여 자동로그인을 구현할지 말지를 정한다. 
						"autoLogin" : autoLogin
				};
				// ajax 시작!
				$.ajax({
					type : "post",
					url : "/user/loginCheck",
					contentType : "application/json",
					dataType : "text",
					data : JSON.stringify(userInfo),
					success : function(result) {
						console.log("통신 성공" + result);
						if(result === 'idFail'){
							//console.log('아이디가 없습니다.');
							$('#idCheck').css('background-color', 'pink');
						 	$('#idCheck').html('<b style="font-size: 14px; color: red">[아이디가 없습니다.]</b>');
						 	$('#signInPw').val('');
						 	$("#singInPw").focus(); // 커서 이동 및 스크롤도 해당 위치로 이동시키는 함수 
						 	 chk2 = false;
						 	
						}else if (result === 'pwFail') {
							//console.log('비밀번호가 틀렸습니다.');
							$('#pwCheck').css('background-color', 'pink');
						 	$('#pwCheck').html('<b style="font-size: 14px; color: red">[비밀번호가 틀렷습니다.]</b>');
						 	$('#signInPw').val('');
						 	$("#singInId").focus(); // 커서 이동 및 스크롤도 해당 위치로 이동시키는 함수 
						 	chk1 = false;  chk2 = false;
						}else{
							console.log('로그인 성공');
							location.href = '/';
						}
					},
					error : function() {
						alert('로그인 실패!');
					}
				}); // ajax 종료!
				
			}else{
				alert('입력값을 다시 확인하세요');
			}
		}); /

if(chk1 && chk2){ 의값으로 

아이디 및 비밀번호 입력값이 모두가 true값이 진행되면 로그인 이벤트 처리 

ajax(비동기 통신으로 진행) 이제 컨트롤러 작성하러 가보자! 😀

 


3. 스프링 작성 🤩 

 

 

자 이제 컨트롤러 및 서비스 > 매퍼 및 xml을 작성 해보자!!

 

이번에도 비동기 통신임으로 

@RestController 
@RequestMapping("/user")

를 진행해준다. 그리고 user라는 url로 진행하였다. 

 

ajax에서 loginCheck라는 url으로 컨트롤러에 요청을 보내니 loginCheck와 같은 이름으로 작성한다. 

 

@PostMapping("/loginCheck")
	public String loginCheck(@RequestBody UserVO vo, HttpSession session , HttpServletResponse respones  ) {//HttpServletRequest request) {
		System.out.println("/user/logincheck : post");
		System.out.println("param : " + vo);
		
		// 매개값으로 httpsession 객체 받아서 사용
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		UserVO dbData =  serivce.selectOne(vo.getAccount());
		// mybatis는 조회된 데이터가 없을경우 null이 온다.
		if(dbData != null) {
			if(encoder.matches(vo.getPassword(), dbData.getPassword())) {
				//로그인 성공 히원을 대상으로 세션 정보를 생성
				session.setAttribute("login", dbData);
					serivce.keepLogin(session.getId(), limitDate, vo.getAccount());
				}
				return "loginSuccess";
			}else {
				return "pwFail";
			}
		}else {
			return "idFail";
		}
		
	}

이때 session.setAttribute("login", dbData); 세션을 생성해준다. 

스프링에서는 간단하게 세션을 만들수 있다. 라는 login이라는 이름으로 dbDate의 값을 만들었다 . 이곳에는 userVO의 객체의 값이 들어있다. 

 

이때 selectOne의 서비스와 keeplogin의 코드를 작성한다. 

 

 

	@Override
	public UserVO selectOne(String account) {
		return mapper.selectOne(account);
	}
    
    	@Override
	public void keepLogin(String session, Date limitTime, String account) {
		Map<String, Object> datas = new HashMap<String, Object>();
		datas.put("sessionId", session);
		datas.put("limitTime", limitTime);
		datas.put("account", account);
		
		mapper.keepLogin(datas);
	}

 

이때 SeletOne은 로그인하는 아이디의 검증을 위해 사용된다. 

keeplogin은 세션의 유지 등에 사용된다. 

 

package com.spring.mvc.user.repository;

import java.util.Map;

import com.spring.mvc.user.model.UserVO;

public interface IUserMapper {
	
	//아이디 중복체크 기능
	int checkId(String account);
	
	//회원 가입기능
	void register(UserVO vo);
	
	//회원 정보 조회 기능 
	UserVO selectOne(String account);
	
	//회원 탈퇴 기능 
	void delete(String account);
	
	//자동 로그인 쿠키값 db 저장 처리
	//sql > update 문으로 작성
	void keepLogin(Map<String, Object> datas);
	
	//세션아이디를 통한 회원 정보조회기능
	/*
	  - 자동로그인을 하고 싶다는 사람한테 뭘 만들어 줬죠? > 쿠키(세션Id)
	  그리고 나서 그 사람이 나중에 우리사이트에 다시 방문했다고 칩시다. 
	  단연히 우리 서버에 요청을 보낼 거고 , 요청과 함게 쿠키도 같이 전달이 되겠죠? 
	  우리는 쿠키 안에 들어있는 세션 id로 회원 정보를 조회해서 마치 이사람이 로그인 중인 것 처럼 
	  세션 데이터를 만들어 주자는 겁니다. (login이라는 세션 데이터 -> 로그인 중이라는 징표)
	 */
	UserVO getUserWithSessionId(String sessionId);
	
	
}

 

매퍼 작성 후 xml을 작성해준다. 

 

	<!-- 자동로그인을 희망하는 경우 쿠키값(세션아이디)와 유효시간을 갱신 -->
	<update id="keepLogin">
		update mvc_user set session_id = #{sessionId}, LIMIT_TIME = #{limitTime}
		where account = #{account}
	</update>
    
         <select id="selectOne" resultMap="UserMap">
		SELECT * FROM mvc_user
		WHERE account=#{account}
	</select>

 

 

이제 세션이 얻어 졌으니 로그인 유지 작업을 해보자!

 


세션의 활용 로그인 유지 HTML,JS 작업😀

 

  <c:if test="${login != null}">
	           <li class="nav-item">
	            <a class="nav-link js-scroll-trigger" href="#">My Page</a>
	          </li>
	           <li class="nav-item">
	            <a class="nav-link js-scroll-trigger" href="<c:url value='/user/logout'/>" onclick="confirm('로그아웃 하시겠음?')">LOGOUT</a>
	          </li>
	          
           </c:if>
          
          <c:if test="${login == null}">
	           <li class="nav-item">
	            <a class="nav-link js-scroll-trigger" data-toggle="modal" data-target="#log-in">LOGIN</a>
	          </li>
  </c:if>

 

jstl을 활용하여 로그인 전에는 로그인 버튼이 생성되게

로그인 후에는 로그아웃 및 마이페이지가 생성  되게 하였다. 

 

 


마무리 확인 작업 😁

🙏

🙏

🙏

🙏

🙏

🙏

🙏

자!! 기도 메타 한번 가고

 

로그인 후의 navbar 화면
로그인 하기 전의 화면 

 

이렇게 세션 적용은 완료되었다. !

 

 

chk 1 && chk2 가 모두 true일 경우
둘중 하나가 false인 경우 로그인이 안된다.
모두 false일 경우

반응형

+ Recent posts