343 lines
9.4 KiB
Markdown
343 lines
9.4 KiB
Markdown
# WACE ERP Backend - 아키텍처 요약
|
||
|
||
> **작성일**: 2026-02-06
|
||
> **목적**: 워크플로우 문서 통합용 백엔드 아키텍처 요약
|
||
|
||
---
|
||
|
||
## 1. 기술 스택
|
||
|
||
```
|
||
언어: TypeScript (Node.js 20.10.0+)
|
||
프레임워크: Express.js
|
||
데이터베이스: PostgreSQL (pg 라이브러리, Raw Query)
|
||
인증: JWT (jsonwebtoken)
|
||
스케줄러: node-cron
|
||
메일: nodemailer + IMAP
|
||
파일업로드: multer
|
||
외부DB: MySQL, MSSQL, Oracle 지원
|
||
```
|
||
|
||
## 2. 계층 구조
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ Controller │ ← API 요청 수신, 응답 생성
|
||
└────────┬────────┘
|
||
│
|
||
┌────────▼────────┐
|
||
│ Service │ ← 비즈니스 로직, 트랜잭션 관리
|
||
└────────┬────────┘
|
||
│
|
||
┌────────▼────────┐
|
||
│ Database │ ← PostgreSQL Raw Query
|
||
└─────────────────┘
|
||
```
|
||
|
||
## 3. 디렉토리 구조
|
||
|
||
```
|
||
backend-node/src/
|
||
├── app.ts # Express 앱 진입점
|
||
├── config/ # 환경설정
|
||
├── controllers/ # 70+ 컨트롤러
|
||
├── services/ # 80+ 서비스
|
||
├── routes/ # 70+ 라우터
|
||
├── middleware/ # 인증/권한/에러핸들러
|
||
├── database/ # DB 연결 (pg Pool)
|
||
├── types/ # TypeScript 타입 (26개)
|
||
└── utils/ # 유틸리티 (JWT, 암호화, 로거)
|
||
```
|
||
|
||
## 4. 미들웨어 스택 순서
|
||
|
||
```typescript
|
||
1. Process Level Exception Handlers (unhandledRejection, uncaughtException)
|
||
2. Helmet (보안 헤더)
|
||
3. Compression (Gzip)
|
||
4. Body Parser (JSON, URL-encoded, 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
|
||
```
|
||
|
||
## 5. 인증/인가 시스템
|
||
|
||
### 5.1 인증 흐름
|
||
|
||
```
|
||
로그인 요청
|
||
↓
|
||
AuthController.login()
|
||
↓
|
||
AuthService.processLogin()
|
||
├─ loginPwdCheck() → 비밀번호 검증 (bcrypt)
|
||
├─ getPersonBeanFromSession() → 사용자 정보 조회
|
||
├─ insertLoginAccessLog() → 로그인 이력 저장
|
||
└─ JwtUtils.generateToken() → JWT 토큰 생성
|
||
↓
|
||
응답: { token, userInfo, firstMenuPath }
|
||
```
|
||
|
||
### 5.2 JWT Payload
|
||
|
||
```json
|
||
{
|
||
"userId": "user123",
|
||
"userName": "홍길동",
|
||
"companyCode": "ILSHIN",
|
||
"userType": "COMPANY_ADMIN",
|
||
"iat": 1234567890,
|
||
"exp": 1234654290,
|
||
"iss": "PMS-System"
|
||
}
|
||
```
|
||
|
||
### 5.3 권한 체계 (3단계)
|
||
|
||
| 권한 | company_code | userType | 권한 범위 |
|
||
|------|--------------|----------|-----------|
|
||
| **SUPER_ADMIN** | `*` | `SUPER_ADMIN` | 전체 회사, DDL 실행, 회사 생성/삭제 |
|
||
| **COMPANY_ADMIN** | `ILSHIN` | `COMPANY_ADMIN` | 자기 회사만, 사용자/설정 관리 |
|
||
| **USER** | `ILSHIN` | `USER` | 자기 회사만, 읽기/쓰기 |
|
||
|
||
## 6. 멀티테넌시 구현
|
||
|
||
### 핵심 원칙
|
||
```typescript
|
||
// ✅ 올바른 패턴
|
||
const companyCode = req.user!.companyCode; // JWT에서 추출
|
||
|
||
if (companyCode === "*") {
|
||
// 슈퍼관리자: 모든 데이터 조회
|
||
query = "SELECT * FROM table ORDER BY company_code";
|
||
} else {
|
||
// 일반 사용자: 자기 회사 + 슈퍼관리자 데이터 제외
|
||
query = "SELECT * FROM table WHERE company_code = $1 AND company_code != '*'";
|
||
params = [companyCode];
|
||
}
|
||
|
||
// ❌ 잘못된 패턴 (보안 위험!)
|
||
const companyCode = req.body.companyCode; // 클라이언트에서 받음
|
||
```
|
||
|
||
### 슈퍼관리자 숨김 규칙
|
||
```sql
|
||
-- 일반 회사 사용자에게 슈퍼관리자(company_code='*')는 보이면 안 됨
|
||
SELECT * FROM user_info
|
||
WHERE company_code = $1
|
||
AND company_code != '*' -- 필수!
|
||
```
|
||
|
||
## 7. API 라우트 (70+개)
|
||
|
||
### 7.1 인증/관리자
|
||
- `POST /api/auth/login` - 로그인
|
||
- `GET /api/auth/me` - 현재 사용자 정보
|
||
- `POST /api/auth/switch-company` - 회사 전환 (슈퍼관리자)
|
||
- `GET /api/admin/users` - 사용자 목록
|
||
- `GET /api/admin/menus` - 메뉴 목록
|
||
|
||
### 7.2 테이블/화면
|
||
- `GET /api/table-management/tables` - 테이블 목록
|
||
- `POST /api/table-management/tables/:table/data` - 데이터 조회
|
||
- `POST /api/table-management/multi-table-save` - 다중 테이블 저장
|
||
- `GET /api/screen-management/screens` - 화면 목록
|
||
|
||
### 7.3 플로우
|
||
- `GET /api/flow/definitions` - 플로우 정의 목록
|
||
- `POST /api/flow/move` - 데이터 이동 (단건)
|
||
- `POST /api/flow/move-batch` - 데이터 이동 (다건)
|
||
|
||
### 7.4 외부 연동
|
||
- `GET /api/external-db-connections` - 외부 DB 연결 목록
|
||
- `POST /api/external-db-connections/:id/test` - 연결 테스트
|
||
- `POST /api/multi-connection/query` - 멀티 DB 쿼리
|
||
|
||
### 7.5 배치
|
||
- `GET /api/batch-configs` - 배치 설정 목록
|
||
- `POST /api/batch-management/:id/execute` - 배치 즉시 실행
|
||
|
||
### 7.6 메일
|
||
- `POST /api/mail/send` - 메일 발송
|
||
- `GET /api/mail/sent` - 발송 이력
|
||
|
||
### 7.7 파일
|
||
- `POST /api/files/upload` - 파일 업로드
|
||
- `GET /uploads/:filename` - 정적 파일 서빙
|
||
|
||
## 8. 비즈니스 도메인 (8개)
|
||
|
||
| 도메인 | 컨트롤러 | 주요 기능 |
|
||
|--------|----------|-----------|
|
||
| **관리자** | `adminController` | 사용자/메뉴/권한 관리 |
|
||
| **테이블/화면** | `tableManagementController` | 메타데이터, 동적 화면 생성 |
|
||
| **플로우** | `flowController` | 워크플로우 엔진, 데이터 이동 |
|
||
| **데이터플로우** | `dataflowController` | ERD, 관계도 |
|
||
| **외부 연동** | `externalDbConnectionController` | 외부 DB/REST API |
|
||
| **배치** | `batchController` | Cron 스케줄러, 데이터 동기화 |
|
||
| **메일** | `mailSendSimpleController` | 메일 발송/수신 |
|
||
| **파일** | `fileController` | 파일 업로드/다운로드 |
|
||
|
||
## 9. 데이터베이스 접근
|
||
|
||
### Connection Pool 설정
|
||
```typescript
|
||
{
|
||
min: 2~5, // 최소 연결 수
|
||
max: 10~20, // 최대 연결 수
|
||
connectionTimeout: 30000, // 30초
|
||
idleTimeout: 600000, // 10분
|
||
statementTimeout: 60000 // 쿼리 실행 60초
|
||
}
|
||
```
|
||
|
||
### Raw Query 패턴
|
||
```typescript
|
||
// 1. 다중 행
|
||
const users = await query('SELECT * FROM user_info WHERE company_code = $1', [companyCode]);
|
||
|
||
// 2. 단일 행
|
||
const user = await queryOne('SELECT * FROM user_info WHERE user_id = $1', [userId]);
|
||
|
||
// 3. 트랜잭션
|
||
await transaction(async (client) => {
|
||
await client.query('INSERT INTO table1 ...', [...]);
|
||
await client.query('INSERT INTO table2 ...', [...]);
|
||
});
|
||
```
|
||
|
||
## 10. 외부 시스템 연동
|
||
|
||
### 지원 데이터베이스
|
||
- PostgreSQL
|
||
- MySQL
|
||
- Microsoft SQL Server
|
||
- Oracle
|
||
|
||
### Connector Factory Pattern
|
||
```typescript
|
||
DatabaseConnectorFactory
|
||
├── PostgreSQLConnector
|
||
├── MySQLConnector
|
||
├── MSSQLConnector
|
||
└── OracleConnector
|
||
```
|
||
|
||
## 11. 배치/스케줄 시스템
|
||
|
||
### Cron 스케줄러
|
||
```typescript
|
||
// node-cron 기반
|
||
// 매일 새벽 2시: "0 2 * * *"
|
||
// 5분마다: "*/5 * * * *"
|
||
// 평일 오전 8시: "0 8 * * 1-5"
|
||
|
||
// 서버 시작 시 자동 초기화
|
||
BatchSchedulerService.initializeScheduler();
|
||
```
|
||
|
||
### 배치 실행 흐름
|
||
```
|
||
1. 소스 DB에서 데이터 조회
|
||
↓
|
||
2. 컬럼 매핑 적용
|
||
↓
|
||
3. 타겟 DB에 INSERT/UPDATE
|
||
↓
|
||
4. 실행 로그 기록 (batch_execution_logs)
|
||
```
|
||
|
||
## 12. 파일 처리
|
||
|
||
### 업로드 경로
|
||
```
|
||
uploads/
|
||
└── {company_code}/
|
||
└── {timestamp}-{uuid}-{filename}
|
||
```
|
||
|
||
### Multer 설정
|
||
- 최대 파일 크기: 10MB
|
||
- 허용 타입: 이미지, PDF, Office 문서
|
||
- 파일명 중복 방지: 타임스탬프 + UUID
|
||
|
||
## 13. 보안
|
||
|
||
### 암호화
|
||
- **비밀번호**: bcrypt (12 rounds)
|
||
- **민감정보**: AES-256-CBC (외부 DB 비밀번호 등)
|
||
- **JWT Secret**: 환경변수 관리
|
||
|
||
### 보안 헤더
|
||
- Helmet (CSP, X-Frame-Options)
|
||
- CORS (credentials: true)
|
||
- Rate Limiting (1분 10000회)
|
||
|
||
### SQL Injection 방지
|
||
- Parameterized Query 사용 (pg 라이브러리)
|
||
- 동적 쿼리 빌더 패턴
|
||
|
||
## 14. 에러 핸들링
|
||
|
||
### PostgreSQL 에러 코드 매핑
|
||
- `23505` → "중복된 데이터"
|
||
- `23503` → "참조 무결성 위반"
|
||
- `23502` → "필수 입력값 누락"
|
||
|
||
### 프로세스 레벨
|
||
- `unhandledRejection` → 로깅 (서버 유지)
|
||
- `uncaughtException` → 로깅 (서버 유지, 주의)
|
||
- `SIGTERM/SIGINT` → Graceful Shutdown
|
||
|
||
## 15. 로깅 (Winston)
|
||
|
||
### 로그 파일
|
||
- `logs/error.log` - 에러만 (10MB × 5파일)
|
||
- `logs/combined.log` - 전체 로그 (10MB × 10파일)
|
||
|
||
### 로그 레벨
|
||
```
|
||
error (0) → warn (1) → info (2) → debug (5)
|
||
```
|
||
|
||
## 16. 성능 최적화
|
||
|
||
### Pool 모니터링
|
||
- 5분마다 상태 체크
|
||
- 대기 연결 5개 이상 시 경고
|
||
|
||
### 느린 쿼리 감지
|
||
- 1초 이상 걸린 쿼리 자동 경고
|
||
|
||
### 캐싱 (Redis)
|
||
- 메뉴 목록: 10분 TTL
|
||
- 공통코드: 30분 TTL
|
||
|
||
### Gzip 압축
|
||
- 1KB 이상 응답만 압축 (레벨 6)
|
||
|
||
---
|
||
|
||
## 🎯 핵심 체크리스트
|
||
|
||
### 개발 시 반드시 지켜야 할 규칙
|
||
|
||
✅ **모든 쿼리에 `company_code` 필터 추가**
|
||
✅ **JWT 토큰에서 `company_code` 추출 (클라이언트 신뢰 금지)**
|
||
✅ **Parameterized Query 사용 (SQL Injection 방지)**
|
||
✅ **슈퍼관리자 데이터 숨김 (`company_code != '*'`)**
|
||
✅ **비밀번호는 bcrypt, 민감정보는 AES-256**
|
||
✅ **에러 핸들링 try/catch 필수**
|
||
✅ **트랜잭션이 필요한 경우 `transaction()` 사용**
|
||
✅ **파일 업로드는 회사별 디렉토리 분리**
|
||
|
||
---
|
||
|
||
**문서 버전**: 1.0
|
||
**마지막 업데이트**: 2026-02-06
|