[TIL] 210629 ~ 210630

2K1·2021년 7월 4일
0

TIL

목록 보기
16/31
post-thumbnail

프로젝트에서 원하는 CRUD는 다 끝났다. 이제는 하고싶었던 이미지 관련기능을 해볼려고 하루종일 찾아봤다. Multer라는 모듈을 사용하면 이미지 업로드를 수월하게 해줄수있다고한다. 하지만 업로드만 생각해보니 아마 db가 메모리가 부족해서 터질거같다는 생각이 들었다. 이미지기능을 만들려면 생각해야할 점 몇가지가 있다.

  • 게시물 생성과 동시에 이미지 업로드 (create)
  • 게시물 수정하면 게시물 db에 연결되있는 이미지 파일이름 변경해주기 (update)
  • 게시물 수정이나 삭제하면 전에 있던 이미지 삭제해주기 (delete)
  • 구글링 한 결과,
    업로드는 multer 로
    수정과 삭제는 fs 라는 file system으로 변경해주자고 결정을하고 살짝 코드를 많이 추가해줬다.

    -------🖥️ Frontend-------

    📂이미지 업로드

    <input type="file" id="image" name="image" class="form-control-file">

    먼저 클라이언트가 올린 이미지를 받아와주고
    기존에 했던 jQuery 방식으로 데이터를 서버쪽으로 쏴주는데 자꾸 오류가 난다.

    type이 file인 input은 form-data로 묶어서 보내줘야한다고 한다. 근데 또 오류가난다.

    form-data로 보낼때는 cache, contenType, processData는 false로 해야한다.
    왜냐하면 contentType은 default 값이 application/x-www-form-urlencoded charset=UTF-8라서 multipart/form-data 로 전송이 되게 false 로 넣어준다. 한마디로 form-data 보내는 방식이 multipart이기 때문에 default에서 따로 설정을 바꿔줘야한다.

    function posting(){
          
          let formData = new FormData();
    
          formData.append("title", $('#title').val());
          formData.append("subtitle", $('#subtitle').val());
          formData.append("category", $('#category').val());
          formData.append("context", $('#context').val());
          formData.append("date", getDate());
          formData.append("img", $('#image')[0].files[0]);
          formData.append("writer", $('#writer').val());
          formData.append("password", $('#password').val());
          
          $.ajax({
            type: "POST",
            url: 'api/posts',
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            success: function(response) {
                if (response["result"] == "success") {
                    alert("쓰기완료");
                    window.location.href= "/list"
                }
          }
        })
      }

    수정할때도 클라이언트가 입력한 값 그대로 formdata에 append를 해서 쏴주니 값이 안바뀌고 전데이터 그대로 넘어갔다. 그래서 위에서 따로 한번 받아와서 그걸 다시 formdata로 넣어주고 쏴주니 성공했다.

    function editPost(){
            
            title1 = $('#title').val()
            subtitle1 = $('#subtitle').val()
            category1 = $('#category').val()
            context1 = $('#context').val()
            writer1 = $('#writer').val()
            img1 = $('#image')[0].files[0]
    
            let formData = new FormData();
    
            formData.append("title", title1)
            formData.append("subtitle", subtitle1)
            formData.append("category", category1)
            formData.append("context", context1)
            formData.append("writer", writer1)
            formData.append("img", img1)
          
            $.ajax({
              type: "PUT",
              url: `api/posts/${postId}/edit`,
              data: formData,
              cache: false,
              contentType: false,
              processData: false,
              success: function(response) {
                  if (response["result"] == "success") {
                      alert("수정완료");
                      window.location.href= `/read?postId=${postId}`
                  }
            }
          })
        }

    -------⚙️ Backend-------

    npm으로 multer 다운을 받고 fs는 다운을 따로 안해줘도 된다.

    npm i multer -s

    먼저 위에서 불러주고

    const multer = require("multer");
    const fs = require("fs");

    📂multer options

    const storage = multer.diskStorage({
      destination: (req, file, cb) => {	//이미지 저장 경로
        cb(null, "./public/static/");
      },
      filename: (req, file, cb) => {	//이미지 저장 이름
        cb(null, Date.now() + "-" + file.originalname);
      },
    });
    
    const fileFilter = (req, file, cb) => {		//이미지 저장 타입
      if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
        cb(null, true);
      } else {				// 그외 불가능
        cb(null, false);
      }
    };
    
    const upload = multer({		//이미지 최대 크기 지정
      storage: storage,
      limits: {
        fileSize: 1024 * 1024 * 5,
      },
      fileFilter: fileFilter,
    });

    📂포스트 업로드

    그리고 대망의 이미지 업로드 api
    중간에 upload.single('img')를 넣어줘야 이미지가 들어왔을때 위에 적어뒀던 이미지 경로,이름,타입,최대크기를 거치고 들어온다. 미들웨어 같이 작용을 하는거 같다.
    이미지는 무조건 넣어야 하는게 아니기 때문에 클라이언트가 이미지를 안넣어줬을땐 req.file이 undefined이므로 req.body로 들어왔던것들만 저장해줬다.
    이렇게 하면 mongoDB에는 img라는 필드에 파일이름이 저장된다.

    router.post("/posts", upload.single("img"), async (req, res) => {
      const { title, subtitle, context, category, date, writer, password } =
        req.body;
      if (req.file === undefined) {
        await Posts.create({
          title,
          subtitle,
          context,
          category,
          date,
          writer,
          password,
        });
      } else {
        const img = req.file.filename;
        await Posts.create({
          title,
          subtitle,
          context,
          category,
          date,
          writer,
          password,
          img,
        });
      }
      res.send({ result: "success" });
    });

    📂포스트 수정

    그리고 수정하는 api
    수정할때 다른 이미지로 클라이언트가 수정할수도 있기때문에 다시한번 upload.single("img") 들고 온다. 수정할 이미지가 없으면 그냥 req.body로만 업데이트해주고 수정할 이미지가 있으면 전에 있던 이미지 경로를 postId가 같은 post를 찾아서 거기에 있는 이미지파일로 path를 선언해주고 fs.unlink에 그 path를 넣어주면 삭제한다. 그러고 클라이언트가 수정할 이미지로 mongoDB img필드에 갈아끼워준다.

    router.put("/posts/:postId/edit", upload.single("img"), async (req, res) => {
      const { postId } = req.params;
      const { title, subtitle, context, category, writer } = req.body;
    
      if (req.file === undefined) {
        await Posts.updateOne(
          { _id: ObjectID(postId) },
          { $set: { title, subtitle, context, category, writer } }
        );
      } else {
        posts = await Posts.findOne({ _id: ObjectID(postId) });
        const path = `./public/static/${posts["img"]}`;
        fs.unlink(path, (err) => {
          if (err) {
            console.error(err);
            return;
          }
        });
        const img = req.file.filename;
        await Posts.updateOne(
          { _id: ObjectID(postId) },
          { $set: { title, subtitle, context, category, writer, img } }
        );
      }

    📂포스트 삭제

    삭제할때는 mongoDB에서 해당 document만 없애주기 때문에 이미지파일은 static폴더에 남는다. 그러기때문에 메모리가 폭팔하기 전에 수정api랑 똑같이 경로 선언 후 fs.unlink에 path를 넣고 삭제하는 코드를 추가해주면 된다.

    router.delete("/delete/:postId", async (req, res) => {
      const { postId } = req.params;
      posts = await Posts.findOne({ _id: ObjectID(postId) });
      const path = `./public/static/${posts["img"]}`;
      fs.unlink(path, (err) => {
        if (err) {
          console.error(err);
          return;
        }
      });
      await Posts.deleteOne({ _id: ObjectID(postId) });
      res.send({ result: "success" });
    });
    profile
    📌dev_log

    0개의 댓글