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
실제 로컬에 사용자가 올린 파일을 저장하기 위해 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>
<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
ResponseEntity<>(응답 객체에 담을 내용, 헤더에 담을 내용, 상태 메세지)
이미지 태그로 서버에 요청을 보내 로컬에 저장되어 있는 이미지를 가져오는 로직 구현
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);
// 로컬에 저장되어 있는 이미지를 불러옴
}
);
}); // 상세보기 처리 끝
비동기 방식으로 글 상세보기 처리
// 비동기 통신
@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
}); // 삭제 처리 끝
삭제 권한 유무를 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하면 안됨 (누적해서 불러와야 하기 때문)
}
}); // 무한 스크롤 끝