
부산항 9부두의 선박, 상하차, 장치 관리 등 모든 항만 운영 사항을 모니터링하고 관제하는 지능형 항만 시스템 개발을 위한 계획을 제안합니다. 이 시스템은 데이터의 신뢰성과 실시간 관제 능력을 핵심 목표로 하며, 기술 스택은 제시하신 MySQL, Nuxt.js, Nest.js를 활용합니다.
1. 🏗️ 시스템 아키텍처 및 기술 스택
핵심 기술 스택
| 구분 | 기술 | 역할 |
| 데이터베이스 | MySQL | 선박 정보, 컨테이너 재고, 장치 위치 등 모든 운영 데이터의 안정적인 저장 및 관리. |
| 백엔드 (API 서버) | Nest.js (Node.js/TypeScript) | 비즈니스 로직 처리, 데이터 조회/수정 API 제공. 모듈화 및 확장성이 뛰어남. MySQL과의 데이터 통신 담당. |
| 프론트엔드 (웹 UI) | Nuxt.js (Vue.js) | 실시간 모니터링 대시보드 및 관제 화면 개발. 사용자 친화적인 인터페이스 제공. (선박 위치, 컨테이너 구성 등 시각화) |
| 실시간 통신 | WebSockets (Nest.js 통합) | 선박 상태, 장치 이동 등의 실시간 변화를 프론트엔드로 즉시 전송하여 관제 시스템의 즉각적인 반응성 확보. |

시스템 구성 다이어그램

2. 📋 핵심 기능 모듈 및 데이터 모델
1. 선박 관제 모듈 (Vessel Management)
- 기능:
- 선박 추적: 접근, 대기, 접안, 출항 등 단계별 실시간 상태 모니터링.
- 입출항 관리: ETA/ETD 예측 및 등록, 입출항 허가 절차 관리.
- 베이 플랜 (Bay Plan) 시각화: 선박 내 컨테이너 적재 위치(베이/로우/티어) 3D 시각화 및 조회.
- 주요 데이터: 선박ID, IMO번호, 상태(접근/대기/접안), ETA/ETD, 접안부두, 베이 플랜 정보(컨테이너ID, 위치).
2. 상하차 및 컨테이너 관리 모듈 (Cargo & Container Management)
- 기능:
- 상하차 작업 지시: QC (Quay Crane) 및 YC (Yard Crane)에 대한 작업 스케줄링 및 지시 생성.
- 작업 진행률 모니터링: 계획 대비 실시간 상하차 물량 추적 및 진척률 표시.
- 컨테이너 이력 관리: 컨테이너의 반입/반출, 이동, 상하차 모든 이력 추적.
- 주요 데이터: 컨테이너ID, 컨테이너 종류/크기, B/L번호, 작업지시ID, 작업상태(대기/진행/완료), 현재 위치(선박/야드/게이트).
3. 장치 및 야드 관리 모듈 (Equipment & Yard Management)
- 기능:
- 장치 위치 추적: QC, YC, RTG, TT(Tractor Trailer) 등 모든 장치의 실시간 위치 (RTLS 연동) 및 상태(운행/정지/고장) 모니터링.
- 재고량 및 여유량 관리: 야드의 블록별 컨테이너 재고량, 최대 적재 가능량, 여유 공간 시각화.
- 장치 할당 및 스케줄링: 작업 부하를 고려한 장치 효율적 할당 최적화.
- 주요 데이터: 장치ID, 장치종류, 실시간 위치($x, y$), 상태, 야드 블록ID, 재고량, 여유량.
4. 관제 및 알림 모듈 (Monitoring & Alert)
- 기능:
- 종합 대시보드: 선박, 상하차, 장치 정보를 통합한 핵심 운영 지표 (KPI) 시각화.
- 예외 상황 알림: 선박 지연, 장치 고장, 위험물 컨테이너 처리, 야드 포화 임박 등 설정된 임계값 초과 시 실시간 알림 (WebSockets 활용).
- 주요 데이터: 알림ID, 알림유형, 발생시간, 관련객체ID(선박/장치), 처리상태.
3. 📅 개발 단계별 계획 (5단계)
| 단계 | 기간 | 주요 목표 | 기술 스택 활용 | 산출물 |
| 1단계 | 4주 | 기반 환경 구축 및 데이터 모델링 | MySQL, Nest.js | 데이터베이스 스키마 설계, Nest.js 기본 환경 설정, CRUD API 초안. |
| 2단계 | 8주 | 핵심 백엔드 로직 구현 (데이터 연동) | MySQL, Nest.js | 선박/컨테이너/장치 핵심 데이터 관리 API 완성, 기초 비즈니스 로직(상태 업데이트) 구현. |
| 3단계 | 8주 | 프론트엔드 및 실시간 연동 | Nuxt.js, WebSockets (Nest.js) | 핵심 대시보드 (선박 리스트, 야드 레이아웃) 개발, WebSockets 기반 실시간 상태 업데이트 구현. |
| 4단계 | 6주 | 고도화 기능 구현 및 통합 | All | 베이 플랜 시각화, 장치 최적 할당 로직, 예외 알림 시스템 구현, 모듈 간 통합 테스트. |
| 5단계 | 4주 | 테스트 및 안정화 | All | 부하 테스트, 보안 취약점 점검, 운영 데이터 연동 최종 테스트, 사용자 교육 및 시스템 오픈. |
4. ⚙️ 개발 상세 전략 (Nest.js & Nuxt.js 활용)
1. Nest.js (백엔드)
- 모듈화: 선박(VesselModule), 컨테이너(ContainerModule), 장치(EquipmentModule) 등 기능별로 모듈을 분리하여 개발 및 유지보수 용이성 확보.
- 데이터 접근: TypeORM 또는 Sequelize를 사용하여 MySQL과의 데이터 접근을 효율적으로 관리하고 타입 안정성(TypeScript)을 활용.
- 관제 서비스: Nest.js의 Gateways 기능을 사용하여 WebSockets 서버를 구축하고, 특정 이벤트(예: 선박 상태 변경) 발생 시 프론트엔드로 실시간 메시지 푸시.
2. Nuxt.js (프론트엔드)
- 컴포넌트 기반: Vue.js의 컴포넌트 구조를 활용하여 재사용 가능한 관제 위젯 (예: 선박 카드, 블록별 재고 현황 그래프) 개발.
- 상태 관리: Vuex를 사용하여 복잡한 관제 데이터 상태를 중앙에서 효율적으로 관리. (예: 실시간 장치 위치 정보)
- 데이터 시각화: D3.js, ECharts, 또는 Three.js 라이브러리를 활용하여 야드 및 선박 베이 플랜을 시각적으로 구현하여 관제 효율을 극대화.
💾 데이터베이스 테이블 설계 초안 (MySQL)
제시해주신 지능형 항만 시스템 개발 계획의 다음 단계로, MySQL 데이터베이스 테이블 설계 초안을 핵심 모듈별로 제안합니다. 이 설계는 선박, 컨테이너, 장치 관리에 필요한 최소한의 필수 데이터를 담고 있습니다.
1. 🚢 선박 및 입출항 관리 테이블
| 테이블명 | 필드명 | 데이터 타입 | 제약 조건 | 설명 |
| vessel (선박 기본 정보) | vessel_id | INT | PK, AUTO_INCREMENT | 선박 고유 ID |
| imo_no | VARCHAR(10) | UNIQUE, NOT NULL | IMO 번호 | |
| vessel_name | VARCHAR(100) | NOT NULL | 선박명 | |
| call_sign | VARCHAR(20) | 호출 부호 | ||
| vessel_type | VARCHAR(50) | 선박 종류 (컨테이너선, 벌크선 등) | ||
| voyage (항차 정보) | voyage_id | INT | PK, AUTO_INCREMENT | 항차 고유 ID |
| vessel_id | INT | FK (vessel) | 관련 선박 ID | |
| in_voyage_no | VARCHAR(20) | NOT NULL | 입항 항차 번호 | |
| out_voyage_no | VARCHAR(20) | NOT NULL | 출항 항차 번호 | |
| berth_id | INT | FK (berth) | 접안 부두 ID | |
| status | ENUM | NOT NULL | 현재 상태 (접근, 대기, 접안, 출항) | |
| estimated_arrival_time | DATETIME | 예상 도착 시간 (ETA) | ||
| actual_arrival_time | DATETIME | 실제 도착 시간 (ATA) | ||
| estimated_departure_time | DATETIME | 예상 출항 시간 (ETD) | ||
| actual_departure_time | DATETIME | 실제 출항 시간 (ATD) | ||
| berth (부두 정보) | berth_id | INT | PK, AUTO_INCREMENT | 부두 고유 ID |
| berth_name | VARCHAR(50) | NOT NULL | 부두 명칭 (예: 901호선석) |
2. 📦 컨테이너 및 야드 관리 테이블
| 테이블명 | 필드명 | 데이터 타입 | 제약 조건 | 설명 |
| container (컨테이너 기본 정보) | container_id | INT | PK, AUTO_INCREMENT | 컨테이너 고유 ID |
| container_no | VARCHAR(11) | UNIQUE, NOT NULL | 컨테이너 번호 | |
| iso_code | VARCHAR(10) | NOT NULL | ISO 코드 (20GP, 40HC 등) | |
| owner_code | VARCHAR(4) | 소유주 코드 | ||
| cargo_weight | DECIMAL(10, 3) | 화물 중량 (톤) | ||
| is_full | BOOLEAN | NOT NULL | FCL/LCL 구분 (만재/공) | |
| container_inventory (컨테이너 재고 및 위치) | inventory_id | INT | PK, AUTO_INCREMENT | 재고 정보 ID |
| container_id | INT | FK (container) | 관련 컨테이너 ID | |
| current_location_type | ENUM | NOT NULL | 현재 위치 유형 (VESSEL, YARD, GATE) | |
| yard_block_id | INT | FK (yard_block) | 야드 위치 (블록 ID) | |
| stack_row | INT | 야드 내 행 위치 | ||
| stack_tier | INT | 야드 내 단 위치 | ||
| vessel_voyage_id | INT | FK (voyage) | 선박 위치 시 관련 항차 ID | |
| vessel_bay | INT | 선박 내 베이 위치 | ||
| vessel_row | INT | 선박 내 로우 위치 | ||
| vessel_tier | INT | 선박 내 티어 위치 | ||
| yard_block (야드 블록 정보) | yard_block_id | INT | PK, AUTO_INCREMENT | 야드 블록 고유 ID |
| block_name | VARCHAR(20) | NOT NULL | 블록 명칭 (예: T1, M3) | |
| max_capacity | INT | NOT NULL | 최대 적재 가능 TEU | |
| current_teu | INT | NOT NULL | 현재 적재 TEU |
3. 🏗️ 장치 및 작업 관리 테이블
| 테이블명 | 필드명 | 데이터 타입 | 제약 조건 | 설명 |
| equipment (장치 기본 정보) | equipment_id | INT | PK, AUTO_INCREMENT | 장치 고유 ID |
| eq_name | VARCHAR(50) | NOT NULL | 장치 명칭 | |
| eq_type | ENUM | NOT NULL | 장치 종류 (QC, YC, RTG, TT 등) | |
| status | ENUM | NOT NULL | 장치 상태 (가동, 정지, 고장, 점검) | |
| current_latitude | DECIMAL(10, 8) | 현재 위도 ($x$ 좌표) | ||
| current_longitude | DECIMAL(11, 8) | 현재 경도 ($y$ 좌표) | ||
| work_order (작업 지시 정보) | order_id | INT | PK, AUTO_INCREMENT | 작업 지시 고유 ID |
| order_type | ENUM | NOT NULL | 작업 유형 (상차, 하차, 이적, 반입, 반출) | |
| container_id | INT | FK (container) | 대상 컨테이너 ID | |
| equipment_id | INT | FK (equipment) | 할당된 장치 ID | |
| start_location | VARCHAR(100) | 작업 시작 위치 (좌표 또는 블록/베이) | ||
| end_location | VARCHAR(100) | 작업 종료 위치 | ||
| status | ENUM | NOT NULL | 작업 진행 상태 (대기, 진행 중, 완료, 취소) | |
| created_at | DATETIME | NOT NULL | 작업 지시 생성 시간 | |
| completed_at | DATETIME | 작업 완료 시간 |
💻 Nest.js 백엔드 개발 계획
Nest.js는 모듈화 및 타입스크립트 기반으로 엔터프라이즈급 애플리케이션 개발에 적합합니다. MySQL 테이블 구조를 그대로 활용하여 **Entity(엔티티)**를 정의하고, 이를 처리하는 **API 엔드포인트(Controller)**를 설계합니다.
1. Nest.js 엔티티 정의 (Entity Definition)
TypeORM 또는 Sequelize와 같은 ORM(Object-Relational Mapping)을 사용한다고 가정하고, 데이터베이스 테이블을 나타내는 TypeScript 클래스(Entity)를 정의합니다.
| 엔티티 (Entity) | 대응 테이블 | 주요 필드 및 관계 |
| Vessel | vessel | vesselId (PK), imoNo, vesselName |
| Voyage | voyage | voyageId (PK), vessel (FK to Vessel), status |
| Berth | berth | berthId (PK), berthName |
| Container | container | containerId (PK), containerNo, isoCode |
| ContainerInventory | container_inventory | inventoryId (PK), container (FK), currentLocationType, yardBlock (FK) |
| YardBlock | yard_block | yardBlockId (PK), blockName, maxCapacity, currentTeu |
| Equipment | equipment | equipmentId (PK), eqType, status, currentLatitude, currentLongitude |
| WorkOrder | work_order | orderId (PK), orderType, container (FK), equipment (FK) |
2. API 엔드포인트 (Controller) 설계
프론트엔드(Nuxt.js) 및 기타 외부 시스템과의 데이터 통신을 위한 RESTful API 엔드포인트를 설계합니다.
2.1. 선박 관제 API (/api/vessels)
| 기능 | HTTP 메서드 | 엔드포인트 (URL) | 설명 |
| 접안 선박 목록 | GET | /vessels/berthing | 현재 접안 또는 대기 중인 항차 목록 및 요약 정보 조회. |
| 항차 상세 정보 | GET | /vessels/voyages/:voyageId | 특정 항차의 상세 정보 (ETA/ETD, 작업 지시 목록) 조회. |
| 베이 플랜 조회 | GET | /vessels/voyages/:voyageId/bayplan | 선박의 컨테이너 적재 구성(베이 플랜) 데이터를 3D 시각화용으로 조회. |
| 상태 업데이트 | PATCH | /vessels/voyages/:voyageId/status | 선박 상태 (접근, 접안 등) 및 시간 정보 (ATA, ATD) 업데이트. |
2.2. 화물 및 재고 관리 API (/api/cargo)
| 기능 | HTTP 메서드 | 엔드포인트 (URL) | 설명 |
| 전체 재고 현황 | GET | /cargo/inventory | 야드, 선박, 게이트별 컨테이너 재고량 요약 조회. |
| 야드 블록 상세 | GET | /cargo/yard/blocks/:blockId | 특정 야드 블록의 재고량, 여유 공간, 적재된 컨테이너 목록 조회. |
| 컨테이너 검색 | GET | /cargo/containers/:containerNo | 특정 컨테이너의 상세 정보 및 현재 위치 (ContainerInventory) 조회. |
| 컨테이너 이력 | GET | /cargo/containers/:containerNo/history | 특정 컨테이너의 반입/반출/이동 등 모든 작업 이력 조회. |
2.3. 장치 및 작업 관리 API (/api/equipment, /api/workorders)
| 기능 | HTTP 메서드 | 엔드포인트 (URL) | 설명 |
| 장치 목록 및 위치 | GET | /equipment | 모든 장치 (Equipment)의 목록, 타입, 최신 위치 조회. |
| 작업 지시 생성 | POST | /workorders | 상차/하차 등의 신규 작업 지시 (WorkOrder) 생성. |
| 장치별 할당 작업 | GET | /workorders/assigned/:eqId | 특정 장치에 할당된 진행 중인 작업 목록 조회. |
| 작업 완료 처리 | PATCH | /workorders/:orderId/complete | 작업 완료 처리 및 ContainerInventory의 위치 자동 업데이트. |
3. 📡 실시간 관제를 위한 WebSocket (Nest.js Gateway) 설계
지능형 항만 시스템의 핵심은 실시간 모니터링입니다. Nest.js의 @WebSocketGateway() 기능을 활용하여 별도의 실시간 통신 채널을 구축합니다.
| WebSocket Channel | Nest.js Gateway (Event) | 전달 데이터 예시 | 용도 |
| vesselUpdates | server.emit('vesselStatusChanged') | { voyageId: 10, newStatus: '접안' } | 선박 상태 변화 (접근 -> 대기 -> 접안) 즉시 알림. |
| equipmentLocation | server.emit('eqLocationUpdated') | { eqId: 5, lat: 35.1012, lng: 129.0205 } | 장치 (QC, RTG 등)의 실시간 위치 변화를 맵 UI로 전송. |
| systemAlerts | server.emit('newAlert') | { type: 'YARD_CONGESTION', blockId: 'T3', message: '포화 임박' } | 야드 혼잡, 장치 고장, 위험물 작업 등 관제 경고 발생 즉시 전송. |
🖥️ Nuxt.js 프론트엔드 (관제 대시보드) UI/UX 설계
Nest.js 백엔드 API 및 WebSocket 채널을 통해 수신되는 데이터를 사용자 친화적으로 시각화하고 관제할 수 있도록 Nuxt.js 기반의 프론트엔드 UI/UX 설계 계획을 제시합니다.
1. 🖼️ 핵심 화면 구성 및 레이아웃
시스템의 효율적인 관제를 위해 대시보드는 정보 밀도와 직관성을 최우선으로 설계합니다.
A. 메인 관제 대시보드 (Dashboard)
| 영역 | 구성 요소 | 데이터 소스 (API / WebSocket) | 목적 |
| 좌측 | 선박/항차 리스트 | /api/vessels/berthing, vesselUpdates | 현재 접안 및 대기 중인 선박의 상태(ETA, 작업 진행률) 요약 표시. |
| 중앙 | 종합 야드 시각화 | /api/cargo/yard/blocks, equipmentLocation | 9부두 야드 블록의 실시간 재고 현황 (TEU) 및 장치 (RTG, YC) 위치를 2D/3D 지도 형태로 표시. |
| 우측 | 실시간 알림 패널 | systemAlerts | 선박 지연, 장치 고장, 야드 포화 임계치 초과 등 관제 경고 메시지 실시간 표시. |
| 상단 | KPI 요약 | /api/cargo/inventory 등 | 시간당 처리량(MPH), 야드 점유율, 현재 운영 중인 장치 수 등 핵심 운영 지표(KPI) 표시. |
B. 선박 상세 관제 화면 (Vessel Detail)
- 상세 정보: 항차 정보, 입출항 시간, 선박 제원.
- 베이 플랜 시각화: 선택된 선박의 베이(Bay)별 컨테이너 적재 현황을 3차원 형태로 시각화. 컨테이너를 클릭하면 상세 정보(화주, 종류 등) 팝업 표시.
- 작업 지시 모니터링: 해당 선박에 할당된 상하차 작업 지시 목록 및 실시간 진행률 표시.
C. 컨테이너/재고 검색 화면 (Inventory Search)
- 컨테이너 이력 추적: 컨테이너 번호 입력 시, 반입부터 현재 위치까지의 모든 이동 경로와 이력을 타임라인 형태로 제공.
- 야드 재고 보고서: 블록별, ISO 코드별, 만재/공 컨테이너별 필터링 및 통계 보고서 제공.
2. 🎨 Nuxt.js 개발 및 시각화 전략
1. 상태 관리 및 데이터 연동
- Vuex (또는 Pinia): Nuxt.js의 상태 관리 패턴을 활용하여 백엔드에서 받은 실시간 데이터를 중앙 집중식으로 관리합니다. 특히 장치 위치나 알림 목록과 같은 실시간 변경 데이터의 일관성을 유지합니다.
- Axios: Nest.js의 RESTful API 엔드포인트와 통신하여 선박 목록, 이력, 정적 데이터를 조회합니다.
2. 고성능 시각화 구현
- 실시간 야드 맵: Leaflet, OpenLayers 또는 사용자 정의 캔버스를 기반으로 9부두의 물리적 레이아웃을 구현하고, equipmentLocation WebSocket 데이터를 활용하여 장치의 아이콘을 실시간으로 이동시킵니다.
- 베이 플랜 3D: Three.js와 같은 3D 라이브러리를 Nuxt.js 환경에 통합하여, 선박의 베이(Bay), 로우(Row), 티어(Tier)에 컨테이너 박스를 입체적으로 렌더링합니다. 컨테이너 색상을 위험물 여부, 만재 여부 등에 따라 다르게 표시하여 직관적인 관제를 돕습니다.
3. 실시간 통신 처리 (WebSockets)
- Socket.io Client: Nuxt.js 앱에 Socket.io 클라이언트를 통합하여 Nest.js Gateway와 연결합니다.
- 즉각적인 반영: 수신된 vesselUpdates, equipmentLocation 이벤트는 지연 없이 Vuex 상태를 업데이트하고, 이는 즉시 대시보드의 해당 컴포넌트(선박 리스트, 장치 맵)에 반영됩니다.
📝 모듈별 상세 기능 명세 및 작업 지시 로직 (백엔드 로직 플로우)
네, 이제 시스템의 핵심 운영 로직이 되는 상세 기능 명세와 작업 지시 처리 플로우를 Nest.js 백엔드 관점에서 구체적으로 설계하겠습니다. 특히 상하차 작업 지시 처리 로직은 시스템의 성능과 정확성을 결정하는 가장 중요한 부분입니다.
1. 🏗️ 선박 상하차 작업 지시 처리 플로우 (핵심 로직)
이 플로우는 **컨테이너의 위치 이동(Inventory Update)**과 작업 이력 기록을 보장하는 핵심 프로세스입니다.
1. 하차 작업 (Discharge: 선박 → 야드) 로직 플로우
- 작업 지시 생성: 관제사 또는 자동 스케줄링 모듈이 항차(Voyage), 대상 컨테이너(Container), 장치(QC, TT), 목표 야드 위치(YardBlock)를 지정하여 하차 작업 지시(WorkOrder)를 생성하고 상태를 **'대기'**로 설정합니다.
- 데이터: work_order 테이블에 기록.
- 작업 시작: QC(선측 크레인)가 컨테이너를 들어 올릴 때, 장치 시스템에서 작업 시작 메시지를 백엔드 API로 전송합니다.
- Nest.js: WorkOrder 상태를 **'진행 중'**으로 변경.
- 위치 임시 변경: 컨테이너가 TT(야드 트랙터)에 실리는 순간, 해당 컨테이너의 재고(ContainerInventory) 위치를 'TT 이동 중' 상태로 임시 업데이트합니다.
- 야드 도착 및 적재: TT가 목표 YardBlock에 도착하고 YC(야드 크레인)가 지정된 stack_row와 stack_tier에 컨테이너를 적재합니다. 장치 시스템에서 작업 완료 메시지를 백엔드 API로 전송합니다.
- Nest.js:
- ContainerInventory를 최종 위치 (YARD, yard_block_id, stack_row, stack_tier)로 갱신합니다.
- YardBlock 테이블의 current_teu 값을 증가시킵니다.
- WorkOrder 상태를 **'완료'**로 변경하고 completed_at을 기록합니다.
- equipmentLocation WebSocket 채널을 통해 장치 위치 정보를 업데이트합니다.
- Nest.js:
2. 상차 작업 (Loading: 야드 → 선박) 로직 플로우
- 작업 지시 생성: 선박 베이 플랜에 따라 상차 작업 지시를 생성하고 **'대기'**로 설정합니다. (출발 위치: 야드, 도착 위치: 선박의 특정 Bay/Row/Tier).
- 야드 반출: YC가 지정된 야드 위치에서 컨테이너를 반출하여 TT에 실을 때, WorkOrder 상태를 **'진행 중'**으로 변경합니다.
- Nest.js: YardBlock 테이블의 current_teu 값을 감소시키고, ContainerInventory 위치를 **'TT 이동 중'**으로 임시 업데이트.
- 선박 적재: 컨테이너가 QC를 통해 선박의 지정된 vessel_bay, vessel_row, vessel_tier에 적재될 때, 작업 완료 메시지를 전송합니다.
- Nest.js:
- ContainerInventory를 최종 위치 (VESSEL, vessel_voyage_id, 베이/로우/티어)로 갱신합니다.
- WorkOrder 상태를 **'완료'**로 변경합니다.
- vesselUpdates WebSocket 채널을 통해 베이 플랜 변경 정보를 전송합니다.
- Nest.js:
2. 🚨 관제 및 알림 모듈 상세 명세
시스템의 자동 관제 기능을 정의하여 비정상적인 상황을 즉시 감지하고 알립니다.
| 기능 명세 | 발생 조건 (트리거) | 처리 로직 (Nest.js Service) | 알림 채널 |
| 야드 혼잡 경고 | 특정 YardBlock의 current_teu가 max_capacity의 **90%**를 초과할 때. | 해당 블록 ID와 경고 수준을 기록하고 '긴급' 알림을 생성. | systemAlerts (WebSocket) |
| 선박 지연 알림 | 선박의 estimated_arrival_time (ETA) 대비 3시간 이상 지연이 감지될 때. | Voyage 상태를 확인하고, 관련 항차 정보와 함께 지연 알림을 생성. | systemAlerts (WebSocket), 관제 화면 |
| 장치 고장 알림 | 장치(Equipment) 시스템에서 고장 코드 또는 장시간 위치 미변화가 감지될 때. | Equipment 상태를 **'고장'**으로 자동 변경하고, 관련 작업 지시(WorkOrder)를 **'장치 대기'**로 임시 변경. | systemAlerts (WebSocket), 장치 관제 맵 |
| 위험물 처리 감시 | 위험물 컨테이너 처리 작업(WorkOrder)이 정상 시간을 초과하거나 지정 구역이 아닌 곳에 적재될 때. | 작업 로그를 확인하고 '긴급 감시' 알림을 생성. | systemAlerts (WebSocket) |
3. 🌐 API 응답 및 통신 규약
- API 버전 관리: v1을 사용하여 향후 확장성을 고려합니다. (예: /api/v1/vessels)
- 응답 규격: 모든 API는 JSON 형식으로 응답하며, 성공 시 HTTP 200/201, 실패 시 명확한 오류 코드와 메시지를 반환합니다 (HTTP 4xx, 5xx).
- 데이터 모델: Nuxt.js에서 사용하기 편리하도록 Nest.js **DTO (Data Transfer Object)**를 사용하여 응답 데이터를 규격화합니다.
4. 🎨 Nuxt.js 프론트엔드 개발 상세 전략
프론트엔드 개발은 모듈성, 재사용성, 그리고 고성능의 실시간 시각화에 중점을 둡니다.
1. Nuxt.js 아키텍처 및 모듈 구성
프로젝트는 백엔드의 도메인 구조와 일치하는 모듈 기반 아키텍처를 채택하여 개발 효율성을 높입니다.
| 구분 | 경로/모듈 | 주요 역할 |
| 레이아웃 | layouts/default.vue | 헤더, 사이드바, 실시간 알림 패널 (systemAlerts 연결) 등 전역 UI 구조 정의. |
| 페이지 | pages/index.vue | 메인 관제 대시보드 뷰. 모든 핵심 컴포넌트를 통합. |
| 페이지 | pages/vessels/[id].vue | 선박 상세 및 베이 플랜 전용 뷰. |
| 페이지 | pages/equipment/map.vue | 장치 실시간 위치 모니터링 뷰. |
| API 통신 | composables/useApi.ts | Axios를 사용하여 Nest.js RESTful API와 통신하는 재사용 가능한 훅(Hook) 정의. |
| 소켓 통신 | plugins/socket.client.ts | Socket.io 클라이언트 초기화 및 WebSocket 이벤트 수신 로직 처리. |
2. 컴포넌트 구조 (Container/Presentational 패턴)
복잡한 상태 관리 로직과 순수한 렌더링 로직을 분리하여 유지보수를 용이하게 합니다.
| 컴포넌트 유형 | 예시 컴포넌트 | 역할 |
| 컨테이너 (Container) | containers/VesselListContainer.vue | 데이터 로딩, Pinia 스토어 구독, 이벤트 처리 등 로직 담당. (예: vesselStore에서 항차 목록을 가져옴) |
| 프레젠테이셔널 (Presentational) | ui/VesselStatusCard.vue | 컨테이너 컴포넌트에서 받은 데이터를 순수하게 렌더링만 담당. (재사용 가능한 UI 요소). |
| 시각화 (Visualization) | visual/YardMap3D.vue | Three.js 등 외부 라이브러리를 사용하여 복잡한 시각화를 처리. |
3. 🎯 상태 관리 전략 (Pinia)
Nuxt.js 3의 권장 상태 관리 라이브러리인 Pinia를 사용하여 실시간 데이터를 효율적으로 관리합니다.
| Pinia Store | 주요 데이터 (State) | 연동 백엔드 | 목적 |
| vessel Store | 항차 목록, 선택된 선박의 베이 플랜 데이터. | RESTful API, vesselUpdates (WS) | 선박 상태 변화를 즉시 반영. |
| equipment Store | 장치 목록 및 실시간 GPS 좌표. | RESTful API, equipmentLocation (WS) | 장치 맵의 부드러운 이동을 위한 핵심 데이터. |
| workOrder Store | 할당된 작업 지시 목록, 작업 진행률(%) | RESTful API | 작업의 생성, 할당, 완료 상태 관리. |
| alert Store | 실시간 관제 경고 메시지 리스트. | systemAlerts (WS) | 우측 알림 패널에 즉시 메시지 추가 및 표시. |
4. 📊 핵심 시각화 구현 상세
A. 실시간 야드 맵 관제 (2D/2.5D)
- 데이터 연동: equipment Store에 저장된 장치 (QC, RTG, YC)의 current_latitude, current_longitude 값을 활용하여 맵 상에 아이콘을 렌더링합니다.
- 애니메이션: WebSocket으로 위치 데이터가 업데이트될 때, Vue의 반응성을 통해 아이콘 위치를 CSS transition을 사용하여 부드럽게 이동시켜 실시간성을 강조합니다.
- 블록 상태: inventory Store의 블록별 점유율에 따라 해당 야드 블록 영역에 🚦 신호등 색상 (Green, Yellow, Red) 오버레이를 적용하여 혼잡도를 직관적으로 표시합니다.
B. 선박 베이 플랜 3D 시각화
- 기술 선택: Three.js를 사용하여 웹GL 기반의 3D 렌더링을 구현합니다.
- 데이터 변환: Nest.js API에서 반환된 컨테이너 위치 데이터 (Bay, Row, Tier)를 Three.js의 **3D 좌표계 (X, Y, Z)**로 변환하는 로직을 구현합니다.
- 시각적 구분:
- 색상: 컨테이너의 iso_code (사이즈/타입) 또는 위험물 여부에 따라 색상을 다르게 지정합니다.
- 상호작용: 3D 모델 위에 마우스를 올리면 해당 컨테이너의 번호와 화주 정보를 보여주는 툴팁을 표시합니다.
🚀 5. 시스템 배포 환경 및 테스트 전략
이 시스템은 항만 운영의 핵심 관제 기능을 수행하므로 안정성, 확장성, 실시간 데이터 처리 능력을 최우선으로 고려해야 합니다.
1. ⚙️ 배포 환경 전략 (Deployment Strategy)
Nest.js(백엔드)와 Nuxt.js(프론트엔드)는 모듈화가 잘 되어 있어, **컨테이너화(Containerization)**를 통해 안정적인 배포 환경을 구축하는 것이 유리합니다.
| 구분 | 환경 | 기술 및 역할 | 설명 |
| 운영 환경 | Production (Prod) | Docker & Kubernetes (K8s) 또는 ECS | 고가용성(High Availability) 및 부하 분산(Load Balancing)을 위해 컨테이너 오케스트레이션 필수. |
| 스테이징 환경 | Staging (Stag) | Prod와 동일 구성 | 운영 환경으로 배포하기 전, 최종적인 통합 테스트 및 성능 검증을 위한 환경. |
| 개발 환경 | Development (Dev) | Docker Compose | 개발자의 로컬 환경에서 DB, Nest.js, Nuxt.js를 단일 명령어로 쉽게 구동할 수 있도록 설정. |
| 데이터베이스 | MySQL | RDS 또는 전용 서버 | 미션 크리티컬 데이터의 안정성을 위해 클라우드 서비스(AWS RDS, Google Cloud SQL 등) 또는 고성능 서버에 독립적으로 구성. |
| 프론트엔드 | Nuxt.js | Nginx & CDN | Nuxt.js는 빌드 후 정적 파일 형태로 Nginx를 통해 서비스하고, CDN을 적용하여 관제 대시보드의 로딩 속도 최적화. |
2. 🛡️ 테스트 전략 (Testing Strategy)
실시간 관제 시스템은 데이터의 정확도가 중요하므로, 다각적인 테스트를 통해 신뢰성을 확보해야 합니다.
A. 백엔드 테스트 (Nest.js)
- 단위 테스트 (Unit Test):
- 대상: 서비스(Service) 레이어의 개별 로직 (예: 야드 재고량 계산, 선박 상태 업데이트 로직).
- 도구: Jest (Nest.js 기본 내장).
- 통합 테스트 (Integration Test):
- 대상: 컨트롤러(Controller)와 서비스 레이어의 상호작용 및 DB 연결 확인 (Mocking 없이 실제 MySQL 연결 테스트 포함).
- 목적: 작업 지시 생성 시 (Controller 호출 시) WorkOrder와 ContainerInventory 테이블이 동시에 정확히 업데이트되는지 검증.
- 성능 및 부하 테스트 (Load Test):
- 대상: API 엔드포인트 (/api/cargo/inventory, /api/equipment) 및 WebSocket 채널.
- 목적: 다수의 장치 및 관제사 접속 상황(동시 접속자)에서도 WebSocket 실시간 업데이트의 지연이 발생하지 않는지 확인.
B. 프론트엔드 테스트 (Nuxt.js)
- 컴포넌트 테스트:
- 대상: 개별 UI 컴포넌트 (예: YardMap3D.vue, VesselStatusCard.vue).
- 도구: Vue Test Utils 또는 Jest.
- 목적: Pinia 스토어에서 데이터를 받을 때 UI가 정확하게 렌더링되고 사용자 상호작용(클릭, 확대/축소)이 예상대로 작동하는지 검증.
- E2E 테스트 (End-to-End Test):
- 도구: Cypress 또는 Playwright.
- 목적: 사용자의 관제 플로우 (선박 선택 → 베이 플랜 조회 → 작업 지시 생성) 전체를 자동화하여 테스트. 프론트엔드와 백엔드 API 연동 전체 과정 검증.
💻 Nest.js 핵심 코드 예시
1. 엔티티 정의 (DB 테이블 매핑)
MySQL의 container_inventory와 work_order 테이블을 매핑하는 TypeORM 엔티티 클래스입니다.
A. ContainerInventory Entity (container-inventory.entity.ts)
import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm';
import { Container } from './container.entity';
@Entity('container_inventory')
export class ContainerInventory {
@PrimaryGeneratedColumn()
inventoryId: number;
@OneToOne(() => Container)
@JoinColumn({ name: 'container_id' })
container: Container;
@Column({ type: 'enum', enum: ['VESSEL', 'YARD', 'GATE'] })
currentLocationType: 'VESSEL' | 'YARD' | 'GATE';
// 야드 위치 정보
@Column({ nullable: true })
yardBlockId: number;
@Column({ nullable: true })
stackRow: number;
@Column({ nullable: true })
stackTier: number;
// 선박 위치 정보
@Column({ nullable: true })
vesselVoyageId: number;
@Column({ nullable: true })
vesselBay: number;
// (생략된 기타 필드...)
}
B. WorkOrder Entity (work-order.entity.ts)
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { Container } from './container.entity';
import { Equipment } from './equipment.entity';
@Entity('work_order')
export class WorkOrder {
@PrimaryGeneratedColumn()
orderId: number;
@Column({ type: 'enum', enum: ['LOADING', 'DISCHARGE', 'SHIFT', 'GATE_IN', 'GATE_OUT'] })
orderType: 'LOADING' | 'DISCHARGE' | 'SHIFT' | 'GATE_IN' | 'GATE_OUT';
@ManyToOne(() => Container)
@JoinColumn({ name: 'container_id' })
container: Container;
@ManyToOne(() => Equipment)
@JoinColumn({ name: 'equipment_id' })
equipment: Equipment;
@Column({ type: 'enum', enum: ['PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELED'] })
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELED';
@Column()
startLocation: string; // 시작 위치 문자열 (예: T1/01/01 또는 Bay 05)
@Column()
endLocation: string; // 종료 위치 문자열
@Column({ nullable: true })
completedAt: Date;
}
2. 서비스 로직 (핵심 비즈니스 처리)
작업 지시가 완료되었을 때, 관련 재고 정보를 트랜잭션으로 묶어 처리하는 WorkOrderService 예시입니다.
WorkOrderService (work-order.service.ts)
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { WorkOrder } from './work-order.entity';
import { ContainerInventory } from '../inventory/container-inventory.entity';
import { YardBlock } from '../inventory/yard-block.entity';
import { WorkOrderGateway } from './work-order.gateway'; // WebSocket Gateway
@Injectable()
export class WorkOrderService {
constructor(
@InjectRepository(WorkOrder)
private workOrderRepository: Repository<WorkOrder>,
private dataSource: DataSource, // 트랜잭션을 위한 DataSource 주입
private workOrderGateway: WorkOrderGateway, // WebSocket 주입
) {}
/**
* 작업 지시를 완료 처리하고 컨테이너 재고 및 야드 정보를 업데이트합니다.
* @param orderId 완료할 작업 지시 ID
* @param details 최종 위치 정보 (여기서는 단순화)
*/
async completeWorkOrder(orderId: number, completedAt: Date): Promise<WorkOrder> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. 작업 지시 조회 및 상태 업데이트
const order = await this.workOrderRepository.findOne({
where: { orderId },
relations: ['container', 'equipment'], // 관련 엔티티 로드
});
if (!order) {
throw new NotFoundException(`WorkOrder with ID ${orderId} not found`);
}
order.status = 'COMPLETED';
order.completedAt = completedAt;
await queryRunner.manager.save(order);
// 2. 컨테이너 재고 업데이트 (핵심 로직)
const inventory = await queryRunner.manager.findOneBy(ContainerInventory, {
container: { containerId: order.container.containerId }
});
if (!inventory) {
throw new NotFoundException('Container inventory not found');
}
// 예시: 'DISCHARGE' (하차) 작업 완료 시 -> 위치를 YARD로 업데이트
if (order.orderType === 'DISCHARGE') {
// endLocation을 파싱하여 yardBlockId, stackRow, stackTier를 설정해야 합니다. (여기서는 단순화)
inventory.currentLocationType = 'YARD';
inventory.yardBlockId = 10; // 임시 블록 ID
inventory.stackRow = 1;
inventory.stackTier = 1;
// 야드 블록 재고 증가
await queryRunner.manager.increment(YardBlock, { yardBlockId: inventory.yardBlockId }, 'currentTeu', 1);
}
// 예시: 'LOADING' (상차) 작업 완료 시 -> 위치를 VESSEL로 업데이트
else if (order.orderType === 'LOADING') {
// 선박 위치 정보 설정 (베이 플랜 업데이트)
inventory.currentLocationType = 'VESSEL';
inventory.vesselBay = 5; // 임시 베이
inventory.vesselRow = 2;
inventory.vesselTier = 3;
// 야드 블록 재고 감소 (상차 시작 시 감소되었으므로, 여기서는 생략 가능)
}
await queryRunner.manager.save(inventory);
// 3. 트랜잭션 커밋
await queryRunner.commitTransaction();
// 4. WebSocket 알림 전송 (DB 트랜잭션 성공 후)
this.workOrderGateway.sendWorkOrderUpdate(order);
return order;
} catch (err) {
// 오류 발생 시 롤백
await queryRunner.rollbackTransaction();
throw err;
} finally {
// 연결 해제
await queryRunner.release();
}
}
}
3. 컨트롤러 및 실시간 알림 (API 엔드포인트)
클라이언트의 요청을 받아 WorkOrderService를 호출하고, 실시간 알림 기능을 담당하는 WebSocket Gateway 코드입니다.
A. WorkOrderController (work-order.controller.ts)
import { Controller, Patch, Param, Body, HttpStatus } from '@nestjs/common';
import { WorkOrderService } from './work-order.service';
@Controller('api/v1/workorders')
export class WorkOrderController {
constructor(private readonly workOrderService: WorkOrderService) {}
// POST /api/v1/workorders/:orderId/complete
@Patch(':orderId/complete')
async completeOrder(@Param('orderId') orderId: number) {
// 실제 관제 시스템에서는 장치 시스템으로부터 받은 완료 시간 데이터를 사용합니다.
const completedAt = new Date();
// 트랜잭션으로 처리되는 서비스 로직 호출
const completedOrder = await this.workOrderService.completeWorkOrder(
orderId,
completedAt,
);
return {
statusCode: HttpStatus.OK,
message: `Work Order ${orderId} completed successfully.`,
data: completedOrder,
};
}
}
B. WebSocket Gateway (work-order.gateway.ts)
import { WebSocketGateway, WebSocketServer, SubscribeMessage } from '@nestjs/platform-ws';
import { Server } from 'ws';
import { WorkOrder } from './work-order.entity';
// WebSocketGateway를 정의하고 포트 번호 등을 설정합니다.
@WebSocketGateway(8080, { cors: { origin: '*' } })
export class WorkOrderGateway {
@WebSocketServer()
server: Server; // WebSocket 서버 인스턴스
/**
* 작업 완료 시 모든 구독 클라이언트에게 실시간 알림을 전송합니다.
* @param order 완료된 WorkOrder 객체
*/
sendWorkOrderUpdate(order: WorkOrder) {
// 프론트엔드에서 'workOrderUpdate' 채널을 구독하게 됩니다.
this.server.emit('workOrderUpdate', {
orderId: order.orderId,
containerNo: order.container.containerNo,
type: order.orderType,
status: 'COMPLETED',
completedAt: order.completedAt,
// 필요한 추가 데이터 (예: 새 위치 정보)
});
}
// (추가: 클라이언트 접속/해제 로직, 실시간 장치 위치 업데이트 로직 등 추가 가능)
}
'IT 일반,소식' 카테고리의 다른 글
| 📝 AI의 영원한 굴레: 완벽함이라는 허상과 베타 버전의 운명 (0) | 2025.12.10 |
|---|---|
| 🔗 HATEOAS (Hypermedia As The Engine Of Application State) 상세 설명 (0) | 2025.12.10 |
| 🏢 빌딩 엘리베이터 시뮬레이션 개발계획(MySQL, Nuxt.js, Nest.js 기반) (0) | 2025.12.07 |
| NestJS + NuxtJS로 실시간 서버 헬스 모니터링 대시보드 만들기 (WebSocket 기반) (0) | 2025.12.05 |
| AI는 일자리를 없애지 않는다 – 방직기 시대가 증명해준 250년 전 이야기, 그리고 지금 (0) | 2025.12.02 |