IT 일반,소식

🚢 부산항 9부두 지능형 항만 시스템 개발 계획 (MySQL, Nuxt.js, Nest.js 기반)

_Blue_Sky_ 2025. 12. 7. 14:06
728x90

부산항 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: 선박 → 야드) 로직 플로우

  1. 작업 지시 생성: 관제사 또는 자동 스케줄링 모듈이 항차(Voyage), 대상 컨테이너(Container), 장치(QC, TT), 목표 야드 위치(YardBlock)를 지정하여 하차 작업 지시(WorkOrder)를 생성하고 상태를 **'대기'**로 설정합니다.
    • 데이터: work_order 테이블에 기록.
  2. 작업 시작: QC(선측 크레인)가 컨테이너를 들어 올릴 때, 장치 시스템에서 작업 시작 메시지를 백엔드 API로 전송합니다.
    • Nest.js: WorkOrder 상태를 **'진행 중'**으로 변경.
  3. 위치 임시 변경: 컨테이너가 TT(야드 트랙터)에 실리는 순간, 해당 컨테이너의 재고(ContainerInventory) 위치를 'TT 이동 중' 상태로 임시 업데이트합니다.
  4. 야드 도착 및 적재: 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 채널을 통해 장치 위치 정보를 업데이트합니다.

2. 상차 작업 (Loading: 야드 → 선박) 로직 플로우

  1. 작업 지시 생성: 선박 베이 플랜에 따라 상차 작업 지시를 생성하고 **'대기'**로 설정합니다. (출발 위치: 야드, 도착 위치: 선박의 특정 Bay/Row/Tier).
  2. 야드 반출: YC가 지정된 야드 위치에서 컨테이너를 반출하여 TT에 실을 때, WorkOrder 상태를 **'진행 중'**으로 변경합니다.
    • Nest.js: YardBlock 테이블의 current_teu 값을 감소시키고, ContainerInventory 위치를 **'TT 이동 중'**으로 임시 업데이트.
  3. 선박 적재: 컨테이너가 QC를 통해 선박의 지정된 vessel_bay, vessel_row, vessel_tier에 적재될 때, 작업 완료 메시지를 전송합니다.
    • Nest.js:
      • ContainerInventory를 최종 위치 (VESSEL, vessel_voyage_id, 베이/로우/티어)로 갱신합니다.
      • WorkOrder 상태를 **'완료'**로 변경합니다.
      • vesselUpdates WebSocket 채널을 통해 베이 플랜 변경 정보를 전송합니다.

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)

  1. 단위 테스트 (Unit Test):
    • 대상: 서비스(Service) 레이어의 개별 로직 (예: 야드 재고량 계산, 선박 상태 업데이트 로직).
    • 도구: Jest (Nest.js 기본 내장).
  2. 통합 테스트 (Integration Test):
    • 대상: 컨트롤러(Controller)와 서비스 레이어의 상호작용 및 DB 연결 확인 (Mocking 없이 실제 MySQL 연결 테스트 포함).
    • 목적: 작업 지시 생성 시 (Controller 호출 시) WorkOrder와 ContainerInventory 테이블이 동시에 정확히 업데이트되는지 검증.
  3. 성능 및 부하 테스트 (Load Test):
    • 대상: API 엔드포인트 (/api/cargo/inventory, /api/equipment) 및 WebSocket 채널.
    • 목적: 다수의 장치 및 관제사 접속 상황(동시 접속자)에서도 WebSocket 실시간 업데이트의 지연이 발생하지 않는지 확인.

B. 프론트엔드 테스트 (Nuxt.js)

  1. 컴포넌트 테스트:
    • 대상: 개별 UI 컴포넌트 (예: YardMap3D.vue, VesselStatusCard.vue).
    • 도구: Vue Test Utils 또는 Jest.
    • 목적: Pinia 스토어에서 데이터를 받을 때 UI가 정확하게 렌더링되고 사용자 상호작용(클릭, 확대/축소)이 예상대로 작동하는지 검증.
  2. 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,
      // 필요한 추가 데이터 (예: 새 위치 정보)
    });
  }

  // (추가: 클라이언트 접속/해제 로직, 실시간 장치 위치 업데이트 로직 등 추가 가능)
}
728x90