From 55b6e4ecc656babc377527c81e46babfe375ed81 Mon Sep 17 00:00:00 2001 From: 5solbin Date: Fri, 22 May 2026 16:37:28 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix=20:=20CORS=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/valanse/valanse/common/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java index 762a436..400cec9 100644 --- a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java +++ b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java @@ -102,6 +102,7 @@ public CorsConfigurationSource corsConfigurationSource() { "https://valan-se-web.vercel.app", "https://valanse.kr", "https://develop.valanse.kr", + "https://valanserver.store", "http://valanserver.store", "http://valanserver.store:8080", "http://valanserver.store:8081", From c90bfcf386c045883d0b347f74155244728e4419 Mon Sep 17 00:00:00 2001 From: 5solbin <122352841+5solbin@users.noreply.github.com> Date: Fri, 22 May 2026 16:45:58 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Revert=20"fix=20:=20CORS=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/valanse/valanse/common/config/SecurityConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java index 400cec9..762a436 100644 --- a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java +++ b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java @@ -102,7 +102,6 @@ public CorsConfigurationSource corsConfigurationSource() { "https://valan-se-web.vercel.app", "https://valanse.kr", "https://develop.valanse.kr", - "https://valanserver.store", "http://valanserver.store", "http://valanserver.store:8080", "http://valanserver.store:8081", From 8df8b9d5f7fb59bbc1d38df4d4f726aa44f7a0ed Mon Sep 17 00:00:00 2001 From: 5solbin <122352841+5solbin@users.noreply.github.com> Date: Fri, 22 May 2026 16:46:40 +0900 Subject: [PATCH 3/4] Revert "Dev" --- .github/workflows/deploy-prod.yml | 7 +- build.gradle | 3 +- .../valanse/common/config/R2Config.java | 26 ------ .../valanse/common/config/R2Properties.java | 64 --------------- .../valanse/common/config/SecurityConfig.java | 8 +- .../valanse/controller/StorageController.java | 32 -------- .../dto/Storage/ImageUploadResponse.java | 6 -- .../StorageService/R2StorageService.java | 82 ------------------- .../StorageService/StorageService.java | 7 -- .../StorageService/R2StorageServiceTest.java | 61 -------------- src/test/resources/application-test.yml | 9 -- 11 files changed, 9 insertions(+), 296 deletions(-) delete mode 100644 src/main/java/com/valanse/valanse/common/config/R2Config.java delete mode 100644 src/main/java/com/valanse/valanse/common/config/R2Properties.java delete mode 100644 src/main/java/com/valanse/valanse/controller/StorageController.java delete mode 100644 src/main/java/com/valanse/valanse/dto/Storage/ImageUploadResponse.java delete mode 100644 src/main/java/com/valanse/valanse/service/StorageService/R2StorageService.java delete mode 100644 src/main/java/com/valanse/valanse/service/StorageService/StorageService.java delete mode 100644 src/test/java/com/valanse/valanse/service/StorageService/R2StorageServiceTest.java diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 6bf0797..b4ab98f 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -48,16 +48,11 @@ jobs: - name: SSH로 EC2에 접속하여 Production 서버 재배포 uses: appleboy/ssh-action@v1.0.3 - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: ap-northeast-2 with: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USERNAME }} key: ${{ secrets.EC2_PRIVATE_KEY }} script_stop: true - envs: AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_DEFAULT_REGION script: | cd ~/valanse @@ -71,4 +66,4 @@ jobs: docker compose up -d valanse-server # 사용하지 않는 이미지 정리 - docker image prune -f + docker image prune -f \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9936c0a..245813d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'software.amazon.awssdk:s3:2.25.40' // Swagger 설정 implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' @@ -76,4 +75,4 @@ sourceSets { tasks.named('test') { useJUnitPlatform() // JUnit5 명시적 활성화 -} +} \ No newline at end of file diff --git a/src/main/java/com/valanse/valanse/common/config/R2Config.java b/src/main/java/com/valanse/valanse/common/config/R2Config.java deleted file mode 100644 index 8577b4d..0000000 --- a/src/main/java/com/valanse/valanse/common/config/R2Config.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.valanse.valanse.common.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3Client; - -import java.net.URI; - -@Configuration -public class R2Config { - - @Bean - public S3Client s3Client(R2Properties properties) { - return S3Client.builder() - .region(Region.of("auto")) - .endpointOverride(URI.create(properties.getEndpoint())) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()) - )) - .forcePathStyle(true) - .build(); - } -} diff --git a/src/main/java/com/valanse/valanse/common/config/R2Properties.java b/src/main/java/com/valanse/valanse/common/config/R2Properties.java deleted file mode 100644 index 9cbb93d..0000000 --- a/src/main/java/com/valanse/valanse/common/config/R2Properties.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.valanse.valanse.common.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -@Component -@ConfigurationProperties(prefix = "cloudflare.r2") -public class R2Properties { - - private String accountId; - private String endpoint; - private String bucket; - private String accessKey; - private String secretKey; - private String publicUrl; - - public String getAccountId() { - return accountId; - } - - public void setAccountId(String accountId) { - this.accountId = accountId; - } - - public String getEndpoint() { - return endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public String getBucket() { - return bucket; - } - - public void setBucket(String bucket) { - this.bucket = bucket; - } - - public String getAccessKey() { - return accessKey; - } - - public void setAccessKey(String accessKey) { - this.accessKey = accessKey; - } - - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - public String getPublicUrl() { - return publicUrl; - } - - public void setPublicUrl(String publicUrl) { - this.publicUrl = publicUrl; - } -} diff --git a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java index 762a436..3551cbc 100644 --- a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java +++ b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java @@ -62,7 +62,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(HttpMethod.POST, "/votes/*/vote-options/*").authenticated() .requestMatchers(HttpMethod.POST, "/votes/*/comments").authenticated() .requestMatchers(HttpMethod.POST, "/comments/*/like").authenticated() - .requestMatchers(HttpMethod.POST, "/storage/images").authenticated() // PUT - 인증 필요 (수정) .requestMatchers(HttpMethod.PUT, "/votes/*").authenticated() @@ -102,6 +101,13 @@ public CorsConfigurationSource corsConfigurationSource() { "https://valan-se-web.vercel.app", "https://valanse.kr", "https://develop.valanse.kr", + "https://backendbase.store", + "http://backendbase.store:8080", + "http://backendbase.store:8081", + "http://backendbase.store:8082", + "https://backendbase.store:8080", + "https://backendbase.store:8081", + "https://backendbase.store:8082", "http://valanserver.store", "http://valanserver.store:8080", "http://valanserver.store:8081", diff --git a/src/main/java/com/valanse/valanse/controller/StorageController.java b/src/main/java/com/valanse/valanse/controller/StorageController.java deleted file mode 100644 index ec5f4ab..0000000 --- a/src/main/java/com/valanse/valanse/controller/StorageController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.valanse.valanse.controller; - -import com.valanse.valanse.dto.Storage.ImageUploadResponse; -import com.valanse.valanse.service.StorageService.StorageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -@Tag(name = "파일 업로드 API", description = "이미지 업로드 관련 기능") -@RestController -@RequestMapping("/storage") -@RequiredArgsConstructor -public class StorageController { - - private final StorageService storageService; - - @Operation( - summary = "이미지 업로드", - description = "이미지 파일을 Cloudflare R2에 업로드하고 공개 URL을 반환합니다." - ) - @PostMapping(value = "/images", consumes = "multipart/form-data") - public ResponseEntity uploadImage(@RequestPart("file") MultipartFile file) { - String imageUrl = storageService.uploadImage(file, "images"); - return ResponseEntity.ok(new ImageUploadResponse(imageUrl)); - } -} diff --git a/src/main/java/com/valanse/valanse/dto/Storage/ImageUploadResponse.java b/src/main/java/com/valanse/valanse/dto/Storage/ImageUploadResponse.java deleted file mode 100644 index b9ad372..0000000 --- a/src/main/java/com/valanse/valanse/dto/Storage/ImageUploadResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.valanse.valanse.dto.Storage; - -public record ImageUploadResponse( - String imageUrl -) { -} diff --git a/src/main/java/com/valanse/valanse/service/StorageService/R2StorageService.java b/src/main/java/com/valanse/valanse/service/StorageService/R2StorageService.java deleted file mode 100644 index 3e30859..0000000 --- a/src/main/java/com/valanse/valanse/service/StorageService/R2StorageService.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.valanse.valanse.service.StorageService; - -import com.valanse.valanse.common.api.ApiException; -import com.valanse.valanse.common.config.R2Properties; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.model.S3Exception; - -import java.io.IOException; -import java.util.Set; -import java.util.UUID; - -@Service -@RequiredArgsConstructor -public class R2StorageService implements StorageService { - - private static final long MAX_IMAGE_SIZE = 5 * 1024 * 1024; - private static final Set ALLOWED_IMAGE_TYPES = Set.of( - "image/jpeg", - "image/png", - "image/webp", - "image/gif" - ); - - private final S3Client s3Client; - private final R2Properties properties; - - @Override - public String uploadImage(MultipartFile file, String directory) { - validateImage(file); - - String objectKey = buildObjectKey(file, directory); - PutObjectRequest request = PutObjectRequest.builder() - .bucket(properties.getBucket()) - .key(objectKey) - .contentType(file.getContentType()) - .contentLength(file.getSize()) - .build(); - - try { - s3Client.putObject(request, RequestBody.fromInputStream(file.getInputStream(), file.getSize())); - } catch (IOException e) { - throw new ApiException("이미지 파일을 읽을 수 없습니다.", HttpStatus.BAD_REQUEST); - } catch (S3Exception e) { - throw new ApiException("이미지 업로드에 실패했습니다.", HttpStatus.BAD_GATEWAY); - } - - return properties.getPublicUrl().replaceAll("/+$", "") + "/" + objectKey; - } - - private void validateImage(MultipartFile file) { - if (file == null || file.isEmpty()) { - throw new ApiException("업로드할 이미지 파일을 선택해주세요.", HttpStatus.BAD_REQUEST); - } - - if (file.getSize() > MAX_IMAGE_SIZE) { - throw new ApiException("이미지는 5MB 이하만 업로드할 수 있습니다.", HttpStatus.BAD_REQUEST); - } - - if (!ALLOWED_IMAGE_TYPES.contains(file.getContentType())) { - throw new ApiException("jpg, png, webp, gif 이미지 파일만 업로드할 수 있습니다.", HttpStatus.BAD_REQUEST); - } - } - - private String buildObjectKey(MultipartFile file, String directory) { - String cleanDirectory = StringUtils.hasText(directory) - ? directory.replaceAll("^/+|/+$", "") - : "images"; - String extension = StringUtils.getFilenameExtension(file.getOriginalFilename()); - String filename = extension == null - ? UUID.randomUUID().toString() - : UUID.randomUUID() + "." + extension.toLowerCase(); - - return cleanDirectory + "/" + filename; - } -} diff --git a/src/main/java/com/valanse/valanse/service/StorageService/StorageService.java b/src/main/java/com/valanse/valanse/service/StorageService/StorageService.java deleted file mode 100644 index 2d7f562..0000000 --- a/src/main/java/com/valanse/valanse/service/StorageService/StorageService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.valanse.valanse.service.StorageService; - -import org.springframework.web.multipart.MultipartFile; - -public interface StorageService { - String uploadImage(MultipartFile file, String directory); -} diff --git a/src/test/java/com/valanse/valanse/service/StorageService/R2StorageServiceTest.java b/src/test/java/com/valanse/valanse/service/StorageService/R2StorageServiceTest.java deleted file mode 100644 index 0736dfd..0000000 --- a/src/test/java/com/valanse/valanse/service/StorageService/R2StorageServiceTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.valanse.valanse.service.StorageService; - -import com.valanse.valanse.common.api.ApiException; -import com.valanse.valanse.common.config.R2Properties; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockMultipartFile; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -class R2StorageServiceTest { - - private S3Client s3Client; - private R2StorageService storageService; - - @BeforeEach - void setUp() { - s3Client = mock(S3Client.class); - - R2Properties properties = new R2Properties(); - properties.setBucket("test-bucket"); - properties.setPublicUrl("https://cdn.example.com/"); - - storageService = new R2StorageService(s3Client, properties); - } - - @Test - void 이미지를_R2에_업로드하고_공개_URL을_반환한다() { - MockMultipartFile file = new MockMultipartFile( - "file", - "sample.png", - "image/png", - "image".getBytes() - ); - - String imageUrl = storageService.uploadImage(file, "images"); - - assertThat(imageUrl).startsWith("https://cdn.example.com/images/"); - assertThat(imageUrl).endsWith(".png"); - verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class)); - } - - @Test - void 이미지가_아닌_파일은_업로드하지_않는다() { - MockMultipartFile file = new MockMultipartFile( - "file", - "sample.txt", - "text/plain", - "text".getBytes() - ); - - assertThrows(ApiException.class, () -> storageService.uploadImage(file, "images")); - } -} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index ac9c63a..55457c4 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -26,12 +26,3 @@ oauth: kakao: client-id: test-client-id redirect-uri: http://localhost - -cloudflare: - r2: - account-id: test-account-id - endpoint: http://localhost:9000 - bucket: test-bucket - access-key: test-access-key - secret-key: test-secret-key - public-url: http://localhost:9000/test-bucket From d026683331ce72add1e4724e48e14668717f9d17 Mon Sep 17 00:00:00 2001 From: 5solbin Date: Tue, 26 May 2026 16:54:11 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix=20:=20cors=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/valanse/valanse/common/config/SecurityConfig.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java index 762a436..805cdc9 100644 --- a/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java +++ b/src/main/java/com/valanse/valanse/common/config/SecurityConfig.java @@ -101,6 +101,7 @@ public CorsConfigurationSource corsConfigurationSource() { "https://test-front-security.netlify.app", "https://valan-se-web.vercel.app", "https://valanse.kr", + "https://www.valanse.kr", "https://develop.valanse.kr", "http://valanserver.store", "http://valanserver.store:8080", @@ -112,7 +113,12 @@ public CorsConfigurationSource corsConfigurationSource() { )); configuration.setAllowedOriginPatterns(Arrays.asList( "http://localhost:*", - "http://127.0.0.1:*" + "http://127.0.0.1:*", + "https://*.valanse.kr", + "https://*.vercel.app", + "https://*.netlify.app", + "http://valanserver.store:[*]", + "https://valanserver.store:[*]" )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));