[우아한테크코스] 7월 26일 TIL

2 minute read

[Spring] svg파일을 png파일로 변환해 S3에 저장하기

  1. svg 파일을 png파일로 변환한다.
    디펜던시 추가
     //svg
     implementation 'org.apache.xmlgraphics:batik-all:1.12'
     implementation 'org.apache.xmlgraphics:xmlgraphics-commons:2.4'
     implementation 'xml-apis:xml-apis:1.4.01'
     implementation 'xml-apis:xml-apis-ext:1.3.04'
    
public File convertSvgToPng(final String mapSvgData) {
    try {
        String tmpFileName = UUID.randomUUID().toString();  //파일이 중복되어 저장되지 않도록 랜덤 이름 생성
        ByteArrayInputStream svgInput = new ByteArrayInputStream(mapSvgData.getBytes());    //svg를 byteInputStream으로 변환(굳이 파일로 만들고 싶지 않아 바이트로 변환함)  
        TranscoderInput transcoderInput = new TranscoderInput(svgInput);    //TranscoderInput에는 InputStream 필요

        OutputStream outputStream = new FileOutputStream("src/main/resources/tmp/" + tmpFileName + ".png"); //프로젝트 내 png 파일 생성
        TranscoderOutput transcoderOutput = new TranscoderOutput(outputStream); //Output할 파일 전달

        PNGTranscoder pngTranscoder = new PNGTranscoder();
        pngTranscoder.transcode(transcoderInput, transcoderOutput); //변환해서 output에 write

        outputStream.flush();   //사용한 스트림 닫기
        outputStream.close();

        return new File("src/main/resources/tmp/" + tmpFileName + ".png");  //우리가 넣고 싶은 png 파일 형태로 반환
    } catch (Exception e) {
        e.printStackTrace();
        throw new IllegalArgumentException(e.getMessage());
    }
}

inputStream, OutputStream을 통해 파일로 만들어낼 수도 있지만 우리는 s3 저장이 목표이므로 굳이 만들어내지 않았다.

  1. S3Uploader 구현
    디펜던시 추가
     implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
    

프로퍼티 추가

#s3
cloud.aws.s3.bucket_name=2021-zzimkkong-thumbnail
cloud.aws.stack.auto=false  //Spring cloud 프로젝트를 실행하면 기본으로 구성되는 CloudFormation 구성 사용하지 않도록 함, 없으면 프로젝트 시작이 안됨 
cloud.aws.crecentials.instanceProfile=true //ACL로 확인한 Key들을 사용한다. AWS의 instanceProfile을 사용한다.

원래는 버킷에 접근하기 위한 key를 등록해야하지만 현상태로는 접근 권한을 가진 인스턴스 ec2-s3-deploy를 보안 정책으로 삼고 있기 때문에 필요하지 않다.
원래는 버킷 권한에 AmazonS3FullAccess를 원래는 권한 선택을 해주어야한다.

@Component
public class S3Uploader {
    private final AmazonS3 amazonS3;

    public S3Uploader(AmazonS3 amazonS3) {
        this.amazonS3 = amazonS3;
    }

    @Value("${aws.s3.bucket_name}")
    private String bucket;  //해당 버킷 주입

    public String upload(File uploadFile) {
        String fileName = "thumbnails/" + uploadFile.getName(); 
        String uploadImageUrl = putS3(uploadFile, fileName);    //S3에 저장한다

        uploadFile.delete();    //저장된 파일을 삭제하고

        return uploadImageUrl;  //해당 url을 넘겨준다
    }

    private String putS3(File uploadFile, String fileName) {
        amazonS3.putObject(new PutObjectRequest(bucket, fileName, uploadFile));
        return amazonS3.getUrl(bucket, fileName).toString();
    }
}

403이 뜨면서 s3의 권한 정책을 바꿔주려 했지만 이미 권한을 가진 인스턴스를 보안 정책으로 삼고 있기 때문에 불필요했다.
실제로 권한 정책 설정은 아래와 같다. 정책 생성기도 있으나 policy가 올바르지 않으면 오류가 발생하니 주의해야 한다.

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E2BULBMNUD9HBL"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::2021-zzimkkong-thumbnail/*"
        }, 
        {
            "Sid": "2",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam:843255971531:role/ec2-s3-deploy"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::2021-zzimkkong-thumbnail/*"
        }
    ]
}

PutObjectRequest 생성시에 .withCannedAcl(CannedAccessControlList.PublicRead) 옵션과 amazonS3 빌더에 .withCredentials(InstanceProfileCredentialsProvider.getInstance()) 때문에 애를 먹었는데 withCannedAcl은 새로운 객체에 대한 접근 권한을 주는 것이다. PublicRead라는 것은 모두에게 읽을 수 있다는 것을 의미하는 것 같은데 왜 안됐는지 모르겠다.
withCredential은 amazonS3의 권한 설정을 따르겠다는 것인데 권한이 ec2에 적용되어있는 것을 저 메서드가 죽여서 실패했던 것 같다.

메서드 분리로 더 예쁘게 만드는 작업이 필요할듯 하다.

참고

원래는 Blob을 이용해 바이너리 파일로 저장할까 했는데 프론트에서 png 파일 주소 자체를 넘겨받기를 원했다. db에 png 바이너리 파일로 저장하는 것은 MultipartFile이라는 것을 이용하는 듯 했다.
input output 스트림을 다시 공부해보아야 겠다.