AI 기반 프라이버시 관리 시스템 백엔드 인프라입니다.
ai-privacy-system/
backend/
collaboration-server/
src/
config/ # 환경변수 관리
routes/ # API 라우트 (status, masking)
db/ # PostgreSQL 연결
storage/ # MinIO 스토리지 연결
middlewares/ # HTTP 로거, JWT 인증
services/ # 마스킹 서비스
websocket/ # Socket.io 협업 처리
.env.example # 환경변수 템플릿
docker-compose.yml # 인프라 컨테이너 설정
| 분류 | 기술 |
|---|---|
| Runtime | Node.js + TypeScript |
| Framework | Express v5 |
| WebSocket | Socket.io v4 |
| Database | PostgreSQL 16 |
| Object Storage | MinIO (S3 호환) |
| Cache | Redis 7 |
| Container | Docker |
| 서버 | 역할 | 주소 |
|---|---|---|
| Django | 메인 백엔드 (인증, 파일 업로드) | localhost:8000 |
| collaboration-server | 실시간 협업, 마스킹 | localhost:4000 |
| React FE | 프론트엔드 | localhost:5173 |
cp backend/collaboration-server/.env.example backend/collaboration-server/.env.env 파일을 열어 JWT_SECRET 등 값을 채워넣어.
docker-compose up -d| 서비스 | 주소 |
|---|---|
| PostgreSQL | localhost:5560 |
| MinIO API | localhost:9000 |
| MinIO 콘솔 | http://localhost:9001 |
| Redis | localhost:6379 |
MinIO 기본 계정: minioadmin / minioadmin
cd backend/collaboration-server
npm install
npm run devGET http://localhost:4000/status
응답:
{ "ok": true, "message": "collaboration-server is running" }| 변수 | 설명 | 기본값 |
|---|---|---|
| PORT | 서버 포트 | 4000 |
| NODE_ENV | 실행 환경 | development |
| PG_HOST | PostgreSQL 호스트 | localhost |
| PG_PORT | PostgreSQL 포트 | 5432 |
| PG_USER | PostgreSQL 유저 | postgres |
| PG_PASSWORD | PostgreSQL 비밀번호 | postgres123 |
| PG_DATABASE | PostgreSQL DB명 | ai_privacy |
| MINIO_ENDPOINT | MinIO 호스트 | localhost |
| MINIO_PORT | MinIO 포트 | 9000 |
| MINIO_ACCESS_KEY | MinIO 액세스 키 | minioadmin |
| MINIO_SECRET_KEY | MinIO 시크릿 키 | minioadmin |
| MINIO_BUCKET | 기본 버킷 이름 | documents |
| REDIS_HOST | Redis 호스트 | localhost |
| REDIS_PORT | Redis 포트 | 6379 |
| JWT_SECRET | JWT 서명 키 (Django와 동일) | - |
주의:
JWT_SECRET은 Django 서버와 동일한 값으로 설정해야 합니다.
cd C:\dev\ai-privacy-system
docker-compose up -ddocker ps
# ai-postgres, ai-minio, ai-redis 세 컨테이너가 Up 상태여야 함cd backend/collaboration-server
npm install # 최초 1회
npm run dev # ts-node-dev로 핫리로드 실행서버가 http://localhost:4000 에서 시작됩니다.
파일 업로드/다운로드는 Django 서버(
:8000)에서 처리합니다.
curl http://localhost:4000/status
# { "ok": true, "message": "collaboration-server is running" }curl -X POST http://localhost:4000/masking/text \
-H "Content-Type: application/json" \
-d '{"text": "홍길동 010-1234-5678 주민번호 901010-1234567"}'응답:
{
"original": "홍길동 010-1234-5678 주민번호 901010-1234567",
"masked": "홍길동 010-****-**** 주민번호 901010-*******",
"detections": [
{ "type": "전화번호", "original": "010-1234-5678", "masked": "010-****-****", "index": 4 },
{ "type": "주민등록번호", "original": "901010-1234567", "masked": "901010-*******", "index": 16 }
]
}curl -X POST http://localhost:4000/masking/detect \
-H "Content-Type: application/json" \
-d '{"text": "이메일: user@example.com"}'curl -X POST http://localhost:4000/masking/file \
-F "file=@/path/to/document.txt"
# 마스킹된 파일 반환지원 PII 유형: 주민등록번호, 전화번호, 이메일, 카드번호, 계좌번호, 여권번호, 운전면허번호, IP 주소
Socket.io 클라이언트 기준 예시입니다.
WebSocket 연결 시 Django에서 발급한 JWT 토큰을 반드시 전달해야 합니다.
import { io } from "socket.io-client";
const socket = io("http://localhost:4000", {
auth: { token: "Bearer eyJ..." } // Django accessToken
});
socket.on("connect_error", (err) => {
console.error("인증 실패:", err.message);
});userId와 userName은 JWT에서 자동으로 추출되므로 클라이언트가 보낼 필요 없습니다.
// 룸 입장
socket.emit("room:join", {
roomId: "project-room-1",
sessionToken: null // 재연결 시 이전 sessionToken 전달
});
// 입장 확인 수신
socket.on("room:joined", ({ users, sessionToken, isReconnect }) => {
console.log("현재 룸 유저:", users);
localStorage.setItem("sessionToken", sessionToken); // 재연결용 보관
});// 내가 어느 영역에서 작업 중인지 알리기
socket.emit("presence:update", {
roomId: "project-room-1",
activeArea: "section-2",
status: "active" // "active" | "idle"
});
// 다른 유저 프레즌스 변경 수신
socket.on("presence:updated", (presence) => {
console.log(`${presence.userName}이 ${presence.activeArea} 작업 중`);
});// 커서 위치 전송
socket.emit("cursor:move", {
roomId: "project-room-1",
x: 120,
y: 350,
areaId: "section-2"
});
// 다른 유저 커서 수신
socket.on("cursor:moved", ({ userId, userName, x, y, areaId }) => {
// 커서 렌더링
});// 영역 잠금 요청
socket.emit("area:lock", {
roomId: "project-room-1",
areaId: "section-2"
});
// 잠금 결과 수신
socket.on("area:locked", ({ areaId, lock, denied }) => {
if (denied) {
console.log("이미 다른 사람이 편집 중");
} else {
// 잠금 성공 → 편집 가능
socket.emit("area:change", {
roomId: "project-room-1",
areaId: "section-2",
changes: { content: "수정된 내용" }
});
}
});
// 편집 완료 후 잠금 해제
socket.emit("area:unlock", {
roomId: "project-room-1",
areaId: "section-2"
});socket.on("connect", () => {
const token = localStorage.getItem("sessionToken");
if (token) {
socket.emit("room:join", {
roomId: "project-room-1",
sessionToken: token // 세션 복원 (10초 이내 재연결 시 상태 유지)
});
}
});| 방향 | 이벤트 | 페이로드 |
|---|---|---|
| C→S | room:join |
{ roomId, sessionToken? } |
| C→S | room:leave |
{ roomId } |
| S→C | room:joined |
{ users, sessionToken, isReconnect } |
| S→Room | room:user-joined |
{ user } |
| S→Room | room:user-left |
{ user } |
| S→Room | room:reconnected |
{ user } |
| 방향 | 이벤트 | 페이로드 |
|---|---|---|
| C→S | presence:update |
{ roomId, activeArea, status } |
| C→S | cursor:move |
{ roomId, x, y, areaId } |
| S→C | presence:sync |
전체 프레즌스 맵 |
| S→Room | presence:updated |
presence 객체 |
| S→Room | cursor:moved |
{ userId, userName, x, y, areaId } |
| 방향 | 이벤트 | 페이로드 |
|---|---|---|
| C→S | area:lock |
{ roomId, areaId } |
| C→S | area:unlock |
{ roomId, areaId } |
| C→S | area:change |
{ roomId, areaId, changes } |
| S→C | area:state-sync |
전체 영역 상태 |
| S→Room | area:locked |
{ areaId, lock, denied? } |
| S→Room | area:unlocked |
{ areaId } |
| S→Room | area:changed |
{ areaId, changes, userId } |
브라우저에서 http://localhost:9001 접속
- ID:
minioadmin - PW:
minioadmin
마스킹된 파일은 documents 버킷의 masked/{document_id}/{uuid}/ 경로에 저장됩니다.
| 상황 | 해결 |
|---|---|
| DB 테이블 확인 | docker exec -it ai-postgres psql -U postgres -d ai_privacy -c "\dt" |
| PostgreSQL 직접 접속 | psql -h localhost -p 5560 -U postgres -d ai_privacy |
| MinIO 버킷 초기화 안 됨 | 서버 재시작 (index.ts에서 자동 생성) |
| 서버 재시작 후 룸 상태 사라짐 | 정상 동작 (인메모리, Redis 미연동) |
| WebSocket 인증 오류 | JWT_SECRET이 Django와 동일한지 확인 |
| 포트 충돌 | .env에서 PORT 변경 |