"인터넷을 위한 확장 가능하고 안전하며, 내구성이 뛰어난 객체 스토리지 서비스"
AWS S3는 이미지, 동영상, 백업 데이터 등 모든 유형의 데이터를 저장하고 검색할 수 있도록 설계되었습니다. 웹사이트 호스팅, 백업, 아카이브, 엔터프라이즈 애플리케이션 등 다양한 용도로 사용됩니다.
| 용어 (Term) | 설명 (Description) | 비유 (Analogy) |
|---|---|---|
| 버킷 (Bucket) | • S3에 데이터를 저장하기 위한 최상위 컨테이너 • 전 세계적으로 고유한 이름을 가져야 함 • 객체들을 그룹화하여 관리하는 역할 | 🗂️ 윈도우의 'C 드라이브' 또는 '최상위 폴더' |
| 객체 (Object) | • S3에 저장되는 실제 데이터 파일 • 파일 데이터 + 메타데이터(설명 정보)를 포함하는 기본 저장 단위 | 📄 실제 파일 (사진, 문서 등) |
| 키 (Key) | • 버킷 내에서 각 객체를 고유하게 식별하는 이름 • 디렉토리 구조처럼 보이지만 실제로는 긴 이름의 문자열(파일 경로 역할) | 🔑 파일의 전체 경로 (예: /images/logo.png) |
스프링 부트 프로젝트 안에 리액트 프로젝트를 포함하여, 하나의 JAR 파일로 배포하기 위한 설정입니다.
리액트의 빌드 과정을 스프링 부트의 빌드 과정에 포함시키는 스크립트입니다. node가 설치되어 있어야 하며, 플러그인을 통해 자동화합니다.
💡 프로세스 순서:
1.npm install(의존성 설치)
2.npm run build(리액트 앱 빌드)
3. 빌드된 결과물(dist등)을 스프링의resources/static으로 복사
// 1. npm install 실행 Task (React 의존성 설치)
task npmInstallReact(type: NpmTask) {
description = "Install Node.js dependencies from package.json"
// ⚠️ 중요: 실제 리액트 프로젝트가 위치한 폴더명으로 변경해야 합니다.
workingDir = file("${project.projectDir}/src/main/리액트폴더명")
args = ['install']
// 변경 사항 감지를 위한 입력/출력 파일 지정 (빌드 최적화)
inputs.file("src/main/리액트폴더명/package.json")
inputs.file("src/main/리액트폴더명/package-lock.json")
outputs.dir("src/main/리액트폴더명/node_modules")
}
// 2. npm run build 실행 Task (React 앱 빌드)
task npmBuildReact(type: NpmTask) {
description = "Build the React application using npm run build"
dependsOn npmInstallReact // install이 먼저 실행되도록 의존성 설정
workingDir = file("${project.projectDir}/src/main/리액트폴더명")
args = ['run', 'build']
// 소스 코드가 변경되었을 때만 재빌드하도록 설정
inputs.dir("src/main/리액트폴더명/src")
inputs.dir("src/main/리액트폴더명/public")
inputs.file("src/main/리액트폴더명/package.json")
// ⚠️ 중요: Vite 사용 시 'dist', CRA 사용 시 'build' 폴더 확인 필수!
outputs.dir("src/main/리액트폴더명/dist")
}
// 3. 리액트 빌드 결과물을 스프링의 resources 폴더로 복사
// -> 스프링 서버(8080) 하나로 리액트 화면까지 띄우기 위함
task copyReactBuild(type: Copy) {
description = "Copy React build output to Spring Boot static resources"
dependsOn npmBuildReact // 빌드가 완료된 후 복사 실행
// 원본 위치 (리액트 빌드 폴더)
from "${project.projectDir}/src/main/리액트폴더명/dist"
// 목적지 위치 (스프링 정적 리소스 폴더)
into "${project.buildDir}/resources/main/static"
}
@Component // Spring Bean으로 등록하여 필터 체인에 자동으로 추가되도록 함
public class SpaWebFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(getClass());
// 필터링에서 제외할 경로 시작 패턴 목록 (API 경로, 정적 리소스 등)
// 실제 프로젝트의 API 경로 접두사, 정적 리소스 경로에 맞게 수정해야 합니다.
private final List<String> EXCLUDE_PATHS = Arrays.asList(
"/api/", // 모든 API 호출
"/static/", // 정적 리소스 (Create React App 기본)
"/assets/" // 정적 리소스 (Vite 기본)
// 다른 백엔드 전용 경로 추가 가능
);
// 필터링에서 제외할 특정 파일 확장자 또는 파일명
// точка(.)를 포함하지만 SPA 라우팅으로 처리해야 하는 경우는 여기에 추가하지 않음
private final List<String> EXCLUDE_FILES = Arrays.asList(
"/favicon.ico",
"/robots.txt"
// 다른 특정 정적 파일 추가 가능
);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. HTTP 요청이 들어오면 해당 URL 들어오기
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getRequestURI();
// 2. 백엔드 내에 존재하지 않는 경로는 리액트로 이동
if (shouldForwardToSpa(path)) {
logger.debug("SPA WebFilter: Forwarding request to /index.html for path: {}", path);
RequestDispatcher dispatcher = request.getRequestDispatcher("/index.html");
dispatcher.forward(request, response);
return; // 포워딩 후 필터 체인 중단
}
// API 호출이나 정적 리소스 요청 등은 그대로 필터 체인 계속 진행
chain.doFilter(request, response);
}
private boolean shouldForwardToSpa(String path) {
// 1. 명시적인 제외 파일/경로인지 확인
if (EXCLUDE_FILES.stream().anyMatch(p -> path.equals(p))) {
return false;
}
// 2. 제외 경로 패턴으로 시작하는지 확인 (API, static 등)
if (EXCLUDE_PATHS.stream().anyMatch(p -> path.startsWith(p))) {
return false;
}
// 3. 경로에 .(점)이 포함되어 파일 확장자를 가질 가능성이 높은 경우 제외 (단순 휴리스틱)
if (path.contains(".") && path.lastIndexOf('.') > path.lastIndexOf('/')) {
// 예외: 점을 포함하지만 SPA 라우트인 경우 (예: /profile/user.name) 여기에 로직 추가 가능
return false; // 일단 확장자 있는 경로는 제외
}
// 위 조건에 모두 해당하지 않으면 SPA 라우팅 대상일 가능성이 높음
return true;
}
// init, destroy 메서드는 기본 구현 사용 가능
}