docs:E-Sign 기술 스택 문서 업데이트 (실제 구현 반영)

- FPDI/FPDF → FPDI/TCPDF (PDF 서명 합성, MNG PdfSignatureService)
- DOCX→PDF 변환 추가 (LibreOffice headless, MNG DocxToPdfConverter)
- GD 확장, 나눔 폰트, Lucide 아이콘 등 실제 사용 기술 반영
- 4개 문서 일괄 업데이트 (technical-design, implementation-guide, operations-guide, changelog)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-13 13:02:16 +09:00
parent d88ee4b67e
commit aab9dc0799
4 changed files with 98 additions and 37 deletions

View File

@@ -217,7 +217,8 @@ projects/e-sign/
### v1.1.0 (예정) ### v1.1.0 (예정)
- [ ] PDF 서명 합성 (`EsignPdfService::composeSigned`) - FPDI/FPDF 라이브러리 - [x] PDF 서명 합성 - FPDI/TCPDF (MNG `PdfSignatureService`)
- [x] DOCX→PDF 변환 - LibreOffice headless (MNG `DocxToPdfConverter`)
- [ ] 감사 증적 페이지 추가 (`EsignPdfService::addAuditPage`) - [ ] 감사 증적 페이지 추가 (`EsignPdfService::addAuditPage`)
- [ ] 만료 계약 자동 처리 (Laravel Scheduler) - [ ] 만료 계약 자동 처리 (Laravel Scheduler)
- [ ] Queue Worker 설정 (이메일 비동기 발송) - [ ] Queue Worker 설정 (이메일 비동기 발송)

View File

@@ -18,7 +18,8 @@ API 프로젝트에 백엔드 로직(모델, 서비스, 컨트롤러, 라우트)
|------|------| |------|------|
| 데이터베이스 | 마이그레이션 4개 (esign_ 접두사 테이블) | | 데이터베이스 | 마이그레이션 4개 (esign_ 접두사 테이블) |
| 모델 | 4개 (EsignContract, EsignSigner, EsignSignField, EsignAuditLog) | | 모델 | 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엔드포인트) | | API 컨트롤러 | 2개 (Contract 10엔드포인트, Sign 6엔드포인트) |
| FormRequest | 4개 (Store, FieldConfigure, SignSubmit, SignReject) | | FormRequest | 4개 (Store, FieldConfigure, SignSubmit, SignReject) |
| 메일 | 1개 (EsignRequestMail) | | 메일 | 1개 (EsignRequestMail) |
@@ -86,6 +87,10 @@ app/Http/Controllers/ESign/
├── EsignController.php # 인증 필요 (5개 메서드) ├── EsignController.php # 인증 필요 (5개 메서드)
└── EsignPublicController.php # 비인증 (3개 메서드) └── EsignPublicController.php # 비인증 (3개 메서드)
app/Services/ESign/
├── DocxToPdfConverter.php # DOCX→PDF 변환 (LibreOffice headless)
└── PdfSignatureService.php # PDF 서명 합성 (FPDI/TCPDF)
resources/views/esign/ resources/views/esign/
├── dashboard.blade.php # 대시보드 (통계 + 목록) ├── dashboard.blade.php # 대시보드 (통계 + 목록)
├── create.blade.php # 계약 생성 (PDF 업로드) ├── create.blade.php # 계약 생성 (PDF 업로드)
@@ -349,16 +354,46 @@ signer() → BelongsTo(EsignSigner)
- 최대 5회 시도 (`otp_attempts`) - 최대 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 해시 생성 | 구현 완료 | | `generateHash(string $filePath)` | SHA-256 해시 생성 | 구현 완료 |
| `verifyIntegrity(string $filePath, string $expectedHash)` | 해시 비교 검증 (hash_equals) | 구현 완료 | | `verifyIntegrity(string $filePath, string $expectedHash)` | 해시 비교 검증 (hash_equals) | 구현 완료 |
| `composeSigned(...)` | 원본 PDF + 서명 이미지 합성 | 스텁 (FPDI 추후) | | `composeSigned(...)` | 원본 PDF + 서명 이미지 합성 | 스텁 (MNG로 이관) |
| `addAuditPage(...)` | 감사 증적 페이지 추가 | 스텁 (FPDI 추후) | | `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 ### 5.4 EsignAuditService
@@ -691,6 +726,8 @@ NOTIFIED/AUTHENTICATED → REJECTED (거절)
| React 하이브리드 | `views/finance/journal-entries.blade.php` | CDN React + Babel 패턴 참고 | | React 하이브리드 | `views/finance/journal-entries.blade.php` | CDN React + Babel 패턴 참고 |
| HX-Redirect | 컨트롤러 패턴 | HTMX 부분 로드 시 전체 리로드 | | HX-Redirect | 컨트롤러 패턴 | HTMX 부분 로드 시 전체 리로드 |
| SidebarMenu | `app/Services/SidebarMenuService.php` | DB 기반 메뉴 | | 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 마지막에 감사 정보 페이지 추가 | | 감사 증적 페이지 | 높음 | 완료 PDF 마지막에 감사 정보 페이지 추가 |
| 파일 암호화 (AES-256) | 중간 | 원본 PDF 암호화 저장 | | 파일 암호화 (AES-256) | 중간 | 원본 PDF 암호화 저장 |
| 만료 자동 처리 | 중간 | 스케줄러로 expires_at 초과 계약 expired 처리 | | 만료 자동 처리 | 중간 | 스케줄러로 expires_at 초과 계약 expired 처리 |
| 리마인더 자동 발송 | 낮음 | 만료 3일 전 자동 리마인드 | | 리마인더 자동 발송 | 낮음 | 만료 3일 전 자동 리마인드 |
| SMS OTP | 낮음 | 이메일 외 SMS 인증 지원 | | SMS OTP | 낮음 | 이메일 외 SMS 인증 지원 |
| OTP bcrypt 해싱 | 중간 | 현재 평문 저장 → bcrypt 해싱 | | OTP bcrypt 해싱 | 중간 | 현재 평문 저장 → bcrypt 해싱 |
| PDF 서명 텍스트 한글 | 낮음 | TCPDF CJK 폰트 추가 (현재 helvetica만 사용) |
--- ---

View File

@@ -71,14 +71,21 @@
### 1.4 기술 스택 ### 1.4 기술 스택
| 항목 | 버전 | | 항목 | 버전 | 비고 |
|------|------| |------|------|------|
| PHP | 8.3 | | PHP | 8.3 | |
| Laravel | 11 (API), 11 (MNG) | | Laravel | 11 (API), 11 (MNG) | |
| MySQL | 8.0 | | MySQL | 8.0 | Multi-tenant |
| Nginx | 최신 | | Nginx | 최신 | |
| Docker Compose | v2 | | Docker Compose | v2 | |
| Node.js | React 18 (CDN, 빌드 불필요) | | 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. 확장 가이드
### 11.1 v1.1 추가 예정 패키지 ### 11.1 현재 설치된 패키지 (구현 완료)
```bash ```bash
# PDF 서명 합성 (FPDI + FPDF) # FPDI/TCPDF (PDF 서명 합성) - MNG 프로젝트에 설치됨
docker exec sam-api-1 composer require setasign/fpdi setasign/fpdf # setasign/fpdi: ^2.6, tecnickcom/tcpdf: ^6.10
# 서버 # LibreOffice (DOCX→PDF 변환) - MNG Docker 컨테이너에 설치됨
cd /home/webservice/api # libreoffice-writer-nogui, fonts-nanum, fonts-nanum-extra
composer require setasign/fpdi setasign/fpdf
# GD 확장 (서명 이미지 처리) - MNG Docker 컨테이너에 설치됨
``` ```
### 11.2 큐 워커 설정 (운영 권장) ### 11.2 큐 워커 설정 (운영 권장)

View File

@@ -39,14 +39,20 @@
| 영역 | 기술 | 비고 | | 영역 | 기술 | 비고 |
|------|------|------| |------|------|------|
| Backend | Laravel 11 (PHP 8.3) | SAM API 프로젝트 확장 | | Backend | Laravel 11 (PHP 8.3) | SAM MNG + API 프로젝트 |
| Frontend | HTMX + Alpine.js + Tailwind CSS | SAM MNG 프로젝트 확장 | | Frontend | React 18 + Babel (CDN) | 브라우저 트랜스파일링 |
| Database | MySQL 8.0 | 기존 SAM DB 공유 | | Navigation | HTMX | SPA 없이 네비게이션 |
| PDF 렌더링 | pdf.js (프론트) | 브라우저 PDF 표시 | | Styling | Tailwind CSS | 유틸리티 퍼스트 |
| Database | MySQL 8.0 (Multi-tenant) | 기존 SAM DB 공유 |
| PDF 렌더링 | PDF.js (프론트) | 브라우저 PDF 표시 |
| 서명 캡처 | signature_pad.js | 터치/마우스 서명 | | 서명 캡처 | signature_pad.js | 터치/마우스 서명 |
| PDF 합성 | FPDI + FPDF (백엔드) | 원본 PDF에 서명 삽입 | | DOCX→PDF 변환 | LibreOffice (headless) | MNG Docker 컨테이너 |
| 파일 저장 | Laravel Storage (local/S3) | 암호화 저장 | | PDF 서명 합성 | FPDI + TCPDF (백엔드) | 원본 PDF에 서명 오버레이 |
| 알림 | Laravel Notification (Mail) | 이메일 발송 | | 서명 이미지 처리 | GD 확장 | PNG 서명 이미지 처리 |
| 한글 지원 | 나눔 폰트 (fonts-nanum) | DOCX→PDF 한글 렌더링 |
| 아이콘 | Lucide | React 아이콘 라이브러리 |
| 파일 저장 | Laravel Storage (local) | MNG 로컬 스토리지 |
| 알림 | Laravel Mail | 이메일 발송 |
--- ---
@@ -75,10 +81,12 @@
│ │ - 계약 관리 화면 │ │ - 계약 CRUD API │ │ │ │ - 계약 관리 화면 │ │ - 계약 CRUD API │ │
│ │ - PDF 뷰어/서명 UI │ │ - 서명 처리 API │ │ │ │ - PDF 뷰어/서명 UI │ │ - 서명 처리 API │ │
│ │ - 대시보드 │ │ - 인증 API (OTP) │ │ │ │ - 대시보드 │ │ - 인증 API (OTP) │ │
│ │ │ │ - PDF 합성 서비스 │ │ │ │ - DOCX→PDF 변환 │ │ - 알림 서비스 (이메일) │ │
│ │ HTMX + Alpine.js │ │ - 알림 서비스 (이메일) │ │ │ │ - PDF 서명 합성 │ │ - 감사 로그 서비스 │ │
│ │ + pdf.js │ │ - 감사 로그 서비스 │ │ │ │ │ │ - 문서 해시 검증 서비스 │ │
│ │ + signature_pad │ │ - 문서 해시 검증 서비스 │ │ │ │ React 18 + HTMX │ │ │ │
│ │ + PDF.js │ │ │ │
│ │ + signature_pad │ │ │ │
│ └──────────┬───────────┘ └──────────────┬─────────────────┘ │ │ └──────────┬───────────┘ └──────────────┬─────────────────┘ │
│ │ │ │ │ │ │ │
│ └────────────┬───────────────────┘ │ │ └────────────┬───────────────────┘ │
@@ -104,11 +112,15 @@
### 2.2 서비스 레이어 구조 ### 2.2 서비스 레이어 구조
``` ```
app/Services/ESign/ api/app/Services/ESign/
├── EsignContractService.php # 계약 CRUD, 상태 관리, 발송/리마인더 ├── EsignContractService.php # 계약 CRUD, 상태 관리, 발송/리마인더
├── EsignSignService.php # 서명 처리, OTP 인증, 토큰 관리 ├── EsignSignService.php # 서명 처리, OTP 인증, 토큰 관리
├── EsignPdfService.php # PDF 합성, 해시 생성/검증 ├── EsignPdfService.php # 해시 생성/검증
└── EsignAuditService.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주차 | 서명 위치 지정 화면 (pdf.js + 드래그) | MNG |
| 2주차 | OTP 인증 + 서명 캡처 (signature_pad) | MNG + API | | 2주차 | OTP 인증 + 서명 캡처 (signature_pad) | MNG + API |
| 2주차 | 이메일 발송 (서명 요청/완료) | API | | 2주차 | 이메일 발송 (서명 요청/완료) | API |
| 2주차 | PDF 합성 (FPDI + 서명 이미지) | API | | 2주차 | PDF 서명 합성 (FPDI/TCPDF) + DOCX→PDF (LibreOffice) | MNG |
### Phase 2: 보안 강화 (1주) ### Phase 2: 보안 강화 (1주)
@@ -1562,8 +1574,9 @@ signer_order = 2 → 현재 계약에서 sign_order = 2인 서명자의 signer_i
| 기능 | 현재 상태 | 구현 방안 | | 기능 | 현재 상태 | 구현 방안 |
|------|----------|----------| |------|----------|----------|
| PDF 서명 합성 | 스텁 구현 | FPDI + FPDF 라이브러리로 원본 PDF에 서명 이미지 삽입 | | PDF 서명 합성 | **구현 완료** | FPDI + TCPDF로 원본 PDF에 서명 이미지 오버레이 (MNG PdfSignatureService) |
| 감사 증적 페이지 | 스텁 구현 | 완료 PDF 마지막 페이지에 서명 이력 자동 추가 | | DOCX→PDF 변환 | **구현 완료** | LibreOffice headless + 나눔 폰트 (MNG DocxToPdfConverter) |
| 감사 증적 페이지 | 미구현 | 완료 PDF 마지막 페이지에 서명 이력 자동 추가 |
| 파일 암호화 | 미구현 | AES-256-CBC로 원본 PDF 암호화 저장 | | 파일 암호화 | 미구현 | AES-256-CBC로 원본 PDF 암호화 저장 |
| 자동 만료 처리 | 미구현 | Laravel Scheduler로 만료된 계약 상태 자동 변경 | | 자동 만료 처리 | 미구현 | Laravel Scheduler로 만료된 계약 상태 자동 변경 |
| 자동 리마인더 | 미구현 | 만료 3일 전 자동 알림 이메일 발송 | | 자동 리마인더 | 미구현 | 만료 3일 전 자동 알림 이메일 발송 |