백엔드 연동 (fetcher)

뷰어의 모든 백엔드 통신은 fetcher 객체로 위임됩니다.

1.fetcher 계약

함수호출 시점반환
open()viewer.open()문서 모델 JSON
pageRender({ page, renderScale })페이지 표시페이지 이미지 Blob (PNG)
annotationRender({ annotation, renderScale })이미지 주석 표시주석 이미지 Blob
save({ saveFormData })viewer.save()저장 후 문서 모델 JSON
digest({ saveFormData })viewer.sign() 1단계서명 대상 해시 (hex 문자열)
sign({ saveFormData })viewer.sign() 2단계서명 완료 문서 모델 JSON

2.백엔드 엔드포인트

백엔드 라이브러리(JAR)를 사용해 아래 형태의 엔드포인트를 구성합니다.

메서드경로 (예시)역할
GET/document/{이름}PDF 로드 → 문서 모델 JSON
GET/document/{이름}/page/{번호}/render?scale=페이지 이미지
GET/document/{이름}/annotation/{번호}/{세대}/render주석 이미지
POST/document/{이름}/save/{저장이름}문서 모델 반영 후 저장
POST/document/{이름}/digitalsignature/digest서명 해시 생성
POST/document/{이름}/digitalsignature/sign서명 주입, 저장

3.구현 예

JSfetcher 구현
const BACKEND = 'https://내-서버';
const fileName = 'devsite.pdf';

const fetcher = {
  open: async () => {
    const response = await fetch(`${BACKEND}/document/${encodeURIComponent(fileName)}`);
    return await response.json();
  },

  pageRender: async ({ page, renderScale = 2 }) => {
    const response = await fetch(
      `${BACKEND}/document/${encodeURIComponent(fileName)}/page/${page.pageIndex}/render?scale=${renderScale}`);

    return await response.blob();
  },

  save: async ({ saveFormData }) => {
    const response = await fetch(
      `${BACKEND}/document/${encodeURIComponent(fileName)}/save/${encodeURIComponent(fileName)}`,
      { method: 'POST', body: saveFormData });

    return await response.json();
  },
};

viewer.setFetcher(fetcher);
• 참고

saveFormData는 뷰어가 만들어 넘겨주는 FormData입니다. document 파트(문서 모델 JSON)와 imageFileList 파트(추가된 이미지)가 들어 있으며, 백엔드에서 @RequestPart로 받으면 됩니다.

4.백엔드 쪽 구현 (Java)

JAVA문서 열기/저장 엔드포인트
// 열기: PDF 로드 → 문서 모델 JSON 반환
@GetMapping("/document/{name}")
public Document open(@PathVariable String name) throws IOException {
  return Document.loadFromPDF(pdfPath.resolve(name), cachePath.resolve(name));
}

// 저장: 모델 반영(applyToPDF) 후 파일로 저장(saveToPDF)
@PostMapping("/document/{name}/save/{saveName}")
public Document save(@PathVariable String name, @PathVariable String saveName,
    @RequestPart("document") Document document,
    @RequestPart(name = "imageFileList", required = false) MultipartFile[] imageFileList) throws IOException {

  HashMap<String, byte[]> imageFileMap = toMap(imageFileList);

  document.applyToPDF(pdfPath.resolve(name), imageFileMap);
  document.saveToPDF(pdfPath.resolve(saveName), cachePath.resolve(saveName));

  return document;
}

5.에러 처리

fetcher 함수가 예외를 던지지 않으면 뷰어는 실패를 인지하지 못합니다. 특히 open에서 서버 에러(500 등)를 그대로 통과시키면 에러 응답 본문을 문서로 오인할 수 있으므로, 반드시 response.ok를 확인하고 실패 시 throw 하십시오.

JS응답 검사와 예외 처리
open: async () => {
  const response = await fetch(`${BACKEND}/document/${u}`);

  if (!response.ok) {
    const body = await response.json().catch(() => ({}));
    throw new Error(`문서 열기 실패 (HTTP ${response.status}): ${body.message ?? response.statusText}`);
  }

  return await response.json();
},

호출 측에서는 viewer.open()/viewer.save()를 try/catch로 감싸 사용자에게 안내합니다.

JS호출 측
try {
  await viewer.save();
} catch (err) {
  alert(err.message);
}