
이전 포스트들과 이어집니다
1. [Spring/WebSocket] 순수 WebSocket으로 채팅방 만들기
2. [Spring/WebSocket] WebSocket + STOMP으로 귓속말 가능한 채팅방 만들기
3. [Spring/WebSocket] WebSocket + STOMP + DOCKER 실습 : 여러 서버 띄우기
[Spring/WebSocket] WebSocket + STOMP + DOCKER 실습 : 여러 서버 띄우기에서 사용한 프로젝트에 redis를 붙여보자
implementation 'org.springframework.boot:spring-boot-starter-data-redis’
package com.example.backendproject.stompwebsocket.redis;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component
public class RedisPublisher {
/** 메세지를 발행하는 클래스 **/
private final StringRedisTemplate stringRedisTemplate;
/// stomp -> pub -> sub -> stomp
public void publish(String channel,String message){
stringRedisTemplate.convertAndSend(channel,message);
}
}
package com.example.backendproject.stompwebsocket.redis;
import com.example.backendproject.stompwebsocket.dto.ChatMessage;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RedisSubscriber implements MessageListener{
private final SimpMessagingTemplate simpMessagingTemplate;
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String msgBody = new String(message.getBody());
ChatMessage chatMessage = objectMapper.readValue(msgBody, ChatMessage.class);
if (chatMessage.getTo() != null && !chatMessage.getTo().isEmpty()) {
// 귓속말
simpMessagingTemplate.convertAndSendToUser(chatMessage.getTo(), "/queue/private", chatMessage);
} else {
// 일반 메시지
simpMessagingTemplate.convertAndSend("/topic/room." + chatMessage.getRoomId(), chatMessage);
}
}
catch (Exception e) {
}
}
}
package com.example.backendproject.stompwebsocket.redis;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
@RequiredArgsConstructor
public class RedisConfig {
private final RedisSubscriber redisSubscriber;
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
RedisMessageListenerContainer Container = new RedisMessageListenerContainer();
Container.setConnectionFactory(redisConnectionFactory);
Container.addMessageListener(new MessageListenerAdapter(redisSubscriber),new PatternTopic("room.*"));
Container.addMessageListener(new MessageListenerAdapter(redisSubscriber), new PatternTopic("private.*")); //귓속말
return Container;
}
}
DB_SERVER=database
DB_PORT=3306
DB_USER=root
DB_PASS=1234
REDIS_HOST=redis
db.server=${DB_SERVER:localhost}
db.port=${DB_PORT:3307}
db.username=${DB_USER:root}
db.password=${DB_PASS:1234}
REDIS.HOST=${REDIS_HOST:localhost}
spring.data.redis.host=${REDIS.HOST}
spring.data.redis.port=6379
spring.datasource.url=jdbc:mysql://${db.server}:${db.port}/backendDB?serverTimezone=Asia/Seoul&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.datasource.username=${db.username}
spring.datasource.password=${db.password}
services:
redis:
image: redis
container_name: redis
ports:
- "6379:6379"
database:
...
ports:
- "3307:3306"
environment:
MYSQL_DATABASE: backendDB
MYSQL_ROOT_PASSWORD: ${DB_PASS}
...
backend1: # 서비스 이름은 컨테이너간 통신하기 위한 이름
image: backend
container_name: backend1
environment:
PROJECT_NAME: 백앤드 서버1
DB_SERVER: ${DB_SERVER}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
REDIS_HOST: ${REDIS_HOST}
depends_on:
- database
- redis
backend2: # 서비스 이름은 컨테이너간 통신하기 위한 이름
image: backend
container_name: backend2
environment:
PROJECT_NAME: 백앤드 서버2
DB_SERVER: ${DB_SERVER}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
REDIS_HOST: ${REDIS_HOST}
depends_on:
- database
- redis
backend3: # 서비스 이름은 컨테이너간 통신하기 위한 이름
image: backend
container_name: backend3
environment:
PROJECT_NAME: 백앤드 서버3
DB_SERVER: ${DB_SERVER}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
REDIS_HOST: ${REDIS_HOST}
depends_on:
- database
- redis
nginx:
image: nginx:1.25
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- backend1
- backend2
- backend3
//STOMP는 이게 끝.
@Controller
@RequiredArgsConstructor
public class ChatController {
//서버가 클라이언트에게 수동으로 메세지를 보낼 수 있도록 하는 클래스
private final SimpMessagingTemplate template;
//동적으로 방 생성 가능
@Value("${PROJECT_NAME:web Server}")
private String instansName;
//redis 관련 변수
private final RedisPublisher redisPublisher;
private ObjectMapper objectMapper = new ObjectMapper();
@MessageMapping("/chat.sendMessage")
public void sendmessage(ChatMessage message) throws JsonProcessingException {
message.setMessage(instansName+" "+message.getMessage());
String channel = null;
String msg = null;
if (message.getTo() != null && !message.getTo().isEmpty()) {
// 귓속말
//내 아이디로 귓속말경로를 활성화 함
channel = "private."+message.getRoomId();
msg = objectMapper.writeValueAsString(message);
} else {
// 일반 메시지
channel = "room."+message.getRoomId();
msg = objectMapper.writeValueAsString(message);
}
redisPublisher.publish(channel,msg);
}
}


redis 설정 후에는 서버가 달라도 채팅 가능!