코딩 노트

DM을 어떻게 보내는가 본문

Spring

DM을 어떻게 보내는가

newbyeol 2023. 10. 23. 16:04

전체 메세지일 경우 hello

보낼 때도 JSON처리 (json의 기본 규칙: 이름이 있어야 한다.)

{"content":"hello"}

 

DM일 경우 /w target hello

DM일 때도 JSOM으로 처리

{"target":"대상의ID", "content":"hello"}

 

sockjs.jsp 구문 수정

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

 

<!doctype html>

<html lang="ko">

 

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Bootstrap demo</title>

 

<!-- 아이콘 사용을 위한 Font Awesome 6 CDN -->

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">

 

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">

<link href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/sandstone/bootstrap.min.css" rel="stylesheet">

<link href="test.css" rel="stylesheet">

 

<style>

.btn-userlist {

display: none;

}

.message-list {

height: 65vh;

overflow-y: scroll;

padding-bottom: 15px;

}

::-webkit-scrollbar {

width: 1px; /* 스크롤바 너비 */

background-color: black

}

::-webkit-scrollbar-thumb {

background: var(--bs-secondary); /* 스크롤바 색상 */

}

 

@media screen and (max-width:768px) {

.client-list {

position: fixed;

top:0;

left:-250px;

bottom:0;

width:250px;

z-index: 9999999;

padding-top: 90px;

transition:left 0.2s ease-out;

}

.client-list.active {

left:0;

}

.btn-userlist {

display:block;

position: fixed;

top:1em;

right:1em;

}

}

</style>

</head>

 

<body>

<div class="container-fluid">

<div class="row">

<div class="col-md-10 offset-md-1">

 

<div class="row mt-4">

<div class="col">

<h1>

전체 채팅

<button class="btn btn-secondary btn-userlist">

<i class="fa-solid fa-users"></i>

</button>

</h1>

</div>

</div>

 

<div class="row mt-4">

<div class="col-md-4 client-list"></div>

<div class="col-md-8">

 

<div class="row">

<div class="col">

<div class="input-group">

<input type="text" class="form-control message-input" placeholder="메세지 내용 작성">

<button type="button" class="btn btn-primary send-btn">

<i class="fa-regular fa-paper-plane"></i>

보내기

</button>

</div>

</div>

</div>

 

<!-- 메세지 표시 영역 -->

<div class="row mt-4">

<div class="col message-list"></div>

</div>

 

</div>

</div>

 

</div>

</div>

</div>

 

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>

<!-- 웹소켓 서버가 SockJS일 경우 페이지에서도 SockJS를 사용해야 한다 -->

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script>

<script>

//연결 생성

window.socket = new SockJS("${pageContext.request.contextPath}/ws/sockjs");

//연결 후 해야할 일들을 콜백함수로 지정(onopen, onclose, onerror, onmessage)

window.socket.onmessage = function(e){

//console.log(e);

var data = JSON.parse(e.data);

//console.log(data);

 

//사용자가 접속하거나 종료했을 때 서버에서 오는 데이터로 목록을 갱신

//사용자가 메세지를 보냈을 때 서버에서 이를 전체에게 전달한다

//data.clients에 회원 목록이 있다

if(data.clients) {//목록 처리

$(".client-list").empty();

 

var ul = $("<ul>").addClass("list-group");

for(var i=0; i < data.clients.length; i++) {

$("<li>")

.addClass("list-group-item d-flex justify-content-between align-items-center")

.text(data.clients[i].memberId)

.append(

$("<span>").addClass("badge bg-primary badge-pill")

.text(data.clients[i].memberLevel)

)

.appendTo(ul);

}

ul.appendTo(".client-list");

}

else if(data.content) {//메세지 처리

var memberId;

if(data.dm == true) {//DM이라면

if(data.target) {//target이 있다면 (내가 DM을 보내서 찍히는 메세지라면)

memberId = $("<strong>").text(data.target + " 님에게 보낸 DM");

}

else {//target이 없다면 (내가 DM을 받아서 찍히는 메세지라면)

memberId = $("<strong>").text(data.memberId + " 님으로부터의 DM");

}

}

else {//DM이 아니라면

memberId = $("<strong>").text(data.memberId);

}

var memberLevel = $("<span>").text(data.memberLevel)

.addClass("badge bg-primary badge-pill ms-2");

var content = $("<div>").text(data.content);

 

//메세지를 화면에 추가

$("<div>").addClass("border border-secondary rounded p-2 mt-2")

.append(memberId)

.append(memberLevel)

.append("<hr>")

.append(content)

.appendTo(".message-list");

 

//스크롤바를 맨 아래로 이동

$(".message-list").scrollTop($(".message-list")[0].scrollHeight);

}

};

 

//메세지를 전송하는 코드

//- 메세지가 @로 시작하면 DM으로 처리(아이디 유무 검사정도 하면 좋고)

//- @아이디 메세지

$(".send-btn").click(function(){

var text = $(".message-input").val();

if(text.length == 0) return;

 

//window.socket.send(text);//일반 텍스트 형식으로 보낼 때

 

if(text.startsWith("@")) {//@로 시작하면

var space = text.indexOf(" ");

if(space == -1) return;

 

var obj = {

target: text.substring(1, space),

content: text.substring(space+1)

};

var str = JSON.stringify(obj);//객체를 JSON 문자열로 변환

window.socket.send(str);//JSON 형식으로 보낼 때

$(".message-input").val("");

}

else {

var obj = {

content:text

};

var str = JSON.stringify(obj);//객체를 JSON 문자열로 변환

window.socket.send(str);//JSON 형식으로 보낼 때

$(".message-input").val("");

}

 

});

 

//.btn-userlist를 누르면 사용자 목록에 active를 붙였다 떼었다 하도록 처리

$(".btn-userlist").click(function(){

$(".client-list").toggleClass("active");

});

</script>

</body>

 

</html>

ChatDto 생성

@Data @NoArgsConstructor @AllArgsConstructor @Builder

public class ChatDto {

private int chatNo;

private String chatSender, chatSenderLevel, chatReceiver;

private String chatContent;

private Date chatTime;

}

ChatDao 생성

public interface ChatDao {

void insert(ChatDto dto);

List<ChatDto> list();

}

ChatDaoImpl 생성

@Repository

public class ChatDaoImpl implements ChatDao{

@Autowired

private SqlSession sqlSession;

 

@Override

public void insert(ChatDto dto) {

sqlSession.insert("chat.add", dto);

}

@Override

public List<ChatDto> list() {

return sqlSession.selectList("chat.list");

}

chat-mapper.xml 생성

<?xml version="1.0" encoding="UTF-8"?>

 

<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"https://mybatis.org/dtd/mybatis-3-mapper.dtd">

 

<mapper namespace="chat">

 

<insert id="add">

insert into chat(chat_no, chat_content, chat_sender, chat_sender_level, chat_receiver)

values(

chat_seq.nextval,

#{chatContent},

#{chatSender},

#{chatSenderLevel},

#{chatReceiver}

)

</insert>

 

<select id="list" resultType="ChatDto">

select * from chat order by chat_no asc

</select>

 

</mapper>

properties 수정

# project setting file

# key=value

 

#sever setting

#server.servlet.context-path=/khacademy

##server.port=9999

 

#view resolver setting

spring.mvc.view.prefix=/WEB-INF/views/

spring.mvc.view.suffix=.jsp

 

 

# database setting

spring.datasource.driver-class-name=oracle.jdbc.OracleDriver

spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe

spring.datasource.username=C##home

spring.datasource.password=home

 

spring.datasource.hikari.data-source-properties.oracle.jdbc.timezoneAsRegion=false

 

# mybatis setting

mybatis.type-aliases-package=com.kh.spring20.dto,com.kh.spring20.vo

mybatis.mapper-locations=/mybatis/**/*-mapper.xml

mybatis.configuration.map-underscore-to-camel-case=true

mybatis.configuration.jdbc-type-for-null=VARCHAR

 

#logging setting

logging.level.root=warn

logging.level.com.kh=debug

#logging.level.member=debug

logging.level.chat=debug

 

logging.pattern.console=[%-5level] %msg - %c [%d{yyyy-MM-dd HH:mm:ss.S}] %n

 

# logging file setting

#logging.file.name= logs/server.log

#logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss.S} [%-5level] %msg - %c %n

#logging.logback.rollingpolicy.max-file-size=10MB

#logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}-%d{yyyy-MM-dd-HH}-%i.log

 

#custom properties setting

#custom.fileupload.home=C:/upload/profile

 

#email setting

#custom.email.host=smtp.gmail.com

#custom.email.port=587

#custom.email.username=qufquf12120

#custom.email.password=ajeprfgyoraxwuks

SockJsWebSocketServer 파일 수정

@Slf4j @Service

public class SockJsWebSocketServer extends TextWebSocketHandler {

 

//저장소

//private Set<WebSocketSession> clients = new CopyOnWriteArraySet<>();

private Set<ClientVO> clients = new CopyOnWriteArraySet<>();//전체 회원

private Set<ClientVO> members = new CopyOnWriteArraySet<>();//로그인한 회원

 

//JSON 변환기

private ObjectMapper mapper = new ObjectMapper();

 

@Autowired

private ChatDao chatDao;

 

@Override

public void afterConnectionEstablished(WebSocketSession session) throws Exception {

ClientVO client = new ClientVO(session);

clients.add(client);

 

if(client.isMember()) {

members.add(client);

}

 

log.debug("사용자 접속! 현재 {}명", clients.size());

log.debug("접속한 사용자 = {}", client);

 

//모든 접속자에게 접속자 명단을 전송

sendClientList();

sendMessageList(client);

}

 

@Override

public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

ClientVO client = new ClientVO(session);

clients.remove(client);

 

if(client.isMember()) {

members.remove(client);

}

 

log.debug("사용자 종료! 현재 {}명", clients.size());

 

//모든 접속자에게 접속자 명단을 전송

sendClientList();

}

 

//접속자 명단(clients)을 모든 접속자에게 전송하는 메소드

public void sendClientList() throws IOException {

//1. clients를 전송 가능한 형태(JSON 문자열)로 변환한다

ObjectMapper mapper = new ObjectMapper();

 

Map<String, Object> data = new HashMap<>();

//data.put("clients", clients);//전체회원 명단(null이 문제가됨)

data.put("clients", members);//로그인한 회원명단

String clientJson = mapper.writeValueAsString(data);

 

//2. 모든 사용자에게 전송

TextMessage message = new TextMessage(clientJson);

for(ClientVO client : clients) {

client.send(message);

}

}

 

//접속한 사용자에게 메세지 이력을 전송하는 메소드

public void sendMessageList(ClientVO client) throws IOException {

List<ChatDto> list = chatDao.list();

 

for(ChatDto dto : list) {

//dto의 내용을 메세지 형식으로 만들어서 전송

//- dto에 chatReceiver가 있으면 DM, 없으면 DM이 아님

//- 시간은 FE 미구현으로 첨부하지 않음

 

if(dto.getChatReceiver() == null) {//전체채팅인 경우 - chatReceiver가 null인 경우

Map<String, Object> map = new HashMap<>();

map.put("content", dto.getChatContent());

map.put("memberId", dto.getChatSender());

map.put("memberLevel", dto.getChatSenderLevel());

String messageJson = mapper.writeValueAsString(map);

TextMessage message = new TextMessage(messageJson);

client.send(message);

}

else {//DM이라면

if(client.isMember() == false)

continue;//비회원 컷트

if(client.getMemberId().equals(dto.getChatSender()) == false &&

client.getMemberId().equals(dto.getChatReceiver()) == false)

continue;//작성자나 수신자가 아니면(제3자인 경우) 컷트

 

//접속한 사람이 보낸 메세지라면 5개의 데이터를 전송(dm, memberId, memberLevel, content, target)

if(client.getMemberId().equals(dto.getChatSender())) {

Map<String, Object> map = new HashMap<>();

map.put("content", dto.getChatContent());

map.put("memberId", dto.getChatSender());

map.put("memberLevel", dto.getChatSenderLevel());

map.put("dm", true);

map.put("target", dto.getChatReceiver());

String messageJson = mapper.writeValueAsString(map);

TextMessage message = new TextMessage(messageJson);

client.send(message);

}

else {//접속한 사람이 받은 메세지라면 4개의 데이터를 전송

Map<String, Object> map = new HashMap<>();

map.put("content", dto.getChatContent());

map.put("memberId", dto.getChatSender());

map.put("memberLevel", dto.getChatSenderLevel());

map.put("dm", true);

String messageJson = mapper.writeValueAsString(map);

TextMessage message = new TextMessage(messageJson);

client.send(message);

}

}

 

}

}

 

@Override

protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

//사용자가 보낸 메세지를 모두에게 broadcast

ClientVO client = new ClientVO(session);

if(client.isMember() == false) return;

 

//(+추가) 사용자는 메세지를 JSON 형태로 보내므로 이를 해석해야 한다(ObjectMapper)

Map params = mapper.readValue(message.getPayload(), Map.class);

//log.debug("params = {}", params);

//log.debug("DM인가요 = {}", params.get("target") != null);

 

//DM일 경우와 아닐 경우를 구분하여 처리

boolean isDM = params.get("target") != null;

if(isDM) {//DM일 경우

//정보를 Map에 담아서 변환 후 전송

Map<String, Object> map = new HashMap<>();

map.put("dm", true);

map.put("memberId", client.getMemberId());

map.put("memberLevel", client.getMemberLevel());

map.put("content", params.get("content"));

 

//시간 추가 등

String messageJson = mapper.writeValueAsString(map);

TextMessage tm = new TextMessage(messageJson);

 

for(ClientVO c : members) {

if(c.getMemberId().equals(params.get("target"))) {//내가 찾던 사람이라면

c.send(tm);//대상에게 메세지 전송

}

}

 

//수신자에게 target 항목을 추가하여 다시 메세지 전송

map.put("target", params.get("target"));

messageJson = mapper.writeValueAsString(map);

tm = new TextMessage(messageJson);

 

client.send(tm);//작성자에게 메세지 전송

 

//DB insert (DM일 경우 내용,발신자,발신자등급,수신자를 저장)

chatDao.insert(ChatDto.builder()

.chatContent((String)params.get("content"))

.chatSender(client.getMemberId())

.chatSenderLevel(client.getMemberLevel())

.chatReceiver((String)params.get("target"))

.build());

}

else {//전체 채팅일 경우

//정보를 Map에 담아서 변환 후 전송

Map<String, Object> map = new HashMap<>();

map.put("memberId", client.getMemberId());

map.put("memberLevel", client.getMemberLevel());

map.put("content", params.get("content"));

//시간 추가 등

 

String messageJson = mapper.writeValueAsString(map);

TextMessage tm = new TextMessage(messageJson);

 

//메세지 발송

for(ClientVO c : clients) {

c.send(tm);

}

 

//DB insert (전체 메세지일 경우 내용,발신자,발신자등급을 저장)

chatDao.insert(ChatDto.builder()

.chatContent((String)params.get("content"))

.chatSender(client.getMemberId())

.chatSenderLevel(client.getMemberLevel())

.build());

}

 

 

}

 

}

'Spring' 카테고리의 다른 글

결제02 - 단건결제  (0) 2023.10.25
결제01 - (카카오페이 api) + 카멜케이스 변환  (0) 2023.10.24
웹소켓 - 실시간 채팅, 반응형 디자인  (0) 2023.10.23
실시간 채팅(WebSocket)  (0) 2023.10.20
HTTP 쿠키(Cookie)  (0) 2023.10.18