9.2. 스토리지 구조
9.2.1. 저장 경로 전략
날짜 기반 디렉토리 구조로 파일을 분산 저장합니다.
/data/uploads/
├── 2026/
│ └── 02/
│ ├── 28/
│ │ ├── 550e8400-e29b-41d4-a716-446655440000.pdf
│ │ └── 6ba7b810-9dad-11d1-80b4-00c04fd430c8.png
│ └── 27/
│ └── ...
└── ...java
public class StoragePath {
public static Path resolve(String basePath, String storedFilename) {
LocalDate now = LocalDate.now();
return Path.of(basePath,
String.valueOf(now.getYear()),
String.format("%02d", now.getMonthValue()),
String.format("%02d", now.getDayOfMonth()),
storedFilename);
}
}9.2.2. 메타데이터 테이블
파일 메타데이터를 관리하는 테이블을 생성합니다.
sql
-- V10__create_file_metadata_table.sql
CREATE TABLE file_metadata (
id BIGSERIAL PRIMARY KEY,
original_filename VARCHAR(500) NOT NULL,
stored_filename VARCHAR(255) NOT NULL,
file_path VARCHAR(1000) NOT NULL,
content_type VARCHAR(255) NOT NULL,
file_size BIGINT NOT NULL,
uploaded_by BIGINT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT fk_file_metadata_users
FOREIGN KEY (uploaded_by) REFERENCES users (id)
);
CREATE INDEX idx_file_metadata_uploaded_by ON file_metadata (uploaded_by);9.2.3. 파일 조회/다운로드 API
java
@RestController
@RequestMapping("/api/files")
@RequiredArgsConstructor
@Tag(name = "파일", description = "파일 업로드/다운로드 API")
public class FileController {
private final FileService fileService;
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "파일 업로드")
public ResponseEntity<FileResponse> upload(
@RequestParam("file") MultipartFile file,
@AuthenticationPrincipal UserPrincipal principal) {
FileResponse response = fileService.upload(file, principal.getId());
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@GetMapping("/{id}")
@Operation(summary = "파일 다운로드")
public ResponseEntity<Resource> download(@PathVariable Long id) {
FileDownload download = fileService.download(id);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(download.contentType()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + download.originalFilename() + "\"")
.body(download.resource());
}
}