개인 프로젝트/프로젝트

연애대작전_SemiProject_기능정리_즐겨찾기

Leo.K 2022. 6. 11. 20:31

오늘은 필자가 구현해보고자 했던 기능 중에서 가장 장시간에 걸쳐 완성한 기능이다. 단순하게 생각해낸 알고리즘으로 접근을 했는데 생각보다 많은 난관에 봉착했었다. 구글링을 통해 검색을 해보아도 필자의 검색 능력이 부족한 탓인지,, 관련된 내용을 찾지 못했다. 기능을 구현하기에만 앞서서 효율성은 하나도 고려하지 않았다는 단점이 있다. 일단 스스로 구현한 기능을 정리하고 차후에 더 나은 방법을 발견하게 되면 추가로 수정해서 정리하도록 하겠다. 혹시라도 이글을 읽고 피드백을 주신다면 감사히 받겠다. 

프로젝트의 모든 내용을 설명할 수는 없기 때문에 기본적인 내용만 설명해주고 추가한 기능에 대해서 정리하고자 한다. 

[ 기본 상황 설명]

  • 메인 페이지에서 코스 추천을 누르면 타인이 등록한 데이트 코스들이 조회수 순서대로 목록이 나타난다. 
  • 로그인 한 경우에는 "나의 리스트"에 빈 별이 기본적으로 표시되어 있다. 
  • 로그인을 하지 않은 일반 방문자에게는 빈 별이 표시되어 있지 않다. 
  • 로그인 후 별을 누르면 괜찮은 게시물을 스크랩 하듯이 해당 게시물을 나의 리스트에 추가할 수 있다. 
  • 별을 누르면 별이 채워진 별로 바뀌게 되는데, 이 때 별을 다시 누르면 빈별로 toggle되면서 나의 리스트에서 삭제된다.
  • 추가와 삭제과정은 로그인한 사용자가 별을 누르면 현재 세션에 있는 로그인 정보를 이용해서 사용자의 닉네임을 사용자가 클릭한 별의 게시물 번호를 조건문으로 사용해서 테이블에 저장했다. 이때 닉네임이 저장되는 컬럼은 기본값으로 '없음'이 저장되어 있다.
  • 나의 리스트에서는 테이블에서 닉네임이 현재 로그인한 사용자의 닉네임과 같은 레코드만을 출력한다.

 

[ 문제점 & 해결 고민 ]

  • 코스 추천 페이지와 나의 리스트 페이지가 다르기 때문에 코스 추천 페이지에서 별을 눌러 추가를 한 후, 확인을 하기 위해 나의 리스트로 가면 리스트가 정확하게 추가되고 조회가 된다. 이때 다시 버튼을 눌러서 코스 추천으로 돌아가면 처음에 사용자가 눌렀던 별이 채워진 별이어야 하는데 빈별이 되어있다. 
  • 즉, 사용자가 선택한 별에 대한 정보가 페이지를 이동하면서 공유되지 않았다는 뜻이다 -> 세션 트래킹이 이뤄지지 않았다. 
  • 이를 해결하기 위해 사용자가 누른 별의 게시물 번호를 세션에 넣어서 공유할 까 했는데, 학원 선생님께서 쿠키와는 다르게 세션 정보는 서버단에서 저장&관리하기 때문에 최소한의 정보만을 담아야한다고, 서버를 이용하는 사용자가 많아지게 되면 접속하는 모든 사용자마다 동일한 세션을 할당해줘야 하는데 서버의 큰 부담이 되고, 심한 경우 서버가 다운되버리는 악몽같은 상황이 펼쳐진다고 했다. 
  • 그래서 조금은 번거로운 과정일지는 몰라도 개발자의 코드 작성 편의성보다 사용자의 사용 편의성이 우대되어야 한다는 모토를 지키기 위해서 다음의 과정을 생각해보았다. 

 

 [ 즐겨찾기 세션 트래킹 알고리즘 ] 

  1. 전체 데이트 코스를 조회해서 출력할 때는 JSTL의 forEach문을 사용했는데, 별을 출력하는 태그들은 star라는 같은 클래스를 주어서 공통적으로 css를 적용시키고, id값으로는 해당 게시글의 idx로 하였다. (EX> 1번게시글의 id="1")
  2. 가장 중요한 점은 초기값을 무조건 빈별로만 할 것이 아니라 실시간으로 값을 가져와서 바꾸어 주어야 한다고 생각하여 ajax통신을 활용했다. 
      1. 사용자가 별을 눌러서 추가하고, 페이지를 이동했다가 돌아오는 과정에서 별이 채워진 상태가 유지가 되지 않는 것이 문제였다. 돌아온 시점에서 별은 원하는 결과가 아니지만, 별을 누름으로써 테이블의 nickname값을 현재 로그인한 사용자의 nickname으로 수정했기 때문에 테이블에는 원하는 결과가 있지 않은가? 
      2. 따라서 브라우저 내의 모든 요소가 배치된 경우 -> $(document.ready(function(){})
      3. 현재 로그인한 사용자의 nickname을 json타입으로 서버로 전송했다.  
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $(document).ready(function (){
        $.ajax({
            type:'GET',
            url:'check_select.do',
            data:{'nickname':'${user.nickname}'},
            dataType:'json',
            success:function(res_data){
     
            },
            error:function(err){
                alert(err.responseText);
            }
        });
    });
    cs

 

3. [ 사용자의 닉네임을 파라미터로 수신하는 서블릿 ]

파라미터를 수신하고 해당 파라미터와 동일한 닉네임을 가진 레코드의 idx번호를 list에 담아서 가져온다. 이때, 아직 사용자가 즐겨찾기를 누르지 않고, 초기 상황이라면 list는 생성은 되었지만, 값이 없는 상태로 반환이 될 것이다. 이때 주의해야 할 점은 이 리스트는 null이 아니라 비어있다라는 점이다. 실제로 값을 찍어보면 null이 아니라 []이 출력된다. 

빈배열이 전달되는 것을 방지하기 위해 방어적인 코드를 작성해 만약, 리스트의 사이즈가 0보다 큰 경우에만 전송하고, 그 외의 경우 0인경우(음수가 될 수는 없음)에는 -1값을 전송하도록 했다. 

또한 사용자가 선택한 별이 하나이지는 않기 때문에 json으로 포장을 하되 {"key": [{"key1":val1}, {"key2":val2}] } 이런식으로 value값에 배열을 넣음으로써 약간 복잡하게 구성했다. 사실 리스트에 있는 내용을 어떻게 보내야할지 몰라서 이런식으로 포장을 했는데, 더 단순화할 수 있는 방법이 있다면 추가적으로 고민해서 수정하겠다. json으로 포장해서 서블릿을 비동기로 호출한 곳으로 돌려주는 소스코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package Controller;
 
import java.io.IOException;
import java.util.List;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
 
import model.dao.FavoritesDao;
 
/**
 * Servlet implementation class servletCheckSelectAction
 */
@WebServlet("/favorites/check_select.do")
public class servletCheckSelectAction extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    /**
     * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response)
     */
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        String nickname = request.getParameter("nickname");
        
        List<Integer> list = FavoritesDao.getInstance().selectedCheck(nickname);
        //System.out.println(list.toString());
        JSONObject json = new JSONObject();
        if(list.size() > 0) {
            
            try {
//배열생성
                JSONArray jarr = new JSONArray();
                
                for(int i=0; i<list.size();i++) {
                    JSONObject arrobj = new JSONObject();//배열 내에 들어갈 json
                    
                    arrobj.put("idx", list.get(i));
                    //배열의 요소를 json형식으로 저장한다.
                    jarr.add(arrobj);
                }
                
//생성한 배열을 json에 담는다.
                json.put("idx_arr", jarr);
                
            } catch (Exception e) {
                // TODO: handle exception
            }
            
        }else {//list.size() == 0
            json.put("idx"-1);
        }
        
        String json_str = json.toJSONString();
        //System.out.println(json_str);
        response.setContentType("text/json; charset=utf-8;");
        response.getWriter().print(json_str);
        
    }
 
}
 
 
cs

 

4.  [ 자바 스크립트에서 json을 수신하기 ]

성공적으로 서버에서 데이터를 수신하게 되면 res_data에 서버에서 응답한 데이터가 담기게 된다. 이 부분에서 필자는 바보같은 실수를 해서 오랜 시간을 보냈다.. 처음에는 json에 포장할 때, 배열의 key값을 콜백함수의 인자인 res_data와 동일하게 지정하여 res_data[0] 이런식으로 배열을 읽으려고 했더니 당연히 에러가 발생했다. 

res_data = json_str인 것이고,  res_data = {"res_data": [{"key1":val1}, {"key2":val2}] }; 이런식으로 데이터가 전달된 것이다. 따라서 올바르게 하려면 res_data.res_data[0].idx 이런식으로 코드를 짰어야 정확한 접근 방법이다. 확실히 개념적인 부분이 너무 중요하다는 생각이 들었다.

만일 전달된 값이 -1이라면 아직 즐겨찾기가 없다는 내용을 alert해준다. 

배열값이 들어왔다면 들어온 값을 배열의 크기만큼 반복을 돌려서, 넘어온 인덱스 데이터와 id값이 동일한 레코드의 채워진 별로 초기화 해주었다. 이렇게 하니 어느페이지로 이동을 하더라도 돌아왔을 때, 현재 로그인 되어 있는 사용자의 닉네임 정보를 사용해서 값을 읽어와 사용했기 때문에 기존에 사용자가 나의 리스트에 추가해둔 게시글들의 별은 모두 채워진 상태로 초기화가 완료된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$(document).ready(function (){
    $.ajax({
        type:'GET',
        url:'check_select.do',
        data:{'nickname':'${user.nickname}'},
        dataType:'json',
        success:function(res_data){
            if(res_data.idx == -1){
                alert('즐찾 없음');
                return;
            }
            for(var i=0; i<res_data.idx_arr.length;i++){
                //alert(res_data.idx_arr[i].idx);
                var s = res_data.idx_arr[i].idx;
                //console.log(s);
                $("#"+s).val("★");
            }
        },
        error:function(err){
            alert(err.responseText);
        }
    });
    
    
    $(".star").click(function(){
        if($(this).val() == "☆"){
            $(this).val("★");
            $.ajax({
                type:'GET',
                url :'update_mylist.do',
                data:{'nickname':'${user.nickname}''idx':idx},
                success:function(res){
                    console.log(1);
                }
            });
            alert('즐겨찾기에 추가되었습니다.');
        }else {
            $(this).val("☆");
            $.ajax({
                type:'GET',
                url :'delete_mylist.do',
                data:{'idx':idx},
                success:function(res){
                    console.log(1);
                }
            });
            alert('즐겨찾기에서 삭제되었습니다.');
        }
    });
});
 
cs

 

[ 정리 ]

  1. 로그인 사용자의 닉네임을 비동기로 서버로 전송한다. (물론 닉네임에는 유니크 속성이 있다.)
  2. 수신한 닉네임과 DB에 저장된 닉네임이 같은 레코드 중 idx값만 list에 담아서 반환한다. 
  3. 모든 별은 '☆'으로 초기화한다. 서버로부터 수신한 데이터를 id로 가지는 별은 '★으로 초기화한다.'