feat(chat-room): 7.3 로컬 DB 동기화 및 메시지 상태/정리 로직 구현

- ChatMessageDao에 상태 업데이트/정리 보조 쿼리 추가
- ChatRepository에 로컬 저장, 상태 업데이트, 오래된 메시지 정리 API 추가
- Activity 전송/상태 변경 시 DB 반영 및 로딩 후 정리 트리거
This commit is contained in:
2025-08-13 23:36:50 +09:00
parent 9fa270da10
commit 7fc72da905
3 changed files with 74 additions and 0 deletions

View File

@@ -44,6 +44,8 @@ class ChatRepository(
val entities = data.messages.map { it.toEntity(roomId) }
Single.fromCallable {
kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) }
// 오래된 메시지 정리(최신 200개 유지)
kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, 200) }
data
}.subscribeOn(Schedulers.io())
}
@@ -60,11 +62,50 @@ class ChatRepository(
val entities = data.messages.map { it.toEntity(roomId) }
Single.fromCallable {
kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) }
// 오래된 메시지 정리(최신 200개 유지)
kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, 200) }
data
}.subscribeOn(Schedulers.io())
}
}
/**
* 로컬 임시 메시지 저장(SENDING 등)
*/
fun saveLocalMessage(roomId: Long, message: ChatMessage): Completable {
return Completable.fromAction {
kotlinx.coroutines.runBlocking { chatDao.insertMessage(message.toEntity(roomId)) }
}.subscribeOn(Schedulers.io())
}
/**
* localId 기반 상태 업데이트(SENDING→SENT/FAILED)
*/
fun updateMessageStatusByLocalId(roomId: Long, localId: String, newStatus: MessageStatus): Completable {
return Completable.fromAction {
// enum을 문자열로 변환하여 쿼리 파라미터로 전달
kotlinx.coroutines.runBlocking { chatDao.updateStatusByLocalId(roomId, localId, newStatus.name) }
}.subscribeOn(Schedulers.io())
}
/**
* 오래된 메시지 정리(방별 최신 keepLatest개 유지)
*/
fun trimOldMessages(roomId: Long, keepLatest: Int = 200): Completable {
return Completable.fromAction {
kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, keepLatest) }
}.subscribeOn(Schedulers.io())
}
private suspend fun trimOldMessagesInternal(roomId: Long, keepLatest: Int) {
if (keepLatest <= 0) return
val offset = keepLatest - 1
val cutoff = chatDao.getNthLatestCreatedAt(roomId, offset)
if (cutoff != null) {
chatDao.deleteOldMessages(roomId, cutoff)
}
}
/**
* 로그아웃 시 모든 로컬 메시지 삭제
*/

View File

@@ -270,6 +270,11 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
isGrouped = false
)
appendMessage(ChatListItem.UserMessage(userMsg))
// DB에도 임시 메시지 저장(7.3)
compositeDisposable.add(
chatRepository.saveLocalMessage(roomId, userMsg)
.subscribe({ }, { /* 로컬 저장 실패는 UI 진행에 영향 주지 않음 */ })
)
// 2) 타이핑 인디케이터 표시
chatAdapter.showTypingIndicator()
@@ -301,6 +306,14 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
items[index] = ChatListItem.UserMessage(updated)
chatAdapter.setItems(items)
autoScrollIfAtBottom()
// DB에도 상태 업데이트 반영(7.3)
updated.localId?.let { lid ->
compositeDisposable.add(
chatRepository.updateMessageStatusByLocalId(roomId, lid, newStatus)
.subscribe({ }, { /* 실패 시 무시(다음 동기화 시 정합성 확보) */ })
)
}
}
}
@@ -385,6 +398,12 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
// 최신 메시지 위치로
scrollToBottom()
isLoading = false
// 7.3: 오래된 메시지 정리(백그라운드)
compositeDisposable.add(
chatRepository.trimOldMessages(roomId, keepLatest = 200)
.subscribe({ }, { /* 무시 */ })
)
}, { error ->
isLoading = false
showToast(error.message ?: "채팅방 데이터를 불러오지 못했습니다.")
@@ -489,6 +508,12 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
}
isLoading = false
// 7.3: 오래된 메시지 정리(백그라운드)
compositeDisposable.add(
chatRepository.trimOldMessages(roomId, keepLatest = 200)
.subscribe({ }, { /* 무시 */ })
)
}, { error ->
isLoading = false
showToast(error.message ?: "이전 메시지를 불러오지 못했습니다.")

View File

@@ -21,6 +21,14 @@ interface ChatMessageDao {
@Update
suspend fun updateMessage(message: ChatMessageEntity)
// 메시지 상태를 localId 기준으로 업데이트(Converter로 문자열 상태 저장)
@Query("UPDATE chat_messages SET status = :status WHERE roomId = :roomId AND localId = :localId")
suspend fun updateStatusByLocalId(roomId: Long, localId: String, status: String): Int
// N번째 최신(createdAt DESC) 항목의 createdAt (OFFSET 사용)
@Query("SELECT createdAt FROM chat_messages WHERE roomId = :roomId ORDER BY createdAt DESC LIMIT 1 OFFSET :offset")
suspend fun getNthLatestCreatedAt(roomId: Long, offset: Int): Long?
@Query("DELETE FROM chat_messages WHERE roomId = :roomId AND createdAt < :cutoffTime")
suspend fun deleteOldMessages(roomId: Long, cutoffTime: Long)