[PROJECT] MulterError: Unexpected field

2024. 2. 6. 02:27·PROJECT

🚨 Trouble Shooting

프로젝트에서 매장의 메뉴를 추가하기 위해 서버측 코드를 수정하던 중 아래와 같은 에러가 발생했습니다.

Multer는 Node.JS에서 파일을 관리하는 모듈인데, 저는 이미지를 저장하기 위해 사용했습니다. 

 

1차적으로 확인한건 multer를 사용하면서 선언한 변수들을 확인했습니다. 

 

이미지를 저장하기 위한 NodeJs 서버의 로컬 저장소의 경로를 지정합니다.

// 디스크에 이미지 저장을 위한 multer 설정
const storeMainImageStorage = multer.diskStorage({
    destination: '/store_images_volume/main',
    filename: (req, file, cb) => {
        const fileName = 'main_img' + Date.now() +'.'+ file.originalname.split('.').pop();
        cb(null, fileName);
    },
    fileFilter : fileFilter,
    //최대 30MB
    limits: { fileSize: 30 * 1024 * 1024 } 
});

 

Multer 인스턴스를 생성하고 위에서 생성 경로를 저장소로 지정합니다.

const uploadStoreMainImage = multer({ storage: storeMainImageStorage });

 

POST 요청을 받기 위한 로직을 만들어주었습니다.

// 새로운 매장 정보 저장
app.post("/db/store-registration", uploadStoreMainImage.single('storeMainImage'), (req, res) => {
    console.log("post 요청이 수신 되었습니다.");

    try {
        const file = req.file;
        const storeName = req.body.storeName;
        const ceoName = req.body.ceoName;
        const CRN = req.body.crn;
        const contact = req.body.contact;
        const address = req.body.address;
        const latitude = req.body.latitude;
        const longitude = req.body.longitude;
        const kind = req.body.kind;
        console.log("storeName : " + storeName);
        if (file) {
            const filePath = file.path;

            connection.query(
                'INSERT INTO STORE_INFO (CRN, STORE_NAME, IMAGE_PATH, CEO_NAME, CONTACT, ADDRESS, LATITUDE, LONGITUDE, KIND) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
                [CRN, storeName, filePath, ceoName, contact, address, latitude, longitude, kind],

                (err, results, fields) => {
                    if (!err) {
                        console.log("이미지가 성공적으로 저장되었습니다.");
                        res.status(200).send("이미지가 성공적으로 저장되었습니다.");
                    } else {
                        console.error("이미지 저장 실패: ", err);
                        res.status(200).send("이미지 저장 실패");
                    }
                }
            );
        }
    } catch (err) {
        console.error(err);
        res.status(500).send("오류 발생");
    }
});

 

uploadStoreMainImage.single('storeMainImage')
  • Multer 미들웨어에서 파일 업로드를 처리하는 방법을 지정하는 부분입니다.
  • 하나의 파일만 업로드되고, 이 파일의 필드 이름이 'storeMainImage'임을 나타냅니다.
  • 클라이언트가 서버에 이미지를 업로드할 때, 해당 이미지가 'storeMainImage'라는 필드 이름으로 전송되어야 합니다.

 

안드로이드의 서버 요청을 위한 Retrofit 인터페이스 입니다. 중요한 점은 서버의 필드 이름과 동일해야 합니다.

interface StoreInfoApi {
    /**
     * Multipart : 이미지, 오디오, 비디오 등과 같은 여러 종류의 데이터를 서버에 업로드하거나 전송할 때 사용
     * 업로드할 데이터를 @Part 어노테이션을 사용해 파라미터로 지정
     * */
    @Multipart
    @POST("/db/store-registration") // 서버 엔드포인트 URL, HTTP POST 요청
    suspend fun storeRegistration(
        @Part storeMainImage: MultipartBody.Part?,  // 이미지 데이터를 나타내는 파라미터
        @Part("storeName") storeName: RequestBody, // 매장명
        @Part("ceoName") ceoName: RequestBody,     // 점주명
        @Part("crn") crn: RequestBody,             // 사업자 등록 번호
        @Part("contact") contact: RequestBody,     // 전화번호
        @Part("address") address: RequestBody,     // 주소
        @Part("latitude") latitude: RequestBody,   // 위도
        @Part("longitude") longitude: RequestBody, // 경도
        @Part("kind") kind: RequestBody           // 타입
    ): ResponseBody
    // 서버로부터 받은 응답을 처리하기 위한 Retrofit Call 객체를 반환
    // @Part로 나눠진 파라미터를 하나의 객체로 만들어 전송

 

위와 같이 코드를 작성하고 서버에서 지정한 이미지를 식별하기 위한 이름도 지정했는데 문제가 해결되지 않았습니다. 

분명 문제는 서버측의 필드 이름과 일치하지 않아서 발생하는 문젠데 분명 현재 코드에는 문제가 없어 보였습니다.

 

httpLoggingInterceptor를 사용해 로그를 분석하다 이상한걸 발견했습니다. 서버에 전송될 때 필드 네임이 storeMainImage이 아닌 storeImage로 전송되는 것이었습니다!!

 

데이터를 레트로핏 인터페이스에 전달하기까지의 과정에서 storeImage로 전달받은 원인을 찾았습니다

이미지를 서버에 전송하기 위해 MultipartBody.Part 타입으로 변환하는 과정에서 필드 네임을 지정해주게 되는데

이 때 지정해 주는 필드 네임을 기준으로 서버에서 데이터를 받게 됩니다.

찾았다 이 쥐색기 같은놈!

 

필드 네임을 서버에서 지정한 storeMainImage로 변경해 주었고 서버에 저장하는데 성공했습니다! 

 

정말 생각치도 못한곳에서 에러가 발생했습니다. 뭔가 에러를 수정하는 과정에서 시야가 너무 좁았다는 생각이 들어서 앞으로는 좀 더 넓게 볼 수 있게 노력해야겠습니당~

'PROJECT' 카테고리의 다른 글

안드로이드 리사이클러뷰 성능 개선 일지 1편(부제: Recyclerview Deep Dive)  (0) 2024.08.03
안드로이드 클린 아키텍처 도메인 레이어 설계  (0) 2024.07.13
버튼 중복 클릭을 막아보자 (Android ThrottleFirst)  (1) 2024.06.24
[PROJECT] Fused Location Provider  (1) 2024.01.28
[PROJECT] 프로젝트에 DataBinding & @BindingAdapter 사용해보기  (3) 2024.01.24
'PROJECT' 카테고리의 다른 글
  • 안드로이드 클린 아키텍처 도메인 레이어 설계
  • 버튼 중복 클릭을 막아보자 (Android ThrottleFirst)
  • [PROJECT] Fused Location Provider
  • [PROJECT] 프로젝트에 DataBinding & @BindingAdapter 사용해보기
빨주노초잠만보
빨주노초잠만보
  • 빨주노초잠만보
    과거의 나를 통해 미래의 나를 성장시키자
    빨주노초잠만보
  • 전체
    오늘
    어제
    • 분류 전체보기 (104)
      • 우아한테크코스 (5)
      • TEKHIT ANDROID SCHOOL (4)
      • Android Architecture (8)
      • Android (36)
      • PROJECT (11)
      • KOTLIN (10)
        • 코루틴의 정석 (3)
      • BACK END (12)
      • CS (3)
      • 컨퍼런스 (4)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    android view lifecylce
    value class
    2025우아콘
    MVI
    android clean architecture
    callbackflow
    repository
    retrofit call
    sealed class
    android Room
    STATEFLOW
    orbit
    view 생명주기
    Two pass process
    Room
    ThrottleFirst
    process Context Switching
    retrofit awit
    Repository Pattern
    thread Context Switching
    Clean Architecture
    의존성 주입
    DI
    2025 우아콘 후기
    DataSource
    Throttle
    retrofit coroutine
    코틀린 코루틴의 정석
    coroutine Context Switching
    flow
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
빨주노초잠만보
[PROJECT] MulterError: Unexpected field
상단으로

티스토리툴바