web/SpringBoot

파일다운로드 시 PDF 워터마크 처리하기

뽀리님 2023. 10. 17. 14:11

현재 진행하고 있는 프로젝트에서 나한테 말도 없이 파일다운로드 시 워터마크처리 하는걸 날 끼워 놓곤 던져줬다.

그것도 기간도 3일주더라 ㅋㅋㅋ

 

그래서 2일만에 해줬다. 그게 뭐라고 ㅋㅋㅋ

시간이 남아서 포스팅에 개발과정을 정리해보기로 했다.

 

 

<개발스펙>
Spring Boot 3.1.x + Gradle + IntelliJ + OPENJDK17
RESTfulAPI

<파일서버>
AWS S3 Bucket

 

일단 PDF 파일을 다루기 위해 지원되는 라이브러리를 찾다보니 대표적으로 iText 를 제일 많이 이용하더라

나는 무료 버전인 iText5.5.13 버전을 이용하기로 하였다.

 

1. Gradle에 추가

implementation 'com.itextpdf:itextpdf:5.5.13'
implementation 'com.amazonaws:aws-java-sdk:1.12.563'

Aws S3 버킷을 이용할 예정이므로 SDK 도 추가해줬다.

 

 

2.  파일다운로드 API endpoint 를 정의한다.

@PostMapping("/all")
public ResponseEntity<?> fileDownloaToAll(HttpServletRequest request, @RequestBody @Valid FileDownloadDto fileDownload) {
    try {
            // 다운로드 시 파일명 지정
            String downloadFileName = fileDownloadService.getEncodedFilename(request.getHeader("User-Agent"), fileDownload.getFileNm());
            // 파일 다운로드
            InputStreamResource inputStreamResource = fileDownloadService.fileDownloadProc(fileDownload.getFilePath());

            // HTTP 헤더 설정
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", downloadFileName);
            headers.setCacheControl("no-cache, no-store, must-revalidate");

            return ResponseEntity.ok()
                    .headers(headers)
                    .body(inputStreamResource);
        } catch (AmazonS3Exception ae){
            log.error("[AMAZONS3_EXCEPTION]:"+ae.getMessage(), ae);
            return ResponseEntity.noContent().build();
        } catch (Exception e){
            log.error("[EXCEPTION]:"+e.getMessage(),e);
            return ResponseEntity.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).build();
        }
}

 

다운로드시 파일명에 한글이 깨지지 않게 하도록 하기위해, User-Agent 체크를 하여 UTF-8로 각각 인코딩해주었다.

HTTP 헤더 미디어 타입에는 응답 컨텐츠 다운로드를 위해 바이너리 타입으로 지정한다.

응답값 저장이나, 캐싱사용을 막기위해 캐시컨트롤로 설정해주었다.

 

 

3. S3에서 파일을 가져온 다음, PDF 파일일경우에만 워터마크 처리한다.

public InputStreamResource fileDownloadProc(String filePath) throws Exception {
    InputStreamResource resource = null;
    // 1.S3에서 파일가져오기
    log.info("==== 1. S3 파일가져오기");
    ByteArrayInputStream s3FileInputStream = fileDownloadFromAwsS3(filePath);

    if(ObjectUtils.isNotEmpty(s3FileInputStream)){
        if(PdfUtils.PdfFileCheck(filePath)) {
            // 2.해당파일 워터마크처리
            log.info("==== 2. 파일워터마크 처리");
            resource = fileWatermarkProc(s3FileInputStream);
        }
        else
            resource = new InputStreamResource(s3FileInputStream);
    }
    return resource;
}

모든 파일을 다운로드 받을수 있지만 PDF 일경우에는 확장자를 체크한 후 회원일 경우에만 워터마크 처리를 하기로 하였다.

 

 

4. S3에서 파일가져오기.

private ByteArrayInputStream fileDownloadFromAwsS3(String path) throws Exception {
    ByteArrayInputStream s3FileStream;
    // 버킷에 있는 파일 가져온후 파일 스트림생성
    try(S3ObjectInputStream objectInputStream = getLoadS3File(path).getObjectContent()){  
        byte[] bytes = IOUtils.toByteArray(objectInputStream); // 바이트로 변환
        s3FileStream = new ByteArrayInputStream(bytes);
    } catch (AmazonS3Exception ae){
        log.error("[파일다운로드]"+ae.getMessage());
        throw new AmazonS3Exception("파일을 찾을 수 없습니다.");
    } catch (Exception e){
        log.error("[파일다운로드]"+e.getMessage());
        throw e;
    }
    return s3FileStream;
}

private AmazonS3 getS3Client(){
        return AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials( accessKey, secretKey )))
                .withRegion(region)
                .build();
    }

Stream은 try-catch-resource 로 autoclose 해줬다.

Aws S3 AccessKey와 SecretKey, 버킷명, 지역으로 클라이언트정보를 가져올수 있다. (이건 검색하면 다양한방법이있음)

 

 

5. 파일을 가져왔다면 해당파일을 다시 워터마크 처리해준다.

public static void pdfAddWatermark(ByteArrayInputStream byteArrayInputStream, ByteArrayOutputStream byteArrayOutputStream, String watermarkText) throws DocumentException, IOException {
    PdfContentByte over;
    PdfReader pdfReader = new PdfReader(byteArrayInputStream);
    PdfStamper pdfStamper = new PdfStamper(pdfReader, byteArrayOutputStream);
    int total = pdfReader.getNumberOfPages() + 1;
    BaseFont baseFont = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);

    for (int i = 1; i < total; i++) {
        Rectangle rectangle = pdfReader.getPageSizeWithRotation(i);
        float x = rectangle.getWidth() / X_AMOUNT;
        float y = rectangle.getHeight() / Y_AMOUNT;
        PdfGState gs = new PdfGState();
        gs.setFillOpacity(OPACITY);
        over = pdfStamper.getOverContent(i);
        over.setGState(gs);
        over.saveState();
        over.beginText();
        over.setColorFill(BASE_COLOR);
        over.setFontAndSize(baseFont, FONT_SIZE);

        for (int n = 0; n < X_AMOUNT + 1; n++) {
            for (int m = 0; m < Y_AMOUNT + 1; m++) {
                over.showTextAligned(Element.ALIGN_CENTER, "watermarkText", x * n, y * m, ROTATION);
            }
        }
        over.endText();
    }
    pdfStamper.close();
    pdfReader.close();
}

첫번째 인자인 InputStream에 아까 S3에서 받아온 파일스트림을 넣어주면된다.

 

 

[결과]

 

 

 

 

 

-끝-