From a7f5f61726cc9426995b121a7dfe292c22f26d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=9C=EC=9D=80?= <61147083+BangSun98@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:25:56 +0900 Subject: [PATCH] add blob --- .../service/SnsContentService.java | 10 ++- .../usecase/SnsContentUseCase.java | 5 +- .../config/AzureBlobStorageConfig.java | 72 +++++++++++++++++++ .../domain/service/BlobStorageService.java | 2 +- .../service/BlobStorageServiceImpl.java | 7 +- .../controller/ContentController.java | 10 ++- .../src/main/resources/application.yml | 10 ++- 7 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/AzureBlobStorageConfig.java diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java index 074b39b..6119226 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java @@ -9,12 +9,14 @@ import com.won.smarketing.content.domain.model.CreationConditions; import com.won.smarketing.content.domain.model.Platform; import com.won.smarketing.content.domain.repository.ContentRepository; import com.won.smarketing.content.domain.service.AiContentGenerator; +import com.won.smarketing.content.domain.service.BlobStorageService; import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest; import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse; import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.List; @@ -30,6 +32,7 @@ public class SnsContentService implements SnsContentUseCase { private final ContentRepository contentRepository; private final AiContentGenerator aiContentGenerator; + private final BlobStorageService blobStorageService; /** * SNS 콘텐츠 생성 @@ -39,14 +42,17 @@ public class SnsContentService implements SnsContentUseCase { */ @Override @Transactional - public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request) { + public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request, List files) { + //파일들 주소 가져옴 + List urls = blobStorageService.uploadImage(files); + request.setImages(urls); + // AI를 사용하여 SNS 콘텐츠 생성 String content = aiContentGenerator.generateSnsContent(request); return SnsContentCreateResponse.builder() .content(content) .build(); - } /** diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java index d2c6751..23c23f1 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java @@ -4,6 +4,9 @@ package com.won.smarketing.content.application.usecase; import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest; import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse; import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; /** * SNS 콘텐츠 관련 UseCase 인터페이스 @@ -16,7 +19,7 @@ public interface SnsContentUseCase { * @param request SNS 콘텐츠 생성 요청 * @return SNS 콘텐츠 생성 응답 */ - SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request); + SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request, List files); /** * SNS 콘텐츠 저장 diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/AzureBlobStorageConfig.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/AzureBlobStorageConfig.java new file mode 100644 index 0000000..09d27bd --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/AzureBlobStorageConfig.java @@ -0,0 +1,72 @@ +// store/src/main/java/com/won/smarketing/store/config/AzureBlobStorageConfig.java +package com.won.smarketing.content.config; + +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.common.StorageSharedKeyCredential; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Azure Blob Storage 설정 클래스 + * Azure Blob Storage와의 연결을 위한 설정 + */ +@Configuration +@Slf4j +public class AzureBlobStorageConfig { + + @Value("${azure.storage.account-name}") + private String accountName; + + @Value("${azure.storage.account-key:}") + private String accountKey; + + @Value("${azure.storage.endpoint:}") + private String endpoint; + + /** + * Azure Blob Storage Service Client 생성 + * + * @return BlobServiceClient 인스턴스 + */ + @Bean + public BlobServiceClient blobServiceClient() { + try { + // Managed Identity 사용 시 (Azure 환경에서 권장) + if (accountKey == null || accountKey.isEmpty()) { + log.info("Azure Blob Storage 연결 - Managed Identity 사용"); + return new BlobServiceClientBuilder() + .endpoint(getEndpoint()) + .credential(new DefaultAzureCredentialBuilder().build()) + .buildClient(); + } + + // Account Key 사용 시 (개발 환경용) + log.info("Azure Blob Storage 연결 - Account Key 사용"); + StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName, accountKey); + return new BlobServiceClientBuilder() + .endpoint(getEndpoint()) + .credential(credential) + .buildClient(); + + } catch (Exception e) { + log.error("Azure Blob Storage 클라이언트 생성 실패", e); + throw new RuntimeException("Azure Blob Storage 연결 실패", e); + } + } + + /** + * Storage Account 엔드포인트 URL 생성 + * + * @return 엔드포인트 URL + */ + private String getEndpoint() { + if (endpoint != null && !endpoint.isEmpty()) { + return endpoint; + } + return String.format("https://%s.blob.core.windows.net", accountName); + } +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageService.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageService.java index 92a6daf..76ea929 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageService.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageService.java @@ -17,7 +17,7 @@ public interface BlobStorageService { * @param file 업로드할 파일 * @return 업로드된 파일의 URL */ - List uploadImage(List file, String containerName); + List uploadImage(List file); /** diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageServiceImpl.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageServiceImpl.java index fc35673..3bb63f1 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageServiceImpl.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/service/BlobStorageServiceImpl.java @@ -57,11 +57,10 @@ public class BlobStorageServiceImpl implements BlobStorageService { * 이미지 파일 업로드 * * @param files 업로드할 파일들 - * @param containerName 컨테이너 이름 * @return 업로드된 파일의 URL */ @Override - public List uploadImage(List files, String containerName) { + public List uploadImage(List files) { // 파일 유효성 검증 validateImageFile(files); List urls = new ArrayList<>(); @@ -71,10 +70,10 @@ public class BlobStorageServiceImpl implements BlobStorageService { for(MultipartFile file : files) { String fileName = generateMenuImageFileName(file.getOriginalFilename()); - ensureContainerExists(containerName); + ensureContainerExists(posterImageContainer); // Blob 클라이언트 생성 - BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName); + BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(posterImageContainer); BlobClient blobClient = containerClient.getBlobClient(fileName); // 파일 업로드 (간단한 방식) diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/controller/ContentController.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/controller/ContentController.java index b454fb1..eba9ed8 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/controller/ContentController.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/controller/ContentController.java @@ -9,10 +9,13 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; +import org.springframework.web.multipart.MultipartFile; + import java.util.List; /** @@ -36,9 +39,10 @@ public class ContentController { * @return 생성된 SNS 콘텐츠 정보 */ @Operation(summary = "SNS 게시물 생성", description = "AI를 활용하여 SNS 게시물을 생성합니다.") - @PostMapping("/sns/generate") - public ResponseEntity> generateSnsContent(@Valid @RequestBody SnsContentCreateRequest request) { - SnsContentCreateResponse response = snsContentUseCase.generateSnsContent(request); + @PostMapping(path = "/sns/generate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> generateSnsContent(@Valid @RequestPart SnsContentCreateRequest request, + @RequestPart("files") List images) { + SnsContentCreateResponse response = snsContentUseCase.generateSnsContent(request, images); return ResponseEntity.ok(ApiResponse.success(response, "SNS 콘텐츠가 성공적으로 생성되었습니다.")); } diff --git a/smarketing-java/marketing-content/src/main/resources/application.yml b/smarketing-java/marketing-content/src/main/resources/application.yml index 871f8d8..819d127 100644 --- a/smarketing-java/marketing-content/src/main/resources/application.yml +++ b/smarketing-java/marketing-content/src/main/resources/application.yml @@ -37,7 +37,15 @@ logging: external: ai-service: base-url: ${AI_SERVICE_BASE_URL:http://20.249.113.247:5001} - +azure: + storage: + account-name: ${AZURE_STORAGE_ACCOUNT_NAME:stdigitalgarage02} + account-key: ${AZURE_STORAGE_ACCOUNT_KEY:} + endpoint: ${AZURE_STORAGE_ENDPOINT:https://stdigitalgarage02.blob.core.windows.net} + container: + menu-images: ${AZURE_STORAGE_MENU_CONTAINER:smarketing-menu-images} + store-images: ${AZURE_STORAGE_STORE_CONTAINER:smarketing-store-images} + max-file-size: ${AZURE_STORAGE_MAX_FILE_SIZE:10485760} # 10MB management: endpoints: web: