240 lines
10 KiB
JSON
240 lines
10 KiB
JSON
{
|
||
"status": "success",
|
||
"confidence": "high",
|
||
"result": {
|
||
"summary": "WACE ERP 백엔드 전체 아키텍처 분석 완료",
|
||
"details": "Node.js + Express + TypeScript + PostgreSQL Raw Query 기반 멀티테넌시 시스템. 70+ 라우트, 70+ 컨트롤러, 80+ 서비스로 구성된 계층형 아키텍처. JWT 인증, 3단계 권한 체계(SUPER_ADMIN/COMPANY_ADMIN/USER), company_code 기반 완전한 데이터 격리 구현.",
|
||
"files_affected": [
|
||
"docs/backend-architecture-detailed-analysis.md (상세 분석 문서)",
|
||
"docs/backend-architecture-summary.md (요약 문서)",
|
||
"docs/backend-api-route-mapping.md (API 라우트 전체 매핑)"
|
||
],
|
||
"key_findings": {
|
||
"architecture_pattern": "Layered Architecture (Controller → Service → Database)",
|
||
"tech_stack": {
|
||
"language": "TypeScript",
|
||
"runtime": "Node.js 20.10.0+",
|
||
"framework": "Express.js",
|
||
"database": "PostgreSQL (pg 라이브러리, Raw Query)",
|
||
"authentication": "JWT (jsonwebtoken)",
|
||
"scheduler": "node-cron",
|
||
"external_db_support": ["PostgreSQL", "MySQL", "MSSQL", "Oracle"]
|
||
},
|
||
"directory_structure": {
|
||
"controllers": "70+ 파일 (API 요청 수신, 응답 생성)",
|
||
"services": "80+ 파일 (비즈니스 로직, 트랜잭션 관리)",
|
||
"routes": "70+ 파일 (API 라우팅)",
|
||
"middleware": "4개 (인증, 권한, 슈퍼관리자, 에러핸들러)",
|
||
"types": "26개 (TypeScript 타입 정의)",
|
||
"utils": "유틸리티 함수 (JWT, 암호화, 로거)"
|
||
},
|
||
"middleware_stack": [
|
||
"1. Process Level Exception Handlers",
|
||
"2. Helmet (보안 헤더)",
|
||
"3. Compression (Gzip)",
|
||
"4. Body Parser (10MB limit)",
|
||
"5. Static Files (/uploads)",
|
||
"6. CORS (credentials: true)",
|
||
"7. Rate Limiting (1분 10000회)",
|
||
"8. Token Auto Refresh (1시간 이내 만료 시 갱신)",
|
||
"9. API Routes (70+개)",
|
||
"10. 404 Handler",
|
||
"11. Error Handler"
|
||
],
|
||
"authentication_flow": {
|
||
"step1": "로그인 요청 → AuthController.login()",
|
||
"step2": "AuthService.processLogin() → loginPwdCheck() (bcrypt 검증)",
|
||
"step3": "getPersonBeanFromSession() → 사용자 정보 조회",
|
||
"step4": "insertLoginAccessLog() → 로그인 이력 저장",
|
||
"step5": "JwtUtils.generateToken() → JWT 토큰 생성",
|
||
"step6": "응답: { token, userInfo, firstMenuPath }"
|
||
},
|
||
"jwt_payload": {
|
||
"userId": "사용자 ID",
|
||
"userName": "사용자명",
|
||
"companyCode": "회사 코드 (멀티테넌시 키)",
|
||
"userType": "권한 레벨 (SUPER_ADMIN/COMPANY_ADMIN/USER)",
|
||
"exp": "만료 시간 (24시간)"
|
||
},
|
||
"permission_levels": {
|
||
"SUPER_ADMIN": {
|
||
"company_code": "*",
|
||
"userType": "SUPER_ADMIN",
|
||
"capabilities": [
|
||
"전체 회사 데이터 접근",
|
||
"DDL 실행",
|
||
"회사 생성/삭제",
|
||
"시스템 설정 변경"
|
||
]
|
||
},
|
||
"COMPANY_ADMIN": {
|
||
"company_code": "특정 회사 (예: ILSHIN)",
|
||
"userType": "COMPANY_ADMIN",
|
||
"capabilities": [
|
||
"자기 회사 데이터만 접근",
|
||
"자기 회사 사용자 관리",
|
||
"회사 설정 변경"
|
||
]
|
||
},
|
||
"USER": {
|
||
"company_code": "특정 회사",
|
||
"userType": "USER",
|
||
"capabilities": [
|
||
"자기 회사 데이터만 접근",
|
||
"읽기/쓰기 권한만"
|
||
]
|
||
}
|
||
},
|
||
"multi_tenancy": {
|
||
"principle": "모든 쿼리에 company_code 필터 필수",
|
||
"pattern": "JWT 토큰에서 company_code 추출 (클라이언트 신뢰 금지)",
|
||
"super_admin_visibility": "일반 회사 사용자에게 슈퍼관리자(company_code='*') 숨김",
|
||
"correct_pattern": "WHERE company_code = $1 AND company_code != '*'",
|
||
"wrong_pattern": "req.body.companyCode 사용 (보안 위험!)"
|
||
},
|
||
"api_routes": {
|
||
"total_count": "200+개",
|
||
"categories": {
|
||
"인증/관리자": "15개",
|
||
"테이블/화면": "40개",
|
||
"플로우": "15개",
|
||
"데이터플로우": "5개",
|
||
"외부 연동": "15개",
|
||
"배치": "10개",
|
||
"메일": "5개",
|
||
"파일": "5개",
|
||
"기타": "90개"
|
||
}
|
||
},
|
||
"business_domains": {
|
||
"관리자": {
|
||
"controller": "adminController.ts",
|
||
"service": "adminService.ts",
|
||
"features": ["사용자 관리", "메뉴 관리", "권한 그룹 관리", "시스템 설정"]
|
||
},
|
||
"테이블/화면": {
|
||
"controller": "tableManagementController.ts, screenManagementController.ts",
|
||
"service": "tableManagementService.ts, screenManagementService.ts",
|
||
"features": ["테이블 메타데이터", "화면 정의", "화면 그룹", "테이블 로그", "엔티티 관계"]
|
||
},
|
||
"플로우": {
|
||
"controller": "flowController.ts",
|
||
"service": "flowExecutionService.ts, flowDefinitionService.ts",
|
||
"features": ["워크플로우 설계", "단계 관리", "데이터 이동", "조건부 이동", "오딧 로그"]
|
||
},
|
||
"데이터플로우": {
|
||
"controller": "dataflowController.ts, dataflowDiagramController.ts",
|
||
"service": "dataflowService.ts, dataflowDiagramService.ts",
|
||
"features": ["테이블 관계 정의", "ERD", "다이어그램 시각화", "관계 실행"]
|
||
},
|
||
"외부 연동": {
|
||
"controller": "externalDbConnectionController.ts, externalRestApiConnectionController.ts",
|
||
"service": "externalDbConnectionService.ts, dbConnectionManager.ts",
|
||
"features": ["외부 DB 연결", "Connection Pool 관리", "REST API 프록시"]
|
||
},
|
||
"배치": {
|
||
"controller": "batchController.ts, batchManagementController.ts",
|
||
"service": "batchService.ts, batchSchedulerService.ts",
|
||
"features": ["Cron 스케줄러", "외부 DB → 내부 DB 동기화", "컬럼 매핑", "실행 이력"]
|
||
},
|
||
"메일": {
|
||
"controller": "mailSendSimpleController.ts, mailReceiveBasicController.ts",
|
||
"service": "mailSendSimpleService.ts, mailReceiveBasicService.ts",
|
||
"features": ["메일 발송 (nodemailer)", "메일 수신 (IMAP)", "템플릿 관리", "첨부파일"]
|
||
},
|
||
"파일": {
|
||
"controller": "fileController.ts, screenFileController.ts",
|
||
"service": "fileSystemManager.ts",
|
||
"features": ["파일 업로드 (multer)", "파일 다운로드", "화면별 파일 관리"]
|
||
}
|
||
},
|
||
"database_access": {
|
||
"connection_pool": {
|
||
"min": "2~5 (환경별)",
|
||
"max": "10~20 (환경별)",
|
||
"connectionTimeout": "30000ms",
|
||
"idleTimeout": "600000ms",
|
||
"statementTimeout": "60000ms"
|
||
},
|
||
"query_patterns": {
|
||
"multi_row": "query('SELECT ...', [params])",
|
||
"single_row": "queryOne('SELECT ...', [params])",
|
||
"transaction": "transaction(async (client) => { ... })"
|
||
},
|
||
"sql_injection_prevention": "Parameterized Query 사용 (pg 라이브러리)"
|
||
},
|
||
"external_integration": {
|
||
"supported_databases": ["PostgreSQL", "MySQL", "MSSQL", "Oracle"],
|
||
"connector_pattern": "Factory Pattern (DatabaseConnectorFactory)",
|
||
"rest_api": "axios 기반 프록시"
|
||
},
|
||
"batch_scheduler": {
|
||
"library": "node-cron",
|
||
"timezone": "Asia/Seoul",
|
||
"cron_examples": {
|
||
"매일 새벽 2시": "0 2 * * *",
|
||
"5분마다": "*/5 * * * *",
|
||
"평일 오전 8시": "0 8 * * 1-5"
|
||
},
|
||
"execution_flow": [
|
||
"1. 소스 DB에서 데이터 조회",
|
||
"2. 컬럼 매핑 적용",
|
||
"3. 타겟 DB에 INSERT/UPDATE",
|
||
"4. 실행 로그 기록"
|
||
]
|
||
},
|
||
"file_handling": {
|
||
"upload_path": "uploads/{company_code}/{timestamp}-{uuid}-{filename}",
|
||
"max_file_size": "10MB",
|
||
"allowed_types": ["이미지", "PDF", "Office 문서"],
|
||
"library": "multer"
|
||
},
|
||
"security": {
|
||
"password_encryption": "bcrypt (12 rounds)",
|
||
"sensitive_data_encryption": "AES-256-CBC (외부 DB 비밀번호)",
|
||
"jwt_secret": "환경변수 관리",
|
||
"security_headers": ["Helmet (CSP, X-Frame-Options)", "CORS (credentials: true)", "Rate Limiting (1분 10000회)"],
|
||
"sql_injection_prevention": "Parameterized Query"
|
||
},
|
||
"error_handling": {
|
||
"postgres_error_codes": {
|
||
"23505": "중복된 데이터",
|
||
"23503": "참조 무결성 위반",
|
||
"23502": "필수 입력값 누락"
|
||
},
|
||
"process_level": {
|
||
"unhandledRejection": "로깅 (서버 유지)",
|
||
"uncaughtException": "로깅 (서버 유지, 주의)",
|
||
"SIGTERM/SIGINT": "Graceful Shutdown"
|
||
}
|
||
},
|
||
"logging": {
|
||
"library": "Winston",
|
||
"log_files": {
|
||
"error.log": "에러만 (10MB × 5파일)",
|
||
"combined.log": "전체 로그 (10MB × 10파일)"
|
||
},
|
||
"log_levels": "error (0) → warn (1) → info (2) → debug (5)"
|
||
},
|
||
"performance_optimization": {
|
||
"pool_monitoring": "5분마다 상태 체크, 대기 연결 5개 이상 시 경고",
|
||
"slow_query_detection": "1초 이상 걸린 쿼리 자동 경고",
|
||
"caching": "Redis (메뉴: 10분 TTL, 공통코드: 30분 TTL)",
|
||
"compression": "Gzip (1KB 이상 응답, 레벨 6)"
|
||
}
|
||
},
|
||
"critical_rules": [
|
||
"✅ 모든 쿼리에 company_code 필터 추가",
|
||
"✅ JWT 토큰에서 company_code 추출 (클라이언트 신뢰 금지)",
|
||
"✅ Parameterized Query 사용 (SQL Injection 방지)",
|
||
"✅ 슈퍼관리자 데이터 숨김 (company_code != '*')",
|
||
"✅ 비밀번호는 bcrypt, 민감정보는 AES-256",
|
||
"✅ 에러 핸들링 try/catch 필수",
|
||
"✅ 트랜잭션이 필요한 경우 transaction() 사용",
|
||
"✅ 파일 업로드는 회사별 디렉토리 분리"
|
||
]
|
||
},
|
||
"needs_from_others": [],
|
||
"questions": []
|
||
}
|