Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OMCT-413] 실시간 채팅 기능 구현 #216

Merged
merged 10 commits into from
Feb 6, 2024
5 changes: 5 additions & 0 deletions bucketback-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// chat
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:stomp-websocket:2.3.3'
}

jar {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.programmers.bucketback.domains.chat.api;

import java.time.LocalDateTime;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import com.programmers.bucketback.domains.chat.api.dto.request.ChatCreateRequest;
import com.programmers.bucketback.domains.chat.api.dto.response.ChatGetResponse;

import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class ChatController {

private final SimpMessagingTemplate simpMessagingTemplate;

/**
* STOMP 프로토콜을 사용하여 채팅 메시지를 처리하고 구독자들에게 메시지를 전송하는 메소드.
*
* /publish/messages 경로로 메시지를 전송하면 이 메소드가 호출됩니다.
*/
@MessageMapping("/publish/messages")
public void sendMessage(final ChatCreateRequest request) {
LocalDateTime createdAt = LocalDateTime.now();
ChatGetResponse chatGetResponse = new ChatGetResponse(
request.message(),
request.userNickname(),
createdAt
);

/*
SimpMessagingTemplate를 사용하여 구독자들에게 메시지를 전송합니다.
메시지는 "/subscribe/rooms/{chatRoomId}" 주소로 전송되며,
이는 해당 채팅방을 구독하는 클라이언트들에게 전달됩니다.
*/
simpMessagingTemplate.convertAndSend("/subscribe/rooms/" + request.chatRoomId(), chatGetResponse);
}

@GetMapping("/chat/{roomId}")
public String chatPage(
@PathVariable("roomId") final int roomId,
final Model model
) {
model.addAttribute("roomId", roomId);

return "chat";
}

@GetMapping("/chat-rooms")
public String chatRoomPage() {
return "chat-rooms";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.programmers.bucketback.domains.chat.api.dto.request;

public record ChatCreateRequest(
String userNickname,
Long chatRoomId,
String message
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.programmers.bucketback.domains.chat.api.dto.response;

import java.time.LocalDateTime;

public record ChatGetResponse(
String message,
String sendUserName,
LocalDateTime createdAt
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.programmers.bucketback.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {

/**
* STOMP 프로토콜을 사용하여 클라이언트와 서버가 메시지를 주고받을 수 있도록 엔드포인트를 등록합니다.
*/
@Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp") // ws-stomp 엔드포인트를 통해 클라이언트가 서버와 연결할 수 있습니다.
.setAllowedOriginPatterns("*")
.withSockJS(); // withSockJS() 메소드는 SockJS를 사용할 수 있도록 합니다.
}

/**
* 클라이언트가 메시지를 구독할 수 있도록 메시지 브로커를 등록합니다.
*/
@Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
// /subscribe로 시작하는 경로를 구독할 수 있도록 등록합니다.
registry.enableSimpleBroker("/subscribe");

// /app으로 시작하는 경로로 들어오는 메시지를 컨트롤러에서 처리할 수 있도록 등록합니다.
registry.setApplicationDestinationPrefixes("/app");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws E
.requestMatchers("/api/{nickname}/buckets/**").permitAll()

.requestMatchers("/api/hobbies").permitAll()
.requestMatchers("/js/**").permitAll()
.requestMatchers("/css/**").permitAll()
.requestMatchers("/images/**").permitAll()

.requestMatchers("/chat/**").permitAll()
.requestMatchers("/chat-rooms").permitAll()

.requestMatchers("/ws-stomp/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
Expand Down
158 changes: 158 additions & 0 deletions bucketback-api/src/main/resources/static/css/chat.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
body, html {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
background-color: #EDEDED;
}

.chat-rooms-container, .chat-container {
width: 400px;
margin: 50px auto;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.chat-rooms-container h2, .chat-header h3 {
background-color: #0088cc;
color: #fff;
padding: 20px;
margin: 0;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}

.rooms-list, .chat-messages {
height: 400px;
overflow-y: auto;
padding: 10px;
border-top: 1px solid #ddd;
}

.room {
padding: 10px;
border-bottom: 1px solid #ddd;
color: #333;
text-decoration: none;
display: block;
}

.room:hover {
background-color: #f4f4f4;
}

.chat-container {
width: 400px;
margin: 50px auto;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.chat-header {
background-color: #0088cc;
color: #fff;
padding: 20px;
margin: 0;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
text-align: center;
}

.chat-messages {
height: 500px;
overflow-y: auto;
padding: 10px;
border-top: 1px solid #ddd;
}

.chat-message {
display: flex;
margin-bottom: 10px;
align-items: center;
}

.my-message .message-avatar {
order: 2;
margin-left: 10px;
}

.other-message .message-avatar {
margin-right: 10px;
}

.my-message .message-info,
.other-message .message-info {
max-width: 80%;
}

.message-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background-position: center center;
background-size: contain;
}

.message-info {
text-align: left;
}

.message-author {
font-size: 14px;
color: #333;
font-weight: 600;
}

.message-time {
font-size: 12px;
color: #999;
}

.message-text {
background: #DCF8C6;
padding: 10px;
border-radius: 10px;
margin: 2px 0;
}

.my-message {
flex-direction: row-reverse;
}

.my-message .message-text {
background: #0088cc;
color: white;
text-align: right;
}

.other-message .message-text {
background: #f4f4f4;
}

.chat-input {
display: flex;
padding: 10px;
border-top: 1px solid #ddd;
}

.chat-input input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
}

.chat-input button {
padding: 10px 20px;
background-color: #0088cc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.chat-input button:hover {
background-color: #006699;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading