지난 1편에서는 로컬에 이미지를 올리는 것을 기록해두었다.
2편에서는 S3 에 올려보자.
S3 로 이동하자.
https://s3.console.aws.amazon.com/s3/home?region=ap-northeast-2
콘솔에 로그인 => s3
버킷 생성
이 버킷의 퍼블릭 액세스 차단 설정 => 해제
권한 => 정책
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:ListBucket",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::버킷이름",
"arn:aws:s3:::버킷이름/*"
]
}
]
}
저장
내 보안자격 증명 클릭
액세스키 => 새 액세스키 만들기
rootkey 다운로드가 되고, 그 액셀파일에 아이디 비번이 있음.
yarn add multer-s3 aws-sdk
.env 에
S3_ACCESS_KEY_ID= rootkey 아이디
S3_SECRET_ACCESS_KEY= rootkey 비밀번호
(보통의 경우 ACCESS KEY 가 아닌 IAM 방식으로 많이함)
코드가 변경되는 부분은 크지 않다. /api/imgupload/index.js 살짝과
imgup.tsx 의 경로 부분만 약간 달라진다.
(실제 s3 에 올라가기 전에, 한 파일당 1mb 이상이면, user 에게
한 파일당 1mb 이상 올라갈 수 없다고 하고 종료된다.
lamda 로 이미지 리사이징을 해도 되지만, 미리 user 가
적절한 크기에 맞게 올리는 것도 좋은 일이다.
단, 만약 sns 같은 서비스를 한다면, 핸드폰에 있는 사진을
찍어서 올릴 것이기때문에, 이런 경우, lamda 로 이미지
리사이징을 해주는 것이 좋을 것이다.
3편에서는 lamda 로 이미지 리사이징을 하고,
올리는 것 외에, 삭제하는 것까지를 하고,
이미지 관리 부분을 마무리 지으려 한다.)
/api/imgupload/index.js
import nextConnect from "next-connect";
import multer from "multer";
import path from "path";
import dayjs from "dayjs";
import multerS3 from "multer-s3";
import AWS from "aws-sdk";
const app = nextConnect({
onError(error, req, res) {
res
.status(501)
.json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
},
});
AWS.config.update({
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "ap-northeast-2",
});
var upload = multer({
storage: multerS3({
s3: new AWS.S3(),
bucket: "cultureplace",
key(req, file, cb) {
const nowDate = dayjs(Date.now()).format("YYMMDDHHMM");
cb(null, `original/${nowDate}_${file.originalname}`);
},
}),
limits: { fileSize: 1024 * 1024 }, // 1mb
});
app.post(upload.array("file"), function (req, res) {
return res.json(req.files.map((v) => v.location));
});
export default app;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
/components/imgup.tsx
import React, { useCallback, useState } from "react";
import axios from "axios";
import { UiFileInputButton } from "./components/UiFileInputButton";
const IndexPage = () => {
const [thumb, setThumb] = useState<string[]>([]);
const [progress, setProgress] = useState<number>(0);
const onChange = useCallback(
async (formData: FormData) => {
const config = {
headers: { "content-type": "multipart/form-data" },
onUploadProgress: (event: { loaded: number; total: number }) => {
setProgress(Math.round((event.loaded * 100) / event.total));
},
};
axios
.post<any>("/api/imgupload", formData, config)
.then((res) => {
setThumb([...thumb, ...res.data]);
})
.catch(function (error) {
if (
error.response.data.error ===
"Sorry something Happened! File too large"
)
return alert(
"한 파일당 1MB 이상 올리 실 수 없습니다. 확인 후 다시 올려주세요"
);
});
},
[thumb]
);
return (
<>
<p>
<span>이미지 업로드</span>
<span>{progress}</span>
</p>
<UiFileInputButton
label="Upload Single File"
// acceptedFileTypes 로 파일 올릴 수 있는 것을 제한 가능
acceptedFileTypes="image/*"
allowMultipleFiles={true}
uploadFileName="file"
onChange={onChange}
/>
<ul>
{thumb &&
thumb.map((item: string, i: number) => {
console.log("item", item);
return (
<li key={i}>
<img src={item} width="300" alt="업로드이미지" />
</li>
);
})}
</ul>
</>
);
};
export default IndexPage;