Skip to content

Commit 62a2ef6

Browse files
committed
feat(be): userChat service,controller,SSE 추가
1 parent 85007ed commit 62a2ef6

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.back.koreaTravelGuide.domain.userChat
2+
3+
import org.springframework.stereotype.Component
4+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
5+
import java.util.concurrent.ConcurrentHashMap
6+
7+
// Websocket,Stomp 사용 전 임시로 만들었음
8+
// 테스트 후 제거 예정
9+
10+
@Component
11+
class UserChatSseEvents {
12+
private val emitters = ConcurrentHashMap<Long, MutableList<SseEmitter>>()
13+
14+
fun subscribe(roomId: Long): SseEmitter {
15+
val emitter = SseEmitter(0L)
16+
emitters.computeIfAbsent(roomId) { mutableListOf() }.add(emitter)
17+
emitter.onCompletion { emitters[roomId]?.remove(emitter) }
18+
emitter.onTimeout { emitter.complete() }
19+
return emitter
20+
}
21+
22+
fun publishNew(
23+
roomId: Long,
24+
lastMessageId: Long,
25+
) {
26+
emitters[roomId]?.toList()?.forEach {
27+
try {
28+
it.send(SseEmitter.event().name("NEW").data(lastMessageId))
29+
} catch (_: Exception) {
30+
it.complete()
31+
}
32+
}
33+
}
34+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.back.koreaTravelGuide.domain.userChat.chatmessage.service
2+
3+
import com.back.koreaTravelGuide.domain.userChat.chatmessage.entity.ChatMessage
4+
import com.back.koreaTravelGuide.domain.userChat.chatmessage.repository.ChatMessageRepository
5+
import com.back.koreaTravelGuide.domain.userChat.chatroom.repository.ChatRoomRepository
6+
import org.springframework.stereotype.Service
7+
import org.springframework.transaction.annotation.Transactional
8+
9+
@Service
10+
class ChatMessageService(
11+
private val msgRepository: ChatMessageRepository,
12+
private val roomRepository: ChatRoomRepository,
13+
) {
14+
data class SendMessageReq(val senderId: Long, val content: String)
15+
16+
fun getlistbefore(
17+
roomId: Long,
18+
limit: Int,
19+
): List<ChatMessage> = msgRepository.findTop50ByRoomIdOrderByIdDesc(roomId).asReversed()
20+
21+
fun getlistafter(
22+
roomId: Long,
23+
afterId: Long,
24+
): List<ChatMessage> = msgRepository.findByRoomIdAndIdGreaterThanOrderByIdAsc(roomId, afterId)
25+
26+
@Transactional
27+
fun send(
28+
roomId: Long,
29+
req: SendMessageReq,
30+
): ChatMessage {
31+
val saved = msgRepository.save(ChatMessage(roomId = roomId, senderId = req.senderId, content = req.content))
32+
roomRepository.findById(roomId).ifPresent {
33+
roomRepository.save(it.copy(updatedAt = saved.createdAt, lastMessageId = saved.id))
34+
}
35+
return saved
36+
}
37+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.back.koreaTravelGuide.domain.userChat.chatroom.controller
2+
3+
import com.back.koreaTravelGuide.domain.userChat.UserChatSseEvents
4+
import com.back.koreaTravelGuide.domain.userChat.chatmessage.service.ChatMessageService
5+
import com.back.koreaTravelGuide.domain.userChat.chatroom.service.ChatRoomService
6+
import org.springframework.http.MediaType
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.PathVariable
9+
import org.springframework.web.bind.annotation.PostMapping
10+
import org.springframework.web.bind.annotation.RequestBody
11+
import org.springframework.web.bind.annotation.RequestMapping
12+
import org.springframework.web.bind.annotation.RequestParam
13+
import org.springframework.web.bind.annotation.RestController
14+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
15+
16+
@RestController
17+
@RequestMapping("/api/userchat/rooms")
18+
class ChatRoomController(
19+
private val roomSvc: ChatRoomService,
20+
private val msgSvc: ChatMessageService,
21+
private val events: UserChatSseEvents,
22+
) {
23+
// Room API
24+
@PostMapping
25+
fun create(
26+
@RequestBody req: ChatRoomService.CreateRoomReq,
27+
) = roomSvc.create(req)
28+
29+
@GetMapping("/{roomId}")
30+
fun get(
31+
@PathVariable roomId: Long,
32+
) = roomSvc.get(roomId)
33+
34+
// Message API (룸 하위 리소스)
35+
@GetMapping("/{roomId}/messages")
36+
fun listMessages(
37+
@PathVariable roomId: Long,
38+
@RequestParam(required = false) after: Long?,
39+
@RequestParam(defaultValue = "50") limit: Int,
40+
) = if (after == null) msgSvc.getlistbefore(roomId, limit) else msgSvc.getlistafter(roomId, after)
41+
42+
@PostMapping("/{roomId}/messages")
43+
fun sendMessage(
44+
@PathVariable roomId: Long,
45+
@RequestBody req: ChatMessageService.SendMessageReq,
46+
) = msgSvc.send(roomId, req).also { saved ->
47+
events.publishNew(roomId, saved.id!!)
48+
}
49+
50+
// SSE 구독도 여기 포함
51+
@GetMapping("/{roomId}/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
52+
fun subscribe(
53+
@PathVariable roomId: Long,
54+
): SseEmitter = events.subscribe(roomId)
55+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.back.koreaTravelGuide.domain.userChat.chatroom.service
2+
3+
import com.back.koreaTravelGuide.domain.userChat.chatroom.entity.ChatRoom
4+
import com.back.koreaTravelGuide.domain.userChat.chatroom.repository.ChatRoomRepository
5+
import org.springframework.stereotype.Service
6+
import org.springframework.transaction.annotation.Transactional
7+
import java.time.Instant
8+
9+
@Service
10+
class ChatRoomService(
11+
private val roomRepository: ChatRoomRepository,
12+
) {
13+
data class CreateRoomReq(val title: String, val ownerId: Long)
14+
15+
@Transactional
16+
fun create(req: CreateRoomReq): ChatRoom =
17+
roomRepository.save(ChatRoom(title = req.title, ownerId = req.ownerId, updatedAt = Instant.now()))
18+
19+
fun get(roomId: Long): ChatRoom = roomRepository.findById(roomId).orElseThrow { NoSuchElementException("room not found: $roomId") }
20+
}

0 commit comments

Comments
 (0)