diff --git a/projects/e-sign/changelog.md b/projects/e-sign/changelog.md index 020afec..6a749a0 100644 --- a/projects/e-sign/changelog.md +++ b/projects/e-sign/changelog.md @@ -217,7 +217,8 @@ projects/e-sign/ ### v1.1.0 (예정) -- [ ] PDF 서명 합성 (`EsignPdfService::composeSigned`) - FPDI/FPDF 라이브러리 +- [x] PDF 서명 합성 - FPDI/TCPDF (MNG `PdfSignatureService`) +- [x] DOCX→PDF 변환 - LibreOffice headless (MNG `DocxToPdfConverter`) - [ ] 감사 증적 페이지 추가 (`EsignPdfService::addAuditPage`) - [ ] 만료 계약 자동 처리 (Laravel Scheduler) - [ ] Queue Worker 설정 (이메일 비동기 발송) diff --git a/projects/e-sign/implementation-guide.md b/projects/e-sign/implementation-guide.md index ac8b7b1..f4a7f4f 100644 --- a/projects/e-sign/implementation-guide.md +++ b/projects/e-sign/implementation-guide.md @@ -18,7 +18,8 @@ API 프로젝트에 백엔드 로직(모델, 서비스, 컨트롤러, 라우트) |------|------| | 데이터베이스 | 마이그레이션 4개 (esign_ 접두사 테이블) | | 모델 | 4개 (EsignContract, EsignSigner, EsignSignField, EsignAuditLog) | -| 서비스 | 4개 (Contract, Sign, Pdf, Audit) | +| API 서비스 | 4개 (Contract, Sign, Pdf, Audit) | +| MNG 서비스 | 2개 (DocxToPdfConverter, PdfSignatureService) | | API 컨트롤러 | 2개 (Contract 10엔드포인트, Sign 6엔드포인트) | | FormRequest | 4개 (Store, FieldConfigure, SignSubmit, SignReject) | | 메일 | 1개 (EsignRequestMail) | @@ -86,6 +87,10 @@ app/Http/Controllers/ESign/ ├── EsignController.php # 인증 필요 (5개 메서드) └── EsignPublicController.php # 비인증 (3개 메서드) +app/Services/ESign/ +├── DocxToPdfConverter.php # DOCX→PDF 변환 (LibreOffice headless) +└── PdfSignatureService.php # PDF 서명 합성 (FPDI/TCPDF) + resources/views/esign/ ├── dashboard.blade.php # 대시보드 (통계 + 목록) ├── create.blade.php # 계약 생성 (PDF 업로드) @@ -349,16 +354,46 @@ signer() → BelongsTo(EsignSigner) - 최대 5회 시도 (`otp_attempts`) - 초과 시 토큰 무효화 -### 5.3 EsignPdfService +### 5.3 EsignPdfService (API) -**파일**: `app/Services/ESign/EsignPdfService.php` +**파일**: `api/app/Services/ESign/EsignPdfService.php` | 메서드 | 설명 | 상태 | |--------|------|------| | `generateHash(string $filePath)` | SHA-256 해시 생성 | 구현 완료 | | `verifyIntegrity(string $filePath, string $expectedHash)` | 해시 비교 검증 (hash_equals) | 구현 완료 | -| `composeSigned(...)` | 원본 PDF + 서명 이미지 합성 | 스텁 (FPDI 추후) | -| `addAuditPage(...)` | 감사 증적 페이지 추가 | 스텁 (FPDI 추후) | +| `composeSigned(...)` | 원본 PDF + 서명 이미지 합성 | 스텁 (MNG로 이관) | +| `addAuditPage(...)` | 감사 증적 페이지 추가 | 스텁 (추후) | + +### 5.5 DocxToPdfConverter (MNG) + +**파일**: `mng/app/Services/ESign/DocxToPdfConverter.php` +**의존성**: LibreOffice (headless), 나눔 폰트 + +| 메서드 | 설명 | 상태 | +|--------|------|------| +| `convertAndStore(UploadedFile $file)` | DOCX/DOC 파일을 LibreOffice로 PDF 변환 후 저장 | 구현 완료 | + +**동작 방식**: +- Word 파일(.doc, .docx) 업로드 시 자동 감지 +- `libreoffice --headless --convert-to pdf` 명령으로 변환 +- 나눔 폰트로 한글 정상 렌더링 지원 +- 변환된 PDF를 `storage/app/private/esign/{tenant_id}/originals/` 에 저장 + +### 5.6 PdfSignatureService (MNG) + +**파일**: `mng/app/Services/ESign/PdfSignatureService.php` +**의존성**: FPDI, TCPDF, GD 확장 + +| 메서드 | 설명 | 상태 | +|--------|------|------| +| `mergeSignatures(EsignContract $contract)` | 원본 PDF에 모든 서명 이미지 오버레이 합성 | 구현 완료 | + +**동작 방식**: +- FPDI로 원본 PDF 임포트 +- 서명 필드를 페이지별로 그룹핑 +- 필드 타입별 렌더링: signature/stamp(이미지), date(텍스트), text(텍스트), checkbox(체크마크) +- 서명된 PDF를 `storage/app/private/esign/{tenant_id}/signed/` 에 저장 ### 5.4 EsignAuditService @@ -691,6 +726,8 @@ NOTIFIED/AUTHENTICATED → REJECTED (거절) | React 하이브리드 | `views/finance/journal-entries.blade.php` | CDN React + Babel 패턴 참고 | | HX-Redirect | 컨트롤러 패턴 | HTMX 부분 로드 시 전체 리로드 | | SidebarMenu | `app/Services/SidebarMenuService.php` | DB 기반 메뉴 | +| DOCX→PDF | `app/Services/ESign/DocxToPdfConverter.php` | LibreOffice headless 변환 | +| PDF 서명 합성 | `app/Services/ESign/PdfSignatureService.php` | FPDI/TCPDF 서명 오버레이 | --- @@ -698,13 +735,15 @@ NOTIFIED/AUTHENTICATED → REJECTED (거절) | 항목 | 우선순위 | 설명 | |------|---------|------| -| PDF 합성 (FPDI) | 높음 | 원본 PDF에 서명 이미지 오버레이 | +| ~~PDF 합성 (FPDI)~~ | ~~높음~~ | ~~원본 PDF에 서명 이미지 오버레이~~ → **구현 완료** (MNG PdfSignatureService) | +| ~~DOCX→PDF 변환~~ | ~~높음~~ | ~~Word 문서 지원~~ → **구현 완료** (MNG DocxToPdfConverter + LibreOffice) | | 감사 증적 페이지 | 높음 | 완료 PDF 마지막에 감사 정보 페이지 추가 | | 파일 암호화 (AES-256) | 중간 | 원본 PDF 암호화 저장 | | 만료 자동 처리 | 중간 | 스케줄러로 expires_at 초과 계약 expired 처리 | | 리마인더 자동 발송 | 낮음 | 만료 3일 전 자동 리마인드 | | SMS OTP | 낮음 | 이메일 외 SMS 인증 지원 | | OTP bcrypt 해싱 | 중간 | 현재 평문 저장 → bcrypt 해싱 | +| PDF 서명 텍스트 한글 | 낮음 | TCPDF CJK 폰트 추가 (현재 helvetica만 사용) | --- diff --git a/projects/e-sign/operations-guide.md b/projects/e-sign/operations-guide.md index 0ad9739..cf24cb9 100644 --- a/projects/e-sign/operations-guide.md +++ b/projects/e-sign/operations-guide.md @@ -71,14 +71,21 @@ ### 1.4 기술 스택 -| 항목 | 버전 | -|------|------| -| PHP | 8.3 | -| Laravel | 11 (API), 11 (MNG) | -| MySQL | 8.0 | -| Nginx | 최신 | -| Docker Compose | v2 | -| Node.js | React 18 (CDN, 빌드 불필요) | +| 항목 | 버전 | 비고 | +|------|------|------| +| PHP | 8.3 | | +| Laravel | 11 (API), 11 (MNG) | | +| MySQL | 8.0 | Multi-tenant | +| Nginx | 최신 | | +| Docker Compose | v2 | | +| React | 18 (CDN + Babel) | 브라우저 트랜스파일링 | +| FPDI/TCPDF | 2.6 / 6.10 | PDF 서명 합성 (MNG) | +| LibreOffice | headless (writer-nogui) | DOCX→PDF 변환 (MNG) | +| GD 확장 | PHP 내장 | 서명 이미지 처리 (MNG) | +| 나눔 폰트 | fonts-nanum | DOCX 한글 렌더링 (MNG) | +| PDF.js | CDN | 브라우저 PDF 표시 | +| signature_pad.js | 4.x | 터치/마우스 서명 캡처 | +| Lucide | CDN | 아이콘 | --- @@ -803,15 +810,16 @@ docker exec sam-nginx-1 grep "client_max_body_size" /etc/nginx/nginx.conf ## 11. 확장 가이드 -### 11.1 v1.1 추가 예정 패키지 +### 11.1 현재 설치된 패키지 (구현 완료) ```bash -# PDF 서명 합성 (FPDI + FPDF) -docker exec sam-api-1 composer require setasign/fpdi setasign/fpdf +# FPDI/TCPDF (PDF 서명 합성) - MNG 프로젝트에 설치됨 +# setasign/fpdi: ^2.6, tecnickcom/tcpdf: ^6.10 -# 서버 -cd /home/webservice/api -composer require setasign/fpdi setasign/fpdf +# LibreOffice (DOCX→PDF 변환) - MNG Docker 컨테이너에 설치됨 +# libreoffice-writer-nogui, fonts-nanum, fonts-nanum-extra + +# GD 확장 (서명 이미지 처리) - MNG Docker 컨테이너에 설치됨 ``` ### 11.2 큐 워커 설정 (운영 권장) diff --git a/projects/e-sign/technical-design.md b/projects/e-sign/technical-design.md index 6405a6d..b5d0332 100644 --- a/projects/e-sign/technical-design.md +++ b/projects/e-sign/technical-design.md @@ -39,14 +39,20 @@ | 영역 | 기술 | 비고 | |------|------|------| -| Backend | Laravel 11 (PHP 8.3) | SAM API 프로젝트 확장 | -| Frontend | HTMX + Alpine.js + Tailwind CSS | SAM MNG 프로젝트 확장 | -| Database | MySQL 8.0 | 기존 SAM DB 공유 | -| PDF 렌더링 | pdf.js (프론트) | 브라우저 PDF 표시 | +| Backend | Laravel 11 (PHP 8.3) | SAM MNG + API 프로젝트 | +| Frontend | React 18 + Babel (CDN) | 브라우저 트랜스파일링 | +| Navigation | HTMX | SPA 없이 네비게이션 | +| Styling | Tailwind CSS | 유틸리티 퍼스트 | +| Database | MySQL 8.0 (Multi-tenant) | 기존 SAM DB 공유 | +| PDF 렌더링 | PDF.js (프론트) | 브라우저 PDF 표시 | | 서명 캡처 | signature_pad.js | 터치/마우스 서명 | -| PDF 합성 | FPDI + FPDF (백엔드) | 원본 PDF에 서명 삽입 | -| 파일 저장 | Laravel Storage (local/S3) | 암호화 저장 | -| 알림 | Laravel Notification (Mail) | 이메일 발송 | +| DOCX→PDF 변환 | LibreOffice (headless) | MNG Docker 컨테이너 | +| PDF 서명 합성 | FPDI + TCPDF (백엔드) | 원본 PDF에 서명 오버레이 | +| 서명 이미지 처리 | GD 확장 | PNG 서명 이미지 처리 | +| 한글 지원 | 나눔 폰트 (fonts-nanum) | DOCX→PDF 한글 렌더링 | +| 아이콘 | Lucide | React 아이콘 라이브러리 | +| 파일 저장 | Laravel Storage (local) | MNG 로컬 스토리지 | +| 알림 | Laravel Mail | 이메일 발송 | --- @@ -75,10 +81,12 @@ │ │ - 계약 관리 화면 │ │ - 계약 CRUD API │ │ │ │ - PDF 뷰어/서명 UI │ │ - 서명 처리 API │ │ │ │ - 대시보드 │ │ - 인증 API (OTP) │ │ -│ │ │ │ - PDF 합성 서비스 │ │ -│ │ HTMX + Alpine.js │ │ - 알림 서비스 (이메일) │ │ -│ │ + pdf.js │ │ - 감사 로그 서비스 │ │ -│ │ + signature_pad │ │ - 문서 해시 검증 서비스 │ │ +│ │ - DOCX→PDF 변환 │ │ - 알림 서비스 (이메일) │ │ +│ │ - PDF 서명 합성 │ │ - 감사 로그 서비스 │ │ +│ │ │ │ - 문서 해시 검증 서비스 │ │ +│ │ React 18 + HTMX │ │ │ │ +│ │ + PDF.js │ │ │ │ +│ │ + signature_pad │ │ │ │ │ └──────────┬───────────┘ └──────────────┬─────────────────┘ │ │ │ │ │ │ └────────────┬───────────────────┘ │ @@ -104,11 +112,15 @@ ### 2.2 서비스 레이어 구조 ``` -app/Services/ESign/ +api/app/Services/ESign/ ├── EsignContractService.php # 계약 CRUD, 상태 관리, 발송/리마인더 ├── EsignSignService.php # 서명 처리, OTP 인증, 토큰 관리 -├── EsignPdfService.php # PDF 합성, 해시 생성/검증 +├── EsignPdfService.php # 해시 생성/검증 └── EsignAuditService.php # 감사 추적 로그 기록 + +mng/app/Services/ESign/ +├── DocxToPdfConverter.php # DOCX→PDF 변환 (LibreOffice headless) +└── PdfSignatureService.php # PDF 서명 합성 (FPDI/TCPDF) ``` --- @@ -1021,7 +1033,7 @@ storage/app/esign/ | 2주차 | 서명 위치 지정 화면 (pdf.js + 드래그) | MNG | | 2주차 | OTP 인증 + 서명 캡처 (signature_pad) | MNG + API | | 2주차 | 이메일 발송 (서명 요청/완료) | API | -| 2주차 | PDF 합성 (FPDI + 서명 이미지) | API | +| 2주차 | PDF 서명 합성 (FPDI/TCPDF) + DOCX→PDF (LibreOffice) | MNG | ### Phase 2: 보안 강화 (1주) @@ -1562,8 +1574,9 @@ signer_order = 2 → 현재 계약에서 sign_order = 2인 서명자의 signer_i | 기능 | 현재 상태 | 구현 방안 | |------|----------|----------| -| PDF 서명 합성 | 스텁 구현 | FPDI + FPDF 라이브러리로 원본 PDF에 서명 이미지 삽입 | -| 감사 증적 페이지 | 스텁 구현 | 완료 PDF 마지막 페이지에 서명 이력 자동 추가 | +| PDF 서명 합성 | **구현 완료** | FPDI + TCPDF로 원본 PDF에 서명 이미지 오버레이 (MNG PdfSignatureService) | +| DOCX→PDF 변환 | **구현 완료** | LibreOffice headless + 나눔 폰트 (MNG DocxToPdfConverter) | +| 감사 증적 페이지 | 미구현 | 완료 PDF 마지막 페이지에 서명 이력 자동 추가 | | 파일 암호화 | 미구현 | AES-256-CBC로 원본 PDF 암호화 저장 | | 자동 만료 처리 | 미구현 | Laravel Scheduler로 만료된 계약 상태 자동 변경 | | 자동 리마인더 | 미구현 | 만료 3일 전 자동 알림 이메일 발송 |