인프런 커뮤니티 질문&답변

hib4888님의 프로필 이미지
hib4888

작성한 질문수

[리뉴얼] React로 NodeBird SNS 만들기

이미지 업로드를 위한 multer

업로드시 POST http://localhost:3065/ 404 (Not Found) 에러질문입니다.

해결된 질문

작성

·

1.6K

0

antd upload컴포넌트를 사용해서 해당 강의를 학습하고 있습니다.

근데 이미지 업로드시 POST http://localhost:3065/ 404 (Not Found) 에러가 발생하고 있습니다.

redux툴을 확인해보니 아래와 같이 upload_images_success는 실행됬는데 redux의 imagepaths에는 파일명이 없습니다.

게시글 작성 _ Recipe.io - Chrome 2022-09-24 오후 4_27_01.png게시글 작성 _ Recipe.io - Chrome 2022-09-24 오후 4_27_37.png게시글 작성 _ Recipe.io - Chrome 2022-09-24 오후 4_27_52.pngapp.js - prepare - Visual Studio Code [Administrator] 2022-09-24 오후 4_37_56.png

확실하지는 않지만 antd upload에 action속성이 잘못되어서 오류가 발생하는것같은데 에러 원인에 대해서 알 수 있을까요?

참고 코드도 첨부하겠습니다.

 

uploadform

 const normFile = useCallback((e) => {
    console.log('Upload event:', e);
  
    if (Array.isArray(e)) {
      return e;
    }
  
    return e?.fileList;
  }, []);

  const onChangeImages = useCallback((e) => {
    console.log('images', e.fileList);
    const imageFormData = new FormData();
    [].forEach.call(e.fileList, (f) => {
      imageFormData.append('image', f);
    });
    dispatch({
      type: UPLOAD_IMAGES_REQUEST,
      data: imageFormData,
    })
  }, []);

        <ImageUploaderWrapper
          name="images"
          rules={[          
            {
              required: true,
              message: '조리사진을 첨부하세요.',
            },
          ]}
          valuePropName="fileList"
          getValueFromEvent={normFile}
        >
          {/* action: 파일을 업로드할 실제 URL -> localhost3065/images */}
          <Upload.Dragger 
            name="image" 
            multiple 
            action="http://localhost:3065" 
            listType="picture"
            onChange={onChangeImages}
          >            
            <p style={{marginBottom: '0.5em'}}>Drag files here OR</p>            
            <Button type='primary' size='large' icon={<UploadOutlined />}>Upload</Button>
          </Upload.Dragger>
        </ImageUploaderWrapper>

 

redux

export const initialState = {
  mainPosts: [],
  imagePaths: [],
  uploadImagesLoading: false, // 이미지 업로드
  uploadImagesDone: false,
  uploadImagesError: null,  
};

export const UPLOAD_IMAGES_REQUEST = 'UPLOAD_IMAGES_REQUEST';
export const UPLOAD_IMAGES_SUCCESS = 'UPLOAD_IMAGES_SUCCESS';
export const UPLOAD_IMAGES_FAILURE = 'UPLOAD_IMAGES_FAILURE';

const reducer = (state = initialState, action) => {
  return produce(state, (draft) => {
    switch (action.type) {    
      case UPLOAD_IMAGES_REQUEST:
        draft.uploadImagesLoading = true;
        draft.uploadImagesDone = false;
        draft.uploadImagesError = null;
        break;
      case UPLOAD_IMAGES_SUCCESS:        
        draft.imagePaths = action.data;
        draft.uploadImagesLoading = false;
        draft.uploadImagesDone = true;        
        break;
      case UPLOAD_IMAGES_FAILURE:
        draft.uploadImagesLoading = false;
        draft.uploadImagesError = action.error;
        break;     
      default:
        break;
    }
  });
};

export default reducer;

 

saga

function uploadImagesAPI(data) {    
  return axios.post('/post/images', data);
}

function* uploadImages(action) {   
  try {        
    const result = yield call(uploadImagesAPI, action.data); 
    yield put({
      type: UPLOAD_IMAGES_SUCCESS,
      data: result.data,
    })
  } catch(err) {
    yield put({ 
      type: UPLOAD_IMAGES_FAILURE,
      data: err.response.data 
    })
  }  
}

function* watchUploadImages() {  
  yield takeLatest(UPLOAD_IMAGES_REQUEST, uploadImages);
}

export default function* postSaga() {
  yield all([   
    fork(watchUploadImages),
  ]);
}

 

router

try {
  fs.accessSync('uploads');
} catch (error) {
  console.log('uploads폴더가 존재하지 않아 생성합니다.');
  fs.mkdirSync('uploads');
}

const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads');
    },
    filename(req, file, done) {
      const ext = path.extname(file.originalname);
      const basename = path.basename();
      done(null, basename + '_' + new Date().getTime() + ext);
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 },
});

router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => {
  try {
    console.log(req.files);
    res.json(req.files.map((v) => v.filename));
  } catch (error) {
    console.error(error);
    next(error);
  }
});

 

app.js

app.use('/', express.static(path.join(__dirname, 'uploads')));

답변 2

0

hib4888님의 프로필 이미지
hib4888
질문자

정말 죄송한데 마지막으로 질문 하나만 드리겠습니다.

이미지를 업로드한 뒤 추가로 업로드하면 다음과 같이 이전 업로드를 포함해서 실행됩니다.

게시글 작성 _ Recipe.io - Chrome 2022-09-24 오후 11_56_23.pngindex.js - prepare - Visual Studio Code [Administrator] 2022-09-24 오후 11_56_30 (2).png게시글 작성 _ Recipe.io - Chrome 2022-09-24 오후 11_57_30.pngindex.js - prepare - Visual Studio Code [Administrator] 2022-09-24 오후 11_57_35 (2).png

onBeforeUpload를 사용해서 업로드 전에 조건을 줘서 첫 업로드를 제외하고 실행할 수 있을것같은데 혹시 이부분에 대해서 답변 받을 수 있을까요?

혼자 해결해보려고 계속 찾아봤는데 답을 찾기 힘들어서 질문드립니다.

postingform

  const onChangeImages = useCallback((e) => {        
    const imageFormData = new FormData();
    
    e.fileList.forEach((f) => {
      imageFormData.append('image', f.originFileObj);
    });

    dispatch({
      type: UPLOAD_IMAGES_REQUEST,
      data: imageFormData,
    })
  }, []); 

  const onBeforeUpload = useCallback((file, fileList) => {
    // Access file content here and do something with it
    // console.log(file)    
    
    // Prevent upload
    return false
  }, []);

req.files 출력결과

router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => {
  try {
    console.log('라우터', req.files);
    res.json(req.files.map((v) => v.filename));
  } catch (error) {
    console.error(error);
    next(error);
  }
});

index.js - prepare - Visual Studio Code [Administrator] 2022-09-25 오전 12_04_42 (2).pngindex.js - prepare - Visual Studio Code [Administrator] 2022-09-25 오전 12_04_44 (2).png

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

리덕스에 들어있어서 그렇습니다. 리덕스에서 이미지 객체를 비우는 액션을 만들어 이미지 업로드 후 제거해세요.

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

혹시 e.preventDefault() 안해서 때문은 아닐까요. 업로드 부분에 form을 쓸 필요가없습니다.

hib4888님의 프로필 이미지
hib4888
질문자

e.preventDefault()는 무슨 이유인지 실행이 안되서

다음과 같이 antd upload action속성을 주석처리하니까 에러는 해결됬습니다.

하지만 이후에도 back/images, redux의 imagePaths state에는 파일명이 저장이 안되네요 ㅜㅜ

 

 <Upload.Dragger 
            name="image" 
            multiple 
            // action="http://localhost:3065"  주석처리하니 에러가 발생안함
            listType="picture"
            onChange={onChangeImages}
          >            
            <p style={{marginBottom: '0.5em'}}>Drag files here OR</p>            
            <Button type='primary' size='large' icon={<UploadOutlined />}>Upload</Button>
          </Upload.Dragger>

image

hib4888님의 프로필 이미지
hib4888
질문자

추가로 콘솔로확인해보니 FormData가 다음과 같이 비어있는 것을 확인했습니다.

image

saga

function uploadImagesAPI(data) {    
  console.log('사가의 데이터', data);
  return axios.post('/post/images', data);
}

function* uploadImages(action) {   
  try {        
    const result = yield call(uploadImagesAPI, action.data); 
    yield put({
      type: UPLOAD_IMAGES_SUCCESS,
      data: result.data,
    })
  } catch(err) {
    yield put({ 
      type: UPLOAD_IMAGES_FAILURE,
      data: err.response.data 
    })
  }  
}

 

upload form onChangeImages

 

  const onChangeImages = useCallback((e) => {    
    console.log('images', e.fileList);
    const imageFormData = new FormData();
    [].forEach.call(e.fileList, (f) => {
      imageFormData.append('image', f);
    });
    dispatch({
      type: UPLOAD_IMAGES_REQUEST,
      data: imageFormData,
    })
  }, []);
제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

formData는 원래 콘솔에서 비어있습니다. 먼저 네트워크 탭에서 POST /post/images 요청에 payload에 formData가 들어있는지 보시고요. 서버쪽에서 console.log(req.files) 확인해보세요.

hib4888님의 프로필 이미지
hib4888
질문자

네트워크 payload를 확인해보니 다음과 같이 image에는 [object object]로 데이터가 들언간것같은데

서버에 작성한 console(req.files)는 출력되지 않네요 ㅜㅜ

imageimageimage

back/routes/post.js

const upload = multer({  
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads');
      console.log('upload1', req.files);
    },
    filename(req, file, done) {
      const ext = path.extname(file.originalname);
      const basename = path.basename();
      done(null, basename + '_' + new Date().getTime() + ext);
      console.log('upload2', req.files);
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 },
});

router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => {
  try {
    console.log('라우터', req.files);
    res.json(req.files.map((v) => v.filename));
  } catch (error) {
    console.error(error);
    next(error);
  }
});
hib4888님의 프로필 이미지
hib4888
질문자

antd upload event 객체는 강의와 다르게 file, fileList 이렇게 되어있는데

혹시 이부분때문에 이런 문제가 발생한걸까요?

image

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

어 object Object로 나오면 안됩니다. e.fileList를 formData에 넣을 때 잘못되었네요

hib4888님의 프로필 이미지
hib4888
질문자

말씀해주신대로 event객체를 잘못 formData에 넣은것같아서 아래와 같이 수정했습니다.

image

  const onChangeImages = useCallback((e) => {    
    console.log('images', e.fileList);
    const imageFormData = new FormData();
    // [].forEach.call(e.fileList, (f) => {
    //   imageFormData.append('image', f);
    // });
    e.fileList.forEach((f) => {
      imageFormData.append('image', f.originFileObj);
    });
    dispatch({
      type: UPLOAD_IMAGES_REQUEST,
      data: imageFormData,
    })
  }, []);

 

근데 실행하니 다음과같은 서버에러가 발생하네요 ㅜㅜ

image

imageimage

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

routes/post.js 65번째줄을 보라고 되어있습니다

hib4888님의 프로필 이미지
hib4888
질문자

제대로 확인을 안하고 무작정 질문만드렸네요 죄송합니다 ㅜㅜ

말씀해주신내용 토대로 오류는 전부 수정해서 잘 동작합니다!! 감사합니다!!

hib4888님의 프로필 이미지
hib4888

작성한 질문수

질문하기