Skip to content

[#28] 이미지 조회 기능 - #30

Merged
JIWON27 merged 7 commits into
mainfrom
feature/28
Sep 16, 2026
Merged

JIWON27 merged 7 commits into
mainfrom
feature/28

Conversation

@JIWON27

@JIWON27 JIWON27 commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator

개요

  • 경매 조회 API의 이미지 필드가 TODO(다운로드 URL 미구현)로 남아있던 부분을 해결
  • 카메라 이미지를 실제로 다운로드할 수 있는 API(GET /api/v1/images/{imageId})를 구현
    • 디스크에 저장된 파일을 읽어 이미지 바이트를 스트리밍 응답
  • AuctionDetailResponse.camera.imageKeys(원본 파일 key 목록)를 imageUrls(다운로드 가능한 경로 /api/v1/images/{id} 목록)로 변경

메인 리뷰어 지정 및 Due Date

  • 메인 리뷰어 : blue , Due Date : 2026-09-10

리뷰 시 참고 사항

  • 현재는 이미지가 백엔드 로컬 디스크에 저장/서빙되고 있어서, 응답에는 상대경로(/api/v1/images/{id})만 내려주고 클라이언트가 이미 알고 있는 API base URL을 붙여 쓰는 구조로 설계했습니다. 추후 S3 등 외부 스토리지로 전환되면 절대 URL(또는 presigned URL) 응답으로 바꿀 예정입니다.

TODO

References

[1] 사용된 레퍼런스에 대한 링크를 남겨주세요.

체크리스트

  • PR 제목을 명령형으로 작성했습니다.
  • PR을 연관되는 github issue에 연결했습니다.
  • 리뷰 리퀘스트 전에 셀프 리뷰를 진행했습니다
  • 변경사항에 대한 테스트코드를 추가했습니다. 또는, 테스트 코드가 필요없는 이유가 있습니다.

@JIWON27 JIWON27 self-assigned this Sep 6, 2026
@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 0be432c1-26a1-47f5-8159-bdaf43526673


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@JIWON27 JIWON27 linked an issue Sep 6, 2026 that may be closed by this pull request
@JIWON27
JIWON27 requested a review from blue000927 September 8, 2026 13:20

@pjy1368 pjy1368 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지 경로 공통화, 캐시 정책, 상세 응답 URL의 다운로드 검증에 관한 제안 3개를 남겼습니다.

List<String> imageKeys = cameraImageRepository.findByCameraId(auction.getCamera().getId()).stream()
.map(CameraImage::getImageKey)
List<String> imageUrls = cameraImageRepository.findByCameraId(auction.getCamera().getId()).stream()
.map(image -> "/api/v1/images/" + image.getId())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지 경로와 URL 생성 규칙을 한 곳에서 관리하면 어떨까요?

현재 컨트롤러의 @RequestMapping, 서비스의 "/api/v1/images/", SecurityConfig의 허용 경로가 각각 정의되어 있습니다. 경로를 변경할 때 세 곳을 함께 수정해야 하는 구조예요.

기본 경로와 URL 생성 메서드를 공유하고, 서비스에서는 이미지 ID만 넘기도록 정리하면 좋겠습니다.

기존 FileUtils, TimeUtils의 정적 유틸 구조를 따르는 제안입니다.

package com.chalkak.common.util;

public final class ImageUrls {

    public static final String BASE_PATH = "/api/v1/images";

    private ImageUrls() {
    }

    public static String download(Long imageId) {
        return BASE_PATH + "/" + imageId;
    }
}

각 파일에 ImageUrls import를 추가하고:

CameraImageController:

-@RequestMapping("/api/v1/images")
+@RequestMapping(ImageUrls.BASE_PATH)

AuctionService:

-    .map(image -> "/api/v1/images/" + image.getId())
+    .map(image -> ImageUrls.download(image.getId()))

SecurityConfig:

-    .requestMatchers(HttpMethod.GET, "/api/v1/images/**").permitAll()
+    .requestMatchers(HttpMethod.GET, ImageUrls.BASE_PATH + "/**").permitAll()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 수정하도록 하겠습니다 !

@GetMapping("/{imageId}")
public ResponseEntity<Resource> download(@PathVariable Long imageId) {
CameraImageDownload download = cameraImageService.download(imageId);
return ResponseEntity.ok()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor) 이미지 응답에는 캐시 정책을 따로 지정하면 어떨까요?

실제 응답을 확인해보니 Spring Security 기본 설정으로 Cache-Control: no-cache, no-store, max-age=0, must-revalidate가 붙습니다. 같은 이미지도 브라우저 캐시를 활용하기 어려운 상태예요.

같은 이미지 ID의 파일을 교체하지 않는 정책이라면, 이 응답에 max-age를 지정해서 반복 다운로드를 줄이면 좋겠습니다.

1시간은 정책 논의를 위한 예시입니다.

+import java.time.Duration;
+import org.springframework.http.CacheControl;
 return ResponseEntity.ok()
+    .cacheControl(CacheControl.maxAge(Duration.ofHours(1)))
     .contentType(download.contentType())
     .body(download.resource());

@JIWON27 JIWON27 Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 경매 수정 시 이미지 변경 작업은 기존 이미지 삭제 후 재등록하는 정책을 생각하고있었어서 캐싱 적용해도 문제없습니다 ! 캐싱 적용하도록 하겠습니다.
그리고 이미지 수정 시 기존 이미지들은 삭제하고 다시 저장하기때문에 캐싱 기간은 넉넉하게 잡았습니다!

.andExpect(jsonPath("$.camera.brand").value(AuctionRequestFixture.DEFAULT_BRAND))
.andExpect(jsonPath("$.camera.modelName").value(AuctionRequestFixture.DEFAULT_MODEL_NAME))
.andExpect(jsonPath("$.camera.imageKeys.length()").value(3));
.andExpect(jsonPath("$.camera.imageUrls.length()").value(3));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor) 상세 응답에서 받은 imageUrls를 그대로 GET해서 업로드한 바이트까지 확인해보면 어떨까요?

현재는 상세 테스트에서 URL 개수·접두사를 확인하고, 다운로드 테스트에서는 직접 만든 이미지 ID로 호출합니다. 상세 응답의 URL을 실제로 사용하는 구간은 검증하지 않고 있어요.

기존 상세 테스트에 다운로드 검증을 이어 붙이면 이번 변경의 연결 구간을 확인할 수 있을 것 같습니다.

기존 MockMvc, ObjectMapper, MultipartFileFixture를 재사용합니다.

+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-        mockMvc.perform(get("/api/v1/auctions/{auctionId}", auctionId))
+        MvcResult result = mockMvc.perform(get("/api/v1/auctions/{auctionId}", auctionId))
             .andExpect(status().isOk())
             .andExpect(jsonPath("$.camera.brand").value(AuctionRequestFixture.DEFAULT_BRAND))
             .andExpect(jsonPath("$.camera.modelName").value(AuctionRequestFixture.DEFAULT_MODEL_NAME))
-            .andExpect(jsonPath("$.camera.imageUrls.length()").value(3));
+            .andExpect(jsonPath("$.camera.imageUrls.length()").value(3))
+            .andReturn();
+
+        var imageUrls = objectMapper
+            .readTree(result.getResponse().getContentAsString())
+            .path("camera")
+            .path("imageUrls");
+        byte[] expectedBytes = MultipartFileFixture.image("image1.jpg").getBytes();
+
+        for (var imageUrl : imageUrls) {
+            mockMvc.perform(get(imageUrl.asText()))
+                .andExpect(status().isOk())
+                .andExpect(content().bytes(expectedBytes));
+        }

현재 fixture의 이미지 내용은 모두 같으므로, 이 테스트는 URL 연결과 바이트 반환까지 검증합니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵! 이미지 업로드랑 다운로드 시 이미지 크기 검증 테스트 코드 추가하도록 하겠습니다 !

@sonarqubecloud

Copy link
Copy Markdown

@JIWON27
JIWON27 merged commit 2156277 into main Sep 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task] 이미지 조회 기능

2 participants