전에 iTextPDF와 Commons-Net으로 PDF 생성 및 FTP 서버 업로드 기능 구현하기 포스트를 통해 서버로 들어 온 사용자 정보를 이용해 pdf 파일을 생성 후 외부 FTP 서버로 업로드 시키는 기능을 구현해보았습니다.
요구사항의 변화로 많은 부분 수정이 있었습니다. 어떻게 수정되었는지 포스트를 작성해 기록해보려 합니다.
- 기능은 이전 포스트에서 작성했으므로, 현재 이 포스트에서는 3, 2, 4 순으로 작성해 보겠습니다.
iTextPDF (5.5.13.3 -> 7.2.4)
텍스트나 html 등의 문서를 pdf로 만들어주는 자바 라이브러리
Commons-Net (3.9.0)
다양한 프로토콜(FTP, TFTP, SMTP, ...)에 대한 지원을 할 수 있도록 도와주는 자바 라이브러리
클라이언트 모듈을 제공합니다.
PDFBox(2.0.30)
서버에서 생성된 pdf 파일을 jpeg 로 변환 할 때 사용했습니다.
JAVA 1.8
Gradle 4.10.3
SpringBoot 2.1.5
조금 더 세밀한 설정과 레퍼런스가 많은 7.x.x 버전으로 변경하기로 했습니다.
// 변경 전
compile 'com.itextpdf:itextpdf:5.5.13.3'
// 변경 후
compile 'com.itextpdf:itext7-core:7.2.4'
pdf파일을 생성하는 메서드에서 뭔가 바꿔주면 되지않을까..? 라고 생각했습니다.
먼저 이 전 포스트의 pdf파일을 생성하는 메서드를 확인해보겠습니다.
public byte[] createPdf(Map<String, Object> data) throws IOException, DocumentException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { // ByteArrayOutputStream -> AutoCloseable 하므로 try-with-resources 사용
// PDF 파일 자체를 나타내는 Document 객체 초기화
Document document = new Document(PageSize.A4, 5, 5, 10, 10);
// Document 객체와 ByteArrayOutputStream 을 연결하여 PDF 파일을 메모리에서 생성하도록 함
PdfWriter.getInstance(document, out);
// Document 객체를 열고 작성 후 닫음
document.open();
document.add(contractMaker(data));
document.close();
// PDF 파일을 FTP 서버로 보낼 수 있도록 PDF 파일의 내용이 저장된 바이트 배열 반환
return out.toByteArray();
}
}
버전이 바뀜에 따라 메서드와 사용법등이 좀 바뀌었습니다.
public byte[] createPdf(Map<String, Object> data) throws IOException, DocumentException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
PdfWriter pdfWriter = new PdfWriter(out);
Document document = new Document(new PdfDocument(pdfWriter), PageSize.A4);
document.add(contractMaker(data));
document.close();
return out.toByteArray();
}
}
자, 이제 변경된 버전은 반영했습니다.
위에서 생성된 pdf파일을 jpeg로 변환 후 압축 해서 내려줘야 하는데...
구글링을 해보니 pdfbox 라이브러리의 메서드를 사용하면 된다고해서 사용해 보았습니다.
compile 'org.apache.pdfbox:pdfbox:2.0.30'
private byte[] pdfTOjpeg(byte[] bytes) throws IOException {
// .load() 를 사용해 파라미터로 들어온 pdf 파일을 메모리에 로드 시켜줍니다.
try (PDDocument pdDocument = PDDocument.load(bytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// pdf의 각 페이지를 이미지로 렌더링
PDFRenderer renderer = new PDFRenderer(pdDocument);
for (int i = 0; i < pdDocument.getNumberOfPages(); i++) {
BufferedImage image = renderer.renderImageWithDPI(i, 300);
float quality = 0.5f;
// 이미지크기는 pdf 파일의 크기(가로x세로) / 2
// Graphics2D 객체를 사용해 크기가 조절된 이미지를 그린다.
int width = image.getWidth() / 2;
int height = image.getHeight() / 2;
BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
try ( ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream); ) {
ImageWriter writer = obtainImageWriter("JPEG");
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
// 압축방식(모드) 설정
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 개발자가 압축 품질 설정한다는 모드
// 압축(0과 1사이의 값으로 설정)
param.setCompressionQuality(quality);
writer.write(null, new IIOImage(resizedImage, null, null), param);
}
}
return outputStream.toByteArray();
}
}
위 메서드에서 반환되는 byte[] 는 변환 후 압축된 jpeg 파일입니다.
자, 이제 내려줄 일만 남았습니다.
요구 사항 변경 : pdf 파일로 압축해서 저장 후 내려주세요.
예예...다 됩니다....
public byte[] createPdf(Map<String, Object> data) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
PdfWriter pdfWriter = new PdfWriter(out);
// pdf 파일 압축 수준 설정 (1~9)
pdfWriter.setCompressionLevel(BEST_COMPRESSION); // 9
Document document = new Document(new PdfDocument(pdfWriter), PageSize.A4);
document.add(contractMaker(data));
document.close();
return out.toByteArray();
}
}
요구 사항도 반영했으니, 내려주도록 해볼까요?
// * 계약서를 생성 후 이용기관 FPT 서버에 저장 후 조회해서 내려줌
@PostMapping("/contract")
public ResponseEntity<Object> contract(@RequestBody Map<String, Object> body) throws IOException, IllegalAccessException {
byte[] pdfData = contractSaveService.saveEndFindViewer(body);
if (pdfData != null) {
ByteArrayResource resource = new ByteArrayResource(pdfData);
return ResponseEntity.ok()
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"contract.pdf\"") // 다운로드
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"contract.pdf\"") // 바로보기
.contentType(MediaType.APPLICATION_PDF)
.contentLength(pdfData.length)
.body(resource);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("파일을 찾지 못했습니다.");
}
public byte[] saveEndFindViewer(Map<String, Object> body) throws IOException, IllegalAccessException {
// 파일명을 정해서 넘겨줘야함 (yyyyMMdd_고객번호)
String fileName = body.get("contractDate") + "_" + body.get("clientCode");
// ftpFileSender.upload(fileName + ".jpeg", pdfMaker.createPdf(body));
return ftpFileSender.upload(fileName + ".pdf", pdfMaker.createPdf(body)) ?
ftpFileSender.download(fileName + ".pdf") : null;
}
MediaType.APPLICATION_PDF는 Spring Framework에서 사용되는 상수로, HTTP 헤더의 'Content-Type'을 'application/pdf'로 설정하는 데 사용됩니다.
이는 주로 PDF 파일을 HTTP 응답으로 전송할 때 사용되며, 클라이언트에게 응답 본문이 PDF 형식임을 명시적으로 알립니다.
이를 통해 웹 애플리케이션은 PDF 문서를 브라우저나 다른 클라이언트에 효율적으로 제공할 수 있습니다.
응답헤더에 응답값의 형식(APPLICATION_PDF)을 명시해 준 후 추가로 파일을 다운로드하려면 Content-Disposition
헤더에 attachment; filename="filename.pdf"
를, 바로 보여주려면 inline; filename="filename.pdf"
를 설정합니다. 이를 통해 브라우저가 파일을 어떻게 처리할지 결정하게 됩니다. attachment
는 파일 다운로드를, inline
은 브라우저 내에서 파일을 바로 보여주는 데 사용됩니다.
jpeg 변환과 압축 부분에서 레퍼런스가 많지 않아 조금 막혔습니다.
좋은 경험이였습니다!