• 오늘도 즐거운 하루 되세요!
subject 모바일 앱 + ERP 웹 연동 채팅방(톡) 기능 개발 계획
author 관리자 date 2026-07-08 hit 22 HIT
채팅방 개발 중.

현재 버전 v0.9.

1. 기능 구현 완료

- 날짜 변환 시 상단 표시 기능
- 파일 첨부 기능
- 웹 채팅 연동

2. To Do List

- 파일 첨부 제한 (1차) : 용량 20Mb 로 제한, 이미지 5개로 제한
- 메시지 푸시 및 소리 알림 기능 : 모바일 & 웹
- 대화방 리스트에서 안 본 메시지 숫자 표시

3. 모바일 앱 + ERP 웹 연동 채팅방(톡) 기능 개발 계획

1. 권장 전체 구조

다음 구조를 기준으로 설계하고 구현한다.

[모바일 앱]
React Native / Expo

- 대화방 목록 화면
- 대화방 상세 화면
- 대화방 생성 화면
- 메시지 입력/전송
- 읽음 표시
- 푸시 토큰 등록

        ↓ REST API / WebSocket

[Node.js 서버]
Express + Socket.IO

- 로그인 토큰 검증
- 대화방 생성
- 대화방 목록 조회
- 메시지 목록 조회
- 메시지 저장
- 실시간 메시지 송수신
- 읽음 처리
- 접속 상태 관리
- 푸시 알림 발송 트리거

        ↓

[DB]
MySQL 또는 MariaDB

- 회원
- 대화방
- 대화방 참여자
- 메시지
- 푸시 토큰
- 읽음 상태

        ↓

[Push]
Firebase Cloud Messaging 또는 Expo Push

- 앱 백그라운드/종료 상태 알림

2. 1차 구현 범위

1차 버전에서는 아래 기능만 구현한다.

1. 대화방 생성
2. 대화방 목록 조회
3. 메시지 목록 조회
4. 텍스트 메시지 전송
5. 실시간 메시지 수신
6. 읽음/안 읽음 처리
7. 푸시 토큰 등록
8. 미접속 사용자 푸시 알림
9. 대화방 나가기

아래 기능은 2차 구현으로 미룬다.

1. 이미지 전송
2. 파일 전송
3. 메시지 수정
4. 메시지 삭제
5. 답장 기능
6. 멘션 기능
7. typing 표시
8. 온라인 상태 표시
9. 방 대표 이미지
10. 공지 메시지
11. 신고/차단
12. 채팅 검색

3. 핵심 설계 원칙

다음 원칙을 반드시 지켜서 구현한다.

1. 메시지는 반드시 서버 DB에 저장한다.
2. 앱끼리 직접 메시지를 주고받는 구조로 만들지 않는다.
3. 모든 메시지 전송/조회/방 입장은 서버에서 권한 검증한다.
4. WebSocket은 실시간 전달용으로 사용한다.
5. REST API는 목록 조회, 방 생성, 이전 메시지 조회에 사용한다.
6. 앱이 꺼져 있거나 백그라운드 상태일 수 있으므로 푸시 알림을 별도로 구현한다.
7. 읽음 처리는 처음부터 DB 구조에 포함한다.
8. 한 사용자가 여러 기기를 사용할 수 있으므로 푸시 토큰은 사용자당 여러 개 저장 가능해야 한다.

4. DB 테이블 설계

기존 회원 테이블이 있다면 users 테이블은 기존 구조를 유지하되, 채팅 관련 테이블은 아래 구조로 신규 생성한다.


4-1. chat_rooms

대화방 기본 정보를 저장한다.

CREATE TABLE chat_rooms (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    room_type VARCHAR(30) NOT NULL DEFAULT 'group',
    title VARCHAR(255) NULL,
    created_by BIGINT UNSIGNED NOT NULL,
    last_message_id BIGINT UNSIGNED NULL,
    last_message_at DATETIME NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_room_type (room_type),
    INDEX idx_created_by (created_by),
    INDEX idx_last_message_at (last_message_at)
);

room_type 값은 다음을 기준으로 한다.

direct  : 1:1 대화방
group   : 그룹 대화방
system  : 시스템 대화방
support : 상담 대화방

1차 구현에서는 direct, group만 사용해도 된다.


4-2. chat_room_members

대화방 참여자 정보를 저장한다.

CREATE TABLE chat_room_members (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    room_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    role VARCHAR(30) NOT NULL DEFAULT 'member',
    joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    left_at DATETIME NULL,
    last_read_message_id BIGINT UNSIGNED NULL,
    last_read_at DATETIME NULL,
    is_muted TINYINT(1) NOT NULL DEFAULT 0,
    is_pinned TINYINT(1) NOT NULL DEFAULT 0,

    UNIQUE KEY uq_room_user (room_id, user_id),
    INDEX idx_user_id (user_id),
    INDEX idx_room_id (room_id),
    INDEX idx_last_read_message_id (last_read_message_id)
);

role 값은 다음을 기준으로 한다.

owner
admin
member

left_at이 NULL이면 현재 참여 중인 상태다.
left_at이 있으면 나간 상태다.


4-3. chat_messages

채팅 메시지를 저장한다.

CREATE TABLE chat_messages (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    room_id BIGINT UNSIGNED NOT NULL,
    sender_id BIGINT UNSIGNED NOT NULL,
    message_type VARCHAR(30) NOT NULL DEFAULT 'text',
    content TEXT NULL,
    attachment_id BIGINT UNSIGNED NULL,
    reply_to_message_id BIGINT UNSIGNED NULL,
    is_deleted TINYINT(1) NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_room_id_id (room_id, id),
    INDEX idx_room_created_at (room_id, created_at),
    INDEX idx_sender_id (sender_id),
    INDEX idx_reply_to_message_id (reply_to_message_id)
);

message_type 값은 다음을 기준으로 한다.

text
image
file
system

1차 구현에서는 text만 처리한다.


4-4. user_push_tokens

사용자의 푸시 토큰을 저장한다.

CREATE TABLE user_push_tokens (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    device_id VARCHAR(255) NULL,
    platform VARCHAR(30) NOT NULL,
    push_token TEXT NOT NULL,
    token_type VARCHAR(30) NOT NULL DEFAULT 'expo',
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    last_used_at DATETIME NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_user_id (user_id),
    INDEX idx_platform (platform),
    INDEX idx_is_active (is_active)
);

platform 값:

ios
android
web

token_type 값:

expo
fcm

4-5. chat_message_reads

정밀한 메시지별 읽음 처리가 필요할 경우 사용하는 테이블이다.

1차 구현에서는 필수는 아니다.
1차에서는 chat_room_members.last_read_message_id 기반으로 읽음 처리를 한다.

필요 시 아래 테이블을 추가한다.

CREATE TABLE chat_message_reads (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    message_id BIGINT UNSIGNED NOT NULL,
    room_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    read_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uq_message_user (message_id, user_id),
    INDEX idx_room_user (room_id, user_id),
    INDEX idx_message_id (message_id)
);

5. REST API 설계

REST API는 대화방 생성, 목록 조회, 이전 메시지 조회, 푸시 토큰 등록 등에 사용한다.


5-1. 대화방 생성

POST /api/chat/rooms

요청 예시:

{
  "room_type": "group",
  "title": "테스트 대화방",
  "member_ids": [1, 2, 3]
}

처리 로직:

1. 로그인 사용자 확인
2. room_type 검증
3. member_ids 검증
4. chat_rooms 생성
5. 생성자를 포함하여 chat_room_members에 참여자 등록
6. 생성된 room_id 반환

응답 예시:

{
  "success": true,
  "room": {
    "id": 10,
    "room_type": "group",
    "title": "테스트 대화방",
    "created_by": 1,
    "created_at": "2026-07-03 15:00:00"
  }
}

5-2. 내 대화방 목록 조회

GET /api/chat/rooms

처리 로직:

1. 로그인 사용자 확인
2. chat_room_members에서 내가 참여 중인 방 조회
3. left_at IS NULL 조건 적용
4. 각 방의 마지막 메시지 조회
5. unread_count 계산
6. last_message_at 기준 최신순 정렬

응답 예시:

{
  "success": true,
  "rooms": [
    {
      "room_id": 10,
      "room_type": "group",
      "title": "테스트 대화방",
      "last_message": "안녕하세요",
      "last_message_at": "2026-07-03 15:10:00",
      "unread_count": 3,
      "member_count": 5,
      "is_muted": false,
      "is_pinned": false
    }
  ]
}

5-3. 대화방 상세 조회

GET /api/chat/rooms/:roomId

처리 로직:

1. 로그인 사용자 확인
2. 해당 사용자가 roomId의 참여자인지 확인
3. 방 정보 조회
4. 참여자 목록 조회
5. 반환

5-4. 메시지 목록 조회

GET /api/chat/rooms/:roomId/messages

쿼리 파라미터:

limit=30
before_message_id=100

처리 로직:

1. 로그인 사용자 확인
2. 사용자가 해당 방 참여자인지 확인
3. 최근 메시지 30개 조회
4. before_message_id가 있으면 해당 ID보다 오래된 메시지 조회
5. created_at 또는 id 기준 정렬

응답 예시:

{
  "success": true,
  "messages": [
    {
      "id": 101,
      "room_id": 10,
      "sender_id": 1,
      "sender_name": "홍길동",
      "message_type": "text",
      "content": "안녕하세요",
      "is_deleted": false,
      "created_at": "2026-07-03 15:10:00"
    }
  ]
}

5-5. 대화방 참여자 추가

POST /api/chat/rooms/:roomId/members

요청 예시:

{
  "member_ids": [4, 5]
}

처리 로직:

1. 로그인 사용자 확인
2. 사용자가 방 관리자 또는 owner인지 확인
3. 신규 참여자 추가
4. 시스템 메시지 저장
5. 참여자에게 실시간 이벤트 또는 푸시 발송

5-6. 대화방 나가기

DELETE /api/chat/rooms/:roomId/members/me

처리 로직:

1. 로그인 사용자 확인
2. 해당 방 참여 여부 확인
3. chat_room_members.left_at 업데이트
4. 시스템 메시지 저장
5. 다른 참여자에게 나가기 이벤트 발송

5-7. 푸시 토큰 등록

POST /api/push-token

요청 예시:

{
  "device_id": "device-unique-id",
  "platform": "android",
  "token_type": "expo",
  "push_token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"
}

처리 로직:

1. 로그인 사용자 확인
2. 동일 device_id 또는 동일 push_token이 있으면 업데이트
3. 없으면 신규 등록
4. is_active = 1 처리

6. Socket.IO 이벤트 설계

WebSocket은 실시간 메시지 송수신과 읽음 처리에 사용한다.


6-1. 연결

connect

클라이언트는 연결 시 로그인 토큰을 전달한다.

예시:

const socket = io(API_BASE_URL, {
  auth: {
    token: accessToken
  }
});

서버 처리:

1. token 검증
2. user_id 확인
3. socket.userId에 저장
4. 현재 접속자 목록에 user_id 등록
5. 사용자가 참여 중인 room 목록 조회
6. socket.join(`room:${roomId}`) 처리

6-2. 방 입장

room:join

요청:

{
  "room_id": 10
}

처리:

1. user_id 확인
2. 해당 사용자가 room_id 참여자인지 검증
3. socket.join(`room:${roomId}`)
4. 필요 시 last_read_message_id 갱신

6-3. 방 나가기

room:leave

요청:

{
  "room_id": 10
}

처리:

1. socket.leave(`room:${roomId}`)

6-4. 메시지 전송

message:send

요청:

{
  "room_id": 10,
  "message_type": "text",
  "content": "안녕하세요"
}

처리 로직:

1. socket에서 user_id 확인
2. 해당 사용자가 room_id의 참여자인지 확인
3. content 검증
4. chat_messages에 메시지 저장
5. chat_rooms.last_message_id 업데이트
6. chat_rooms.last_message_at 업데이트
7. 같은 방 참여자들에게 message:new 이벤트 발송
8. 현재 접속하지 않은 사용자에게 푸시 알림 발송

응답 또는 브로드캐스트 이벤트:

message:new
{
  "id": 101,
  "room_id": 10,
  "sender_id": 1,
  "sender_name": "홍길동",
  "message_type": "text",
  "content": "안녕하세요",
  "created_at": "2026-07-03 15:10:00"
}

6-5. 읽음 처리

message:read

요청:

{
  "room_id": 10,
  "last_read_message_id": 101
}

처리 로직:

1. socket에서 user_id 확인
2. 해당 사용자가 room_id 참여자인지 확인
3. chat_room_members.last_read_message_id 업데이트
4. chat_room_members.last_read_at 업데이트
5. 같은 방 참여자들에게 message:read:update 이벤트 발송

브로드캐스트 이벤트:

message:read:update
{
  "room_id": 10,
  "user_id": 1,
  "last_read_message_id": 101,
  "last_read_at": "2026-07-03 15:11:00"
}

6-6. 연결 종료

disconnect

처리:

1. socket.userId 확인
2. 현재 접속자 목록에서 제거
3. 필요 시 마지막 접속 시간 업데이트

7. 읽음/안 읽음 처리 방식

1차 구현에서는 chat_room_members.last_read_message_id 기반으로 처리한다.

안 읽은 메시지 수 계산 기준:

현재 방의 메시지 중
message.id > 내 last_read_message_id
AND sender_id != 내 user_id
AND is_deleted = 0

예시 SQL:

SELECT COUNT(*) AS unread_count
FROM chat_messages
WHERE room_id = ?
  AND id > COALESCE(?, 0)
  AND sender_id != ?
  AND is_deleted = 0;

방에 처음 들어온 사용자는 last_read_message_id가 NULL일 수 있다.
이 경우 0 또는 참여 시점 이후 메시지만 기준으로 처리한다.

권장 방식:

1. 신규 참여자의 last_read_message_id는 참여 시점의 마지막 메시지 ID로 설정한다.
2. 그래야 초대 전 과거 메시지가 전부 안 읽음으로 잡히지 않는다.

8. 푸시 알림 처리

메시지 저장 후 푸시 대상자를 계산한다.

푸시 대상 조건:

1. 같은 room_id의 참여자
2. sender_id가 아닌 사용자
3. left_at IS NULL
4. is_muted = 0
5. 현재 WebSocket 접속자가 아니거나 해당 room에 접속 중이 아닌 사용자
6. 활성 push_token이 있는 사용자

푸시 내용 예시:

{
  "title": "테스트 대화방",
  "body": "홍길동: 안녕하세요",
  "data": {
    "type": "chat",
    "room_id": 10,
    "message_id": 101
  }
}

앱에서 푸시를 누르면 room_id를 기준으로 해당 대화방 화면으로 이동한다.


9. 앱 화면 구성

모바일 앱에는 최소 3개 화면을 구현한다.


9-1. ChatRoomListScreen

대화방 목록 화면.

표시 항목:

1. 방 제목
2. 마지막 메시지
3. 마지막 메시지 시간
4. 안 읽은 메시지 수
5. 참여자 수
6. 알림 끔 여부

동작:

1. 화면 진입 시 GET /api/chat/rooms 호출
2. Socket.IO 연결 상태 확인
3. message:new 수신 시 해당 방의 마지막 메시지 갱신
4. unread_count 갱신
5. 방 클릭 시 ChatRoomScreen으로 이동

9-2. ChatRoomScreen

대화방 상세 화면.

표시 항목:

1. 메시지 목록
2. 메시지 입력창
3. 전송 버튼
4. 읽음 표시
5. 상단 방 제목

동작:

1. 화면 진입 시 GET /api/chat/rooms/:roomId/messages 호출
2. Socket.IO room:join 호출
3. 메시지 전송 시 message:send 호출
4. message:new 수신 시 메시지 목록에 추가
5. 화면에 들어온 마지막 메시지 기준으로 message:read 호출
6. 위로 스크롤 시 이전 메시지 추가 조회
7. 화면 이탈 시 room:leave 호출

9-3. ChatRoomCreateScreen

대화방 생성 화면.

기능:

1. 참여자 선택
2. 방 제목 입력
3. 대화방 생성
4. 생성 완료 후 ChatRoomScreen으로 이동

호출 API:

POST /api/chat/rooms

10. 서버 폴더 구조 예시

Node.js 서버는 아래 구조를 권장한다.

server/
  src/
    app.js
    server.js

    config/
      db.js
      socket.js
      firebase.js

    middleware/
      authMiddleware.js
      socketAuthMiddleware.js

    routes/
      chatRoutes.js
      pushRoutes.js

    controllers/
      chatController.js
      pushController.js

    services/
      chatService.js
      socketService.js
      pushService.js

    repositories/
      chatRepository.js
      userRepository.js

    utils/
      response.js
      date.js
      logger.js

  package.json
  .env

역할 분리:

routes        : URL 라우팅
controllers   : 요청/응답 처리
services      : 비즈니스 로직
repositories  : DB 쿼리
middleware    : 인증/권한 검증
socketService : Socket.IO 이벤트 처리
pushService   : 푸시 발송 처리

11. 환경 변수 예시

.env 예시:

NODE_ENV=development
PORT=3000

DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=비밀번호
DB_NAME=앱DB명

JWT_SECRET=JWT_SECRET_VALUE

PUSH_PROVIDER=expo
EXPO_ACCESS_TOKEN=

FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=

12. 필요한 npm 패키지 예시

npm install express socket.io mysql2 dotenv cors jsonwebtoken
npm install axios
npm install firebase-admin
npm install dayjs
npm install nodemon --save-dev

Expo Push만 사용할 경우 firebase-admin은 1차에서 제외 가능하다.
FCM HTTP v1을 직접 사용할 경우 firebase-admin 또는 Google Auth 관련 설정이 필요하다.


13. 보안 및 권한 검증 필수 항목

모든 API와 Socket 이벤트에서 아래 검증을 반드시 수행한다.

1. 로그인 토큰이 유효한가?
2. user_id가 확인되는가?
3. 사용자가 해당 room_id의 참여자인가?
4. left_at이 NULL인가?
5. 메시지 내용이 비어 있지 않은가?
6. 메시지 길이가 제한을 넘지 않는가?
7. 요청한 member_ids가 실제 존재하는 사용자인가?
8. 방 생성 권한이 있는가?
9. 방 초대 권한이 있는가?
10. 나간 사용자가 메시지를 보낼 수 없도록 막았는가?

메시지 최대 길이 예시:

텍스트 메시지 최대 2000자

14. 구현 순서

아래 순서대로 작업한다.


1단계. DB 마이그레이션 작성

다음 테이블을 생성한다.

chat_rooms
chat_room_members
chat_messages
user_push_tokens

필요하면 chat_message_reads는 나중에 추가한다.


2단계. Node.js 기본 서버 구성

1. Express 서버 생성
2. MySQL/MariaDB 연결
3. JWT 인증 미들웨어 생성
4. 기본 API 응답 포맷 통일
5. 에러 핸들러 추가

3단계. 채팅 REST API 구현

다음 API를 구현한다.

POST   /api/chat/rooms
GET    /api/chat/rooms
GET    /api/chat/rooms/:roomId
GET    /api/chat/rooms/:roomId/messages
POST   /api/chat/rooms/:roomId/members
DELETE /api/chat/rooms/:roomId/members/me
POST   /api/push-token

4단계. 앱에서 REST API 연결

모바일 앱에서 다음 화면을 구현한다.

1. ChatRoomListScreen
2. ChatRoomScreen
3. ChatRoomCreateScreen

먼저 실시간 기능 없이 REST API만으로 대화방 생성, 목록 조회, 메시지 목록 조회가 되도록 만든다.


5단계. Socket.IO 서버 구현

다음 이벤트를 구현한다.

connect
disconnect
room:join
room:leave
message:send
message:new
message:read
message:read:update

6단계. 앱에서 Socket.IO 연결

앱에서 Socket.IO client를 연결한다.

1. 로그인 후 socket 연결
2. 대화방 진입 시 room:join
3. 메시지 전송 시 message:send
4. message:new 수신 시 화면 업데이트
5. 읽음 처리 시 message:read
6. 화면 이탈 시 room:leave

7단계. 읽음/안 읽음 처리 구현

다음 기능을 구현한다.

1. 대화방 진입 시 마지막 메시지 읽음 처리
2. 대화방 목록에 unread_count 표시
3. 메시지 수신 시 unread_count 증가
4. 현재 보고 있는 방이면 즉시 읽음 처리

8단계. 푸시 알림 구현

다음 기능을 구현한다.

1. 앱에서 푸시 권한 요청
2. 푸시 토큰 발급
3. 서버에 푸시 토큰 저장
4. 메시지 전송 시 미접속자 확인
5. 미접속자에게 푸시 알림 발송
6. 푸시 클릭 시 해당 대화방으로 이동

9단계. 테스트

아래 시나리오를 테스트한다.

1. 사용자 A가 방 생성
2. 사용자 B를 초대
3. 사용자 A가 메시지 전송
4. 사용자 B 앱이 켜져 있으면 실시간 수신
5. 사용자 B 앱이 꺼져 있으면 푸시 수신
6. 사용자 B가 방에 들어가면 읽음 처리
7. 대화방 목록 unread_count 감소
8. 사용자 B가 방을 나가면 더 이상 메시지 수신 불가
9. 나간 사용자가 API로 메시지 전송 시도 시 실패
10. 잘못된 room_id 접근 시 실패

15. API 응답 포맷 통일

성공 응답:

{
  "success": true,
  "data": {}
}

실패 응답:

{
  "success": false,
  "message": "에러 메시지"
}

권장 HTTP 상태 코드:

200 OK
201 Created
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error

16. 메시지 전송 서버 처리 의사코드

async function sendMessage({ roomId, senderId, messageType, content }) {
  // 1. 방 참여 여부 확인
  const member = await chatRepository.findActiveMember(roomId, senderId);
  if (!member) {
    throw new Error('대화방 참여자가 아닙니다.');
  }

  // 2. 메시지 검증
  if (messageType === 'text' && (!content || !content.trim())) {
    throw new Error('메시지 내용이 비어 있습니다.');
  }

  // 3. 메시지 저장
  const message = await chatRepository.createMessage({
    roomId,
    senderId,
    messageType,
    content
  });

  // 4. 방 마지막 메시지 갱신
  await chatRepository.updateRoomLastMessage(roomId, message.id, message.created_at);

  // 5. 실시간 전송
  socketService.emitToRoom(roomId, 'message:new', message);

  // 6. 푸시 대상자 조회
  const pushTargets = await chatRepository.findPushTargets(roomId, senderId);

  // 7. 미접속자에게 푸시 발송
  await pushService.sendChatPush(pushTargets, message);

  return message;
}

17. 읽음 처리 의사코드

async function markAsRead({ roomId, userId, lastReadMessageId }) {
  // 1. 방 참여 여부 확인
  const member = await chatRepository.findActiveMember(roomId, userId);
  if (!member) {
    throw new Error('대화방 참여자가 아닙니다.');
  }

  // 2. last_read_message_id 갱신
  await chatRepository.updateLastReadMessage({
    roomId,
    userId,
    lastReadMessageId
  });

  // 3. 같은 방 참여자에게 읽음 상태 전송
  socketService.emitToRoom(roomId, 'message:read:update', {
    room_id: roomId,
    user_id: userId,
    last_read_message_id: lastReadMessageId,
    last_read_at: new Date()
  });
}

18. 앱 쪽 처리 흐름

앱 실행 시

1. 저장된 로그인 토큰 확인
2. API 서버 연결 확인
3. Socket.IO 연결
4. 푸시 토큰 확인 및 서버 등록
5. 대화방 목록 조회

대화방 목록 진입 시

1. GET /api/chat/rooms 호출
2. rooms 상태 저장
3. message:new 이벤트 수신 시 해당 room 업데이트
4. unread_count 반영

대화방 상세 진입 시

1. GET /api/chat/rooms/:roomId/messages 호출
2. socket.emit('room:join', { room_id })
3. 메시지 목록 표시
4. 마지막 메시지 기준으로 message:read 호출

메시지 전송 시

1. 입력값 검증
2. 임시 메시지 UI 표시 가능
3. socket.emit('message:send', payload)
4. 서버에서 message:new 수신 후 실제 메시지로 갱신

대화방 이탈 시

1. socket.emit('room:leave', { room_id })
2. 필요 시 message:read 호출

19. 주의사항

다음 방식으로 구현하지 말 것.

1. 메시지를 앱 로컬에만 저장하지 말 것
2. WebSocket만으로 메시지 이력을 관리하지 말 것
3. 방 참여자 검증 없이 메시지 조회/전송을 허용하지 말 것
4. 푸시 토큰을 사용자당 1개만 저장하는 구조로 만들지 말 것
5. 읽음 처리를 나중에 붙일 수 있다고 가정하지 말 것
6. 처음부터 이미지/파일 전송까지 한 번에 구현하지 말 것
7. 대화방 목록에서 unread_count 계산을 비효율적으로 전체 메시지 스캔으로 처리하지 말 것

20. 최종 완료 기준

1차 완료 기준은 다음과 같다.

1. 사용자가 대화방을 생성할 수 있다.
2. 자신이 참여한 대화방 목록이 표시된다.
3. 대화방에 들어가면 기존 메시지가 조회된다.
4. 텍스트 메시지를 전송할 수 있다.
5. 같은 방 참여자에게 메시지가 실시간으로 도착한다.
6. 앱을 꺼둔 사용자는 푸시 알림을 받는다.
7. 푸시 알림을 누르면 해당 대화방으로 이동한다.
8. 대화방 목록에 안 읽은 메시지 수가 표시된다.
9. 대화방에 들어가면 읽음 처리가 된다.
10. 방을 나간 사용자는 더 이상 메시지를 받을 수 없다.
11. 방 참여자가 아닌 사용자는 메시지 조회/전송을 할 수 없다.
목록보기
공지  [업데이트] (주)편안 그룹웨어 모바일 앱 v1.0.14  2026-08-12 42
공지  (주)편안 그룹웨어 모바일 앱 업데이트 방법  2026-08-06 49
16  [Ing] (주)편안 모바일 앱 v1.0.10 업데이트 내용  2026-07-23 48
15  푸시 테스트 진행 중!  2026-07-22 39
14  모바일 앱 v1.0.9 업데이트  2026-07-21 44
13  안드로이드 1.0.6 테스트중  2026-07-21 36
12  (주)편안 그룹웨어 모바일 앱 v1.0.5 테스트 시작  2026-07-16 37
11  (주)편안 앱 테스트 중  2026-07-14 40
10  [iOS] (주)편안 모바일 앱 기기 등록 중  2026-07-14 43
9  [Android] (주)편안 모바일 앱 Ver1.0.3  2026-07-14 46
8  7월 문화의 날 행사 일정 - 7월 24일  2026-07-09 43
7  2026.07.09 - 편안 ERP 1차 개발 상황  2026-07-09 57
6  모바일 앱 + ERP 웹 연동 전자결재 시스템 개발 계획  2026-07-09 23
 모바일 앱 + ERP 웹 연동 채팅방(톡) 기능 개발 계획  2026-07-08 22
4  푸시 알림 성공!  2026-06-26 24
1 2