Skip to content

Commit 0c02bc9

Browse files
authored
[Feature] S3 로직을 리팩토링하고 사용자 프로필 사진 삭제를 구현한다 (#23)
* feature: 사용자 프로필 사진 삭제 구현 * refactor: S3 공통 로직을 분리 * refactor: UpdateUserImageService 메서드 추출을 통한 코드 정리
1 parent 7df23fd commit 0c02bc9

File tree

5 files changed

+129
-43
lines changed

5 files changed

+129
-43
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package daybyquest.global.s3;
2+
3+
import com.amazonaws.HttpMethod;
4+
import com.amazonaws.services.s3.AmazonS3;
5+
import com.amazonaws.services.s3.model.DeleteObjectRequest;
6+
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
7+
import com.amazonaws.services.s3.model.ObjectMetadata;
8+
import com.amazonaws.services.s3.model.PutObjectRequest;
9+
import daybyquest.global.error.exception.InvalidFileException;
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.util.Date;
13+
import org.springframework.beans.factory.annotation.Value;
14+
import org.springframework.stereotype.Component;
15+
16+
@Component
17+
public class S3Images {
18+
19+
private final AmazonS3 amazonS3;
20+
21+
private final String bucket;
22+
23+
public S3Images(final AmazonS3 amazonS3, @Value("${aws.bucket}") final String bucket) {
24+
this.amazonS3 = amazonS3;
25+
this.bucket = bucket;
26+
}
27+
28+
public void upload(final String category, final String identifier, final InputStream imageStream) {
29+
try {
30+
final String key = category + "/" + identifier;
31+
final ObjectMetadata metadata = new ObjectMetadata();
32+
metadata.setContentLength(imageStream.available());
33+
final PutObjectRequest putObjectRequest = new PutObjectRequest(
34+
bucket, key, imageStream, metadata
35+
);
36+
amazonS3.putObject(putObjectRequest);
37+
} catch (IOException e) {
38+
throw new InvalidFileException();
39+
}
40+
}
41+
42+
public void remove(final String category, final String identifier) {
43+
final String key = category + "/" + identifier;
44+
final DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(bucket, key);
45+
amazonS3.deleteObject(deleteObjectRequest);
46+
}
47+
48+
public String getPublicUrl(final String category, final String identifier, final Date expiration) {
49+
final String key = category + "/" + identifier;
50+
final GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, key)
51+
.withMethod(HttpMethod.GET)
52+
.withExpiration(expiration);
53+
return amazonS3.generatePresignedUrl(request).toString();
54+
}
55+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package daybyquest.user.application;
2+
3+
import daybyquest.image.vo.BaseImageProperties;
4+
import daybyquest.image.vo.Image;
5+
import daybyquest.user.domain.User;
6+
import daybyquest.user.domain.UserImages;
7+
import daybyquest.user.domain.UserRepository;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
@Service
12+
public class DeleteUserImageService {
13+
14+
private final UserRepository userRepository;
15+
16+
private final UserImages userImages;
17+
18+
private final BaseImageProperties baseImageProperties;
19+
20+
public DeleteUserImageService(final UserRepository userRepository, final UserImages userImages,
21+
final BaseImageProperties baseImageProperties) {
22+
this.userRepository = userRepository;
23+
this.userImages = userImages;
24+
this.baseImageProperties = baseImageProperties;
25+
}
26+
27+
@Transactional
28+
public void invoke(final Long loginId) {
29+
final User user = userRepository.getById(loginId);
30+
userImages.remove(user.getImageIdentifier());
31+
user.updateImage(new Image(baseImageProperties.getUserIdentifier()));
32+
}
33+
}
34+

src/main/java/daybyquest/user/application/UpdateUserImageService.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
import daybyquest.user.domain.UserImages;
77
import daybyquest.user.domain.UserRepository;
88
import java.io.IOException;
9+
import java.io.InputStream;
910
import java.util.UUID;
11+
import lombok.extern.slf4j.Slf4j;
1012
import org.springframework.stereotype.Service;
1113
import org.springframework.transaction.annotation.Transactional;
1214
import org.springframework.web.multipart.MultipartFile;
1315

1416
@Service
17+
@Slf4j
1518
public class UpdateUserImageService {
1619

1720
private final UserRepository userRepository;
@@ -28,14 +31,23 @@ public UpdateUserImageService(final UserRepository userRepository,
2831
public void invoke(final Long loginId, final MultipartFile file) {
2932
final User user = userRepository.getById(loginId);
3033
final String oldIdentifier = user.getImageIdentifier();
31-
final String uuid = UUID.randomUUID().toString();
32-
final String identifier = uuid + file.getOriginalFilename();
34+
final String identifier = createIdentifier(file.getOriginalFilename());
35+
userImages.upload(identifier, getInputStream(file));
36+
user.updateImage(new Image(identifier));
37+
userImages.remove(oldIdentifier);
38+
}
39+
40+
private InputStream getInputStream(final MultipartFile file) {
3341
try {
34-
userImages.upload(identifier, file.getInputStream());
35-
user.updateImage(new Image(identifier));
36-
userImages.remove(oldIdentifier);
42+
return file.getInputStream();
3743
} catch (IOException e) {
44+
log.error(e.getMessage());
3845
throw new InvalidFileException();
3946
}
4047
}
48+
49+
private String createIdentifier(final String originalName) {
50+
final String uuid = UUID.randomUUID().toString();
51+
return uuid + originalName;
52+
}
4153
}
Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,45 @@
11
package daybyquest.user.infra;
22

3-
import com.amazonaws.HttpMethod;
4-
import com.amazonaws.services.s3.AmazonS3;
5-
import com.amazonaws.services.s3.model.DeleteObjectRequest;
6-
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
7-
import com.amazonaws.services.s3.model.ObjectMetadata;
8-
import com.amazonaws.services.s3.model.PutObjectRequest;
9-
import daybyquest.global.error.exception.InvalidFileException;
3+
import daybyquest.global.s3.S3Images;
104
import daybyquest.image.vo.BaseImageProperties;
115
import daybyquest.user.domain.UserImages;
12-
import java.io.IOException;
136
import java.io.InputStream;
147
import java.util.Date;
15-
import org.springframework.beans.factory.annotation.Value;
168
import org.springframework.stereotype.Component;
179

1810
@Component
1911
public class S3UserImages implements UserImages {
2012

21-
private static final String FOLDER_NAME = "USER";
13+
private static final String CATEGORY_NAME = "USER";
2214

23-
private static final long PUBLIC_URL_EXPIRATION = 1000 * 60 * 2;
15+
private static final long PUBLIC_URL_EXPIRATION = 1000 * 60 * 60;
2416

25-
private final AmazonS3 amazonS3;
26-
27-
private final String bucket;
17+
private final S3Images s3Images;
2818

2919
private final BaseImageProperties baseImageProperties;
3020

31-
public S3UserImages(final AmazonS3 amazonS3, @Value("${aws.bucket}") final String bucket,
32-
final BaseImageProperties baseImageProperties) {
33-
this.amazonS3 = amazonS3;
34-
this.bucket = bucket;
21+
public S3UserImages(final S3Images s3Images, final BaseImageProperties baseImageProperties) {
22+
this.s3Images = s3Images;
3523
this.baseImageProperties = baseImageProperties;
3624
}
3725

3826
@Override
3927
public void upload(final String identifier, final InputStream imageStream) {
40-
try {
41-
final String key = FOLDER_NAME + "/" + identifier;
42-
final ObjectMetadata metadata = new ObjectMetadata();
43-
metadata.setContentLength(imageStream.available());
44-
final PutObjectRequest putObjectRequest = new PutObjectRequest(
45-
bucket, key, imageStream, metadata
46-
);
47-
amazonS3.putObject(putObjectRequest);
48-
} catch (IOException e) {
49-
throw new InvalidFileException();
50-
}
28+
s3Images.upload(CATEGORY_NAME, identifier, imageStream);
5129
}
5230

5331
@Override
5432
public void remove(final String identifier) {
5533
if (identifier.equals(baseImageProperties.getUserIdentifier())) {
5634
return;
5735
}
58-
final String key = FOLDER_NAME + "/" + identifier;
59-
final DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(bucket, key);
60-
amazonS3.deleteObject(deleteObjectRequest);
36+
s3Images.remove(CATEGORY_NAME, identifier);
6137
}
6238

6339
@Override
6440
public String getPublicUrl(final String identifier) {
6541
final long expirationLong = System.currentTimeMillis() + PUBLIC_URL_EXPIRATION;
6642
final Date expiration = new Date(expirationLong);
67-
final String key = FOLDER_NAME + "/" + identifier;
68-
final GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, key)
69-
.withMethod(HttpMethod.GET)
70-
.withExpiration(expiration);
71-
return amazonS3.generatePresignedUrl(request).toString();
43+
return s3Images.getPublicUrl(CATEGORY_NAME, identifier, expiration);
7244
}
7345
}

src/main/java/daybyquest/user/presentation/UserController.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import daybyquest.auth.Authorization;
44
import daybyquest.auth.UserId;
5+
import daybyquest.user.application.DeleteUserImageService;
56
import daybyquest.user.application.SaveUserService;
67
import daybyquest.user.application.UpdateUserImageService;
78
import daybyquest.user.application.UpdateUserInterestService;
@@ -13,6 +14,7 @@
1314
import daybyquest.user.dto.request.UpdateUserVisibilityRequest;
1415
import jakarta.validation.Valid;
1516
import org.springframework.http.ResponseEntity;
17+
import org.springframework.web.bind.annotation.DeleteMapping;
1618
import org.springframework.web.bind.annotation.PatchMapping;
1719
import org.springframework.web.bind.annotation.PostMapping;
1820
import org.springframework.web.bind.annotation.RequestBody;
@@ -33,15 +35,19 @@ public class UserController {
3335

3436
private final UpdateUserImageService updateUserImageService;
3537

38+
private final DeleteUserImageService deleteUserImageService;
39+
3640
public UserController(final SaveUserService saveUserService, final UpdateUserService updateUserService,
3741
final UpdateVisibilityService updateVisibilityService,
3842
final UpdateUserInterestService updateUserInterestService,
39-
final UpdateUserImageService updateUserImageService) {
43+
final UpdateUserImageService updateUserImageService,
44+
final DeleteUserImageService deleteUserImageService) {
4045
this.saveUserService = saveUserService;
4146
this.updateUserService = updateUserService;
4247
this.updateVisibilityService = updateVisibilityService;
4348
this.updateUserInterestService = updateUserInterestService;
4449
this.updateUserImageService = updateUserImageService;
50+
this.deleteUserImageService = deleteUserImageService;
4551
}
4652

4753
@PostMapping("/profile")
@@ -81,4 +87,11 @@ public ResponseEntity<Void> updateUserImage(@UserId final Long loginId,
8187
updateUserImageService.invoke(loginId, multipartFile);
8288
return ResponseEntity.ok().build();
8389
}
90+
91+
@DeleteMapping("/profile/image")
92+
@Authorization
93+
public ResponseEntity<Void> deleteUserImage(@UserId final Long loginId) {
94+
deleteUserImageService.invoke(loginId);
95+
return ResponseEntity.ok().build();
96+
}
8497
}

0 commit comments

Comments
 (0)