[RAPID] PCC: MSN

This commit is contained in:
2026-03-30 17:52:57 +09:00
parent 8169420e61
commit e763249342
3 changed files with 370 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
# MSN[계획] 메신저 기능 개발
## 개요
벡스플로어 ERP에 내장 메신저 기능을 추가한다. Gmail 편지쓰기 스타일의 우측 하단 플로팅 모달로 동작하며, Socket.IO 기반 실시간 통신을 제공한다. 모든 화면에서 접근 가능하고, 동일 company_code 내 사용자끼리 1:1 DM / 그룹 채팅 / 채널 대화를 지원한다.
---
## 현재 동작
- 메신저 기능 없음
- 사내 커뮤니케이션 수단 부재
## 변경 후 동작
- 모든 화면 우측 하단에 메신저 FAB 버튼 고정 (z-index: 9999)
- FAB 클릭 시 Gmail 편지쓰기 스타일 모달 팝업 (우측 하단)
- 모달 좌측: 채팅방 목록 (DM / 그룹 / 채널 탭)
- 모달 우측: 채팅 영역 (메시지 입력, 파일 첨부, 이모지, 멘션, 스레드)
- Socket.IO로 실시간 메시지 수신
- 읽지 않은 메시지 수 FAB 배지 표시
- 토스트 알림 on/off 토글 (메신저 설정 내)
---
## 시각적 예시
```
┌─────────────────────────────────────────────────────┐
│ 벡스플로어 화면 │
│ │
│ │
│ ┌────────────────────┐ │
│ │ 채팅방 목록 │ 채팅창 │ │
│ │─────────────────── │ │
│ │ DM 그룹 채널 │ │
│ │ ───────────────── │ │
│ │ 👤 김민호 ● │ │
│ │ 👥 개발팀 │ │
│ │ # 공지사항 │ │
│ └────────────────────┘ │
│ [💬 3] │
└─────────────────────────────────────────────────────┘
```
---
## 아키텍처
```mermaid
graph TB
subgraph Frontend
FAB[MessengerFAB] --> Modal[MessengerModal]
Modal --> RoomList[RoomList 좌측 240px]
Modal --> ChatPanel[ChatPanel 우측 480px]
ChatPanel --> MessageList[MessageList]
ChatPanel --> MessageInput[MessageInput]
MessageInput --> FileUpload[파일 첨부]
MessageInput --> EmojiPicker[이모지]
MessageInput --> MentionDropdown[멘션 @]
end
subgraph SocketIO
SocketClient[socket.io-client] <--> SocketServer[messengerSocket.ts]
end
subgraph Backend
SocketServer --> MessengerService[messengerService.ts]
Route[messengerRoutes.ts] --> Controller[messengerController.ts]
Controller --> MessengerService
MessengerService --> DB[(PostgreSQL)]
FileRoute[파일 업로드] --> Multer[multerMessengerConfig.ts]
end
Frontend <--> SocketIO
Frontend <--> Route
```
---
## 변경 파일
### 신규 생성
| 파일 | 역할 |
|------|------|
| `backend-node/src/types/messenger.ts` | TypeScript 인터페이스 |
| `backend-node/src/services/messengerService.ts` | 비즈니스 로직 |
| `backend-node/src/controllers/messengerController.ts` | HTTP 핸들러 |
| `backend-node/src/routes/messengerRoutes.ts` | REST API 라우트 |
| `backend-node/src/socket/messengerSocket.ts` | Socket.IO 이벤트 핸들러 |
| `backend-node/src/config/multerMessengerConfig.ts` | 파일 업로드 설정 |
| `db/migrations/messenger_tables.sql` | DB 테이블 생성 |
| `frontend/components/messenger/MessengerFAB.tsx` | 플로팅 버튼 |
| `frontend/components/messenger/MessengerModal.tsx` | 메인 모달 컨테이너 |
| `frontend/components/messenger/RoomList.tsx` | 채팅방 목록 |
| `frontend/components/messenger/ChatPanel.tsx` | 채팅 영역 |
| `frontend/components/messenger/MessageItem.tsx` | 메시지 단일 아이템 |
| `frontend/components/messenger/MessageInput.tsx` | 입력창 |
| `frontend/components/messenger/NewRoomModal.tsx` | 방 생성 모달 |
| `frontend/components/messenger/UserAvatar.tsx` | 프로필 아바타 |
| `frontend/hooks/useMessenger.ts` | 메신저 상태 훅 |
| `frontend/hooks/useMessengerSocket.ts` | Socket.IO 훅 |
| `frontend/contexts/MessengerContext.tsx` | 전역 메신저 상태 |
### 수정
| 파일 | 변경 내용 |
|------|-----------|
| `backend-node/src/app.ts` | Socket.IO 서버 초기화, messengerRoutes 등록 |
| `frontend/app/(main)/layout.tsx` | `<MessengerProvider>`, `<MessengerFAB />` 추가 |
---
## 코드 설계
### DB 스키마
```sql
-- 채팅방
messenger_rooms (
room_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_code VARCHAR(50) NOT NULL,
room_type VARCHAR(10) NOT NULL CHECK (room_type IN ('dm', 'group', 'channel')),
room_name VARCHAR(100),
created_by VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
)
-- 참여자
messenger_participants (
room_id UUID REFERENCES messenger_rooms(room_id),
user_id VARCHAR(100) NOT NULL,
company_code VARCHAR(50) NOT NULL,
joined_at TIMESTAMP DEFAULT NOW(),
last_read_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (room_id, user_id)
)
-- 메시지
messenger_messages (
message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID REFERENCES messenger_rooms(room_id),
company_code VARCHAR(50) NOT NULL,
sender_id VARCHAR(100) NOT NULL,
content TEXT,
message_type VARCHAR(10) DEFAULT 'text' CHECK (message_type IN ('text', 'file', 'system')),
parent_message_id UUID REFERENCES messenger_messages(message_id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
is_deleted BOOLEAN DEFAULT FALSE
)
-- 이모지 리액션
messenger_reactions (
message_id UUID REFERENCES messenger_messages(message_id),
user_id VARCHAR(100) NOT NULL,
emoji VARCHAR(10) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (message_id, user_id, emoji)
)
-- 파일 첨부
messenger_files (
file_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID REFERENCES messenger_messages(message_id),
filename VARCHAR(255) NOT NULL,
original_name VARCHAR(255) NOT NULL,
file_size BIGINT NOT NULL,
mime_type VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
)
```
### Socket.IO 이벤트
| 이벤트 | 방향 | 설명 |
|--------|------|------|
| `join_rooms` | Client→Server | 참여 중인 방 전체 구독 |
| `send_message` | Client→Server | 메시지 전송 |
| `new_message` | Server→Client | 새 메시지 수신 |
| `message_read` | Client→Server | 읽음 처리 |
| `user_online` | Server→Client | 온라인 상태 변경 |
| `typing_start/stop` | Client↔Server | 타이핑 표시 |
| `add_reaction` | Client→Server | 이모지 리액션 추가 |
| `reaction_updated` | Server→Client | 리액션 업데이트 |
### REST API
| Method | URL | 설명 |
|--------|-----|------|
| GET | `/api/messenger/rooms` | 내 채팅방 목록 |
| POST | `/api/messenger/rooms` | 채팅방 생성 |
| GET | `/api/messenger/rooms/:roomId/messages` | 메시지 히스토리 |
| POST | `/api/messenger/rooms/:roomId/read` | 읽음 처리 |
| POST | `/api/messenger/files/upload` | 파일 업로드 |
| GET | `/api/messenger/files/:fileId` | 파일 다운로드 |
| GET | `/api/messenger/users` | 회사 내 사용자 목록 |
| PUT | `/api/messenger/rooms/:roomId` | 방 이름/설정 수정 |
---
## 예상 문제
1. **Socket.IO + Next.js**: Next.js는 기본적으로 HTTP 서버를 추상화하므로, Express HTTP 서버에 Socket.IO를 붙이고 프론트엔드는 백엔드 포트로 직접 연결
2. **JWT 인증 with Socket.IO**: handshake 시 Authorization 헤더 또는 auth 옵션으로 토큰 전달, 서버에서 미들웨어로 검증
3. **채널 분리 가능성**: `room_type = 'channel'` 쿼리 조건으로만 필터링하므로, 채널 UI/라우트만 제거하면 기능 완전 제거 가능
---
## 설계 원칙
- 기존 MVC + Service 패턴 준수
- 모든 쿼리에 `company_code` 필터 적용 (멀티테넌시)
- 채널은 `room_type` 값으로만 분리 — 코드 결합도 최소화
- FAB/모달은 기존 레이아웃 DOM에 독립적으로 렌더링 (Portal 또는 최상단 마운트)

View File

@@ -0,0 +1,61 @@
# MSN[맥락] 메신저 기능 개발
## 왜 하는가
벡스플로어 ERP 내에서 사용자 간 실시간 커뮤니케이션 수단이 없다. 업무 맥락을 ERP 밖(카카오톡, 슬랙 등)으로 내보내지 않고 시스템 안에서 처리할 수 있도록 내장 메신저를 도입한다.
---
## 핵심 결정 및 근거
| 결정 | 근거 |
|------|------|
| Socket.IO 신규 도입 | HTTP 폴링은 메신저에 부적합. 실시간 타이핑 표시, 온라인 상태, 즉각적인 메시지 수신이 필요 |
| Gmail 스타일 우측 하단 모달 | 업무 화면을 방해하지 않고 언제든 접근 가능. 별도 페이지 이동 없이 사용 |
| DM + 그룹 + 채널 1차 구현 | 채널은 `room_type` 값으로만 분리하여 나중에 제거 용이하게 설계 |
| company_code 격리 | 기존 멀티테넌시 패턴과 동일하게 적용. 회사 간 데이터 유출 방지 |
| 파일 업로드: multer 로컬 | 기존 메일 첨부파일과 동일한 방식. 인프라 추가 없이 일관성 유지 |
| 프로필: photo BLOB 활용 | `user_info.photo` 컬럼에 이미 이미지 저장됨. 없으면 이름 첫 글자 원형 아바타 |
| 토스트 알림 on/off | 집중 업무 시 알림 방해 방지. localStorage에 설정 저장 |
---
## 관련 파일
### 참고 패턴 (기존 코드)
| 파일 | 참고 목적 |
|------|-----------|
| `backend-node/src/config/multerConfig.ts` | 파일 업로드 설정 패턴 |
| `backend-node/src/services/authService.ts` | user_info.photo BLOB → base64 변환 패턴 |
| `backend-node/src/middleware/authMiddleware.ts` | JWT 인증 미들웨어 (Socket.IO handshake에도 동일 로직 적용) |
| `frontend/components/mail/` | UI 컴포넌트 구조 참고 |
| `frontend/hooks/use-toast.ts` | 기존 토스트 훅 사용 |
| `backend-node/src/app.ts` | 라우트 등록 및 미들웨어 설정 위치 |
---
## 기술 참고
### Socket.IO JWT 인증
```typescript
// 서버: handshake 시 토큰 검증
io.use((socket, next) => {
const token = socket.handshake.auth.token;
const user = verifyJWT(token);
socket.data.user = user;
next();
});
// 클라이언트: 연결 시 토큰 전달
const socket = io(BACKEND_URL, {
auth: { token: localStorage.getItem('token') }
});
```
### 채널 분리 설계
채널 기능은 `room_type = 'channel'` 조건으로만 분리되어 있음.
제거 시: RoomList의 채널 탭 UI 제거 + `/api/messenger/rooms?type=channel` 호출 제거만으로 완전 비활성화 가능. DB 스키마 변경 불필요.
### 멀티테넌시 적용
모든 쿼리에 `WHERE company_code = $n` 조건 필수 적용.
Socket.IO 룸 네이밍: `{company_code}:{room_id}` 형식으로 회사별 격리.

View File

@@ -0,0 +1,97 @@
# MSN[체크] 메신저 기능 개발
## 공정 상태: 0%
---
## 구현 체크리스트
### Phase 1: DB & 백엔드 기반
- [ ] `db/migrations/messenger_tables.sql` 작성 및 실행
- [ ] `backend-node/src/types/messenger.ts` 타입 정의
- [ ] `backend-node/src/services/messengerService.ts` 구현
- [ ] getRooms (내 채팅방 목록)
- [ ] createRoom (DM / 그룹 / 채널)
- [ ] getMessages (메시지 히스토리, 페이지네이션)
- [ ] sendMessage
- [ ] markAsRead
- [ ] getCompanyUsers (사용자 목록)
- [ ] addReaction / removeReaction
- [ ] `backend-node/src/controllers/messengerController.ts` 구현
- [ ] `backend-node/src/routes/messengerRoutes.ts` 구현
- [ ] `backend-node/src/config/multerMessengerConfig.ts` 구현 (파일 업로드)
- [ ] `backend-node/src/socket/messengerSocket.ts` 구현
- [ ] JWT 인증 미들웨어
- [ ] join_rooms 이벤트
- [ ] send_message 이벤트
- [ ] message_read 이벤트
- [ ] typing_start / typing_stop 이벤트
- [ ] add_reaction 이벤트
- [ ] 온라인 상태 관리 (connect / disconnect)
- [ ] `backend-node/src/app.ts` Socket.IO 초기화 및 라우트 등록
### Phase 2: 프론트엔드 컴포넌트
- [ ] `frontend/contexts/MessengerContext.tsx` (전역 상태)
- [ ] `frontend/hooks/useMessengerSocket.ts` (Socket.IO 연결 관리)
- [ ] `frontend/hooks/useMessenger.ts` (채팅방/메시지 React Query)
- [ ] `frontend/components/messenger/UserAvatar.tsx` (프로필 이미지 / 이름 첫 글자)
- [ ] `frontend/components/messenger/MessengerFAB.tsx` (플로팅 버튼 + 배지)
- [ ] `frontend/components/messenger/MessengerModal.tsx` (메인 모달 컨테이너)
- [ ] `frontend/components/messenger/RoomList.tsx` (채팅방 목록 + DM/그룹/채널 탭)
- [ ] `frontend/components/messenger/ChatPanel.tsx` (채팅 영역)
- [ ] `frontend/components/messenger/MessageItem.tsx` (메시지 + 리액션 + 스레드 버튼)
- [ ] `frontend/components/messenger/MessageInput.tsx` (입력창 + 파일 + 이모지 + 멘션)
- [ ] `frontend/components/messenger/NewRoomModal.tsx` (방 생성 모달)
- [ ] `frontend/components/messenger/MessengerSettings.tsx` (토스트 알림 on/off 등)
- [ ] `frontend/app/(main)/layout.tsx` MessengerProvider + MessengerFAB 추가
---
## 검증 체크리스트
### 기본 동작
- [ ] FAB 버튼이 모든 페이지에서 우측 하단 고정 (z-index 최상위)
- [ ] FAB 클릭 시 모달 열기/닫기 동작
- [ ] 모달 크기: 좌측 240px + 우측 480px
### 채팅
- [ ] DM 방 생성 (사용자 선택 → 방 생성)
- [ ] 그룹 방 생성 (여러 사용자 선택 → 방 이름 입력 → 생성)
- [ ] 채널 방 생성
- [ ] 메시지 전송 및 실시간 수신 (Socket.IO)
- [ ] 메시지 히스토리 로드 (스크롤 시 이전 메시지)
### 부가 기능
- [ ] 파일 첨부 업로드/다운로드
- [ ] 이모지 리액션 추가/제거
- [ ] 멘션(@) 자동완성 드롭다운
- [ ] 스레드 답글
- [ ] 타이핑 표시 ("김민호님이 입력 중...")
### 알림
- [ ] 읽지 않은 메시지 수 FAB 배지 표시
- [ ] 다른 방 메시지 수신 시 토스트 알림
- [ ] 토스트 알림 on/off 토글 동작
- [ ] 토스트 설정값 localStorage 저장/복원
### 프로필
- [ ] photo 있는 사용자: 원형 이미지 표시
- [ ] photo 없는 사용자: 이름 첫 글자 원형 아바타 표시
### 멀티테넌시
- [ ] 다른 company_code 사용자 목록에 미노출
- [ ] 다른 회사 채팅방 접근 불가
---
## 정리
- [ ] DB 체크포인트 파일 삭제 (2차 테스트 완료 후)
---
## 변경 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-30 | 최초 설계 확정 |