22.06.28 이미지 다운로드, 좋아요
사용자가 이미지 다운로드 버튼을 누르면 다운로드 진행.
<div class="title">
<p>` + list[i].writer + `</p>
<small>` + timeStamp(list[i].regdate) + `</small>
<a href="<c:url value='/snsBoard/download?fileLoca=` +
list[i].fileloca + `&fileName=` + list[i].filename + `'/>">이미지 다운로드</a>
</div>
파일명에 한글이 포함되어 있을때 브라우저마다 설정을 다르게 해주어야
한글 처리가 제대로 진행된다.
응답하는 본문을 브라우저가 어떻게 표시해야 할 지 알려주는 헤더 정보를 추가합니다.
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
응답 헤더 파일에 Content-Disposition을 attachment로 준다면
브라우저 내에서 다운로드로 처리합니다.
filename= 파일명.확장자로 전송합니다.
<script>
//이미지 다운로드
@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\\jwons\\OneDrive\\바탕 화면\\upload\\" + fileLoca + "\\" + fileName);
ResponseEntity<byte[]> result = null;
HttpHeaders header = new HttpHeaders();
header.add("Content-Disposition", "attachment; filename=" + fileName); //다운로드
try {
result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), header, HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
</script>
CREATE TABLE snslike(
bno NUMBER(10,0) NOT NULL,
userId VARCHAR2(50) NOT NULL,
lno NUMBER PRIMARY KEY
);
CREATE SEQUENCE snslike_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 10000
NOCYCLE
NOCACHE;
public class SnsLikeVO {
private int bno;
private String userId;
private int lno;
}
좋아요 버튼에 id와 bno 추가.
<div class="like-inner">
<!--좋아요-->
<img src="../resources/img/icon.jpg"> <span>522</span>
</div>
<div class="link-inner">
<a id="likeBtn" href="` + list[i].bno + `"><i class="glyphicon glyphicon-thumbs-up"></i>좋아요</a>
<a id="comment" href="` + list[i].bno + `"><i class="glyphicon glyphicon-comment"></i>댓글달기</a>
<a id="delBtn" href="` + list[i].bno + `"><i class="glyphicon glyphicon-remove"></i>삭제하기</a>
</div>
jQuery
<script>
//좋아요 기능 구현
$('#contentDiv').on('click', '#likeBtn', function(e) {
e.preventDefault();
console.log('좋아요 버튼이 클릭됨!');
console.log(e.target);
if(e.target.matches('img')) { //이미지클릭시 좋아요 글씨 클릭한 것과 같은 이벤트 발동
$('#likeBtn').click();
return;
}
const bno = $(this).attr('href');
const id = '${login.userId}'; //현재 로그인 중인 사용자의 아이디
if(id === '') {
alert('로그인이 필요합니다.');
return;
}
$.ajax({
type: 'post',
url: '<c:url value="/snsBoard/like" />',
contentType: 'application/json',
data: JSON.stringify({
'bno': bno,
'userId': id
}),
success: function(result) {
console.log('result: ' + result);
if(result === 'like') {
e.target.firstChild.setAttribute('src', '${pageContext.request.contextPath}/img/like2.png');
e.target.style.color = 'blue';
const $cnt = e.target.parentNode.previousElementSibling.children[1];
console.log($cnt);
$cnt.textContent = Number($cnt.textContent) + 1;
} else {
e.target.firstChild.setAttribute('src', '${pageContext.request.contextPath}/img/like1.png');
e.target.style.color = 'black';
const $cnt = e.target.parentNode.previousElementSibling.children[1];
console.log($cnt);
$cnt.textContent = Number($cnt.textContent) - 1;
}
},
error: function() {
alert('좋아요 진행 에러!');
}
}); //end ajax
});
</script>
<script>
//좋아요 버튼 클릭 처리
@PostMapping("/like")
@ResponseBody
public String likeConfirm(@RequestBody SnsLikeVO vo) {
System.out.println(vo.getBno());
System.out.println(vo.getUserId());
int result = service.searchLike(vo);
if(result == 0) { //중복안됨 좋아요 누르기
service.createLike(vo);
return "like";
} else {
service.deleteLike(vo); //중복됨 좋아요 취소
return "delete";
}
}
</script>
메서드 추가
//좋아요 검색
int searchLike(SnsLikeVO vo);
//좋아요 등록
void createLike(SnsLikeVO vo);
//좋아요 삭제
void deleteLike(SnsLikeVO vo);
<!-- 좋아요 검색 -->
<select id="searchLike" resultType="int">
SELECT COUNT(*) FROM snslike
WHERE bno=#{bno} AND userId=#{userId}
</select>
<!-- 좋아요 등록 -->
<insert id="createLike">
INSERT INTO snslike
(bno, userId, lno)
VALUES(#{bno}, #{userId}, snslike_seq.NEXTVAL)
</insert>
<!-- 좋아요 삭제 -->
<delete id="deleteLike">
DELETE FROM snslike
WHERE bno=#{bno} AND userId=#{userId}
</delete>
메서드 추가
@Override
public int searchLike(SnsLikeVO vo) {
return mapper.searchLike(vo);
}
@Override
public void createLike(SnsLikeVO vo) {
mapper.createLike(vo);
}
@Override
public void deleteLike(SnsLikeVO vo) {
mapper.deleteLike(vo);
}
getList() 호출 시 좋아요 횟수 포함시킨 상태로 불러와야한다.
좋아요 개수가 몇 개인지를 알려주는 변수 추가
//좋아요 개수가 몇 개인지를 알려주는 변수 추가
private int likeCnt;
메서드 추가
//글 목록 요청이 들어왔을 때 게시글마다 좋아요 개수 체크
int likeCnt(int bno);
//현재 로그인 중인 사용자가 글 목록으로 왔을 때 좋아요 한 게시물 체크
List<Integer> listLike(String userId);
<!-- 게시물 당 좋아요 개수 확인 -->
<select id="likecnt" resultType="int">
SELECT COUNT(*) FROM snslike
WHERE bno=#{bno}
</select>
<!-- 리스트 진입 시 좋아요 체크 -->
<select id="listLike" resultType="int">
SELECT bno FROM snslike
WHERE userId=#{userId}
</select>
@Override
public int likeCnt(int bno) {
return mapper.likeCnt(bno);
}
@Override
public List<Integer> listLike(String userId) {
return mapper.listLike(userId);
}
<script>
//비동기 통신 후 가져올 글 목록
@GetMapping("/getList")
@ResponseBody
public List<SnsBoardVO> getList(PageVO paging){
paging.setCpp(3);
List<SnsBoardVO> list = service.getList(paging);
for(SnsBoardVO svo : list) {
svo.setLikeCnt(service.likeCnt(svo.getBno()));
}
return list;
}
</script>
//리스트 작업
let str = '';
let page = 1;
//지금 게시판에 들어온 회원의 좋아요 게시물 목록을 받아오는 함수
function getListLike() {
console.log('먼저 실행되어야 합니다.');
const userId = '${login.userId}';
console.log(userId);
if(userId != ''){
$.ajax({
type: 'post',
url: '<c:url value="/snsBoard/listLike" />',
data: userId,
contentType: 'application/json',
success: function(result) {
console.log('result: ' + result); //게시글 번호들
}
}); //end ajax
}
}
//회원이 글 목록 진입시 좋아요 게시물 수 체크
@PostMapping("/listLike")
@ResponseBody
public List<Integer> listLike(@RequestBody String userId){
System.out.println("listLike id: " + userId );
return service.listLike(userId);
}
getList()가 실행되기 전에 getlistLike()가 먼저 실행 되어야하는데
둘다 비동기방식이라 실행 순서가 보장 되지않는다.
promise()를 사용.
<script>
//리스트 작업
let str = '';
let page = 1;
getListLike(true).done(getList);
//getListLike가 끝난후에 getList를 실행하겠다.
//지금 게시판에 들어온 회원의 좋아요 게시물 목록을 받아오는 함수.
function getListLike(isReset) {
let deferred = $.Deferred();
console.log('먼저 실행되어야 합니다!');
const userId = '${login.userId}';
console.log(userId);
if(userId !== '') {
$.ajax({
type: 'post',
url: '<c:url value="/snsBoard/listLike" />',
data: userId,
contentType: 'application/json',
success: function(result) {
console.log('result: ' + result); //게시글 번호들
if(isReset) {
deferred.resolve(result, page, true);
} else {
deferred.resolve(result, page, false);
}
}
}); //end ajax
} else {
if(isReset) {
deferred.resolve(null, page, true);
} else {
deferred.resolve(null, page, false);
}
}
return deferred.promise();
}
</script>
getList( )의 str 작성란에 조건에 따라서 좋아요 유지여부 판단.
<div class="like-inner">
<!--좋아요-->
<img src="../resources/img/icon.jpg"> <span>` + list[i].likeCnt + `</span>
</div>
<div class="link-inner">`;
if(data != null) { //좋아요를 누른게 있다
if(data.includes(list[i].bno)) {
str += `<a id="likeBtn" href="` + list[i].bno + `"><img src="${pageContext.request.contextPath}/img/like2.png" width="20px" height="20px"> 좋아요</a>`;
} else {
str += `<a id="likeBtn" href="` + list[i].bno + `"><img src="${pageContext.request.contextPath}/img/like1.png" width="20px" height="20px"> 좋아요</a>`;
}
} else {
str += `<a id="likeBtn" href="` + list[i].bno + `"><img src="${pageContext.request.contextPath}/img/like1.png" width="20px" height="20px"> 좋아요</a>`
}
str += `
<a id="comment" href="` + list[i].bno + `"><i class="glyphicon glyphicon-comment"></i>댓글달기</a>
<a id="delBtn" href="` + list[i].bno + `"><i class="glyphicon glyphicon-remove"></i>삭제하기</a>
</div>
만약 9번글에 좋아요를 누르 사용자 들이 있는데 board테이블에서 9번글이
삭제된다면 좋아요 테이블에 내용도 삭제되어야하기 때문에
제약조건으로 두 테이블을 연결 시켜 주자.
ON DELETE CASCADE
외래 키(FK)를 참조할 때, 참조하는 데이터가 삭제되는 경우에
참조하고 있는 데이터도 함께 삭제를 진행하겠다.
ALTER TABLE snslike ADD FOREIGN KEY(bno)
REFERENCES snsboard(bno)
ON DELETE CASCADE;
ALTER TABLE snslike ADD FOREIGN KEY(userId)
REFERENCES users(userId)
ON DELETE CASCADE;