Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LINKER-X] FileUploader #36

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core-api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dependencies {
runtimeOnly(project(":storage"))
runtimeOnly(project(":crawler"))
runtimeOnly(project(":modules:aws"))

runtimeOnly("io.jsonwebtoken:jjwt-impl:$jsonWebTokenVersion")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:$jsonWebTokenVersion")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
Expand All @@ -34,8 +36,19 @@ public ApiResponse<GetMyResponse> getMyProfile(
public ApiResponse<OperationResult> updateMyInfo(
@AuthenticatedUserContext AuthenticatedUserContextHolder userContext,
@RequestBody UpdateMyInfoRequest request) {
System.out.println(request.interests());
OperationResult result = service.update(request.toParam(userContext.getId()));
return ApiResponse.success(result);
}

@PutMapping(
value = "/profile-image",
consumes = {MediaType.ALL_VALUE})
@Operation(summary = "λ‚΄ ν”„λ‘œν•„ 이미지 μˆ˜μ •ν•˜κΈ°")
public ApiResponse<OperationResult> updateMyProfileImage(
@AuthenticatedUserContext AuthenticatedUserContextHolder userContext,
@RequestPart MultipartFile file) {

OperationResult result = service.updateProfileImage(userContext.getId(), file);
return ApiResponse.success(result);
}
}
1 change: 1 addition & 0 deletions core-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ spring:
- monitoring.yml
- oauth/application-oauth-${spring.profiles.active}.yml
- application-storage-${spring.profiles.active}.yml
- application-aws-${spring.profiles.active}.yml
springdoc:
swagger-ui:
path: /swagger
Expand Down
4 changes: 2 additions & 2 deletions core-api/src/main/resources/oauth/application-oauth-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ spring:
client:
registration:
kakao:
client-id: ${KAKAO_CLIENT_ID}
client-secret: ${KAKAO_CLIENT_SECRET}
client-id: ${KAKAO_CLIENT_ID:bb18b99ba9b7d0a13c2678c43f72b1a3}
client-secret: ${KAKAO_CLIENT_SECRET:0OZlNvip4h5OZjiJAYwSbdG5dyXTdY5b}
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ services:
SPRING_PROFILES_ACTIVE: dev
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
AWS_ACCESS_KEY: ${AWS_ACCESS_KEY}
AWS_SECRET_KEY: ${AWS_SECRET_KEY}
AWS_S3_BUCKET: ${AWS_S3_BUCKET}
ports:
- "8080:8080"
depends_on:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ services:
SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD}
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
AWS_ACCESS_KEY: ${AWS_ACCESS_KEY}
AWS_SECRET_KEY: ${AWS_SECRET_KEY}
AWS_S3_BUCKET: ${AWS_S3_BUCKET}
ports:
- "8080:8080"
depends_on:
Expand Down
2 changes: 1 addition & 1 deletion domain/src/main/java/com/imlinker/domain/common/URL.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public class URL {

private static final String URL_REGEX =
"^((http|https)://)?(www\\.)?([a-zA-Z0-9κ°€-힣.]+)\\.[a-z]{2,6}(:\\d{1,5})?(/[a-zA-Z0-9κ°€-힣%._~/?#-]*)?$";
"^((http|https)://)?(www\\.)?([a-zA-Z0-9κ°€-힣-.]+)\\.[a-z]{2,6}(:\\d{1,5})?(/[a-zA-Z0-9κ°€-힣%._~/?#-]*)?";

private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX);

Expand Down
17 changes: 17 additions & 0 deletions domain/src/main/java/com/imlinker/domain/common/file/FileType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.imlinker.domain.common.file;

import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum FileType {
IMAGE(List.of(new String[] {"image/jpeg", "image/png"}));

private final List<String> contentType;

public boolean isSupported(String type) {
return contentType.contains(type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.imlinker.domain.common.file;

import com.imlinker.domain.common.URL;

public interface FileUploader {
URL uploadImage(UploadFileRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.imlinker.domain.common.file;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.web.multipart.MultipartFile;

@Getter
@AllArgsConstructor
public class UploadFileRequest {
private final String fileName;
private final FileType fileType;
private final MultipartFile file;
}
14 changes: 14 additions & 0 deletions domain/src/main/java/com/imlinker/domain/user/UserService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.imlinker.domain.user;

import com.imlinker.domain.common.URL;
import com.imlinker.domain.common.file.FileType;
import com.imlinker.domain.common.file.FileUploader;
import com.imlinker.domain.common.file.UploadFileRequest;
import com.imlinker.domain.contacts.Contacts;
import com.imlinker.domain.schedules.Schedules;
import com.imlinker.domain.tag.Tag;
Expand All @@ -13,11 +17,13 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final FileUploader fileUploader;
private final UserReader userReader;
private final UserUpdater userUpdater;
private final UserContactReader userContactReader;
Expand Down Expand Up @@ -52,4 +58,12 @@ public OperationResult update(UpdateUserParam param) {

return OperationResult.SUCCESS;
}

public OperationResult updateProfileImage(Long userId, MultipartFile file) {
String fileName = String.format("user:%s-profile-%s", userId, LocalDateTime.now());
URL fileUrl = fileUploader.uploadImage(new UploadFileRequest(fileName, FileType.IMAGE, file));

userUpdater.updateProfileImage(userId, fileUrl);
return OperationResult.SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public User updateRefreshToken(Long id, String refreshToken) {
return userRepository.save(updatedUser);
}

public User updateProfileImage(Long userId, URL profileImgUrl) {
User updatedUser = fetch(userId).update(profileImgUrl);
return userRepository.save(updatedUser);
}

public User fetch(Long id) {
return userRepository
.findById(id)
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class URLTest {
"http://www.example.com",
"https://example.com",
"http://example.com:8080",
"https://image.s3.ap-northeast-2.amazonaws.com/user%3A1-profile-2024-01-28T20%3A11%3A06.682821"
})
public void URL_ν˜•μ‹μ΄λΌλ©΄_객체λ₯Ό_μƒμ„±ν•œλ‹€(String validURL) {
URL url = assertDoesNotThrow(() -> URL.of(validURL));
Expand Down
4 changes: 4 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ caffeineVersion=3.1.5
# jwt
jsonWebTokenVersion=0.10.7
jjwtApiVersion=0.10.5

# aws
awsVersion=2.15.0
s3SdkVersion=1.12.174
14 changes: 14 additions & 0 deletions modules/aws/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
dependencies {
implementation(project(":domain"))
implementation(project(":common:enums"))
implementation(project(":common:error"))
implementation("com.amazonaws:aws-java-sdk-s3:1.12.174")
implementation platform("software.amazon.awssdk:bom:$awsVersion")
}
bootJar {
enabled = false
}

jar {
enabled = true
}
32 changes: 32 additions & 0 deletions modules/aws/src/main/java/com/imlinker/s3/S3Configuration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.imlinker.s3;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Configuration {

@Value("${aws.accessKey}")
private String accessKey;

@Value("${aws.secretKey}")
private String secretKey;

@Bean
public AmazonS3 awsS3Client() {

AWSStaticCredentialsProvider credentialsProvider =
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey));

return AmazonS3ClientBuilder.standard()
.withCredentials(credentialsProvider)
.withRegion(Regions.AP_NORTHEAST_2)
.build();
}
}
59 changes: 59 additions & 0 deletions modules/aws/src/main/java/com/imlinker/s3/S3Uploader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.imlinker.s3;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.imlinker.domain.common.URL;
import com.imlinker.domain.common.file.FileUploader;
import com.imlinker.domain.common.file.UploadFileRequest;
import com.imlinker.error.ApplicationException;
import com.imlinker.error.ErrorType;
import java.io.InputStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class S3Uploader implements FileUploader {

@Value("${aws.s3.bucket}")
private String bucket;

private final AmazonS3 s3Client;

@Override
public URL uploadImage(UploadFileRequest request) {
log.info(
"[S3][FileUpload][μ‹œμž‘] (fileName={}, contentType={}",
request.getFileName(),
request.getFileType().getContentType());

if (!request.getFileType().isSupported(request.getFile().getContentType())) {
log.info(
"[S3][FileUpload][μ‹€νŒ¨] μ§€μ›ν•˜μ§€ μ•ŠλŠ” 이미지 파일 ν˜•μ‹(contentType={})",
request.getFile().getContentType());
throw new ApplicationException(ErrorType.INVALID_REQUEST_PARAMETER);
}

try {
InputStream fis = request.getFile().getInputStream();
ObjectMetadata metaData = new ObjectMetadata();
metaData.setContentLength(request.getFile().getSize());
metaData.setContentType(request.getFile().getContentType());

s3Client.putObject(bucket, request.getFileName(), fis, metaData);
String responseUrl = s3Client.getUrl(bucket, request.getFileName()).toString();

return URL.of(responseUrl);
} catch (Exception e) {
log.info(
"[S3][FileUpload][μ‹€νŒ¨] (fileName={}, contentType={}, cause={})",
request.getFileName(),
request.getFile().getContentType(),
e.getMessage());
throw new ApplicationException(ErrorType.INTERNAL_PROCESSING_ERROR, null, e.getCause());
}
}
}
5 changes: 5 additions & 0 deletions modules/aws/src/main/resources/application-aws-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
aws:
accessKey: ${AWS_ACCESS_KEY}
secretKey: ${AWS_SECRET_KEY}
s3:
bucket: ${AWS_S3_BUCKET}
5 changes: 5 additions & 0 deletions modules/aws/src/main/resources/application-aws-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
aws:
accessKey: ${AWS_ACCESS_KEY}
secretKey: ${AWS_SECRET_KEY}
s3:
bucket: ${AWS_S3_BUCKET}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ include(":storage")
include(":common:enums")
include(":common:error")

include(":modules:aws")
include(":modules:logging")
include(":modules:monitoring")
include(":modules:local-cache")
Loading