Skip to content

Commit 421d087

Browse files
authored
feat(be): RabbitMQ 적용 (#131)
* feat(be): add RabbitMQ dependencies # Conflicts: # build.gradle.kts * feat(be):Add publisher implementation for RabbitMQ integration * feat(be): route chat messaging through publisher port * feat(be): enable simple broker in non-prod profiles * feat(be): RabbitMq test environment * fix(be): yml 파일 수정 및 git에서 docker 파일 삭제
1 parent 0d4d32f commit 421d087

File tree

10 files changed

+114
-15
lines changed

10 files changed

+114
-15
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,5 @@ build/reports/
7575
coverage/
7676

7777
# Test file
78-
index.html
78+
index.html
79+
docker-compose.yml

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ dependencies {
5555

5656
// 웹소켓 - 사용자 채팅 기능 (게스트-가이드)
5757
implementation("org.springframework.boot:spring-boot-starter-websocket")
58+
implementation("org.springframework.boot:spring-boot-starter-amqp")
5859

5960
// 레디스 - 캐싱 및 세션 관리
6061
implementation("org.springframework.boot:spring-boot-starter-data-redis")
@@ -75,6 +76,7 @@ dependencies {
7576
testImplementation("org.springframework.boot:spring-boot-starter-test")
7677
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
7778
testImplementation("org.springframework.security:spring-security-test")
79+
testImplementation("org.springframework.amqp:spring-rabbit-test")
7880
testImplementation("io.mockk:mockk:1.13.12")
7981
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
8082
}

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/controller/ChatMessageController.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import com.back.koreaTravelGuide.common.ApiResponse
44
import com.back.koreaTravelGuide.domain.userChat.chatmessage.dto.ChatMessageResponse
55
import com.back.koreaTravelGuide.domain.userChat.chatmessage.dto.ChatMessageSendRequest
66
import com.back.koreaTravelGuide.domain.userChat.chatmessage.service.ChatMessageService
7+
import com.back.koreaTravelGuide.domain.userChat.chatmessage.usecase.ChatMessagePublisher
78
import org.springframework.http.ResponseEntity
8-
import org.springframework.messaging.simp.SimpMessagingTemplate
99
import org.springframework.security.access.AccessDeniedException
1010
import org.springframework.security.core.annotation.AuthenticationPrincipal
1111
import org.springframework.web.bind.annotation.GetMapping
@@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController
2020
@RequestMapping("/api/userchat/rooms")
2121
class ChatMessageController(
2222
private val messageService: ChatMessageService,
23-
private val messagingTemplate: SimpMessagingTemplate,
23+
private val chatMessagePublisher: ChatMessagePublisher,
2424
) {
2525
@GetMapping("/{roomId}/messages")
2626
fun listMessages(
@@ -49,8 +49,8 @@ class ChatMessageController(
4949
val memberId = senderId ?: throw AccessDeniedException("인증이 필요합니다.")
5050
val saved = messageService.send(roomId, memberId, req.content)
5151
val response = ChatMessageResponse.from(saved)
52-
messagingTemplate.convertAndSend(
53-
"/topic/userchat/$roomId",
52+
chatMessagePublisher.publishUserChat(
53+
roomId,
5454
ApiResponse(msg = "메시지 전송", data = response),
5555
)
5656
return ResponseEntity.status(201).body(ApiResponse(msg = "메시지 전송", data = response))

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/controller/ChatMessageSocketController.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ import com.back.koreaTravelGuide.common.ApiResponse
44
import com.back.koreaTravelGuide.domain.userChat.chatmessage.dto.ChatMessageResponse
55
import com.back.koreaTravelGuide.domain.userChat.chatmessage.dto.ChatMessageSendRequest
66
import com.back.koreaTravelGuide.domain.userChat.chatmessage.service.ChatMessageService
7+
import com.back.koreaTravelGuide.domain.userChat.chatmessage.usecase.ChatMessagePublisher
78
import org.springframework.messaging.handler.annotation.DestinationVariable
89
import org.springframework.messaging.handler.annotation.MessageMapping
910
import org.springframework.messaging.handler.annotation.Payload
10-
import org.springframework.messaging.simp.SimpMessagingTemplate
1111
import org.springframework.security.access.AccessDeniedException
1212
import org.springframework.stereotype.Controller
1313
import java.security.Principal
1414

1515
@Controller
1616
class ChatMessageSocketController(
1717
private val chatMessageService: ChatMessageService,
18-
private val messagingTemplate: SimpMessagingTemplate,
18+
private val chatMessagePublisher: ChatMessagePublisher,
1919
) {
2020
@MessageMapping("/userchat/{roomId}/messages")
2121
fun handleMessage(
@@ -26,8 +26,8 @@ class ChatMessageSocketController(
2626
val senderId = principal.name.toLongOrNull() ?: throw AccessDeniedException("인증이 필요합니다.")
2727
val saved = chatMessageService.send(roomId, senderId, req.content)
2828
val response = ChatMessageResponse.from(saved)
29-
messagingTemplate.convertAndSend(
30-
"/topic/userchat/$roomId",
29+
chatMessagePublisher.publishUserChat(
30+
roomId,
3131
ApiResponse(msg = "메시지 전송", data = response),
3232
)
3333
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.back.koreaTravelGuide.domain.userChat.chatmessage.usecase
2+
3+
interface ChatMessagePublisher {
4+
fun publishUserChat(
5+
roomId: Long,
6+
payload: Any,
7+
)
8+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.back.koreaTravelGuide.domain.userChat.chatmessage.usecase
2+
3+
import org.springframework.amqp.rabbit.core.RabbitTemplate
4+
import org.springframework.context.annotation.Profile
5+
import org.springframework.stereotype.Component
6+
7+
@Profile("prod")
8+
@Component
9+
class RabbitChatMessagePublisher(
10+
private val rabbitTemplate: RabbitTemplate,
11+
) : ChatMessagePublisher {
12+
override fun publishUserChat(
13+
roomId: Long,
14+
payload: Any,
15+
) {
16+
val routingKey = "userchat.$roomId"
17+
rabbitTemplate.convertAndSend("amq.topic", routingKey, payload)
18+
}
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.back.koreaTravelGuide.domain.userChat.chatmessage.usecase
2+
3+
import org.springframework.context.annotation.Profile
4+
import org.springframework.messaging.simp.SimpMessagingTemplate
5+
import org.springframework.stereotype.Component
6+
7+
@Profile("!prod")
8+
@Component
9+
class SimpleChatMessagePublisher(
10+
private val messagingTemplate: SimpMessagingTemplate,
11+
) : ChatMessagePublisher {
12+
override fun publishUserChat(
13+
roomId: Long,
14+
payload: Any,
15+
) {
16+
messagingTemplate.convertAndSend("/topic/userchat/$roomId", payload)
17+
}
18+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.back.koreaTravelGuide.domain.userChat.stomp
2+
3+
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter
4+
import org.springframework.amqp.support.converter.MessageConverter
5+
import org.springframework.beans.factory.annotation.Value
6+
import org.springframework.context.annotation.Bean
7+
import org.springframework.context.annotation.Configuration
8+
import org.springframework.context.annotation.Profile
9+
import org.springframework.messaging.simp.config.ChannelRegistration
10+
import org.springframework.messaging.simp.config.MessageBrokerRegistry
11+
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
12+
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
13+
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
14+
15+
// userChat에서만 사용할 것 같아서 전역에 두지 않고 userChat 도메인에 두었음
16+
17+
@Profile("prod")
18+
@Configuration
19+
@EnableWebSocketMessageBroker
20+
class UserChatRabbitWebSocketConfig(
21+
private val userChatStompAuthChannelInterceptor: UserChatStompAuthChannelInterceptor,
22+
@Value("\${spring.rabbitmq.host}") private val rabbitHost: String,
23+
@Value("\${spring.rabbitmq.stomp-port}") private val rabbitStompPort: Int,
24+
@Value("\${spring.rabbitmq.username}") private val rabbitUsername: String,
25+
@Value("\${spring.rabbitmq.password}") private val rabbitPassword: String,
26+
) : WebSocketMessageBrokerConfigurer {
27+
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
28+
registry.addEndpoint("/ws/userchat")
29+
.setAllowedOriginPatterns("*")
30+
.withSockJS()
31+
}
32+
33+
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
34+
registry
35+
.setApplicationDestinationPrefixes("/pub")
36+
.enableStompBrokerRelay("/topic")
37+
.setRelayHost(rabbitHost)
38+
.setRelayPort(rabbitStompPort)
39+
.setClientLogin(rabbitUsername)
40+
.setClientPasscode(rabbitPassword)
41+
.setSystemLogin(rabbitUsername)
42+
.setSystemPasscode(rabbitPassword)
43+
}
44+
45+
override fun configureClientInboundChannel(registration: ChannelRegistration) {
46+
registration.interceptors(userChatStompAuthChannelInterceptor)
47+
}
48+
49+
@Bean
50+
fun rabbitMessageConverter(): MessageConverter = Jackson2JsonMessageConverter()
51+
}

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/config/UserChatWebSocketConfig.kt renamed to src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/stomp/UserChatSimpleWebSocketConfig.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
package com.back.koreaTravelGuide.domain.userChat.config
1+
package com.back.koreaTravelGuide.domain.userChat.stomp
22

33
import org.springframework.context.annotation.Configuration
4+
import org.springframework.context.annotation.Profile
45
import org.springframework.messaging.simp.config.ChannelRegistration
56
import org.springframework.messaging.simp.config.MessageBrokerRegistry
67
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
78
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
89
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
910

10-
// userChat에서만 사용할 것 같아서 전역에 두지 않고 userChat 도메인에 두었음
11-
11+
@Profile("!prod")
1212
@Configuration
1313
@EnableWebSocketMessageBroker
14-
class UserChatWebSocketConfig(
14+
class UserChatSimpleWebSocketConfig(
1515
private val userChatStompAuthChannelInterceptor: UserChatStompAuthChannelInterceptor,
1616
) : WebSocketMessageBrokerConfigurer {
1717
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
@@ -21,8 +21,8 @@ class UserChatWebSocketConfig(
2121
}
2222

2323
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
24-
registry.enableSimpleBroker("/topic")
2524
registry.setApplicationDestinationPrefixes("/pub")
25+
registry.enableSimpleBroker("/topic")
2626
}
2727

2828
override fun configureClientInboundChannel(registration: ChannelRegistration) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.back.koreaTravelGuide.domain.userChat.config
1+
package com.back.koreaTravelGuide.domain.userChat.stomp
22

33
import com.back.koreaTravelGuide.common.security.JwtTokenProvider
44
import org.springframework.messaging.Message

0 commit comments

Comments
 (0)