-
Notifications
You must be signed in to change notification settings - Fork 0
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
✨ use oracle object storage to upload image #426
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,88 @@ | ||
plugins { | ||
id 'java' | ||
id 'org.springframework.boot' version '3.2.7' | ||
id 'io.spring.dependency-management' version '1.1.5' | ||
id 'jacoco' | ||
id 'com.epages.restdocs-api-spec' version '0.18.2' | ||
id 'java' | ||
id 'org.springframework.boot' version '3.2.7' | ||
id 'io.spring.dependency-management' version '1.1.5' | ||
id 'jacoco' | ||
id 'com.epages.restdocs-api-spec' version '0.18.2' | ||
} | ||
|
||
group = 'net.pengcook' | ||
version = '0.0.1-SNAPSHOT' | ||
|
||
java { | ||
toolchain { | ||
languageVersion = JavaLanguageVersion.of(21) | ||
} | ||
toolchain { | ||
languageVersion = JavaLanguageVersion.of(21) | ||
} | ||
} | ||
|
||
configurations { | ||
compileOnly { | ||
extendsFrom annotationProcessor | ||
} | ||
compileOnly { | ||
extendsFrom annotationProcessor | ||
} | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation 'org.springframework.boot:spring-boot-starter-web' | ||
implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5' | ||
implementation 'com.google.firebase:firebase-admin:9.3.0' | ||
implementation 'com.auth0:java-jwt:4.4.0' | ||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' | ||
implementation 'com.github.loki4j:loki-logback-appender:1.5.1' | ||
implementation 'io.micrometer:micrometer-registry-prometheus' | ||
implementation 'org.zalando:logbook-spring-boot-starter:3.9.0' | ||
implementation 'org.springframework.boot:spring-boot-starter-web' | ||
implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5' | ||
implementation 'com.google.firebase:firebase-admin:9.3.0' | ||
implementation 'com.auth0:java-jwt:4.4.0' | ||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' | ||
implementation 'com.github.loki4j:loki-logback-appender:1.5.1' | ||
implementation 'io.micrometer:micrometer-registry-prometheus' | ||
implementation 'org.zalando:logbook-spring-boot-starter:3.9.0' | ||
|
||
runtimeOnly 'mysql:mysql-connector-java:8.0.33' | ||
runtimeOnly 'com.h2database:h2' | ||
compileOnly 'org.projectlombok:lombok' | ||
annotationProcessor 'org.projectlombok:lombok' | ||
runtimeOnly 'mysql:mysql-connector-java:8.0.33' | ||
runtimeOnly 'com.h2database:h2' | ||
compileOnly 'org.projectlombok:lombok' | ||
annotationProcessor 'org.projectlombok:lombok' | ||
|
||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured' | ||
testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2' | ||
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2' | ||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured' | ||
testImplementation 'com.epages:restdocs-api-spec-restassured:0.18.2' | ||
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2' | ||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
|
||
implementation platform('software.amazon.awssdk:bom:2.17.89') | ||
implementation 'software.amazon.awssdk:s3' | ||
implementation platform('com.oracle.oci.sdk:oci-java-sdk-bom:3.54.0') | ||
implementation 'com.oracle.oci.sdk:oci-java-sdk-objectstorage' | ||
implementation 'com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey3' | ||
} | ||
|
||
test { | ||
useJUnitPlatform() | ||
systemProperty 'spring.profiles.active', 'test' | ||
useJUnitPlatform() | ||
systemProperty 'spring.profiles.active', 'test' | ||
} | ||
|
||
openapi3 { | ||
servers = [{ url = "https://dev.pengcook.net" }, { url = "http://localhost:8080" }] | ||
title = 'Pengcook API' | ||
description = 'Pengcook API description' | ||
version = '0.1.0' | ||
format = 'yaml' | ||
servers = [{ url = "https://dev.pengcook.net" }, { url = "http://localhost:8080" }] | ||
title = 'Pengcook API' | ||
description = 'Pengcook API description' | ||
version = '0.1.0' | ||
format = 'yaml' | ||
} | ||
|
||
tasks.register("copyOasToSwagger", Copy) { | ||
dependsOn("openapi3") | ||
dependsOn("openapi3") | ||
|
||
from layout.buildDirectory.file("api-spec/openapi3.yaml").get() | ||
into "src/main/resources/static" | ||
from layout.buildDirectory.file("api-spec/openapi3.yaml").get() | ||
into "src/main/resources/static" | ||
} | ||
|
||
tasks.named("jar") { | ||
enabled = false | ||
enabled = false | ||
} | ||
|
||
jacocoTestReport { | ||
dependsOn("test") | ||
reports { | ||
xml.required = true | ||
csv.required = false | ||
html.required = false | ||
} | ||
dependsOn("test") | ||
reports { | ||
xml.required = true | ||
csv.required = false | ||
html.required = false | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ | |
import net.pengcook.authentication.exception.DuplicationException; | ||
import net.pengcook.authentication.exception.FirebaseTokenException; | ||
import net.pengcook.authentication.exception.NoSuchUserException; | ||
import net.pengcook.image.service.S3ClientService; | ||
import net.pengcook.image.service.ImageClientService; | ||
import net.pengcook.user.domain.User; | ||
import net.pengcook.user.repository.UserRepository; | ||
import org.springframework.stereotype.Service; | ||
|
@@ -28,7 +28,7 @@ public class LoginService { | |
private final FirebaseAuth firebaseAuth; | ||
private final UserRepository userRepository; | ||
private final JwtTokenManager jwtTokenManager; | ||
private final S3ClientService s3ClientService; | ||
private final ImageClientService imageClientService; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
@Transactional(readOnly = true) | ||
public GoogleLoginResponse loginWithGoogle(GoogleLoginRequest googleLoginRequest) { | ||
|
@@ -90,7 +90,7 @@ private User createUser(GoogleSignUpRequest googleSignUpRequest) { | |
userImage = decodedToken.getPicture(); | ||
} | ||
if (!userImage.startsWith("http")) { | ||
userImage = s3ClientService.getImageUrl(userImage).url(); | ||
userImage = imageClientService.getImageUrl(userImage).url(); | ||
} | ||
|
||
return new User( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package net.pengcook.image.config; | ||
|
||
import com.oracle.bmc.Region; | ||
import com.oracle.bmc.auth.AuthenticationDetailsProvider; | ||
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; | ||
import com.oracle.bmc.objectstorage.ObjectStorage; | ||
import com.oracle.bmc.objectstorage.ObjectStorageClient; | ||
import java.io.ByteArrayInputStream; | ||
import java.nio.charset.StandardCharsets; | ||
import net.pengcook.image.service.ImageClientService; | ||
import net.pengcook.image.service.ObjectStorageClientService; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class ImageConfig { | ||
|
||
@Value("${oracle.cloud.user-id}") | ||
private String userId; | ||
|
||
@Value("${oracle.cloud.tenancy-id}") | ||
private String tenancyId; | ||
|
||
@Value("${oracle.cloud.fingerprint}") | ||
private String fingerprint; | ||
|
||
@Value("${oracle.cloud.private-key}") | ||
private String privateKey; | ||
|
||
@Value("${oracle.cloud.region}") | ||
private String region; | ||
|
||
@Bean | ||
public ObjectStorage objectStorage() { | ||
AuthenticationDetailsProvider provider = SimpleAuthenticationDetailsProvider.builder() | ||
.userId(userId) | ||
.tenantId(tenancyId) | ||
.fingerprint(fingerprint) | ||
.privateKeySupplier(() -> { | ||
try { | ||
return new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Failed to load private key", e); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외가 터졌을때 Oracle Object Storage 문제라는것을 바로 알 수 있게 메시지에 추가하는것도 좋아보입니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추가적으로 예외 메시지가 한글인 게 좋을 것 같아 이 코멘트를 반영하며 함께 수정했습니다! |
||
}) | ||
.region(Region.fromRegionId(region)) | ||
.build(); | ||
|
||
return ObjectStorageClient.builder() | ||
.build(provider); | ||
} | ||
|
||
@Bean | ||
public ImageClientService imageClientService() { | ||
return new ObjectStorageClientService(objectStorage()); | ||
} | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package net.pengcook.image.dto; | ||
|
||
public record UploadUrlResponse(String url) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package net.pengcook.image.service; | ||
|
||
import net.pengcook.image.dto.ImageUrlResponse; | ||
import net.pengcook.image.dto.UploadUrlResponse; | ||
|
||
public interface ImageClientService { | ||
|
||
UploadUrlResponse generateUploadUrl(String fileName); | ||
|
||
ImageUrlResponse getImageUrl(String fileName); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package net.pengcook.image.service; | ||
|
||
import com.oracle.bmc.objectstorage.ObjectStorage; | ||
import com.oracle.bmc.objectstorage.model.CreatePreauthenticatedRequestDetails; | ||
import com.oracle.bmc.objectstorage.model.CreatePreauthenticatedRequestDetails.AccessType; | ||
import com.oracle.bmc.objectstorage.requests.CreatePreauthenticatedRequestRequest; | ||
import java.time.Instant; | ||
import java.util.Date; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import net.pengcook.image.dto.ImageUrlResponse; | ||
import net.pengcook.image.dto.UploadUrlResponse; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class ObjectStorageClientService implements ImageClientService { | ||
|
||
private final ObjectStorage client; | ||
|
||
@Value("${oracle.cloud.bucket-name}") | ||
private String bucketName; | ||
|
||
@Value("${oracle.cloud.namespace}") | ||
private String namespace; | ||
|
||
@Value("${oracle.cloud.url-prefix}") | ||
private String urlPrefix; | ||
|
||
private static final int DURATION_SECONDS = 600; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 시간은 어떤값인가요? 600인 기준은 어떤 이유인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이미지 업로드를 위해 제공하는 PAR의 만료 시간입니다. yaml로 해당 값을 관리하는게 유지보수에 더 용이하겠네요! 감사합니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 동의해서 수정했습니다~~ |
||
|
||
public UploadUrlResponse generateUploadUrl(String fileName) { | ||
CreatePreauthenticatedRequestDetails details = CreatePreauthenticatedRequestDetails.builder() | ||
.accessType(AccessType.ObjectWrite) | ||
.name("upload-" + fileName) | ||
.timeExpires(Date.from(Instant.now().plusSeconds(DURATION_SECONDS))) | ||
.objectName(fileName) | ||
.build(); | ||
|
||
CreatePreauthenticatedRequestRequest request = CreatePreauthenticatedRequestRequest.builder() | ||
.namespaceName(namespace) | ||
.bucketName(bucketName) | ||
.createPreauthenticatedRequestDetails(details) | ||
.build(); | ||
|
||
String uploadUri = client.createPreauthenticatedRequest(request) | ||
.getPreauthenticatedRequest() | ||
.getAccessUri(); | ||
|
||
return new UploadUrlResponse(urlPrefix + uploadUri); | ||
} | ||
|
||
public ImageUrlResponse getImageUrl(String fileName) { | ||
return new ImageUrlResponse(String.format("%s/n/%s/b/%s/o/%s", | ||
client.getEndpoint(), | ||
namespace, | ||
bucketName, | ||
fileName)); | ||
} | ||
Comment on lines
+57
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /n/은 어떤 의미인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오라클에서 제공하는 url 형식입니다 |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존은 tab 기반으로 gradle 작성했었고 변경된건 스페이스4개 기준이네요.
혹시 어떤게 컨벤션에 맞는건가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gradle indent 컨벤션에 대해서 저희끼리 논의해봐야 할 것 같아요😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기를 보면 스페이스 4개가 컨벤션같아요~~