상세 컨텐츠

본문 제목

[SPRING BOOT] 2521 과몰입러의 스프링부트 웹소켓 채팅 서비스 만들기(2) 화면구성과 JS

JAVA/SPRING

by ranlan 2022. 3. 15. 00:13

본문

728x90

이전 포스팅 → 2022.03.14 - [java/spring] - [SPRING BOOT] 2521 과몰입러의 스프링부트 웹소켓 채팅 서비스 만들기(1) 자바소스

 

[SPRING BOOT] 2521 과몰입러의 스프링부트 웹소켓 채팅 서비스 만들기(1) 자바소스

블로그 쓰다 한번 날아가서 2회차 쓰는 중 티스토리 넘 빡쳐 🤯 지난 주말 드라마 <스물 다섯, 스물 하나> 보다가 삘 받아서 만든 채팅 웹 서비스 백이진도 백이진이지만.. 태리야끼.. 넘 귀여워..

juran-devblog.tistory.com

내 깃허브에서 코드 보기 → https://github.com/ijo0r98/chatting-web

 

GitHub - ijo0r98/chatting-web: 2521에 빠져 만드는 스프링부트 채팅 웹

2521에 빠져 만드는 스프링부트 채팅 웹. Contribute to ijo0r98/chatting-web development by creating an account on GitHub.

github.com

 

 

컨트롤러 작성

내가 구상한 서비스의 순서는

  1. 채팅에 참여할 아이디 입력
  2. 채팅

이렇게 크게 두 과정으로 각각 과정에 필요한 화면이 필요하다.

 

📄 ChatController

@Controller 
@Slf4j
public class ChatController {

    /* 아이디 입력 화면 */
    @GetMapping("/")
    public String home() {
        return "main";
    }

    /* 채팅 화면 */
    @GetMapping("/chat")
    public String chat(@RequestParam(name = "id") String id, Model model) {
        log.info("id: " + id);
        model.addAttribute("id", id); // 아이디 입력 화면에서 입력한 아이디
        return "chat";
    }
}

아이디 입력 후 chat?id=~ 로 이동한다. 쿼리 파라미터로 입력받은 아이디를 다음 화면에 전달하여 채팅 중 사용할 수 있도록 하였다.

 

* 원래 아이디가 쿼리 파라미터로 남는게 싫어서 입력받는 부분을 PostMapping으로 하고싶었는데 PostMapping을 사용하면 그 반환값을 뷰로 처리하는게 마음대로 안되서 그냥 GetMapping으로 다시 바꿨다. 대신 스크립트로 쿼리 파라미터 지워서 보여주는 작업을 하였는데 그건 이따 써야쥐

아래는 원래 작성하였던 @PostMapping 버전

@PostMapping("/chat")
public String chatPost(@RequestBody Map<String, Object> body, HttpServletResponse response) {
    String id = body.get("id").toString();
    ...
    return "chat";
}

따로 VO 안만들고 Map으로 데이터를 받았다. 하지만 뷰 반환이 안되서 포기함.. 나중에 다시 해봐야지

 

 

화면 구성

이번에는 원래 자주 쓰던 JSP 말고 스프링부트에서 알아서 지원하는 Thymeleaf를 이용해보기로 했다.

난 프론트엔드 개발자가 아니기에.. 이번에도 화면 구성은 부트스트랩을 활용했다.. 부트스트랩 없었으면 나 혼자 웹개발 어찌했나 몰러

예전 PC 통신 느낌을 내기 위해 배경은 파란색, 글씨는 하얀색으로 하고 폰트도 바꿨는데 열심히 구글링해서 css 수정해주었당

 

전체 폰트 적용(구글 무료 폰트 이용 https://fonts.google.com/)

 

Google Fonts

Making the web more beautiful, fast, and open through great typography

fonts.google.com

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap" rel="stylesheet">
<style>
    *{
        font-family: 'Do Hyeon';
    }
</style>

 

📄 main.html

먼저 채팅에 참여하기 위해 채팅에 쓸 아이디를 입력하는 화면

입력 후 참여 버튼을 클릭하거나 엔터키를 누르면 입력한 아이디를 쿼리 파라미터에 담아 채팅 화면으로 넘어간다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>2521과몰입</title>
    <link href="css/styles.css" rel="stylesheet"/>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap" rel="stylesheet">
</head>
<style>
    *{
        font-family: 'Do Hyeon';
    }
</style>
<body>
<div class="container px-4 px-lg-5">
    <div class="row gx-4 gx-lg-5 align-items-center my-5">
        채팅에 참여할 아이디를 입력해 주세요.
        <input placeholder="아이디" id="inputId">
        <button id="btnJoin">참여</button>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
    // 참여 버튼 클릭시 화면 이동
    $('#btnJoin').on({
        click: function () {
            let id = document.getElementById('inputId').value;
            location.href = '/chat?id=' + id;
        }
    });
    
    // 엔터키 누르면 참여 버튼 클릭
    document.querySelector('#inputId').addEventListener('keypress', function (e){
        if (e.key === 'Enter') {
            $('#btnJoin').click();
        }
    });
</script>
</body>
</html>

제이쿼리 $('#') 대신 document.querySelector() 나 document.getElementById() 를 이용해 DOM 객체에 접근하려고 했다.

메인 화면에서 사용한 스크립트는 이게 전부

/

📄 chat.html

아이디 입력 후 보이는 채팅 화면

앞의 화면에서 입력한 아이디를 활용하기 위해 컨트롤러단에서 모델에 담아 전달받아 hidden input 태그로 받았다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>2521과몰입</title>
    <link href="css/styles.css" rel="stylesheet"/>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap" rel="stylesheet">
</head>
<style>
    *{
        font-family: 'Do Hyeon';
    }
</style>
<body>
<div class="container px-4 px-lg-5">
    <!-- 사용자 아이디 숨겨놓음 -->
    <input type="hidden" th:name="id" th:value="${id}" id="id"/>
    <div class="row gx-4 gx-lg-5 align-items-center my-5">
        <table id="chatTable">
            <tr>
                <th>아이디</th>
                <th>내용</th>
                <th>시간</th>
            </tr>
            <tfoot>
            <tr>
                <td><h th:text="${id} + ' : '">ID</h></td>
                <td>
                    <input id="inputMsg" type="text">
                </td>
                <td>
                    <button id="btnSubmit" onclick="send()">전송</button>
                </td>
            </tr>
            <tr>
            </tr>
            </tfoot>
        </table>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="js/chat.js"></script>
<script type="text/javascript" src="js/common.js"></script>
<script type="text/javascript">
    // url에서 파라미터 부분 지우기
    history.replaceState({}, null, location.pathname);

    // 엔터키 누르면 전송 버튼 클릭
    document.querySelector('#inputMsg').addEventListener('keypress', function (e){
        if (e.key === 'Enter') {
            $('#btnSubmit').click();
        }
    });
</script>
</body>
</html>

url에 이전 화면에서 전달받은 쿼리 파라미터(아이디)가 보이는게 마음에 안들어 스크립트로 처리해주었다.

history.replaceState({}, null, location.pathna

/chat

 

 

스크립트

요즘 jQuery는 지양하는 추세라 하니 최대한 안쓰려고 노력하였다. 엄청 불편할 줄 알았는데 생각보다 쉽당. 앞으로 이렇게 습관을 들여야할 듯!

 

📄 chat.js

채팅방 입장

//채팅창에 들어왔을 때
function onOpen(evt) {
    let data = JSON.stringify({
        id: id.value,
        msg: id.value + "님이 방을 입장하셨습니다.",
        timestamp: getTimeStamp()
    });
    websocket.send(data);
}

getTimeStamp()는 내가 작성한 함수임

 

아직 완성하진 않았지만 채팅방 퇴장

function onClose(evt) {
    let data = JSON.stringify({
        id: id.value,
        msg: id.value + "님이 방을 나가셨습니다.",
        timestamp: getTimeStamp()
    });
    websocket.send(data);
}

소켓 연결정보 준비와 숨겨진 아이디 가져오기

const websocket = new WebSocket("ws://localhost:8080/ws/chat");
websocket.onmessage = onMessage; // 해당 경우에 정의한 함수로 동작
websocket.onopen = onOpen;
websocket.onclose = onClose;

// <input type="hidden" th:name="id" th:value="${id}" id="id"/>
id = document.getElementById('id');

소켓 메시지 전송

function send() {
    let msg = document.getElementById("inputMsg"); // 입력한 메시지
    let data = JSON.stringify({ // 메시지와 아이디, 시간 정보
        id: id.value,
        msg: msg.value,
        timestamp: getTimeStamp()
    });
    websocket.send(data);
    msg.value = '';
}

아이디와 입력한 내용, 전송 시간등을 모두 보내기 위해 JSON.stringify() 를 사용해주었다.

 

소켓 메시지 수신

function onMessage(data) {
    let msg = JSON.parse(data.data);
    if(msg != null ) {
        // 상대가 보낸 메시지일 때
        if (msg['id'] != id.value) {
            $('#chatTable').append($('<tr />').append($('<td />').append($('<span />', {
                style: "background-color: white; color: darkblue",
                text: msg['id']
            }))).append($('<td />', {
                text: msg['msg']
            })).append($('<td />', {
                text: msg['timestamp']
            })));
        } else {
            // 본인이 보낸 메시지일 때
            $('#chatTable').append($('<tr />').append($('<td />', {
                text: msg['id']
            })).append($('<td />', {
                text: msg['msg']
            })).append($('<td />', {
                text: msg['timestamp']
            })));
        }

    }
}

위에서 JSON.stringify()로 전달하였음으로 수신시 JSON.parse()로 데이터를 받아 사용한다.

(드라마 보면 상대방이 보낸 메시지는 글자 바탕색이 흰색이고 내가 보낸 메시지는 그냥 파란 바탕이길래 아이디 비교해서 css 추가해주었다ㅎ)

 

📄 common.js

현재 날짜 출력 YYYY-MM-DD

function getDate() {
    var now = new Date();
    var year = now.getFullYear();
    var month = ('0' + (now.getMonth() + 1)).slice(-2);
    var day = ('0' + now.getDate()).slice(-2);
    return year + '-' + month  + '-' + day;
}

현재 시간 출력 YYYY-MM-DD HH:MM:SS

function getTimeStamp() {
    var now = new Date();
    var hours = ('0' + now.getHours()).slice(-2);
    var minutes = ('0' + now.getMinutes()).slice(-2);
    var seconds = ('0' + now.getSeconds()).slice(-2);
    return getDate() + ' ' + hours + ':' + minutes  + ':' + seconds;
}

 

 

실행 화면

드라마에서 나오는 모습

 

 

이제부터는 내가 만든 서비스 실행 모습.. 비교해보니 색감이나 다른 점도 많지만 대충 비슷해..~ 내가 퍼블을 더 다룰 줄 알았다면 하는 아쉬움이 있다 ㅠ

 

채팅방 입장

 

채팅 나누는 모습

 

 

과몰입러로써 뿌듯하구만.. 채팅 종료도 얼른 완성해야지

 

728x90

관련글 더보기

댓글 영역