[SpringBoot] 날짜 별 폴더생성 및 이미지 정렬 (서버->프론트 UI)
먼저 구조부터 정리해보고 구현해보겠다.
에피소드마다 폴더가 생성되고, 그 폴더 이름은 연월일(생성날짜)로 만들어진다.
해당 날짜에 폴더가 없다면 자동으로 생성한다.
그 안에 만화 컷 씬 이미지들이 있고, /comics/20250509/1.jpg 이런식으로 저장된다. 또한 해당 폴더의 파일 목록을 서버가 숫자 순서대로 반환해야 한다.
폴더와 이미지가 들어가는 springBoot 쪽 Controller 부터 건드려보겠다.
//ComicController.java
@RestController
@RequestMapping("/comics")
public class ComicController {
@GetMapping("/episode/{comicId}/{date}/images")
public ResponseEntity<List<String>> getEpisodeImages(
@PathVariable String comicId,
@PathVariable String date) {
String basePath = "D:/comics";
Path episodeDir = Paths.get(basePath, comicId, date);
try {
// 1 폴더가 없으면 생성
if (!Files.exists(episodeDir)) {
System.out.println("폴더 없음, 새로 생성: " + episodeDir.toAbsolutePath());
Files.createDirectories(episodeDir);
// 2(옵션) 기본 이미지 생성 or 복사
Path defaultImage = Paths.get("comics/default-placeholder.jpg");
if(Files.exists(defaultImage)) {
Path targetImage = episodeDir.resolve("1.jpg");
Files.copy(defaultImage, targetImage, StandardCopyOption.REPLACE_EXISTING);
}
}
// 3 파일 목록 가져오기 (숫자 순서로 정렬)
List<String> imageFiles = Files.list(episodeDir)
.filter(Files::isRegularFile)
.map(path -> path.getFileName().toString())
.sorted(Comparator.comparingInt(name -> {
// 파일명에서 숫자 추출 (예: 1.jpg -> 1)
String num = name.replaceAll("\\D", "");
return num.isEmpty() ? 0 : Integer.parseInt(num);
}))
.toList();
return ResponseEntity.ok(imageFiles);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
}
다음은 프론트(React) 의 이미지 요청 부분이다.
//EpisodeViewer.jsx
const EpisodeViewer = () => {
const { id: comicId, epId } = useParams();
const [images, setImages] = useState([]);
// 해당 에피소드 정보 찾기
const episode = episodesData
.find(c => c.comicId === parseInt(comicId, 10))
?.episodes.find(e => e.ep === parseInt(epId, 10));
const date = episode?.date;
useEffect(() => {
if(!date) return;
fetch(`/comics/episode/${comicId}/${date}/images`)
.then(res => res.json())
.then(setImages)
.catch(err => console.error('이미지 불러오기 실패:', err));
}, [comicId, date]);
return (
<div className="episode-content">
<h2>{epId}화 보기</h2>
{images.length === 0 ? (
<p>이미지를 불러오는 중...</p>
) : (
images.map((img, idx) => (
<img
key={idx}
// 캐시 무력화 쿼리스트링 추가
src={`/comics/${comicId}/${date}/${img}?t=${Date.now()}`}
alt={`Page ${idx + 1}`}
style={{ width: '100%', marginBottom: '20px' }}
/>
))
)}
</div>
);
};
export default EpisodeViewer;
이미지 폴더는 정적 리소스로 매핑해야 프론트에서 직접 접근이 가능하다고 한다.
Spring부의 WebConfig 부분에 한번 설정해줘야한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/comics/**")
.addResourceLocations("file:comics/"); // 로컬 폴더 매핑, 루트 폴더의 comics
}
}
파일경로는 spring 프로젝트 루트단에 만들어주었다.
실행이 안될 수도 있는건 React가 백엔드 API를 호출하고 있는게 맞는지 확인해봐야한다. 필자의 경우
fetch(`/comics/episode/${epId}/images`)
이렇게 상대 경로로 요청하고 있는데 React 개발 서버는 3000포트, Spring Boot 서버는 8080 번이라 포트가 다르기 때문에 이 요청은 React 개발 서버의 8080 포트에만 보내고 끝나버린다 (...)
해결방법은 React package.json에
"proxy": "http://localhost:8080"
이거 입력해주면 자동으로 요청을 Spring Boot로 프록시 해주게된다.
다만 이 방법은 서버에서 UI로 사진을 불러오는 처리만 가능하다. 즉, 서버에 이미 사진이 있다는 걸 전제로 하기 때문에 일반적인 상용 서비스들의 기능에 쓸만한 코드는 못 된다. (보통 사용자가 ui에서 파일 선택 후 서버에 저장 -> 서버가 저장된 사진을 ui로 롤백하는 구조이다.)
이걸 생각 안 해보고 단편적으로 코드짜서 멘토 아조씨한테 혼났다 (데헿) 다음 포스팅에서 위 기능은
(Front ui->서버->ui) 다시 코드 짜보겠다.