fix(spa): add SPA forward controller(/admin SPA deeplink)+NoResourceFound->404 — serve admin route, clean 404 for missing static

This commit is contained in:
GUARDiA 2026-06-13 21:07:15 +09:00
parent 4ce97d8807
commit 335a888b48
2 changed files with 40 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
/** /**
* 전역 예외 처리. * 전역 예외 처리.
@ -39,6 +40,14 @@ public class GlobalExceptionHandler {
return ApiResponse.fail(e.getMessage()); return ApiResponse.fail(e.getMessage());
} }
/** 정적 리소스 미존재 — 500 으로 감싸지 않고 깔끔히 404 반환 (SPA 폴백 이후의 실제 미스). */
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> handleNoResource(NoResourceFoundException e) {
log.warn("리소스 없음: {}", e.getResourcePath());
return ApiResponse.fail("ERR-MALL-404: 리소스를 찾을 수 없습니다");
}
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleGeneral(Exception e) { public ApiResponse<Void> handleGeneral(Exception e) {

View File

@ -0,0 +1,31 @@
package com.zioinfo.mall.config;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* SPA(React Router) 폴백 클라이언트 라우트 딥링크/새로고침 지원.
*
* <p>스토어프론트(/, /product/**, /cart, /checkout ...) 관리자(/admin/**)
* 모두 단일 index.html 위에서 React Router 처리한다. 서버에 해당 정적 파일이
* 없으므로, API/정적자원이 아닌 모든 GET 요청을 index.html forward 한다.
*
* <p>제외 대상: /api/**, /actuator/**, /ws/**, /swagger-ui/**, /v3/**,
* 그리고 (.) 포함된 실제 정적 리소스(*.js, *.css, *.jpg, *.png ...)
* Spring 기본 ResourceHandler 먼저 처리하므로 여기로 오지 않는다.
*/
@Controller
public class SpaForwardController {
// (.) 없는 단일/다중 세그먼트 경로 SPA 라우트로 간주하고 index.html forward.
// 정규식: 이상의 경로 세그먼트, 세그먼트에 '.' 미포함.
@RequestMapping(value = {
"/admin", "/admin/**",
"/{path:^(?!api|actuator|ws|swagger-ui|v3|assets|img|images|static)[^.]*}",
"/{path:^(?!api|actuator|ws|swagger-ui|v3|assets|img|images|static)[^.]*}/**"
})
public String forwardSpa() {
return "forward:/index.html";
}
}