React

[React] S3으로 이미지 처리하기(1)

sueeee-e 2024. 11. 5. 16:10

쇼핑몰을 구현하면서 

에디터랑 이미지 업로드하는 부분이 필요해서 에디터는 react-quill을 사용하고 이미지 업로드는 <input> 태그로 구현했다. 

 

이미지를 업로드하고 처리하는 과정에서 

에디터와 파일 업로더 둘 다 base24 코드로 저장하기 때문에 길이가 엄청 길다. 그래서 다들 서버를 통해서 url로 바꿔서 저장한다. 

 

url로 바꿔주기 위해서 AWS S3을 사용하기로 했다. 

그럴려면 버킷을 먼서 만들어주고 권한, 역할, 정책 관련으로 정해줘야 할 게 많은데 

나는  아래의 블로그를 참고했다 지금이랑 조금 달라진 부분도 있어서


https://jforj.tistory.com/351

https://make-somthing.tistory.com/67

https://make-somthing.tistory.com/68


중간에 자격증명풀에서 조금 헤멜 수 있다. 

자격 증명 풀을 다 생성하고도 나중에 편집할 수 있으니까 우선 어떻게든 생성을 하고 poolID 값을 만들어야 한다. 

만들 때 제대로 만들었다면 오류가 나지 않지만 나는 중간에 S3 권한을 안줘서 오류가 났다. 

 

여기서 역할 편집에 들어가서 IAM 역할 보기 -> 만들어져 있는 권한 정책 선택 -> 편집에 들어가서 

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["cognito-sync:*", "cognito-identity:*", "s3:*"],
      "Resource": ["*"]
    }
  ]
}

이걸 넣으면 권한에 대한 오류는 해결이 된다. 

 

 

<결과>

파일을 선택하고 저장하면 콘솔 창에 url이 뜬 것을 확인할 수 있다. 

 

 

1. 파일 업로드 - 전체코드 ( 미리보기, S3 연결)

그리고 완성된 FileUpload.js 전체 코드이다. 따로 config.js를 만들어서 필요한 정보를 입력해놓아야 한다. (블로그 참고)

import { Alert, Button, IconButton } from '@mui/material';
import React, { useState, useRef } from 'react';
import AWS from 'aws-sdk';
import * as config from './../../config.js';

AWS.config.update({
    region: config.awsRegion,
    credentials: new AWS.CognitoIdentityCredentials({
        IdentityPoolId: config.awsIdentityPoolId,
    }),
});

const s3 = new AWS.S3();

const UploadFile = ({ maxImages }) => {
    const [images, setImages] = useState([]);
    const [error, setError] = useState('');
    const inputRef = useRef(null);

    const handleImageChange = (e) => {
        const files = Array.from(e.target.files);
        const currentImageCount = images.length;

        if (currentImageCount + files.length > maxImages) {
            setError(`최대 ${maxImages}개의 이미지만 업로드할 수 있습니다.`);
            e.target.value = '';
            return;
        } else {
            setError('');
        }

        const validImages = files.filter((file) => file.type.startsWith('image/')).slice(0, maxImages - currentImageCount);

        validImages.forEach((file) => {
            const reader = new FileReader();
            reader.onloadend = () => {
                setImages((prevImages) => [...prevImages, { file, preview: reader.result }]);
            };
            reader.readAsDataURL(file);
        });

        e.target.value = '';
    };

    const handleRemoveImage = (index) => {
        setImages((prevImages) => prevImages.filter((_, i) => i !== index));
    };

    const handleButtonClick = () => {
        if (inputRef.current) {
            inputRef.current.click();
        }
    };

    const saveEventHandler = async () => {
        try {
            const uploadedUrls = await Promise.all(
                images.map(async ({ file }) => {
                    const params = {
                        Bucket: config.bucketName,
                        Key: `uploads/${Date.now()}-${file.name}`,
                        Body: file,
                        ContentType: file.type,
                        
                    };

                    const { Location } = await s3.upload(params).promise();
                    return Location;
                })
            );
            
            console.log('Uploaded URLs:', uploadedUrls);
        } catch (err) {
            console.error('S3 Upload Error:', err);
            setError('이미지 업로드 중 오류가 발생했습니다.');
        }
    };

    return (
        <div>
            <input
                type="file"
                accept="image/*"
                multiple
                onChange={handleImageChange}
                ref={inputRef}
                style={{ display: 'none' }}
            />
            <Button
                variant="outlined"
                size="small"
                color="primary"
                onClick={handleButtonClick}
                sx={{ margin: 1 }}
            >
                파일 선택
            </Button>
            {error && <Alert severity="error" variant="outlined">{error}</Alert>}
            <div style={{ display: 'flex', flexWrap: 'wrap', width: '100%' }}>
                {images.map(({ preview }, index) => (
                    <div key={index} style={{ position: 'relative', flexDirection: 'column', margin: '5px' }}>
                        <img
                            src={preview}
                            alt={`preview-${index}`}
                            style={{ width: '100px', height: '100px', objectFit: 'cover' }}
                        />
                        <IconButton
                            onClick={() => handleRemoveImage(index)}
                            sx={{ position: 'absolute', top: '0px', right: '6px' }}
                            size="small"
                        >
                            &times;
                        </IconButton>
                    </div>
                ))}
            </div>
            <Button onClick={saveEventHandler} sx={{ marginLeft: '80%' }}>저장</Button>
        </div>
    );
};

export default UploadFile;

 

다음은 react-quill 에디터에서의 이미지처리도 같은 방법으로 해서 이미지를 url로 바꿔서 백엔드에 보내줄 수 있도록 해보겠다.