Spring+Kotlin) Firebase를 이용하여 특정 기기에 알림 보내기!

성승모·2024년 5월 30일
0

2024.06.04

PostMan과 Google Developer를 이용하여 앱에 알림을 보냈었다. 이를 서비스에서 이용하기 위하여 서버도 수정하려 한다.

헷갈리는 점 정리: 채팅방 로직
abc가 있는 채팅 방에서 a가 채팅을 침 -> 채팅방에 접속해있던 b는 websocket으로 바로 메시지를 받음 -> 접속해 있지 않던 c는 service에서 raw data를 받음 -> raw data를 파싱하여 Room에 '읽지 않은 메시지'로 저장 후 push 알림을 보냄
-> {
1) 알림을 본 c는 알림을 클릭하여 해당 채팅방으로 바로 이동
2) 알림을 지우고 앱을 직접 실행 - Room에서 '읽지 않은 메세지' 조회 후 채팅방 리스트 위에 그 개수를 알려줌.
}
-> 채팅방에 입장한 c의 앱에서 서버에 최근 메시지 리스트를 요청 후 화면에 띄어줌.

헷갈리는 점 정리: 초대 로직
a가 bc와 함께 하는 채팅방을 만듦 -> User table에 저장되어 있는 device token 값으로 해당 기기들을 FirebaseTopic(roomId) 에 구독시킴 -> a가 새로 생성된 채팅방에서 메세지 입력 시 firebase를 통하여 문자를 보냄 -> bc의 앱에선 받은 메시지의 roomId를 확인 후 해당 채팅 방이 없으니 Room에 '채팅방'을 새로 생성 후 '읽지 않은 메시지'에 저장

우선 메시지를 정의해보자.

class MessageForm(
    id: Long,
    roomId: Long,
    title: String,
    body: String,
    sender: String,
    date: LocalDateTime,
    var address: String
) {
    var data = MessageData(
        id, roomId, title, body, sender, date
    )

    fun toJson() = "{" +
            "\"message\":{" +
            "\"token\":\"$address\"," +
            "\"data\":{" +
            "\"id\":\"${data.id}\"," +
            "\"roomId\":\"${data.roomId}\"," +
            "\"title\":\"${data.title}\"," +
            "\"body\":\"${data.body}\"," +
            "\"sender\":\"${data.sender}\"," +
            "\"date\":\"${data.date}\"" +
            "}}}"

    data class MessageData(
        var id: Long,
        var roomId: Long,
        var title: String,
        var body: String,
        var sender: String,
        var date: LocalDateTime
    )
}

필요한 필드를 선언하고
Android에서 요구하는 양식에 맞춰 toJson() 함수를 정의하였다.


Stomp가 redirect한 ChatController에서 rawMessage를 파싱해 MessageForm으로 이용한다.

@Controller
class ChatController {
    @Autowired
    private lateinit var chatService: ChatService
    @Autowired
    private lateinit var simpleMessage: SimpMessageSendingOperations
    private val  fcmManager = FcmManager()

    @MessageMapping("/chat") //여기로 전송되면 메서드 호출 -> WebSocketConfig prefixes 에서 적용한건 앞에 생략
    fun chat(rawMessage: RawMessage): ChatResponseDto {
        //채팅 저장
        val dto = chatService.addChat(rawMessage)
        val roomId = dto.roomId
        
        //TODO: FireBase push notification.
        
        simpleMessage.convertAndSend("/sub/${roomId}", Json.encodeToString(dto))

        return dto
    }
}
  • @MessageMapping으로 websocket_root_url/prefix/chat 으로 들어온 websocket 통신을 처리할 수 있다.
  • android에 보낼 data를 dto에 할당하고 simpleMessage.convertAndSend를 이용하여 roomId를 구독한 클라이언트에게 dto을 json으로 보낸다.

-> TODO를 작성해보자.


FireBase를 관리하는 FirebaseManager를 정의하자.

class FireBaseManager {
    @Value("\${firebase_sdk_dir}")
    private lateinit var firebaseSdkPath: String

    private val fireBaseInstance: FirebaseMessaging by lazy {
        val serviceAccount = FileInputStream(firebaseSdkPath)
        val options = FirebaseOptions.builder()
            .setCredentials(GoogleCredentials.fromStream(serviceAccount))
            .build()

        FirebaseApp.initializeApp(options)
        FirebaseMessaging.getInstance()
    }

    fun sendToSingleDevice(targetToken: String, dto: ChatResponseDto) {
        val message = Message.builder()
            .putAllData(dto.toMap())
            .setToken(targetToken)
            .build()


        try {
            fireBaseInstance.send(message)
            logger.info { "FcmManager:sendToSingleDevice) Successfully sent message" }
        } catch (exception: FirebaseMessagingException) {
            logger.error { "FcmManager:sendToSingleDevice: $exception" }
        }
    }

    fun sendToTopic(topicUrl: String, dto: ChatResponseDto) {
        val message = Message.builder()
            .putAllData(dto.toMap())
            .setTopic(topicUrl)
            .build()

        try {
            fireBaseInstance.send(message)
            logger.info { "FcmManager:sendToTopic) Successfully sent message" }
        } catch (exception: FirebaseMessagingException) {
            logger.error { "FcmManager:sendToTopic: $exception" }
        }
    }
}

모든 채팅방은 topic & subcribe로 관리할 예정이지만 직접 기기에 보내는 기능도 이용해보았다.

ChatController에서 이용해보자!

TODO 아래 작성.

		firebaseManager.sendToTopic(
            rawMessage.topicUrl, dto
        )

aws Ec2에 업로드 후 테스트 해보자!

해볼라 했는데 jwt를 헤더에 넣어야 해서 간단한 테스트가 어려울거 같다... 낼 앱을 수정해야겠다.

다음 해야 할 일 정리

  • app에서 채팅 로직에 따라 구현하기
  • Chat Room Entity 정의
    • topic_url 포함
    • user in chat room 를 HashSet으로 정의 -> 중복 생성 방지
  • Chat Room Creating Api 정의
  • 해당 Chat Room의 최근 n건 메시지 조회
profile
안녕하세요!

0개의 댓글