[ react ] 이미지에 express 전송, express에서 public폴더에 업로드

Suji Kang·2023년 11월 26일
0
post-thumbnail

먼저 ❗️그전에 만들어 놓은 useAuth() custom hook 를 사용해서 token을 받아 로그인한 작성자를 가지고 온다.

ActivityWrite.js

import { useAuth } from "../../components/hooks/hooks";

const ActivityWritePage = () => {
  useAuth();
    return (
        <DashboardLayout target="활동게시판">
          <ActivityCU/>
        </DashboardLayout>
    );
}

export default ActivityWritePage;

로그인한 사람의 email 정보 가져오기

activityCU.js

import { useContext, useEffect, useState } from 'react';
import { UserContext } from '../../App';
import axios from 'axios';

const ActivityCU = () => {
    const [userEmail, setUserEmail] = useState(''); //로그인한 사람의 email 정보를 담을 변수
 const { accessToken } = useContext(UserContext);

    //로그인한 사람의 email 정보 가져오기
    useEffect(() => {
        const tmp = async () => {
            if (!accessToken) return;
            let res = await axios.get('/api/loggedInEmail', { headers: { Authorization: `Bearer ${accessToken}` } });
            setUserEmail(res.data);
        }
        tmp();
    }, [accessToken]);
  
  return(
      <ActivityInputWrap>
                    <label htmlFor="writerEmail">작성자</label>
                    <input disabled value={userEmail} id="writerEmail" />
                </ActivityInputWrap>
    );


여기서 우리가 서버로 전송해야할것은 몇개❓

  1. 게시글제목,
  2. 작성자- 토큰을 주면됨,(대체가능)
  3. 내용,
  4. 이미지파일 2개, 올렸으니까 2개

app.js

🔎 1. formData 타입을 보내줘야한다. formData 객체 생성

🔎 2. 이미지 파일 두개를 담아줘야한다.(img.origin)

activityCU.js

 const handleSubmit = (e) => {
        e.preventDefault();
        let cpy = JSON.parse(JSON.stringify(formInfo));
        cpy.touched.title = true;
        cpy.touched.content = true;
        setFormInfo(cpy);

        //모든 제목하고, 내용이 정상적으로 입력되었다면?
        //제목 에러메세지가 '' 이고, 내용 에러메세지가 '' 이라면? 정상적으로 모든 값이 입력됨.
        if (formInfo.errors.title === '' && formInfo.errors.content === '') {
            // alert('정상적으로 입력되었습니다. 서버로 전송합니다.');
            //서버로 전송
            //🌟1. formData 생성
             let formData = new FormData(); //formData 객체 생성
          //🌟2.이미지 파일 두개를 담아줌
            imgList.forEach((img) => { //imgList에 있는 모든 이미지를 formData에 추가해준다.
                formData.append('image', img.originFile); //image라는 이름으로 img.originFile을 "추가"해준다.
            });
            axios.post('/api/activities')
        }
    }

📝 express 를 사용하려면 라이브러리를 하나 설치해야한다.

🔎 multer

npm i multer

package.json 에서 확인 하면
"multer": "^1.4.5" 를 받은것을 확인할수있다.
그런데, 현재 최신버전에 버그가 있다. 파일을 한글로 적으면 오류가 난다. 그래서 한단계 전버전으로 받겠다.

npm i multer@1.4.4

이렇게 하면 버전이 바뀌는걸 확인할수있다.

📝 multer모듈을 import 해주고 미들웨어로 사용하기 위해 여러 설정을 해준다.

이미지는 mysql 에 저장하지않는다.
왜냐하면❓용량이 너무 커서.
컴퓨터 메모리에 저장? 아니면 disk에 저장?
👉👉 disk는 껏다켜도 저장이된다. 메모리는 껏다키면 사라진다.
실무에선 disk도 저장❓ 안한다. 클라우드에 한다.

limit: 사이즈 설정가능(안줘도 되지만 너무 고화질이면 돈을 많이 내야함)
destination: 어디 경로에 저장할지
filename: 파일을 어떤이름으로 저장할지
originalname : 사용자가 업로드 한 파일 명

destination: (req, file, cb) 매개변수는 3개.
req = 요청에대한정보(express한테)
file = 이미지의 file 정보
cb = cb실질적으로 뭘 실행할건지,

 cb(null, '../public/images/');  🌟//업로드할 경로를 써준다.

📝 multer 라이브러리 설정하기

이미지, 동영상 등을 업로드할 때 사용하는 미들웨어 (그때그때 찾아서 사용하면된다. )

//multer 라이브러리 설정하기
const multer = require('multer');
const upload = multer({
    storage: multer.diskStorage({ //diskStorage에 저장.
        destination: (req, file, cb) => { //어디 경로에 저장할지
            cb(null, '../public/images/');  🌟//업로드할 경로를 써준다.
        },
        filename: (req, file, cb) => { //파일을 어떤이름으로 저장할지
            cb(null, file.originalname);
        }
    }),
    limit: { fileSize: 5 * 1024 * 1024 } //5MB (파일용량)
});

storage에서 파일을 저장 할 경로와 이름을 설정 할 수 있는데, file인수에서 다음 속성으로 파일 정보를 얻을 수 있다.


api/activitiespost 요청이 오면, 원래는 보통 req,res함수가 실행되는데.
여기는 두번째함수가 만들어져서, 두번째함수 "이미지업로드" 부분이 실행되고 난후, req,res함수가 실행

이것을 바로 미들웨어 라고한다. (보통 중복될때사용)

예를들면, 토큰 받아오기,

이렇게 미리 함수를 만들어두면 사용가능 - verifyToken


🔎

upload.array('images')

upload를 실행
array는 여러개, single은 하나.

upload.array('images')

('images') 는 activityCU.js에서 images 라는 이름으로 추가했기때문에 그대로 이름을 가져옴


console.log(req.files) - 2개의 이미지 정보가 들어가있음


console.log(result);
🌟 result.insertId--> 새로 추가된 게시물의 id 값

app.js

app.post('/api/activities', upload.array('images'), async (req, res) => {
    // console.log(req.body);
    // console.log(req.files);
    // console.log(req.headers.authorization)
    //이미지 업로드 완료, 그뒤에 
    //mysql 에 게시글 제목, 내용, 작성자, 작성일자, insert into
    const token = req.headers.authorization.replace('Bearer ', '');
    const user = jwt.verify(token, process.env.JWT_SECRET);
    const { title, content } = req.body; //req.body에 들어있는 title, content
    //req.files[0].filename -->  이미지 파일 이름(우리컴퓨터에 저장된 파일 이름)

    try {
        let sql = `insert into tbl_activities
        (title, content, writer_email, activity_view)
        values
        (?, ?, ?, 0) 
        `; //조회수는 초기값은 0이니까 
        let [result] = await pool.query(sql, [
            title,
            content,
            user.email
        ]);
        //🌟result.insertId --> 새로 추가된 게시물의 id 값
        // console.log(result);

        //이미지 경로도 mysql, tbl_activity_img 테이블에 추가
        sql = `
        insert into tbl_activity_img
        (activity_id, img_url)
        values
        (?, ?)
        `;

        for (let i = 0; i < req.files.length; i++) {
            await pool.query(sql, [result.insertId, '/images/' + req.files[i].filename]);
        } //🌟files안에있는것들 업로드한 이미지들. result.insertId(방금 새로 추가된 게시물 id
      //'/images/' 폴더로 추가된 이미지가 들어간다. 

        //react 에게 응답
        res.json({ id: result.insertId }); //게시글인데 방금 추가된 id 
    } catch (err) {
        console.error(err);
        res.status(500).send("Internal Server Error");
    }
});

mysql 에도 저장되었다.

activityCU.js

 const handleSubmit = async (e) => {
        e.preventDefault();
        let cpy = JSON.parse(JSON.stringify(formInfo));
        cpy.touched.title = true;
        cpy.touched.content = true;
        setFormInfo(cpy);

        //모든 제목하고, 내용이 정상적으로 입력되었다면?
        //제목 에러메세지가 '' 이고, 내용 에러메세지가 '' 이라면? 정상적으로 모든 값이 입력됨.
        if (formInfo.errors.title === '' && formInfo.errors.content === '') {
            // alert('정상적으로 입력되었습니다. 서버로 전송합니다.');
            //서버로 전송
            //1. 🌟 formData 생성
            let formData = new FormData(); //formData 객체 생성
            imgList.forEach((img) => { //imgList에 있는 모든 이미지를 formData에 추가해준다.
                formData.append('images', img.originFile); //images라는 이름으로 img.originFile을 "추가"해준다.
            });
            formData.append('title', formInfo.values.title);
            formData.append('content', formInfo.values.content);
            //2. 🌟 axios로 전송
            let res = await axios.post('/api/activities', formData, {
                headers: { Authorization: `Bearer ${accessToken}` },
            });
           //🌟 방금 새로 생성된 게시글의 id가 들어있다.
            navigate(`/activity/${res.data.id}`, { replace: true });
        };
    }

그런데, 여기서 문제가 생길수도 있다❗️
같은파일 이름을 업로드하면 그전것은 없어지고 최근거로수정이 된다.
그래서 겹칠일없게 여기서 확인할수있게,

uuid(Universally Unique Identifier) 라이브러리를 설치하자 ❗️

npm i uuid

uuid + 시간 (절대로 겹칠일이 없을것이다)

UUID v1- 현재 시간과 노드 식별자를 기반으로 순차적으로 생성되는 시간 기반 UUID
UUID v4 - 완전히 무작위로 생성되어 임의성이 필요한 경우에 사용되는 가장 보편적인 UUID 버전
UUID v5-명시적으로 제공된 이름과 네임스페이스 식별자를 사용하여 생성됨
UUID v3-는 현재 사용되지 않는 UUID v5의 이전 버전으로 MD5 해시 함수를 사용하여 생성됨

v1을 많이 사용하고, 유일성이 보장되지만 보안에 취약함.
그래서 우리는 v4를 사용할것이다.

const multer = require('multer');
const uuid = require('uuid')

const upload = multer({
    storage: multer.diskStorage({ // 어디에 저장할지 
        destination: (req, file, cb) => {
            cb(null, '../public/images/'); //어디에 저장할지
        }, //어디에 저장할지
        filename: (req, file, cb) => { //어떤이름으로 저장할지
            let id = uuid.v4();
            let now = new Date();
            let fileName = `${id}${now.getSeconds()}${file.originalname}`
            cb(null, fileName);
        }
    }),
    limit: { fileSize: 5 * 1024 * 1024 } //5MB
});

이렇게 똑같은 파일을 올려도 파일 이름은 다르게 저장됨. 그래서 겹칠일이 없다.

profile
나를위한 노트필기 📒🔎📝

0개의 댓글