Merge branch 'marketing-contents' of https://github.com/won-ktds/smarketing-backend into poster-content

This commit is contained in:
yuhalog 2025-06-17 17:26:52 +09:00
commit d9ff28e7c5
7 changed files with 101 additions and 12 deletions

View File

@ -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.model.Platform;
import com.won.smarketing.content.domain.repository.ContentRepository; import com.won.smarketing.content.domain.repository.ContentRepository;
import com.won.smarketing.content.domain.service.AiContentGenerator; 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.SnsContentCreateRequest;
import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse; import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse;
import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest; import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@ -30,6 +32,7 @@ public class SnsContentService implements SnsContentUseCase {
private final ContentRepository contentRepository; private final ContentRepository contentRepository;
private final AiContentGenerator aiContentGenerator; private final AiContentGenerator aiContentGenerator;
private final BlobStorageService blobStorageService;
/** /**
* SNS 콘텐츠 생성 * SNS 콘텐츠 생성
@ -39,14 +42,17 @@ public class SnsContentService implements SnsContentUseCase {
*/ */
@Override @Override
@Transactional @Transactional
public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request) { public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request, List<MultipartFile> files) {
//파일들 주소 가져옴
List<String> urls = blobStorageService.uploadImage(files);
request.setImages(urls);
// AI를 사용하여 SNS 콘텐츠 생성 // AI를 사용하여 SNS 콘텐츠 생성
String content = aiContentGenerator.generateSnsContent(request); String content = aiContentGenerator.generateSnsContent(request);
return SnsContentCreateResponse.builder() return SnsContentCreateResponse.builder()
.content(content) .content(content)
.build(); .build();
} }
/** /**

View File

@ -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.SnsContentCreateRequest;
import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse; import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse;
import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest; import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/** /**
* SNS 콘텐츠 관련 UseCase 인터페이스 * SNS 콘텐츠 관련 UseCase 인터페이스
@ -16,7 +19,7 @@ public interface SnsContentUseCase {
* @param request SNS 콘텐츠 생성 요청 * @param request SNS 콘텐츠 생성 요청
* @return SNS 콘텐츠 생성 응답 * @return SNS 콘텐츠 생성 응답
*/ */
SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request); SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request, List<MultipartFile> files);
/** /**
* SNS 콘텐츠 저장 * SNS 콘텐츠 저장

View File

@ -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);
}
}

View File

@ -17,7 +17,7 @@ public interface BlobStorageService {
* @param file 업로드할 파일 * @param file 업로드할 파일
* @return 업로드된 파일의 URL * @return 업로드된 파일의 URL
*/ */
List<String> uploadImage(List<MultipartFile> file, String containerName); List<String> uploadImage(List<MultipartFile> file);
/** /**

View File

@ -57,11 +57,10 @@ public class BlobStorageServiceImpl implements BlobStorageService {
* 이미지 파일 업로드 * 이미지 파일 업로드
* *
* @param files 업로드할 파일들 * @param files 업로드할 파일들
* @param containerName 컨테이너 이름
* @return 업로드된 파일의 URL * @return 업로드된 파일의 URL
*/ */
@Override @Override
public List<String> uploadImage(List<MultipartFile> files, String containerName) { public List<String> uploadImage(List<MultipartFile> files) {
// 파일 유효성 검증 // 파일 유효성 검증
validateImageFile(files); validateImageFile(files);
List<String> urls = new ArrayList<>(); List<String> urls = new ArrayList<>();
@ -71,10 +70,10 @@ public class BlobStorageServiceImpl implements BlobStorageService {
for(MultipartFile file : files) { for(MultipartFile file : files) {
String fileName = generateMenuImageFileName(file.getOriginalFilename()); String fileName = generateMenuImageFileName(file.getOriginalFilename());
ensureContainerExists(containerName); ensureContainerExists(posterImageContainer);
// Blob 클라이언트 생성 // Blob 클라이언트 생성
BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName); BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(posterImageContainer);
BlobClient blobClient = containerClient.getBlobClient(fileName); BlobClient blobClient = containerClient.getBlobClient(fileName);
// 파일 업로드 (간단한 방식) // 파일 업로드 (간단한 방식)

View File

@ -41,9 +41,10 @@ public class ContentController {
* @return 생성된 SNS 콘텐츠 정보 * @return 생성된 SNS 콘텐츠 정보
*/ */
@Operation(summary = "SNS 게시물 생성", description = "AI를 활용하여 SNS 게시물을 생성합니다.") @Operation(summary = "SNS 게시물 생성", description = "AI를 활용하여 SNS 게시물을 생성합니다.")
@PostMapping("/sns/generate") @PostMapping(path = "/sns/generate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ApiResponse<SnsContentCreateResponse>> generateSnsContent(@Valid @RequestBody SnsContentCreateRequest request) { public ResponseEntity<ApiResponse<SnsContentCreateResponse>> generateSnsContent(@Valid @RequestPart SnsContentCreateRequest request,
SnsContentCreateResponse response = snsContentUseCase.generateSnsContent(request); @RequestPart("files") List<MultipartFile> images) {
SnsContentCreateResponse response = snsContentUseCase.generateSnsContent(request, images);
return ResponseEntity.ok(ApiResponse.success(response, "SNS 콘텐츠가 성공적으로 생성되었습니다.")); return ResponseEntity.ok(ApiResponse.success(response, "SNS 콘텐츠가 성공적으로 생성되었습니다."));
} }

View File

@ -37,7 +37,15 @@ logging:
external: external:
ai-service: ai-service:
base-url: ${AI_SERVICE_BASE_URL:http://20.249.113.247:5001} 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: management:
endpoints: endpoints:
web: web: