9. 판매자-구매자 채팅 기능(Chat Application) (3) 완결 - MVC 코드 읽기 (2) & WIL(What I Learned) 정리

2021. 2. 14. 23:51프로젝트/Salle(살래) 중고거래 웹

728x90

지난 글에서 DB 연결방법과 설계를 알아보았고 상품정보 화면(ProductInfo)에서 채팅을 시작하는 REST 방식의 MVC 코드를 봤습니다. 

 

이어서  이번 글은 채팅 리스트 페이지에서 채팅방으로 유입될 때 코드를 살펴보겠습니다. 또한 ChatApplication을 코딩하면서 배우게 된 프로그래밍 지식들을 정리한 WIL(What I Learned)를 끝으로 3부작에 걸쳐 작성한 채팅기능 글을 마무리 하겠습니다.

▶ Github

Salle 프로젝트 코드 Github(진행중)

▶ Chat Application 글 구성

1부: Chat Application 설계, 후기 & WebSocket 설명, Config 설정하기

2부: DB 설계 & 상품 페이지(productInfo)에서 채팅 시작하기

3부:  채팅 리스트(chatList) 만들기 + WIL(What I learned) 정리

 

 

Ⅰ. 채팅 리스트 만들기


 

로그인 화면 Nav bar에 새로운 채팅방 알림이 숫자로 표기되고 클릭 시 채팅 리스트 화면으로 이동합니다. 참여하는 채팅방들이 보여지고 새 메세지 알림이 있는 채팅방은 표시됩니다. 

 

구현에는 페이지가 바뀌지 않아도 새로운 알림을 읽어줄 수 있도록 비동기인 jQuery $.ajax()를 사용했습니다. JSON 타입은 프런트, 백엔드끼리 주고 받기 편이성을 지니기 때문에 이를 이용해 정보를 전달하고 리턴해주었습니다.


 1) 코드

 먼저 채팅 리스트 HTTP request가 오면 Controller에서 REST 방식으로 처리해줍니다. 이 때 View를 띄워주기만 할 뿐 정보는 ajax를 통해 전송/전달됩니다. 

 - chatList JSP 파일

화면에 출력하는 모든 데이터는 Ajax가 Controller로 부터 리턴 받아 jQuery append() 메서드를 활용해 추가하고자 하는 태그 내에 출력해줬습니다. 앞서 말한 것처럼 데이터 타입은 JSON 타입을 이용했고 View와 Controller(프런트와 백)상호 용이성을 지닌 타입이기 때문에 사용했습니다. 

 

Javascript에서 JSON 관련 메서드 두 가지를 자주 볼 수 있습니다. JSON.parse()JSON.stringify() 메서드인데요, 반대되는 성질을 가진 두 메서드는 parsing(분해 또는 해석해서 재결합하다)와 stringify(대략 문자열로 만들다)로 직역할 수 있습니다. 따라서 parse()는 JSON 형식으로 된 String 타입을 Javascript Object으로 conver 시켜주는 메서드 입니다. 반대로 stringify()는 JSON 형식으로 된 Object를 string으로 나타내주며 출력하거나 프런트(View, JSP, HTML)에서 백엔드(서버, Controller)로 전송할 때 주로 사용합니다. 

 

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>ChatList</title>
<link rel="stylesheet" href="/resources/css/chatList.css">
</head>
<body>
 
<%@include file="home.jsp" %>
 
    <div class="wrapper">
    
    </div>
     
     <script type="text/javascript">
     
     //home.jsp에 있는 email 정보를 받아줌
         var email = document.getElementById('emailInput').value;
     //html(JSP)파일이 로드되면 바로 initialize() 메서드를 실행시킴
        $(document).ready(initialize());
        
        function initialize() {
            getChatList();
            getUnreadMessageInfo();
            unreadAlertInfinite();
        }
         
        //async(비동기)로 일정간격 업뎃되지 않아도 되는 img 파일을 불러옴
        function getChatList() {
            console.log("getChatList inprocess");
            $.ajax({
                url: "/chatList/ajax",
                type: "POST",
                data: JSON.stringify({
                    email: email
                }),
                contentType: "application/json",
                //전달을 성공했을때 Controller로부터 data를 return 받아 처리해주는 메서드    
                success: function(data) {
                    
                     var parsed = JSON.parse(data);
                     var length = parsed.chatList.length;
                     for (var i = 0; i < length; i++) {
                         //채팅방 갯수만큼 반복문을 돌면서 채팅방 틀(div, img 태그)를 만들어줌 
                         addChatDivImg(i, parsed.chatList[i].pr_img_1);
                     }
                }
            });
        }
        
         //async(비동기) 방식으로 일정간격 업데이트 되어야 하는 정보들(메세지 알림기능) 
         function getUnreadMessageInfo() {
 
                 $.ajax({
                     url:"/chatUnreadMessageInfo/ajax",
                     type: "POST",
                     data: JSON.stringify({
                         email: email
                     }),
                     contentType: "application/json",
                     success: function(data) {
                         var parsed = JSON.parse(data);
                         var length = parsed.chatList.length;
                         
                         for (var i = 0; i < length; i++) {
                            $('.wrapSellerTitle' + i).html('');
                             addChatList(parsed.chatList[i].pr_id, parsed.chatList[i].buyerId, parsed.chatList[i].senderName, parsed.chatList[i].pr_title, parsed.chatList[i].messageUnread, i);
                         }
                     }
             });
         }
         
         
         //1000milliseconds(==1초) 간격으로 getUnreadMessageInfo()를 실행시키는 반복 메서드
         function unreadAlertInfinite() {
             setInterval(() => {
                 getUnreadMessageInfo();                
            }, 1000);
         }
         
         //일정 간격으로 업데이트된 데이터를 화면에 출력하는 메서드 됨
         function addChatList(pr_id, buyerId, senderName, pr_title, messageUnread, idx) {
 
             var str =
             '<a href="/chatRoom/' + 
             pr_id +
             '/' + 
             buyerId + 
             '">' +
             '<h3><span id="sellerName">' + 
             senderName +
             '&nbsp;</span>' +
             '<span id="title">' + 
             pr_title + 
             '</span><span id="message">' + 
             messageUnread+'</span></h3></div></a>';
             
             //HTML화면의 <div class="wrapSellerTitle0,1,...etc"> 하위에 str 변수를 추가해준다.                  
              $('.wrapSellerTitle' + idx).append(str);
         } 
         
         //페이지가 로드되는 시점 한 번만 출력하면 되는 div, img를 출력하는 메서드
         function addChatDivImg(idx, img) {
                 $(document.body).append('<div class= chatMessageInfo' + idx + '><div class="wrapPr_img"><img class="pr_img" src="' + img + '"></div><div class="wrapSellerTitle' +
                         idx +
                         '"></div></div>');
         }
         
 
         
     </script>
</body>
</html>
cs

 


- chatUnreadMessageInfo ajax 요청을 처리해주는 Controller  

/chatList/ajax 코드도 이와 다르지 않습니다. ajax로부터 전달받은 String 타입의 값을 JSON Object로 변환해주고, 다시 View로 return 해주고 싶다면 @ResponseBody를 통해 어노테이션 설정을 해주어야 Controller가 이를 인식하고 처리해줍니다. 보통 return하지 않는 경우도 많으니까요. 

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
    @RequestMapping(value="/chatUnreadMessageInfo/ajax", method=RequestMethod.POST)
    @ResponseBody
    public String chatListUnread(@RequestBody String json) {
        //ajax가 전송한 String을 key, value로 분류하기 위해 JSON Object convert
        JSONObject jsn = new JSONObject(json);
        //JSON.get([mapped name])으로 value 추출하기
        String email = (String) jsn.get("email");
        //email에 해당되는 모든 chatRoom select 받기
        List<ChatList> chatRoomList = chatRoomService.findByEmail(email);
        //chatRoom 정보는 JSON Array에 저장됨
        JSONArray ja = new JSONArray();
        //email에 해당되는 읽지 않은 chatRoom select 받기
        List<Integer> unreadChatId = chatRoomService.getUnreadChatRoom(email);
 
         for (ChatList chatList : chatRoomList) {
            //chatRoom 정보를 JSON Object에 put 해줌, chatRoom이 반복문에서 넘어갈 때마다 객체 초기화 
             JSONObject jo = new JSONObject();
             jo.put("pr_id", chatList.getPr_id());
             jo.put("buyerId", chatList.getBuyerId());
             jo.put("pr_img_1", chatList.getPr_img_1());
             //리스트에 출력할 상대방 닉네임 확인
         if (chatList.getBuyerId().equals(email)) {
             jo.put("senderName", chatList.getSellerName());
         } else {
             jo.put("senderName", chatList.getBuyerName());
         }
         
              jo.put("pr_title", chatList.getPr_title());
         //읽지 않은 chatRoom이 0개일때
         if (unreadChatId.size() == 0) {
             jo.put("messageUnread""");
             } else {
                 //읽지 않은 chatRoomId들과 현재 chatRoomId 대조 후 처리 
                 for (int ele : unreadChatId) {
                         if (chatList.getId() == ele) {
                             jo.put("messageUnread""새 메세지");
                             break;
                         } else {
                             jo.put("messageUnread""");
                         }
                 }
            }
             ja.put(jo);
        }
         //Javascript에 parsing 할 수 있도록 JSON Object에 Array를 담아줌
         JSONObject jsnResult = new JSONObject();
         jsnResult.put("chatList", ja);
         //String으로 변환해주면 끝, 프런트<->백엔드 전달은 String으로 이루어지며 형식은 JSON을 선택했음 
         String result = jsnResult.toString();
         //View로 result를 return해줌
         return result;
    }
cs

 

 

Ⅱ. WIL(What I Learned) 정리


1) CSS

  • box-sizing - 범위 내에 padding(height, width)을 포함시켜 margin 단위 크기는 같게 만들어 준다.
  • position: absolute - 부모 element position을 relative로 해주면 부모 범위 안에서 절대값 위치가 할당해줄 수 있다.

2) Java

  • URL 한글 깨짐 해결 - URLEncoder.encode(String, "UTF-8") 메서드를 사용해 추가하면 된다.

  • Facade Design pattern - 시스템, 클래스, 라이브러리나 프레임워크를 단순화 시키고자 인터페이스를 사용하는 디자인 패턴

  • CopyOnWriteArrayList - 여러 개의 스레드에서 접근해도 기본 동작이(add, set..etc) 구현될 수 있도록 array를 복사해주는 ArrayList의 한 종류  

  • SLF4J(Simple Logging Facade for Java) - 

    • Logging을 사용하는 이유(링크)

      • Log는 IDE에서 Application을 작동시키면 console에 출력되는 글자들이다.

      • Error나 Process가 성공적으로 발생될 때 Log를 커스텀 해줌으로써 검증이 가능하다. (ex. JS console.log())

  • implements & extends

    • implements(interface)는 2개 이상을 하나의 클래스에 적용할 수 있지만 extends(base class)는 오직 하나의 부모 클래스만 가질 수 있다.

  • Serialization - Object를 Stream 문자로 변경해 네트워크를 통해 전송하거나 DB 스토리지에 저장하는 방법

  • Double Colon(::)

    • <ClassName>::<Method>는 람다 표현식으로 특정 클래스의 메서드를 호출하는 코드이다. 

    • 람다 표현식은 파라미터 변수를 입력해 값을 리턴하는 축약된 형식의 코드이다.

  • Arrow () -> {}

    • (parameter) -> {method body}
  • numerical literal(00_000)
    • 코드 가독성을 높이기 위해 언더바(_)를 천단위 마다 붙여줄 수 있는 Java 기능
  • Thread
    • Main 클래스같이 process를 가진 application을 말한다. Multi-thread는 2개 이상의 스레드들이 동시에 실행되는 프로그램이다.
  • A instance of B
    • A가 B(cclass or interface)의 인스턴스일 때 boolean 타입을 return해준다.

3) JavaScript

  • confirm(message) - 클라이언트가 yes or no를 선택하도록 하는 브라우저 내 팝업창을 띄우는 메서드이다.


4) HTTP

  • Origin - request가 발현되는 도메인

  • Header - HTTP request, response가 전달될 때 추가 정보가 담긴 부분. date, origin domain, status, etc가 포함된다.


5) Spring

  • @Postconstruct - 클래스의 모든 bean properties(e.g @Autowired)가 호출되고 난 다음에 실행되는 어노테이션

  • @PreDestroy - bean properties가 제거되기 전 한번만 호출되는 어노테이션

  • @Payload - 전달된 데이터 중 주 목적이 되는 메시지(e.g message content in Websocket Message)

  • SimpleMessageHeaderAccesor

    • message headers와 함께 표준화된 접근경로, 데이터를 프로토콜로 전송하는 클래스


6) jQuery

  • $.ajax(url, type, data, success, contentType...etc)

    • 비동기 방식으로 HTTP POST를 통해 View와 Controller가 데이터 통신하는 jQuery 메서드