mirror of
https://github.com/won-ktds/smarketing-backend.git
synced 2026-01-21 11:06:23 +00:00
feat: poster content
This commit is contained in:
parent
1c79ea0834
commit
1d548a5f5c
@ -8,16 +8,17 @@ 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.AiPosterGenerator;
|
||||
import com.won.smarketing.content.domain.service.BlobStorageService;
|
||||
import com.won.smarketing.content.presentation.dto.PosterContentCreateRequest;
|
||||
import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse;
|
||||
import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
||||
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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 포스터 콘텐츠 서비스 구현체
|
||||
@ -30,6 +31,7 @@ public class PosterContentService implements PosterContentUseCase {
|
||||
|
||||
private final ContentRepository contentRepository;
|
||||
private final AiPosterGenerator aiPosterGenerator;
|
||||
private final BlobStorageService blobStorageService;
|
||||
|
||||
/**
|
||||
* 포스터 콘텐츠 생성
|
||||
@ -39,10 +41,18 @@ public class PosterContentService implements PosterContentUseCase {
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public PosterContentCreateResponse generatePosterContent(PosterContentCreateRequest request) {
|
||||
public PosterContentCreateResponse generatePosterContent(List<MultipartFile> images, PosterContentCreateRequest request) {
|
||||
|
||||
// 1. 이미지 blob storage에 저장하고 request 저장
|
||||
List<String> imageUrls = blobStorageService.uploadImage(images, "poster-content-original");
|
||||
request.setImages(imageUrls);
|
||||
|
||||
// 2. AI 요청
|
||||
String generatedPoster = aiPosterGenerator.generatePoster(request);
|
||||
|
||||
// 3. 저장
|
||||
Content savedContent = savePosterContent(request, generatedPoster);
|
||||
|
||||
// 생성 조건 정보 구성
|
||||
CreationConditions conditions = CreationConditions.builder()
|
||||
.category(request.getCategory())
|
||||
@ -68,9 +78,8 @@ public class PosterContentService implements PosterContentUseCase {
|
||||
*
|
||||
* @param request 포스터 콘텐츠 저장 요청
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void savePosterContent(PosterContentSaveRequest request) {
|
||||
public Content savePosterContent(PosterContentCreateRequest request, String generatedPoster) {
|
||||
// 생성 조건 구성
|
||||
CreationConditions conditions = CreationConditions.builder()
|
||||
.category(request.getCategory())
|
||||
@ -86,7 +95,7 @@ public class PosterContentService implements PosterContentUseCase {
|
||||
.contentType(ContentType.POSTER)
|
||||
.platform(Platform.GENERAL)
|
||||
.title(request.getTitle())
|
||||
.content(request.getContent())
|
||||
.content(generatedPoster)
|
||||
.images(request.getImages())
|
||||
.status(ContentStatus.PUBLISHED)
|
||||
.creationConditions(conditions)
|
||||
@ -94,6 +103,8 @@ public class PosterContentService implements PosterContentUseCase {
|
||||
.build();
|
||||
|
||||
// 저장
|
||||
contentRepository.save(content);
|
||||
Content savedContent = contentRepository.save(content);
|
||||
|
||||
return savedContent;
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,9 @@ package com.won.smarketing.content.application.usecase;
|
||||
import com.won.smarketing.content.presentation.dto.PosterContentCreateRequest;
|
||||
import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse;
|
||||
import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 포스터 콘텐츠 관련 UseCase 인터페이스
|
||||
@ -12,15 +15,10 @@ import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
||||
public interface PosterContentUseCase {
|
||||
|
||||
/**
|
||||
* 포스터 콘텐츠 생성
|
||||
* 포스터 콘텐츠 생성 및 저장
|
||||
* @param request 포스터 콘텐츠 생성 요청
|
||||
* @return 포스터 콘텐츠 생성 응답
|
||||
*/
|
||||
PosterContentCreateResponse generatePosterContent(PosterContentCreateRequest request);
|
||||
PosterContentCreateResponse generatePosterContent(List<MultipartFile> images, PosterContentCreateRequest request);
|
||||
|
||||
/**
|
||||
* 포스터 콘텐츠 저장
|
||||
* @param request 포스터 콘텐츠 저장 요청
|
||||
*/
|
||||
void savePosterContent(PosterContentSaveRequest request);
|
||||
}
|
||||
@ -27,42 +27,37 @@ import java.util.List;
|
||||
@Builder
|
||||
public class Content {
|
||||
|
||||
// ==================== 기본키 및 식별자 ====================
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "content_id")
|
||||
private Long id;
|
||||
|
||||
// ==================== 콘텐츠 분류 ====================
|
||||
private ContentType contentType;
|
||||
|
||||
private Platform platform;
|
||||
|
||||
// ==================== 콘텐츠 내용 ====================
|
||||
private String title;
|
||||
|
||||
private String content;
|
||||
|
||||
// ==================== 멀티미디어 및 메타데이터 ====================
|
||||
@Builder.Default
|
||||
private List<String> hashtags = new ArrayList<>();
|
||||
|
||||
@Builder.Default
|
||||
private List<String> images = new ArrayList<>();
|
||||
|
||||
// ==================== 상태 관리 ====================
|
||||
private ContentStatus status;
|
||||
|
||||
// ==================== 생성 조건 ====================
|
||||
private CreationConditions creationConditions;
|
||||
|
||||
// ==================== 매장 정보 ====================
|
||||
private Long storeId;
|
||||
|
||||
// ==================== 프로모션 기간 ====================
|
||||
private LocalDateTime promotionStartDate;
|
||||
|
||||
private LocalDateTime promotionEndDate;
|
||||
|
||||
// ==================== 메타데이터 ====================
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Content(ContentId of, ContentType contentType, Platform platform, String title, String content, List<String> strings, List<String> strings1, ContentStatus contentStatus, CreationConditions conditions, Long storeId, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||
|
||||
@ -24,8 +24,6 @@ public class CreationConditions {
|
||||
private String id;
|
||||
private String category;
|
||||
private String requirement;
|
||||
// private String toneAndManner;
|
||||
// private String emotionIntensity;
|
||||
private String storeName;
|
||||
private String storeType;
|
||||
private String target;
|
||||
|
||||
@ -7,12 +7,17 @@ import com.won.smarketing.content.application.usecase.SnsContentUseCase;
|
||||
import com.won.smarketing.content.presentation.dto.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -62,23 +67,33 @@ public class ContentController {
|
||||
* @return 생성된 포스터 콘텐츠 정보
|
||||
*/
|
||||
@Operation(summary = "홍보 포스터 생성", description = "AI를 활용하여 홍보 포스터를 생성합니다.")
|
||||
@PostMapping("/poster/generate")
|
||||
public ResponseEntity<ApiResponse<PosterContentCreateResponse>> generatePosterContent(@Valid @RequestBody PosterContentCreateRequest request) {
|
||||
PosterContentCreateResponse response = posterContentUseCase.generatePosterContent(request);
|
||||
return ResponseEntity.ok(ApiResponse.success(response, "포스터 콘텐츠가 성공적으로 생성되었습니다."));
|
||||
}
|
||||
@PostMapping(value = "/poster/generate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<ApiResponse<PosterContentCreateResponse>> generatePosterContent(
|
||||
@Parameter(
|
||||
description = "참고할 이미지 파일들 (선택사항, 최대 5개)",
|
||||
required = false,
|
||||
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
)
|
||||
@RequestPart(value = "images", required = false) List<MultipartFile> images,
|
||||
@Parameter(
|
||||
description = "포스터 생성 요청 정보",
|
||||
required = true,
|
||||
example = """
|
||||
{
|
||||
"title": "신메뉴 출시 이벤트",
|
||||
"category": "이벤트",
|
||||
"requirement": "밝고 화사한 분위기로 만들어주세요",
|
||||
"eventName": "아메리카노 할인 이벤트",
|
||||
"startDate": "2024-01-15",
|
||||
"endDate": "2024-01-31",
|
||||
"photoStyle": "밝고 화사한"
|
||||
}
|
||||
"""
|
||||
)
|
||||
@RequestPart(value = "request") @Valid PosterContentCreateRequest request) {
|
||||
|
||||
/**
|
||||
* 홍보 포스터 저장
|
||||
*
|
||||
* @param request 포스터 콘텐츠 저장 요청
|
||||
* @return 저장 성공 응답
|
||||
*/
|
||||
@Operation(summary = "홍보 포스터 저장", description = "생성된 홍보 포스터를 저장합니다.")
|
||||
@PostMapping("/poster/save")
|
||||
public ResponseEntity<ApiResponse<Void>> savePosterContent(@Valid @RequestBody PosterContentSaveRequest request) {
|
||||
posterContentUseCase.savePosterContent(request);
|
||||
return ResponseEntity.ok(ApiResponse.success(null, "포스터 콘텐츠가 성공적으로 저장되었습니다."));
|
||||
PosterContentCreateResponse response = posterContentUseCase.generatePosterContent(images, request);
|
||||
return ResponseEntity.ok(ApiResponse.success(response, "포스터 콘텐츠가 성공적으로 생성되었습니다."));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -50,9 +50,7 @@ public class PosterContentCreateRequest {
|
||||
@Schema(description = "이미지 스타일", example = "모던")
|
||||
private String imageStyle;
|
||||
|
||||
@Schema(description = "업로드된 이미지 URL 목록", required = true)
|
||||
@NotNull(message = "이미지는 1개 이상 필수입니다")
|
||||
@Size(min = 1, message = "이미지는 1개 이상 업로드해야 합니다")
|
||||
@Schema(description = "업로드된 이미지 URL 목록")
|
||||
private List<String> images;
|
||||
|
||||
@Schema(description = "콘텐츠 카테고리", example = "이벤트")
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
// smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/PosterContentSaveRequest.java
|
||||
package com.won.smarketing.content.presentation.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@ -19,12 +17,7 @@ import java.util.List;
|
||||
@Schema(description = "포스터 콘텐츠 저장 요청")
|
||||
public class PosterContentSaveRequest {
|
||||
|
||||
// @Schema(description = "콘텐츠 ID", example = "1", required = true)
|
||||
// @NotNull(message = "콘텐츠 ID는 필수입니다")
|
||||
// private Long contentId;
|
||||
|
||||
@Schema(description = "매장 ID", example = "1", required = true)
|
||||
@NotNull(message = "매장 ID는 필수입니다")
|
||||
@Schema(description = "매장 ID", example = "1")
|
||||
private Long storeId;
|
||||
|
||||
@Schema(description = "제목", example = "특별 이벤트 안내")
|
||||
@ -46,12 +39,6 @@ public class PosterContentSaveRequest {
|
||||
@Schema(description = "구체적인 요구사항", example = "신메뉴 출시 이벤트 포스터를 만들어주세요")
|
||||
private String requirement;
|
||||
|
||||
@Schema(description = "톤앤매너", example = "전문적")
|
||||
private String toneAndManner;
|
||||
|
||||
@Schema(description = "감정 강도", example = "보통")
|
||||
private String emotionIntensity;
|
||||
|
||||
@Schema(description = "이벤트명", example = "신메뉴 출시 이벤트")
|
||||
private String eventName;
|
||||
|
||||
|
||||
@ -68,18 +68,6 @@ public class SnsContentCreateRequest {
|
||||
@Schema(description = "콘텐츠 타입", example = "SNS 게시물")
|
||||
private String contentType;
|
||||
|
||||
// @Schema(description = "톤앤매너",
|
||||
// example = "친근함",
|
||||
// allowableValues = {"친근함", "전문적", "유머러스", "감성적", "트렌디"})
|
||||
// private String toneAndManner;
|
||||
|
||||
// @Schema(description = "감정 강도",
|
||||
// example = "보통",
|
||||
// allowableValues = {"약함", "보통", "강함"})
|
||||
// private String emotionIntensity;
|
||||
|
||||
// ==================== 이벤트 정보 ====================
|
||||
|
||||
@Schema(description = "이벤트명 (이벤트 콘텐츠인 경우)",
|
||||
example = "신메뉴 출시 이벤트")
|
||||
@Size(max = 200, message = "이벤트명은 200자 이하로 입력해주세요")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user