Files
sam-react-prod/claudedocs/quality/[PLAN-2026-02-02] document-viewer-architecture.md
유병철 f0987127eb feat(WEB): QMS 검사 모달 개선, 전자결재/생산대시보드/템플릿 기능 수정
- QMS: InspectionModal/InspectionModalV2 개선, mockData 정리
- 전자결재: DocumentCreate 기능 수정
- 생산대시보드: ProductionDashboard 개선
- 템플릿: IntegratedDetailTemplate/UniversalListPage 기능 수정
- 문서: i18n 가이드 업데이트, 문서뷰어 아키텍처 계획 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 09:09:05 +09:00

11 KiB

문서 뷰어 아키텍처 설계

품질인정심사 1일차 기준/매뉴얼 문서 및 범용 문서 뷰어 구현 방안

1. 배경

품질인정심사 시스템의 1일차(기준/매뉴얼 심사)에서 작업표준서, 검사기준서 등 다양한 포맷의 문서를 웹에서 바로 열람해야 함. 문서 포맷은 HWP, HWPX, Excel, PDF 등 다양하며, 수정 없이 읽기 전용으로 표시하는 것이 목적.

적용 대상

  • 품질인정심사 1일차: 스크린 작업표준서, 검사기준서 등
  • 향후 확장: ERP 전반의 문서 열람 기능

2. 기술 검토 결과

2.1 HWP 웹 뷰어 현황

방식 HWP HWPX PDF Excel 비용 비고
LibreOffice headless - 무료 H2Orestart 확장 필요
한컴 공식 API 엔터프라이즈 DocsConverter SDK
CloudConvert API 월 250건 무료 클라우드 의존
hwp.js (클라이언트) 무료 HWP 스펙 ~20% 구현, 사실상 폐기
@ssabrojs/hwpxjs 무료 HWPX만 지원
  • 웨일 브라우저: 네이티브 한글 뷰어 내장 (브라우저 바이너리에 포함). Chrome에서 재현 불가.
  • Google Docs Viewer: HWP 미지원.
  • 순수 클라이언트 HWP 렌더링: 신뢰할 수 있는 라이브러리 없음 (2026년 기준).

2.2 결론

순수 클라이언트에서 HWP를 직접 렌더링하는 것은 현실적으로 불가능. 서버사이드 PDF 변환 후 프론트에서 PDF 표시가 유일한 프로덕션 옵션.


3. 채택 아키텍처: 서버사이드 변환 + react-pdf

3.1 전체 흐름

┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  프론트엔드   │     │  Laravel 백엔드    │     │  파일 스토리지    │
│  (Next.js)   │     │  (PHP)            │     │  (S3/로컬)       │
└──────┬───────┘     └────────┬──────────┘     └────────┬────────┘
       │                      │                         │
       │  1. 문서 열람 요청    │                         │
       │─────────────────────>│                         │
       │                      │  2. 원본 파일 조회       │
       │                      │────────────────────────>│
       │                      │<────────────────────────│
       │                      │                         │
       │                      │  3. PDF 변환 (캐싱)     │
       │                      │  ┌───────────────────┐  │
       │                      │  │ LibreOffice        │  │
       │                      │  │ --headless         │  │
       │                      │  │ --convert-to pdf   │  │
       │                      │  └───────────────────┘  │
       │                      │                         │
       │                      │  4. 변환된 PDF 저장     │
       │                      │────────────────────────>│
       │                      │                         │
       │  5. PDF URL 반환      │                         │
       │<─────────────────────│                         │
       │                      │                         │
       │  6. react-pdf로 표시  │                         │
       │  (DocumentViewer 모달)│                         │

3.2 백엔드 (Laravel)

변환 방식 A: LibreOffice headless (무료, 자체 호스팅)

# 서버 설치
sudo apt install libreoffice

# H2Orestart 확장 설치 (HWP 필터 지원)
# https://extensions.libreoffice.org/en/extensions/show/27504

# 변환 커맨드
libreoffice --headless --infilter="Hwp2002_File" --convert-to pdf:writer_pdf_Export input.hwp
libreoffice --headless --convert-to pdf:writer_pdf_Export input.hwpx
libreoffice --headless --convert-to pdf:writer_pdf_Export input.xlsx
// Laravel Controller 예시
class DocumentConvertController extends Controller
{
    public function convert(Request $request, string $documentId)
    {
        $document = Document::findOrFail($documentId);
        $originalPath = $document->file_path;
        $pdfPath = $this->getCachedPdfPath($document);

        // 캐시된 PDF가 있으면 바로 반환
        if (Storage::exists($pdfPath)) {
            return response()->json(['pdf_url' => Storage::url($pdfPath)]);
        }

        // LibreOffice로 변환
        $outputDir = storage_path('app/converted');
        $command = sprintf(
            'libreoffice --headless --convert-to pdf --outdir %s %s',
            escapeshellarg($outputDir),
            escapeshellarg(Storage::path($originalPath))
        );
        exec($command, $output, $returnCode);

        if ($returnCode !== 0) {
            return response()->json(['error' => '변환 실패'], 500);
        }

        // 변환된 PDF 저장 및 URL 반환
        return response()->json(['pdf_url' => Storage::url($pdfPath)]);
    }
}

변환 방식 B: 한컴 API (유료, 높은 정확도)

// 한컴 DocsConverter API 활용
class HancomConvertController extends Controller
{
    public function convert(Request $request, string $documentId)
    {
        $document = Document::findOrFail($documentId);

        // 한컴 DocsConverter SDK 호출
        // https://developer.hancom.com/docsconverter
        $result = HancomSDK::convert($document->file_path, 'pdf');

        return response()->json(['pdf_url' => $result->getPdfUrl()]);
    }
}

API 엔드포인트 설계

GET  /api/documents/{id}/preview    → PDF URL 반환 (변환 필요시 자동 변환)
POST /api/documents/upload          → 원본 파일 업로드
GET  /api/documents/{id}/download   → 원본 파일 다운로드

PDF 캐싱 전략

원본 파일 업로드 시점 또는 최초 열람 시점에 PDF 변환
  → converted/{document_id}.pdf로 캐싱
  → 원본 파일 변경 시 캐시 무효화 (파일 해시 비교)
  → 이미 PDF인 파일은 변환 없이 바로 반환

3.3 프론트엔드 (Next.js)

라이브러리

npm install react-pdf
# 또는
npm install @react-pdf-viewer/core @react-pdf-viewer/default-layout

구현 구조

품질인정심사 1일차
├── 점검표 항목 (좌측 패널)
├── 기준 문서화 (우측 패널)
│   └── 문서 클릭 시 → DocumentViewer 모달로 PDF 표시
│       ├── 기존 DocumentViewer 모달 재사용 (zoom/drag/print/PDF)
│       └── 내부에 react-pdf PDF 렌더러 삽입
// 개념 코드 (실제 구현은 기획서 확정 후)
import { Document, Page } from 'react-pdf';

function PdfDocumentViewer({ pdfUrl }: { pdfUrl: string }) {
  const [numPages, setNumPages] = useState(0);

  return (
    <Document
      file={pdfUrl}
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    >
      {Array.from({ length: numPages }, (_, i) => (
        <Page key={i} pageNumber={i + 1} />
      ))}
    </Document>
  );
}

모달 통합

기존 DocumentViewer 모달 (2일차에서 사용 중)
├── 헤더: 문서명 + 날짜
├── 툴바: 축소/확대/맞춤/PDF/인쇄/다운로드
├── 본문:
│   ├── 기존: React 컴포넌트 렌더링 (수주서, 성적서 등)
│   └── 추가: PDF 렌더링 (업로드된 HWP/Excel/기타 문서)

4. 지원 포맷별 처리

파일 포맷 처리 방식 변환 필요
PDF 바로 표시
HWP LibreOffice + H2Orestart → PDF
HWPX LibreOffice → PDF
Excel (.xlsx/.xls) LibreOffice → PDF
Word (.docx/.doc) LibreOffice → PDF
PowerPoint (.pptx) LibreOffice → PDF
이미지 (.png/.jpg) 바로 표시 (img 태그)

5. 서버 환경 요구사항

LibreOffice 방식

# Docker 예시
FROM php:8.2-fpm
RUN apt-get update && apt-get install -y libreoffice default-jre
# H2Orestart 확장 설치
COPY h2orestart.oxt /tmp/
RUN libreoffice --headless --norestore --nofirststartwizard \
    "macro:///Tools.Install.installOxt(/tmp/h2orestart.oxt)"
항목 요구사항
LibreOffice 7.x 이상
JRE 8 이상 (H2Orestart용)
디스크 LibreOffice ~500MB + 변환 캐시
메모리 변환당 ~200-500MB
주의사항 LibreOffice는 thread-safe하지 않음 → 동시 변환 시 큐 처리 필요

한컴 API 방식

항목 요구사항
라이선스 엔터프라이즈 B2B (한컴 영업팀 문의)
SDK DocsConverter SDK (서버 설치)
장점 HWP 변환 정확도 최고
단점 비용, 라이선스 관리

6. 1일차 UI 변경 사항 (요약)

현재 구조 (3컬럼)

[ 점검표 항목 ] [ 기준 문서화 ] [ 스크린 작업표준서 뷰어 ]

변경 후 구조 (2컬럼 + 모달)

[ 점검표 항목 ] [ 기준 문서화 ]
                      │
                      └── 문서 클릭 → DocumentViewer 모달 (PDF 표시)
  • 우측 Day1DocumentViewer 패널 제거
  • 기준 문서화에서 문서 클릭 시 2일차와 동일한 DocumentViewer 모달로 표시
  • 모달 내부에서 서버 변환된 PDF를 react-pdf로 렌더링

7. 구현 우선순위

  1. 백엔드 API: 문서 업로드 + LibreOffice PDF 변환 + 캐싱
  2. 프론트엔드: react-pdf 통합 + DocumentViewer 모달 연동
  3. 1일차 UI: 3컬럼 → 2컬럼 레이아웃 변경 + 모달 연동
  4. 선택사항: 한컴 API 도입 (LibreOffice 변환 품질 불만족 시)

8. 참고 자료