Skip to content

Commit 7ce06d6

Browse files
authored
Merge pull request #416 from 2ghrms/oci-migration
[fix/#417] OCI 이미지 PAR URL 저장 문제 수정 및 presigned URL 응답 개선
2 parents d64337c + 4ff3dbf commit 7ce06d6

19 files changed

Lines changed: 140 additions & 98 deletions

File tree

.github/workflows/dev-cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ jobs:
141141
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
142142
143143
cd /home/ubuntu
144-
docker compose -f clokey-api/dev-compose.yml up -d
144+
docker compose -p clokey-dev -f clokey-api/dev-compose.yml up -d
145145
146146
echo "Cleaning up dangling Docker images..."
147147
docker image prune -f

.github/workflows/prod-cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ jobs:
147147
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
148148
149149
cd /home/ubuntu
150-
docker compose -f clokey-api/prod-compose.yml up -d
150+
docker compose -p clokey-prod -f clokey-api/prod-compose.yml up -d
151151
152152
echo "Cleaning up dangling Docker images..."
153153
docker image prune -f

clokey-api/dev-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
name: clokey-dev
2+
13
version: "3.8"
24

35
services:
@@ -46,12 +48,10 @@ services:
4648
# Kakao OIDC
4749
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
4850
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
49-
KAKAO_REDIRECT_URI: ${KAKAO_REDIRECT_URI}
5051

5152
# Apple OIDC
5253
APPLE_CLIENT_ID: ${APPLE_CLIENT_ID}
5354
APPLE_CLIENT_SECRET: ${APPLE_CLIENT_SECRET}
54-
APPLE_REDIRECT_URI: ${APPLE_REDIRECT_URI}
5555

5656
# Firebase
5757
FIREBASE_CREDENTIALS_PATH: /run/secrets/firebase-sa.json

clokey-api/prod-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
name: clokey-prod
2+
13
version: "3.8"
24

35
services:
@@ -46,12 +48,10 @@ services:
4648
# Kakao OIDC
4749
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
4850
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
49-
KAKAO_REDIRECT_URI: ${KAKAO_REDIRECT_URI}
5051

5152
# Apple OIDC
5253
APPLE_CLIENT_ID: ${APPLE_CLIENT_ID}
5354
APPLE_CLIENT_SECRET: ${APPLE_CLIENT_SECRET}
54-
APPLE_REDIRECT_URI: ${APPLE_REDIRECT_URI}
5555

5656
# Firebase
5757
FIREBASE_CREDENTIALS_PATH: /run/secrets/firebase-sa.json

clokey-api/src/main/java/org/clokey/domain/cloth/dto/response/ClothImagesPresignedUrlResponse.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import java.util.List;
55

66
public record ClothImagesPresignedUrlResponse(
7-
@Schema(description = "생성된 presigned url 리스트") List<String> urls) {
8-
public static ClothImagesPresignedUrlResponse of(List<String> urls) {
9-
return new ClothImagesPresignedUrlResponse(urls);
7+
@Schema(description = "업로드용 presigned url 리스트") List<String> urls,
8+
@Schema(description = "저장/조회용 공개 객체 url 리스트") List<String> objectUrls) {
9+
public static ClothImagesPresignedUrlResponse of(List<String> urls, List<String> objectUrls) {
10+
return new ClothImagesPresignedUrlResponse(urls, objectUrls);
1011
}
1112
}

clokey-api/src/main/java/org/clokey/domain/cloth/service/ClothAiServiceImpl.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.clokey.global.util.MemberUtil;
3737
import org.clokey.member.entity.Member;
3838
import org.clokey.properties.WebClientProperties;
39+
import org.clokey.util.PresignedUrlResult;
3940
import org.clokey.util.StorageUtil;
4041
import org.clokey.util.WebClientUtil;
4142
import org.springframework.stereotype.Service;
@@ -61,19 +62,19 @@ public ClothImagesPresignedUrlResponse getClothUploadPresignedUrls(
6162
ClothImagesUploadRequest request) {
6263
final Member currentMember = memberUtil.getCurrentMember();
6364

64-
// 중요 : md5 해시로 변조 확인을 하기 때문에 들어온 순서대로 반환해야함!!
65-
List<String> presignedUrls =
65+
List<PresignedUrlResult> presignedUrlResults =
6666
request.payloads().stream()
6767
.map(
6868
req ->
6969
storageUtil.createPresignedUrl(
7070
ImageType.CLOTH_IMAGE,
7171
currentMember.getId(),
72-
req.fileExtension(),
73-
req.md5Hashes()))
72+
req.fileExtension()))
7473
.toList();
7574

76-
return ClothImagesPresignedUrlResponse.of(presignedUrls);
75+
return ClothImagesPresignedUrlResponse.of(
76+
presignedUrlResults.stream().map(PresignedUrlResult::uploadUrl).toList(),
77+
presignedUrlResults.stream().map(PresignedUrlResult::objectUrl).toList());
7778
}
7879

7980
@Override
@@ -150,7 +151,7 @@ public ClothInfoExtractResponse extractClothInfo(ClothInfoExtractRequest request
150151

151152
for (int i = 0; i < resultItems.size(); i++) {
152153
ClothInfoExtractAiResponseDTO.ResultItem resultItem = resultItems.get(i);
153-
String clothImageUrl = resultItem.uploadedUrl();
154+
String clothImageUrl = storageUtil.toPublicObjectUrl(resultItem.uploadedUrl());
154155

155156
List<ClothInfoExtractAiResponseDTO.CategoryItem> categories =
156157
resultItem.categories();
@@ -346,7 +347,10 @@ public ClothDetectResponse detectClothes(ClothDetectRequest request) {
346347
phaseStartedAtNs = System.nanoTime();
347348
List<ClothDetectResponse.Payload> payloads =
348349
aiResponse.result().uploadedUrls().stream()
349-
.map(ClothDetectResponse.Payload::new)
350+
.map(
351+
url ->
352+
new ClothDetectResponse.Payload(
353+
storageUtil.toPublicObjectUrl(url)))
350354
.toList();
351355
postProcessMs = elapsedMillis(phaseStartedAtNs);
352356

@@ -403,8 +407,10 @@ private List<String> createPresignedUrls(Long memberId, int count) {
403407
return java.util.stream.IntStream.range(0, count)
404408
.mapToObj(
405409
i ->
406-
storageUtil.createPresignedUrlWithoutMd5(
407-
ImageType.CLOTH_IMAGE, memberId, FileExtension.JPEG))
410+
storageUtil
411+
.createPresignedUrl(
412+
ImageType.CLOTH_IMAGE, memberId, FileExtension.JPEG)
413+
.uploadUrl())
408414
.toList();
409415
}
410416

clokey-api/src/main/java/org/clokey/domain/cloth/service/ClothServiceImpl.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class ClothServiceImpl implements ClothService {
4444
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
4545

4646
private final MemberUtil memberUtil;
47+
private final StorageUtil storageUtil;
4748

4849
private final ClothRepository clothRepository;
4950
private final CategoryRepository categoryRepository;
@@ -52,7 +53,6 @@ public class ClothServiceImpl implements ClothService {
5253

5354
private final ApplicationEventPublisher eventPublisher;
5455
private final CoordinateClothRepository coordinateClothRepository;
55-
private final StorageUtil storageUtil;
5656

5757
@Override
5858
@Transactional
@@ -73,7 +73,7 @@ public ClothCreateResponse createClothes(ClothCreateRequests request) {
7373
cr -> {
7474
Category category = categoryMap.get(cr.categoryId());
7575
return Cloth.createCloth(
76-
cr.clothImageUrl(),
76+
storageUtil.toPublicObjectUrl(cr.clothImageUrl()),
7777
cr.clothUrl(),
7878
cr.name(),
7979
cr.brand(),
@@ -168,15 +168,16 @@ public void updateCloth(Long clothId, ClothUpdateRequest request) {
168168
validateChildCategory(category);
169169

170170
// 사진이 바뀌는 경우 기존 imageUrl을 기반으로 S3에서 삭제합니다.
171-
if (!cloth.getClothImageUrl().equals(request.clothImageUrl())) {
171+
String normalizedImageUrl = storageUtil.toPublicObjectUrl(request.clothImageUrl());
172+
if (!storageUtil.toPublicObjectUrl(cloth.getClothImageUrl()).equals(normalizedImageUrl)) {
172173
eventPublisher.publishEvent(ImageDeleteEvent.of(cloth.getClothImageUrl()));
173174
}
174175

175176
// Category 변경 여부 확인
176177
boolean categoryChanged = !cloth.getCategory().getId().equals(category.getId());
177178

178179
cloth.updateCloth(
179-
request.clothImageUrl(),
180+
normalizedImageUrl,
180181
request.clothUrl(),
181182
request.name(),
182183
request.brand(),

clokey-api/src/main/java/org/clokey/domain/coordinate/service/CoordinateServiceImpl.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.clokey.member.entity.Member;
3737
import org.clokey.member.enums.Visibility;
3838
import org.clokey.response.SliceResponse;
39+
import org.clokey.util.StorageUtil;
3940
import org.springframework.context.ApplicationEventPublisher;
4041
import org.springframework.data.domain.Slice;
4142
import org.springframework.data.redis.core.RedisTemplate;
@@ -61,6 +62,7 @@ public class CoordinateServiceImpl implements CoordinateService {
6162
private final RedisTemplate<String, String> redisTemplate;
6263

6364
private final ApplicationEventPublisher eventPublisher;
65+
private final StorageUtil storageUtil;
6466

6567
@Override
6668
@Transactional
@@ -97,7 +99,8 @@ public CoordinateCreateResponse createDailyCoordinate(DailyCoordinateCreateReque
9799
validateDailyCoordinateExist(currentMember.getId(), LocalDate.now(KST));
98100

99101
Coordinate coordinate =
100-
Coordinate.createDailyCoordinate(request.coordinateImageUrl(), currentMember);
102+
Coordinate.createDailyCoordinate(
103+
storageUtil.toPublicObjectUrl(request.coordinateImageUrl()), currentMember);
101104
coordinateRepository.save(coordinate);
102105

103106
List<CoordinateCloth> coordinateClothes =
@@ -160,7 +163,7 @@ public CoordinateCreateResponse createCoordinateManual(CoordinateManualCreateReq
160163
Coordinate.createCoordinateManual(
161164
request.name(),
162165
request.memo(),
163-
request.coordinateImageUrl(),
166+
storageUtil.toPublicObjectUrl(request.coordinateImageUrl()),
164167
currentMember,
165168
lookBook);
166169
coordinateRepository.save(coordinate);
@@ -245,10 +248,11 @@ public void updateCoordinate(Long coordinateId, CoordinateUpdateRequest request)
245248
validateAllClothesOwnership(currentMember, clothes);
246249

247250
/** Coordinate 업데이트 로직 */
248-
if (!Objects.equals(coordinate.getImageUrl(), request.coordinateImageUrl())) {
251+
String normalizedImageUrl = storageUtil.toPublicObjectUrl(request.coordinateImageUrl());
252+
if (!storageUtil.toPublicObjectUrl(coordinate.getImageUrl()).equals(normalizedImageUrl)) {
249253
eventPublisher.publishEvent(ImageDeleteEvent.of(coordinate.getImageUrl()));
250254
}
251-
coordinate.updateCoordinate(request.name(), request.memo(), request.coordinateImageUrl());
255+
coordinate.updateCoordinate(request.name(), request.memo(), normalizedImageUrl);
252256

253257
/** CoordinateCloth 업데이트 로직 */
254258
List<CoordinateCloth> coordinateCloths = coordinate.getCoordinateClothes();

clokey-api/src/main/java/org/clokey/domain/history/dto/response/HistoryImagesPresignedUrlResponse.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import java.util.List;
55

66
public record HistoryImagesPresignedUrlResponse(
7-
@Schema(description = "생성된 presigned url 리스트") List<String> urls) {
8-
public static HistoryImagesPresignedUrlResponse of(List<String> urls) {
9-
return new HistoryImagesPresignedUrlResponse(urls);
7+
@Schema(description = "업로드용 presigned url 리스트") List<String> urls,
8+
@Schema(description = "저장/조회용 공개 객체 url 리스트") List<String> objectUrls) {
9+
public static HistoryImagesPresignedUrlResponse of(List<String> urls, List<String> objectUrls) {
10+
return new HistoryImagesPresignedUrlResponse(urls, objectUrls);
1011
}
1112
}

clokey-api/src/main/java/org/clokey/domain/history/service/HistoryServiceImpl.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.clokey.member.entity.Member;
4141
import org.clokey.member.enums.Visibility;
4242
import org.clokey.report.enums.TargetType;
43+
import org.clokey.util.PresignedUrlResult;
4344
import org.clokey.util.StorageUtil;
4445
import org.springframework.context.ApplicationEventPublisher;
4546
import org.springframework.stereotype.Service;
@@ -105,7 +106,8 @@ public HistoryCreateResponse createHistory(HistoryCreateRequest request) {
105106

106107
for (HistoryCreateRequest.Payload payload : request.payloads()) {
107108
final HistoryImage historyImage =
108-
HistoryImage.createHistoryImage(payload.imageUrl(), history);
109+
HistoryImage.createHistoryImage(
110+
storageUtil.toPublicObjectUrl(payload.imageUrl()), history);
109111
images.add(historyImage);
110112

111113
if (payload.clothTags() != null && !payload.clothTags().isEmpty()) {
@@ -164,18 +166,21 @@ public void updateHistory(Long historyId, HistoryUpdateRequest request) {
164166
existingImages.stream()
165167
.collect(
166168
Collectors.toMap(
167-
HistoryImage::getImageUrl,
169+
image -> storageUtil.toPublicObjectUrl(image.getImageUrl()),
168170
Function.identity(),
169171
(left, right) -> left));
170172

171173
Set<String> requestedImageUrls =
172174
request.payloads().stream()
173-
.map(HistoryUpdateRequest.Payload::imageUrl)
175+
.map(payload -> storageUtil.toPublicObjectUrl(payload.imageUrl()))
174176
.collect(Collectors.toSet());
175177

176178
List<HistoryImage> imagesToDelete =
177179
existingImages.stream()
178-
.filter(image -> !requestedImageUrls.contains(image.getImageUrl()))
180+
.filter(
181+
image ->
182+
!requestedImageUrls.contains(
183+
storageUtil.toPublicObjectUrl(image.getImageUrl())))
179184
.toList();
180185

181186
if (!imagesToDelete.isEmpty()) {
@@ -186,7 +191,10 @@ public void updateHistory(Long historyId, HistoryUpdateRequest request) {
186191

187192
Set<Long> keptImageIds =
188193
existingImages.stream()
189-
.filter(image -> requestedImageUrls.contains(image.getImageUrl()))
194+
.filter(
195+
image ->
196+
requestedImageUrls.contains(
197+
storageUtil.toPublicObjectUrl(image.getImageUrl())))
190198
.map(HistoryImage::getId)
191199
.collect(Collectors.toSet());
192200

@@ -198,12 +206,13 @@ public void updateHistory(Long historyId, HistoryUpdateRequest request) {
198206
List<HistoryClothTag> clothTags = new ArrayList<>();
199207

200208
for (HistoryUpdateRequest.Payload payload : request.payloads()) {
209+
String normalizedImageUrl = storageUtil.toPublicObjectUrl(payload.imageUrl());
201210
final HistoryImage historyImage =
202211
existingImageMap.getOrDefault(
203-
payload.imageUrl(),
204-
HistoryImage.createHistoryImage(payload.imageUrl(), history));
212+
normalizedImageUrl,
213+
HistoryImage.createHistoryImage(normalizedImageUrl, history));
205214

206-
if (!existingImageMap.containsKey(payload.imageUrl())) {
215+
if (!existingImageMap.containsKey(normalizedImageUrl)) {
207216
newImages.add(historyImage);
208217
}
209218

@@ -402,18 +411,19 @@ public HistoryImagesPresignedUrlResponse getHistoryUploadPresignedUrls(
402411
HistoryImagesUploadRequest request) {
403412
final Member currentMember = memberUtil.getCurrentMember();
404413

405-
List<String> presignedUrls =
414+
List<PresignedUrlResult> presignedUrlResults =
406415
request.payloads().stream()
407416
.map(
408417
payload ->
409418
storageUtil.createPresignedUrl(
410419
ImageType.HISTORY_IMAGE,
411420
currentMember.getId(),
412-
payload.fileExtension(),
413-
payload.md5Hashes()))
421+
payload.fileExtension()))
414422
.toList();
415423

416-
return HistoryImagesPresignedUrlResponse.of(presignedUrls);
424+
return HistoryImagesPresignedUrlResponse.of(
425+
presignedUrlResults.stream().map(PresignedUrlResult::uploadUrl).toList(),
426+
presignedUrlResults.stream().map(PresignedUrlResult::objectUrl).toList());
417427
}
418428

419429
private HistoryImage getHistoryImageById(Long historyImageId) {

0 commit comments

Comments
 (0)