식대앱 개발기 #1: SMB 프로토콜과 Express.js로 사내 시스템 개선하기

매일 반복되는 번거로운 점심 식대 입력 작업. 이를 자동화하기 위해 SMB 프로토콜과 Express.js를 활용해 사내 서버와 실시간으로 동기화되는 웹앱을 개발한 과정을 공유합니다.

express.jsSMB
--

🛑 배경: "복지인데 왜 불편하지"

우리 회사의 소중한 복지 중 하나인 중식 식대 지원. 하지만 이 혜택을 누리기 위한 과정은 생각보다 고달팠습니다.

기존 프로세스의 3중 고통

  1. 임시 메모의 굴레: 외부 접속이 안 되는 사내 서버 엑셀 파일 때문에, 점심 직후 카톡 '나에게 보내기'에 식당과 금액을 적어둬야 합니다.
  2. 복귀 후 수동 입력: 사무실에 돌아와 카톡을 뒤져가며 다시 엑셀에 옮겨 적는 이중 작업이 발생합니다.
  3. OS 호환성 잔혹사: macOS 사용자는 파일 저장조차 원활하지 않아 로컬로 옮겼다 서버로 다시 올리는 '파일 셔틀'을 반복해야 했습니다.

💡 해결 아이디어: "식대 전용 웹앱을 만들자"

이 비효율을 끝내기 위해 사내 서버의 엑셀 파일과 실시간으로 동기화되는 웹앱을 기획했습니다.

핵심 개발 목표

  • 실시간 파일 동기화: 앱에서 입력하면 사내 서버 엑셀에 즉시 반영 (SMB 프로토콜 활용).
  • 잔액 실시간 조회: 이번 달 남은 식대 잔액을 메인 화면에 표시.
  • 익숙한 UX: 기존 엑셀 양식을 UI로 녹여내어 거부감 최소화.
  • 접근성 개선: PWA(Progressive Web App)를 통해 모바일 앱처럼 홈 화면에 추가.

🛠️ 기술 스택: 왜 Express.js인가?

가장 중요한 기능인 사내 서버 파일 직접 제어를 위해 Node.js 환경의 Express.js를 선택했습니다.

  • 성숙한 SMB 라이브러리: @marsaud/smb2 라이브러리를 통해 안정적인 네트워크 파일 접근이 가능했습니다.
  • Excel 처리 용이성: xlsx (SheetJS) 라이브러리와의 궁합이 좋아 메모리상에서 엑셀 데이터를 핸들링하기 적합했습니다.

🔍 핵심 기술: SMB(Server Message Block) 프로토콜

식대앱의 엔진은 SMB 프로토콜입니다. 이는 네트워크상에서 파일이나 프린터 같은 리소스를 공유하기 위한 프로토콜로, 주로 Windows 환경의 파일 공유에 사용됩니다.

구현 중 마주친 빌런들 (Trial & Error)

1. 한글 자모 분리 현상 (NFC vs NFD)

Windows 서버와 통신하다 보니 파일명의 한글이 'ㅎㅏㄴㄱㅡㄹ'처럼 분리되는 문제가 있었습니다.

  • 해결: normalize("NFC")를 사용하여 유니코드 정규화를 적용했습니다.

2. Connection Closed 에러

매번 새 연결을 맺는 것이 비효율적이라 판단해 객체를 재사용하려다 연결이 끊기는 현상이 발생했습니다.

  • 해결: SMB 특성상 상태 유지가 까다로워, 안전하게 **매 요청마다 새로운 연결을 생성하고 종료(disconnect)**하는 방식으로 선회했습니다.

🚀 성능 최적화: Download 방식 vs Stream 방식

처음에는 파일을 통째로 다운로드받아 처리하려 했으나, 속도가 너무 느렸습니다.

방식 비교: 어떤 것이 더 효율적일까?

항목로컬 다운로드 방식Stream 방식 (최종 채택)
속도❌ 느림 (전체 다운로드 대기)✅ 빠름 (필요한 부분만 즉시 처리)
I/O 부하❌ 디스크 쓰기 발생✅ 메모리상에서 즉시 처리
동기화⚠️ 파일 덮어쓰기 로직 필요✅ 실시간 버퍼 전송

최종 구현 코드 (Stream 기반):

// 메모리 버퍼를 활용해 SMB 서버에 직접 Read/Write 수행
async function readExcelFromSMB(fileName) {
  const client = createSMBClient();

  return new Promise((resolve, reject) => {
    client.createReadStream(`식대/${fileName}`, (err, readStream) => {
      if (err) return reject(err);

      const chunks = [];
      readStream.on("data", (chunk) => chunks.push(chunk));
      readStream.on("end", () => {
        const buffer = Buffer.concat(chunks);
        const workbook = XLSX.read(buffer, { type: "buffer" });
        resolve(workbook);
      });
      readStream.on("error", reject);
    });
  });
}

결과: 엑셀 조회 속도가 3초에서 0.5초로 약 6배 향상되었습니다.


⚠️ 치명적인 결함: "완벽한 줄 알았는데..."

모든 개발이 끝났다고 생각한 순간, 거대한 벽에 부딪혔습니다. 사내 와이파이를 벗어나면 앱이 작동하지 않는 것이었습니다.

  • 원인: 사내 방화벽이 SMB 포트(445)를 외부로부터 철저히 막고 있었습니다.
  • 팀장님의 피드백: "보안 정책상 외부 IP의 서버 접근은 절대로 허용할 수 없습니다."

외부에서도 편하게 입력하려던 앱의 근간이 흔들리는 순간이었습니다.


🏁 1편 결론: 아키텍처 재설계의 서막

사내망 안에서만 돌아가는 앱은 반쪽짜리에 불과했습니다.

저는 여기서 포기하지 않고, 사내 서버의 보안을 지키면서도 외부에서 접속 가능한 아키텍처를 고민하기 시작했습니다.

다음 편 예고:

  • AWS Lightsail을 활용한 중간 서버(Proxy) 구축
  • SMB를 포기하고 선택한 하이브리드 데이터 동기화 전략
  • 위기를 기회로 바꾼 아키텍처 전면 재설계 과정!

댓글

0/2000
Newsletter

이 글이 도움이 되셨나요?

새로운 글이 발행되면 이메일로 알려드립니다.

뉴스레터 구독하기