From e763249342d25f87f82cbd8f92d4932c9982d090 Mon Sep 17 00:00:00 2001 From: syc0123 Date: Mon, 30 Mar 2026 17:52:57 +0900 Subject: [PATCH] [RAPID] PCC: MSN --- docs/yc/MSN[계획]-메신저기능.md | 212 ++++++++++++++++++++++++++++++++ docs/yc/MSN[맥락]-메신저기능.md | 61 +++++++++ docs/yc/MSN[체크]-메신저기능.md | 97 +++++++++++++++ 3 files changed, 370 insertions(+) create mode 100644 docs/yc/MSN[계획]-메신저기능.md create mode 100644 docs/yc/MSN[맥락]-메신저기능.md create mode 100644 docs/yc/MSN[체크]-메신저기능.md diff --git a/docs/yc/MSN[계획]-메신저기능.md b/docs/yc/MSN[계획]-메신저기능.md new file mode 100644 index 00000000..f75eca41 --- /dev/null +++ b/docs/yc/MSN[계획]-메신저기능.md @@ -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` | ``, `` 추가 | + +--- + +## 코드 설계 + +### 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 또는 최상단 마운트) diff --git a/docs/yc/MSN[맥락]-메신저기능.md b/docs/yc/MSN[맥락]-메신저기능.md new file mode 100644 index 00000000..cd83c315 --- /dev/null +++ b/docs/yc/MSN[맥락]-메신저기능.md @@ -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}` 형식으로 회사별 격리. diff --git a/docs/yc/MSN[체크]-메신저기능.md b/docs/yc/MSN[체크]-메신저기능.md new file mode 100644 index 00000000..50ae06d4 --- /dev/null +++ b/docs/yc/MSN[체크]-메신저기능.md @@ -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 | 최초 설계 확정 |