기존에 있던 등록창에서 파일 업로드 기능을 새로 추가해야해 코드를 리팩토링 하였다.
기존에 있던 데이터들은 json
타입으로 넘어간다.
var data = Object.assign($("#formAdd").serializeObject(), {
gatewayCluster: $("#gatewayCluster").val(),
status: "ENABLED",
methods: makeMethods(),
apiPolicy: $("#apiPolicy").val(),
billPolicy: $("#billPolicy").val(),
service: $("#service").val(),
engineApi: $("#engineApi").val(),
customRouteUri: $("#customRouteUri").val(),
extras: makeSpringProperties(),
scopes: $scopes,
keywords: $("#keywords").val()
});
formData.append("request", JSON.stringify(data));
여기에 파일 업로드를 추가해야하는데, 파일은 multipart/form-data
로 넘겨야 하기 때문에, 따로 formData
를 추가했다.
function completeRegistration() {
// 1) 중복 체크 및 데이터 검증
if (globalVars.dupCheckFlag === "N") {
alert("오픈 API URI 중복 체크를 진행 해주세요");
return false;
}
if (!dataValidation()) return false;
// 2) JSON 데이터 구성
var $scopes = [];
if ($("#scopeList").find(":checked").val() !== "") {
$scopes = $("#scopeList").find(":checked").map(function (i, d) {
return { id: d.value };
}).toArray();
}
var data = Object.assign($("#formAdd").serializeObject(), {
gatewayCluster: $("#gatewayCluster").val(),
status: "ENABLED",
methods: makeMethods(),
apiPolicy: $("#apiPolicy").val(),
billPolicy: $("#billPolicy").val(),
service: $("#service").val(),
engineApi: $("#engineApi").val(),
customRouteUri: $("#customRouteUri").val(),
extras: makeSpringProperties(),
scopes: $scopes,
keywords: $("#keywords").val()
});
if (!confirm("편집 내용을 저장하시겠습니까?")) return;
// 3) 파일 가져오기
const file = document.getElementById("fileInput").files[0];
const formData = new FormData();
// 4) 파일 존재 시 추가
if (file) {
formData.append("uploadedFile", file);
} else if (globalVars.currentFileId) {
// 기존 파일 유지
data.fileId = globalVars.currentFileId;
}
// 5) 기존 json 추가
formData.append("request", JSON.stringify(data));
// 6) Ajax multipart/form-data 요청
$.ajax({
url: "/my-url", // 정확한 백엔드 경로 확인
type: "POST",
data: formData,
processData: false, // jQuery가 데이터를 처리하지 않도록 설정
contentType: false, // jQuery가 Content-Type 헤더를 설정하지 않도록 설정
success: function (d) {
if (d.error) {
alert("오류가 발생했습니다.\n" + d.message);
return false;
}
alert("성공적으로 처리되었습니다.");
$(".m_modal").removeClass("on");
$("body").css("overflow", "auto");
searchPart(); // 예: 목록 갱신 함수
},
error: function (xhr, status, err) {
alert("업로드 중 오류 발생: " + err);
}
});
}
하지만....
2025-01-09 14:11:21.496 WARN 1 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]
백엔드 Controller에서 내가 생각한 multipart/form-data로 넘어가지 않는 듯 했다.
스프링에서 이미지 업로드를 할때 Json 데이터를 Multipart로 같이 보내는 경우 Content-Type을 application/json으로 명시해줘주지 않으면 application/octet-stream not supported 예외가 발생
JSON.stringify를 통해 javascript object를 생성하고 blob 객체의 타입을 application/json으로 만들어서 formdata에 append를 한다.
아래의 코드를
formData.append("request", JSON.stringify(data));
이렇게 수정
formData.append("request", new Blob([JSON.stringify(data)], { type: "application/json" }), "request.json");
Spring Controller에도
@PostMapping(
value = "/my-url",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
명시적으로 consumes
와 produces
를 삽입한다.
클라이언트가 서버에게 보내는 데이터 타입을 명시한다.
@RequestMapping
의 consumes
설정과 Content-Type request
헤더가 일치할 경우에 URL이 호출된다.
HTTP 메시지(요청과 응답 모두)에 담겨 보내는 데이터 형식을 알려주는 헤더이다. 즉, 현재 리퀘스트 또는 리스폰스의 바디에 들어 있는 데이터가 어떤 타입인지를 나타낸다.
GET방식
인 경우무조건 URL 끝에 쿼리스트링(key=value) 형식이기 때문에
Content-Type
헤더가 굳이 필요없으므로 Content-Type
은 POST방식
이나 PUT방식
처럼 BODY
에 데이터를 싣어 보낼 때 중요하다.
이제 매핑이 된다....!!!!