Skip to content

Commit 1811dcc

Browse files
authored
Merge pull request #327 from prgrms-web-devcourse-final-project/refactor/#326
[User] 프로필 이미지 지연삭제 및 검증 리팩토링
2 parents 37acbad + db1dd17 commit 1811dcc

7 files changed

Lines changed: 120 additions & 21 deletions

File tree

src/main/java/com/back/web7_9_codecrete_be/global/initData/BaseInitData.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
package com.back.web7_9_codecrete_be.global.initData;
22

3-
import java.time.LocalDate;
4-
import java.time.LocalDateTime;
5-
6-
import org.springframework.context.annotation.Profile;
7-
import org.springframework.security.crypto.password.PasswordEncoder;
8-
import org.springframework.stereotype.Component;
9-
103
import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert;
114
import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertPlace;
125
import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertPlaceRepository;
136
import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertRepository;
147
import com.back.web7_9_codecrete_be.domain.users.entity.SocialType;
158
import com.back.web7_9_codecrete_be.domain.users.entity.User;
169
import com.back.web7_9_codecrete_be.domain.users.repository.UserRepository;
17-
1810
import jakarta.annotation.PostConstruct;
1911
import lombok.RequiredArgsConstructor;
12+
import org.springframework.context.annotation.Profile;
13+
import org.springframework.security.crypto.password.PasswordEncoder;
14+
import org.springframework.stereotype.Component;
15+
16+
import java.time.LocalDate;
17+
import java.time.LocalDateTime;
2018

2119
@Profile("dev")
2220
@Component
@@ -51,6 +49,8 @@ private void createTestUser() {
5149
.socialId(null)
5250
.build();
5351

52+
testUser.initSetting();
53+
5454
userRepository.save(testUser);
5555
}
5656

@@ -69,6 +69,8 @@ private void createAdminUser() {
6969
.socialId(null)
7070
.build();
7171

72+
adminUser.initSetting();
73+
7274
// dev 전용 어드민 권한 부여
7375
adminUser.promoteToAdmin();
7476

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.back.web7_9_codecrete_be.global.storage;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.GenerationType;
6+
import jakarta.persistence.Id;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Builder;
9+
import lombok.Getter;
10+
import lombok.NoArgsConstructor;
11+
12+
import java.time.LocalDateTime;
13+
14+
@Entity
15+
@Getter
16+
@NoArgsConstructor
17+
@AllArgsConstructor
18+
@Builder
19+
public class FileDeleteQueue {
20+
21+
@Id
22+
@GeneratedValue(strategy = GenerationType.IDENTITY)
23+
private Long id;
24+
25+
private String fileUrl;
26+
27+
private LocalDateTime deleteAt;
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.back.web7_9_codecrete_be.global.storage;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
5+
import java.time.LocalDateTime;
6+
import java.util.List;
7+
8+
public interface FileDeleteQueueRepository extends JpaRepository<FileDeleteQueue, Long> {
9+
List<FileDeleteQueue> findAllByDeleteAtBefore(LocalDateTime now);
10+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.back.web7_9_codecrete_be.global.storage;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.scheduling.annotation.Scheduled;
6+
import org.springframework.stereotype.Component;
7+
import software.amazon.awssdk.services.s3.S3Client;
8+
9+
import java.time.LocalDateTime;
10+
import java.util.List;
11+
12+
@Component
13+
@RequiredArgsConstructor
14+
public class FileDeleteScheduler {
15+
16+
private final FileDeleteQueueRepository repository;
17+
private final S3Client s3Client;
18+
19+
@Value("${cloud.aws.s3.bucket}")
20+
private String bucket;
21+
22+
@Value("${cloud.aws.region.static}")
23+
private String region;
24+
25+
@Scheduled(fixedDelay = 600000) // 10분마다 실행
26+
public void deleteFiles() {
27+
28+
List<FileDeleteQueue> targets =
29+
repository.findAllByDeleteAtBefore(LocalDateTime.now());
30+
31+
for (FileDeleteQueue file : targets) {
32+
try {
33+
String key = extractKey(file.getFileUrl());
34+
35+
s3Client.deleteObject(builder -> builder
36+
.bucket(bucket)
37+
.key(key)
38+
);
39+
40+
} catch (Exception e) {
41+
// 실패해도 계속 진행
42+
}
43+
}
44+
45+
repository.deleteAll(targets);
46+
}
47+
48+
private String extractKey(String fileUrl) {
49+
String prefix = "https://" + bucket + ".s3." + region + ".amazonaws.com/";
50+
return fileUrl.substring(prefix.length());
51+
}
52+
}

src/main/java/com/back/web7_9_codecrete_be/global/storage/ImageFileValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void validateImageFile(MultipartFile file) {
4343
throw new BusinessException(FileErrorCode.EXTENSION_MISMATCH);
4444
}
4545

46-
String extension = filename.substring(filename.lastIndexOf('.') + 1);
46+
String extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
4747

4848
// 확장자 ↔ MIME 타입 매칭
4949
if (!imageMimeType.matches(extension)) {

src/main/java/com/back/web7_9_codecrete_be/global/storage/ImageMimeType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public enum ImageMimeType {
2222

2323
public static ImageMimeType from(String detectedMimeType) {
2424
return Arrays.stream(values())
25-
.filter(it -> it.mimeType.equals(detectedMimeType))
25+
.filter(it -> detectedMimeType.startsWith(it.mimeType))
2626
.findFirst()
2727
.orElseThrow(() ->
2828
new BusinessException(FileErrorCode.INVALID_IMAGE_TYPE)

src/main/java/com/back/web7_9_codecrete_be/global/storage/S3FileStorageService.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
1010

1111
import java.io.IOException;
12+
import java.time.LocalDateTime;
1213
import java.util.UUID;
1314

1415
@Service
1516
@RequiredArgsConstructor
1617
public class S3FileStorageService implements FileStorageService {
1718

1819
private final S3Client s3Client;
20+
private final ImageFileValidator imageFileValidator;
21+
private final FileDeleteQueueRepository fileDeleteQueueRepository;
1922

2023
@Value("${cloud.aws.s3.bucket}")
2124
private String bucket;
@@ -27,7 +30,16 @@ public class S3FileStorageService implements FileStorageService {
2730
@Override
2831
public String upload(MultipartFile file, String basePath) {
2932

30-
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
33+
imageFileValidator.validateImageFile(file);
34+
35+
String originalFilename = file.getOriginalFilename();
36+
37+
// 확장자 추출
38+
String extension = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
39+
40+
// 파일명 제거 (UUID만 사용)
41+
String fileName = UUID.randomUUID() + extension;
42+
3143
String key = basePath + "/" + fileName;
3244

3345
try {
@@ -56,16 +68,11 @@ public void delete(String fileUrl) {
5668
return;
5769
}
5870

59-
String prefix = "https://" + bucket + ".s3." + region + ".amazonaws.com/";
60-
if (!fileUrl.startsWith(prefix)) {
61-
throw new IllegalArgumentException("잘못된 S3 파일 URL입니다.");
62-
}
63-
64-
String key = fileUrl.substring(prefix.length());
65-
66-
s3Client.deleteObject(builder -> builder
67-
.bucket(bucket)
68-
.key(key)
71+
fileDeleteQueueRepository.save(
72+
FileDeleteQueue.builder()
73+
.fileUrl(fileUrl)
74+
.deleteAt(LocalDateTime.now().plusMinutes(30))
75+
.build()
6976
);
7077
}
7178
}

0 commit comments

Comments
 (0)