본문 바로가기
프로젝트 정리/근거리 채팅

근거리 채팅 서비스 개발하기 (2) - 위치 기반 채팅방 검색, 채팅내역 저장

반응형

서론

리액트 네이티브에서 SSID를 가져올 때 사용하는 라이브러리 react-native-wifi-reborn는 안드로이드 13 이후 보안 문제로 가져오지 못하고 IOS 경우 개발자를 구매하지 않아서 기능을 사용하지 못하는 문제가 발생했습니다. 따라서 채팅방을 개설할 때 위치를 기반으로 개설한 후 채팅방을 보여줄 때 사용자가 설정한 범위안에 있는 채팅방만 보이도록 설정했습니다. 

현재까지 구현한 채팅방 생성, 반경 설정한 채팅방 보여주기, 채팅 내역 저장에 대한 내용을 설명하겠습니다. 

테이블 

사용자 위치 경도 테이블  

ID LAT LOT RADIUS USER_ID

USER 테이블과 1:1 관계로 위도,경도, 채팅방 반경, 유저 테이블 정보를 저장합니다.

채팅방 테이블

ROOM_ID CREATE_TIME MODIFY_TIME LAT LOT NAME

채팅방 테이블은 생성 시간, 수정한 시간, 위도, 경도, 이름 정보를 저장합니다.

사용자의 위치 정보는 위치가 바뀔때마다 업데이트 되어서 따로 1:1 테이블로 만들었고 채팅방 테이블은 위도 경도가 고정되어 있기 때문에 같은 테이블로 만들었습니다.

채팅내역 테이블

ID CREATE_TIME MODIFY_TIME MESSAGE SENDER ROOM_ID

채팅내역 테이블은 소켓에 처음 연결될 때 채팅내역을 가져올 때 사용합니다. 채팅 생성시간, 수정 시간, 메세지내용, 보낸사람, 채팅방 아이디를 가지고 있습니다. 

 

채팅방 생성

RoomRequestDto

package com.dongjae.nearChatProejct.Chat.dto;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RoomRequestDto {
    private String name;
    private Double lat;
    private  Double lot;
    @Builder
    public RoomRequestDto(String name,Double lat, Double lot) {
        this.lat = lat;
        this.lot = lot;
        this.name = name;
    }
}

 

 

 

 

 

데이터를 주고받기 위해 Dto를 만들었습니다. 테이블에 저장할 위도, 경도, 채팅방 이름을 가지고 있습니다.

ChatController

    @PostMapping("/create_room")
    public ResponseEntity<RoomResponseDto> createRoom(@AuthenticationPrincipal UserEntity user,
    @RequestBody RoomRequestDto dto) {
        chatRoomService.createRoom(ChatRoom.builder().name(dto.getName()).lat(dto.getLat()).lot(dto.getLot()).build());
        return ResponseEntity.ok(
                new RoomResponseDto(dto.getName(),200)
        );
    }

프론트에서 보내주는 데이터를 Dto를 가지고 저장하고 저장에 성공하면 채팅방이름과 200을 반환해주도록 설정했습니다.

ChatRoomService

    public void createRoom(ChatRoom chatRoom) {
        try {
            this.chatRoomRepository.save(chatRoom);
        }
        catch (Exception e){
            throw new ChatRoomCreateException();
        }
    }

채팅방 생성 중 오류가 발생하면 ChatRoomCreateException을 호출해서 400에러와 함께 메세지를 전달해주도록 설정했습니다.

채팅방 생성

채팅방이 제대로 성생된 것을 확인할 수 있습니다.

채팅방 리스트 보기

채팅방 응답 Dto

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ChatRoomsListResponseDto<T> {
    private T data;
    private int status;

    public ChatRoomsListResponseDto(T data, int status) {
        this.data = data;
        this.status = status;
    }
}

채팅방 정보를 GET 요청을 통해 받게 되면 응답된 리스트를 반환하기 위해 제네릭을 사용했습니다.

ChatRoomController

    @GetMapping("/chatRoomList")
    public ResponseEntity<ChatRoomsListResponseDto> selectRoom(@AuthenticationPrincipal UserEntity user){
        UserLatLotEntity userLatLotEntity = user.getUserLatLotEntity();
        List<ChatRoom> chatRooms = chatRoomService.searchChatRoom(userLatLotEntity);
        return ResponseEntity.ok(new ChatRoomsListResponseDto<>(chatRooms, 200));

    }

채팅방 요청이 들어오면 사용자의 좌표를 가져와서 검색 서비스에 데이터를 넘겨줬습니다. 

ChatRoomService

    public List<ChatRoom> searchChatRoom(UserLatLotEntity dto){
        //radius 는 미터 단
        Double lot = dto.getLot();
        Double lat = dto.getLat();
        int radius = dto.getRadius();

        Query query =  em.createNativeQuery("SELECT b.* FROM Chat_Room AS b where 6371 * acos( cos( radians( :lat ) ) * cos( radians( b.lat) ) * cos( radians( b.lot ) - radians(:lot) ) + sin( radians(:lat) ) * sin( radians(b.lat) ) ) < :radius", ChatRoom.class).setParameter("lat",lat).setParameter("lot",lot).setParameter("radius",radius);

        List resultList = query.getResultList();
        if(resultList.size() == 0 || resultList.size() <0){
            throw new ChatRoomNotFoundException();
        }
        return resultList;
    }

채팅방을 조회하기 위해 createNativeQuery를 이용해 쿼리를 생성했습니다. 쿼리 내용은 사용자 좌표, 채팅방 좌표가 거리가 설정한 반경안에 있으면 채팅방을 가져오는 것입니다. 채팅방이 존재하지 않으면 400 에러를 발생시켰습니다.

채팅방 리스트

반경내에 있는 채팅방만 가져오는 것을 확인할 수 있습니다.

채팅 보내기

MessageEntity

package com.dongjae.nearChatProejct.Chat.domain;

import com.dongjae.nearChatProejct.Common.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.DynamicInsert;

import javax.persistence.*;

@Entity
@DynamicInsert
@Getter
@NoArgsConstructor
public class Message extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(columnDefinition = "TEXT")
    private String message;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "room_id")
    private ChatRoom chatRoom;

    private String sender;

    @Builder
    public Message(String message, ChatRoom chatRoom, String sender) {
        this.message = message;
        this.chatRoom = chatRoom;
        this.sender = sender;
    }
}

메세지를 저장하기 위한 엔티티입니다. 채팅방에서 생성된 메세지를 저장해 이전 채팅 기록을 가져올 때 사용할 수 있습니다.

ChatRoomController

    @MessageMapping("{roomId}")
    @SendTo("/room/{roomId}")
    public Message test(@DestinationVariable Long roomId, Message message){
        ChatRoom byChatId = chatRoomService.findByChatId(roomId);
        Message test = messageService.sendMessage(
                Message.builder().chatRoom(byChatId).message(message.getMessage())
                        .sender(message.getSender()).build()
        );
        return test;
    }

해당 roomId에 메세지가 이벤트가 발생한 경우 채팅방 아이디에 보낸 사람과 메세지 내용을 저장하는 서비스를 호출합니다.

MessageService

@Service
@RequiredArgsConstructor
public class MessageService {
    private final MessageRepository messageRepository;

    public Message sendMessage(Message message) {
        this.messageRepository.save(message);

        return message;
    }
}

 

메세지 서비스에서 해당 메세지를 데이터베이스에 저장합니다.

 

채팅내역 저장

해당 채팅방 구독한 사람에게 메세지가 전송되고 채팅 메시지가 데이터베이스에 저장되는 것을 확인 할 수있습니다.

 

다음으로 구현해야 하는 기능

1. 채팅 메세지 보여주기 - 현재는 보내기까지만 구현 컴포넌트 하나 만들어서 채팅내역 보여주기

2. 좌표 업데이트 - 백그라운드 서비스를 이용해서 현재 좌표를 업데이트해야 함

3.FCM 이용해서 백그라운드에서도 메세지 수신하도록 만들기

 

채팅방 메세지를 보낼 때 좌표를 추가하거나 사용자 좌표가 변경될때마다 채팅방 좌표랑 체크를 한 후 만약 해당 채팅방과 사용자가 거리 반경이 벗어난 경우 소켓 연결 종료

사용자와 채팅방 반경이 벗어났다고 판단되면 FCM 메세지가 전송 안되도록 해야함

 

채팅 메세지를 리스트로 보여주는 것과 좌표를 백그라운드에서도 계속해서 업데이트 할 수 있는 방법을 찾아 구현한 후 다음 글에 작성할 예정입니다.