웹페이지로 만든 sns에서 메시지 기능 구현하기

작성: 2021.02.27

수정: 2021.02.27

읽는시간: 00 분

Programming/Java

반응형

playddit 메시지 기능 구현 설명

저희 팀에서 중간 발표로 준비중인 playddit 프로젝트 에서는 메시지 기능을 선보일 예정입니다.
학원 학생들이 사용하는 SNS라는 프로젝트 주제에서, 구현하고자 하는 기능들을 분류할때
'회원가입 & 로그인', '피드', '공지사항' 에 이어 '메시지, 알림' 또한 필수 기능으로 분류했습니다.

채팅을 구현한다고 하니, 소켓을 이용한 실시간 서버에서의 채팅으로 생각하는 분들이 많았는데요, 데이터 베이스를 이용한 '쪽지' 개념으로 접근하였습니다.

1

개인 간의 메시지와 단체 메시지는 비슷하지만 다르게 구현을 했습니다.
개인 메시지에서 메시지 테이블은 수신자,송신자가 모두 users 테이블의 user_id를 FK로 받아옵니다.

public class MessageVO {
    private String receiver;
    private String sender;
    private String content;
    private String sentdate;
    public String getReceiver() {
        return receiver;
    }
    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }
    public String getSender() {
        return sender;
    }
    public void setSender(String sender) {
        this.sender = sender;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getSentdate() {
        return sentdate;
    }
    public void setSentdate(String sentdate) {
        this.sentdate = sentdate;
    }

}

vo 객체는 위와 같이 만들어 보았습니다. receiver를 개인으로 하면 개인간의 메시지에서 사용할 수 있고, receiver에 특정 그룹의 아이디를 넣으면 그룹채팅으로 같은 객체를 재활용 할 수 있습니다.

    <select id="getClassMessage" parameterClass="String" resultClass="MessageVO">
        select class_msg.class_id as receiver, user_nickname as sender, msg_cont as content, msg_senddate as sentdate
        from class_msg, users
        where class_msg.class_id = #class_id#
        and msg_sender = user_id
        order by class_msg.cmsg_no
    </select>

    <select id="getMessage" parameterClass="map" resultClass="MessageVO">
        select b.user_nickname as receiver, c.user_nickname as sender, a.msg_content as content, to_char(a.msg_senddate,'yyyy-mm-dd hh24:mi:ss') as sentdate
        from message a, users b, users c
        where ((msg_sender = #user# and msg_target_id = #audience#)
        or (msg_target_id = #user# and msg_sender = #audience#))
        and a.msg_target_id = b.user_id
        and a.msg_sender = c.user_id
        order by msg_no
    </select>

각각 그룹메시지(학급)과 개인간의 메시지를 받아오는 쿼리 입니다.쿼리는 다르지만 쿼리에서 불러오는 alias 를 통일 해서 일단 쿼리를 불러 온 뒤로는 둘 다 똑같이 처리를 할 수 있습니다.

    <select id="getAudiences" parameterClass="String" resultClass="UsersVO">
        select audience as user_nickname, (select user_id from users where user_nickname=audience) as user_id
        from
        (select case when (select user_nickname
                            from users
                            where user_id=#user_id#) = b.user_nickname then c.user_nickname
                    else b.user_nickname end as audience, a.msg_content, a.msg_senddate
        from message a, users b, users c
        where (msg_sender = #user_id# or msg_target_id = #user_id#)
        and a.msg_target_id = b.user_id
        and a.msg_sender = c.user_id
        order by msg_no desc)
        group by audience
    </select>

위의 쿼리는 user_id ( 접속중인 유저의 접속 정보를 Session에서 받아 처리합니다) 에 해당하는 유저가 대화했던 유저들의 닉네임 목록을 불러옵니다. 가장 최근에 대화했던 유저가 위로 오고 최근 대화 순으로 아래로 내려오는 목록을 구현하고 싶었는데, DB 쪽 개념이 너무 약해서 도저히 쿼리를 만들어 낼 수가 없었습니다. 가장 최근 대화내용도 미리 보기로 특정 크기만큼 출력을 해 주고 싶었는데 너무 아쉽습니다. 쿼리를 짜낼 수 있게 되면 최대한 빨리 고치고 싶은 쿼리입니다.

    public String process(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 1. 세션에서 프로필 정보 받아와서 유저 아이디 변수에 저장하기.
        HttpSession session = request.getSession();
        String profileJson = (String) session.getAttribute("profile");
        Gson gson = new Gson();
        ProfileVO profile = gson.fromJson(profileJson, ProfileVO.class);
        String user_id = profile.getUser_id();

        // 2. 해당 유저 아이디 기준으로 서비스에서 대화주고받은 사람들 목록 받아오기 
        IMessageService service = MessageServiceImpl.getService();
        List<UsersVO> list = service.getAudiences(user_id);
        String listJson = new Gson().toJson(list);

        // 3. 해당 유저들의 UsersVO가 담긴 List를 json 형태로 jsp 파일에 돌려보내기.
        request.setAttribute("list", listJson);


        return "/message/GetAudiences.jsp";

    }

세션(profile)에서 접속중인 유저의 user_id를 받아 온 뒤, 그걸 parameter로 이용해 최근에 대화했던 유저들의 목록을 UsersVO에 담아 List로 받아옵니다.

jsp로 보내지 않고 ajax 에서 바로 받아 처리를 할 수 있는데, 해당 서블릿을 작성할때는 아직 그걸 할 줄 몰라서 jsp로 보내도록 만들었었네요.

ajax로 바로 보내려면

        response.setContentType("text/html; charset=UTF-8");
        response.getWriter().write(listJson);

        return null;

아래 setAttribute 부분을 없애고 바로 위와같이 utf-8로 write 해서 jsp를 거치지 않고 ajax로 바로 보내주면 됩니다.

간략한 로직을 설명하자면

  1. 메시지를 주고 받은 사람들의 목록을 불러옵니다. (버튼으로 만들어 append 해줍니다)
  2. 해당 사람들을 클릭하면 해당 유저와의 대화 내용을 불러와 출력합니다. (append)
  3. 채팅창에 쓰고싶은 말을 쓰고 '전송'버튼을 누르면 접속중인 유저를 송신자로, 해당 유저를 수신자로 하는 메시지 객체를 만들어 DB에 저장합니다.
  4. 새로운 메시지를 보내며 해당 메시지 목록을 새로 새로고침 해줍니다.

'전송' 을 하거나 새로고침을 따로 눌러주지 않으면 실시간으로 대화하는 느낌이 덜하기 때문에, 나중에 몇초마다 대화내용 변경을 체크 해서, 변경사항이 있을 때마다 대화 내용을 새로 불러오도록 구현 할 예정입니다.

그렇게 임시로 구현해둔 모습은 아래와 같습니다.

img

세 개의 버튼을 만들어 두었습니다. 세션의 접속정보를 기반으로 작동하기 때문에 로그인 정보를 받기 위해 로그인 화면으로 넘어가는 a 링크도 만들어 두었습니다.

img

메시지를 주고 받았던 회원들의 닉네임을 value로 하는 버튼들을 만들었습니다.

img

append된 버튼에는 audience 라는 attribute에 각 회원의 아이디를 숨겨서 보냅니다.

img

그래서 해당 버튼을 누르면, 해당 회원과의 대화 내용을 불러옵니다. '전송'기능을 위해서 해당 유저의 아이디 값이 필요해 disabled 해둔 input 타입에 아이디 값을 보냅니다. 테스트를 위해 눈에 보이도록 만들어 두었는데, hidden 처리 해야 합니다.
새로고침을 누르거나 전송 버튼을 누를때 위 아이디를 parameter로 각각 메시지 불러오는 함수를 호출하거나, 해당 유저의 아이디와 작성한 메시지 내용을 파라미터로 보내 메시지 객체를 생성하고 insert 쿼리를 수행하는 함수를 호출합니다.

학급채팅도 위와 크게 다르지 않게 구현하지만, 더 쉽습니다.
학급은 각 유저의 세션에 저장된 profile에 담겨 있으니, 일단 서블릿단으로 넘어 가서 그 안에서 필요한 파라미터들을 보내 메시지를 받아올 수 있으며, 전송 또한 보내고자 하는 내용만 서버로 보내 해결할 수 있습니다.

메시지를 보낼때 알림 또한 생성하도록 구현했는데요, 메시지를 여러개 보내도 최근의 알림만 작동 하도록 메시지를 보내기 전에 똑같은 내용의 알림을 삭제 한 뒤에 새로운 알림을 추가하도록 구현 했습니다.


DB만으로 대화하는 것 처럼 구현하는게 머리속에선 가능할꺼라 생각이 들었었는데, 막상 이렇게 구현하며 심지어 더 기능 추가가 가능할것으로 예상되는걸 보니 ( 상대방이 메시지를 입력중인 것을 실시간으로 확인한다던가, 내가 보낸 메시지를 확인했는지 여부 등을 추가 가능) 실제 서비스중인 메신저들도 혹시 DB를 이용해서 구현된건 아닐까? 라는 생각이 들었습니다.

반응형