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.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.AiPosterGenerator;
|
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.PosterContentCreateRequest;
|
||||||
import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse;
|
import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse;
|
||||||
import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
||||||
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.util.HashMap;
|
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 ContentRepository contentRepository;
|
||||||
private final AiPosterGenerator aiPosterGenerator;
|
private final AiPosterGenerator aiPosterGenerator;
|
||||||
|
private final BlobStorageService blobStorageService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 포스터 콘텐츠 생성
|
* 포스터 콘텐츠 생성
|
||||||
@ -39,10 +41,18 @@ public class PosterContentService implements PosterContentUseCase {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@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);
|
String generatedPoster = aiPosterGenerator.generatePoster(request);
|
||||||
|
|
||||||
|
// 3. 저장
|
||||||
|
Content savedContent = savePosterContent(request, generatedPoster);
|
||||||
|
|
||||||
// 생성 조건 정보 구성
|
// 생성 조건 정보 구성
|
||||||
CreationConditions conditions = CreationConditions.builder()
|
CreationConditions conditions = CreationConditions.builder()
|
||||||
.category(request.getCategory())
|
.category(request.getCategory())
|
||||||
@ -68,9 +78,8 @@ public class PosterContentService implements PosterContentUseCase {
|
|||||||
*
|
*
|
||||||
* @param request 포스터 콘텐츠 저장 요청
|
* @param request 포스터 콘텐츠 저장 요청
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void savePosterContent(PosterContentSaveRequest request) {
|
public Content savePosterContent(PosterContentCreateRequest request, String generatedPoster) {
|
||||||
// 생성 조건 구성
|
// 생성 조건 구성
|
||||||
CreationConditions conditions = CreationConditions.builder()
|
CreationConditions conditions = CreationConditions.builder()
|
||||||
.category(request.getCategory())
|
.category(request.getCategory())
|
||||||
@ -86,7 +95,7 @@ public class PosterContentService implements PosterContentUseCase {
|
|||||||
.contentType(ContentType.POSTER)
|
.contentType(ContentType.POSTER)
|
||||||
.platform(Platform.GENERAL)
|
.platform(Platform.GENERAL)
|
||||||
.title(request.getTitle())
|
.title(request.getTitle())
|
||||||
.content(request.getContent())
|
.content(generatedPoster)
|
||||||
.images(request.getImages())
|
.images(request.getImages())
|
||||||
.status(ContentStatus.PUBLISHED)
|
.status(ContentStatus.PUBLISHED)
|
||||||
.creationConditions(conditions)
|
.creationConditions(conditions)
|
||||||
@ -94,6 +103,8 @@ public class PosterContentService implements PosterContentUseCase {
|
|||||||
.build();
|
.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.PosterContentCreateRequest;
|
||||||
import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse;
|
import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse;
|
||||||
import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 포스터 콘텐츠 관련 UseCase 인터페이스
|
* 포스터 콘텐츠 관련 UseCase 인터페이스
|
||||||
@ -12,15 +15,10 @@ import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest;
|
|||||||
public interface PosterContentUseCase {
|
public interface PosterContentUseCase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 포스터 콘텐츠 생성
|
* 포스터 콘텐츠 생성 및 저장
|
||||||
* @param request 포스터 콘텐츠 생성 요청
|
* @param request 포스터 콘텐츠 생성 요청
|
||||||
* @return 포스터 콘텐츠 생성 응답
|
* @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
|
@Builder
|
||||||
public class Content {
|
public class Content {
|
||||||
|
|
||||||
// ==================== 기본키 및 식별자 ====================
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(name = "content_id")
|
@Column(name = "content_id")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
// ==================== 콘텐츠 분류 ====================
|
|
||||||
private ContentType contentType;
|
private ContentType contentType;
|
||||||
|
|
||||||
private Platform platform;
|
private Platform platform;
|
||||||
|
|
||||||
// ==================== 콘텐츠 내용 ====================
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
// ==================== 멀티미디어 및 메타데이터 ====================
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private List<String> hashtags = new ArrayList<>();
|
private List<String> hashtags = new ArrayList<>();
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private List<String> images = new ArrayList<>();
|
private List<String> images = new ArrayList<>();
|
||||||
|
|
||||||
// ==================== 상태 관리 ====================
|
|
||||||
private ContentStatus status;
|
private ContentStatus status;
|
||||||
|
|
||||||
// ==================== 생성 조건 ====================
|
|
||||||
private CreationConditions creationConditions;
|
private CreationConditions creationConditions;
|
||||||
|
|
||||||
// ==================== 매장 정보 ====================
|
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
|
|
||||||
// ==================== 프로모션 기간 ====================
|
|
||||||
private LocalDateTime promotionStartDate;
|
private LocalDateTime promotionStartDate;
|
||||||
|
|
||||||
private LocalDateTime promotionEndDate;
|
private LocalDateTime promotionEndDate;
|
||||||
|
|
||||||
// ==================== 메타데이터 ====================
|
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
private LocalDateTime updatedAt;
|
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) {
|
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 id;
|
||||||
private String category;
|
private String category;
|
||||||
private String requirement;
|
private String requirement;
|
||||||
// private String toneAndManner;
|
|
||||||
// private String emotionIntensity;
|
|
||||||
private String storeName;
|
private String storeName;
|
||||||
private String storeType;
|
private String storeType;
|
||||||
private String target;
|
private String target;
|
||||||
|
|||||||
@ -7,12 +7,17 @@ import com.won.smarketing.content.application.usecase.SnsContentUseCase;
|
|||||||
import com.won.smarketing.content.presentation.dto.*;
|
import com.won.smarketing.content.presentation.dto.*;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
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 io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,23 +67,33 @@ public class ContentController {
|
|||||||
* @return 생성된 포스터 콘텐츠 정보
|
* @return 생성된 포스터 콘텐츠 정보
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "홍보 포스터 생성", description = "AI를 활용하여 홍보 포스터를 생성합니다.")
|
@Operation(summary = "홍보 포스터 생성", description = "AI를 활용하여 홍보 포스터를 생성합니다.")
|
||||||
@PostMapping("/poster/generate")
|
@PostMapping(value = "/poster/generate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public ResponseEntity<ApiResponse<PosterContentCreateResponse>> generatePosterContent(@Valid @RequestBody PosterContentCreateRequest request) {
|
public ResponseEntity<ApiResponse<PosterContentCreateResponse>> generatePosterContent(
|
||||||
PosterContentCreateResponse response = posterContentUseCase.generatePosterContent(request);
|
@Parameter(
|
||||||
return ResponseEntity.ok(ApiResponse.success(response, "포스터 콘텐츠가 성공적으로 생성되었습니다."));
|
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) {
|
||||||
|
|
||||||
/**
|
PosterContentCreateResponse response = posterContentUseCase.generatePosterContent(images, request);
|
||||||
* 홍보 포스터 저장
|
return ResponseEntity.ok(ApiResponse.success(response, "포스터 콘텐츠가 성공적으로 생성되었습니다."));
|
||||||
*
|
|
||||||
* @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, "포스터 콘텐츠가 성공적으로 저장되었습니다."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -50,9 +50,7 @@ public class PosterContentCreateRequest {
|
|||||||
@Schema(description = "이미지 스타일", example = "모던")
|
@Schema(description = "이미지 스타일", example = "모던")
|
||||||
private String imageStyle;
|
private String imageStyle;
|
||||||
|
|
||||||
@Schema(description = "업로드된 이미지 URL 목록", required = true)
|
@Schema(description = "업로드된 이미지 URL 목록")
|
||||||
@NotNull(message = "이미지는 1개 이상 필수입니다")
|
|
||||||
@Size(min = 1, message = "이미지는 1개 이상 업로드해야 합니다")
|
|
||||||
private List<String> images;
|
private List<String> images;
|
||||||
|
|
||||||
@Schema(description = "콘텐츠 카테고리", example = "이벤트")
|
@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;
|
package com.won.smarketing.content.presentation.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@ -19,12 +17,7 @@ import java.util.List;
|
|||||||
@Schema(description = "포스터 콘텐츠 저장 요청")
|
@Schema(description = "포스터 콘텐츠 저장 요청")
|
||||||
public class PosterContentSaveRequest {
|
public class PosterContentSaveRequest {
|
||||||
|
|
||||||
// @Schema(description = "콘텐츠 ID", example = "1", required = true)
|
@Schema(description = "매장 ID", example = "1")
|
||||||
// @NotNull(message = "콘텐츠 ID는 필수입니다")
|
|
||||||
// private Long contentId;
|
|
||||||
|
|
||||||
@Schema(description = "매장 ID", example = "1", required = true)
|
|
||||||
@NotNull(message = "매장 ID는 필수입니다")
|
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
|
|
||||||
@Schema(description = "제목", example = "특별 이벤트 안내")
|
@Schema(description = "제목", example = "특별 이벤트 안내")
|
||||||
@ -46,12 +39,6 @@ public class PosterContentSaveRequest {
|
|||||||
@Schema(description = "구체적인 요구사항", example = "신메뉴 출시 이벤트 포스터를 만들어주세요")
|
@Schema(description = "구체적인 요구사항", example = "신메뉴 출시 이벤트 포스터를 만들어주세요")
|
||||||
private String requirement;
|
private String requirement;
|
||||||
|
|
||||||
@Schema(description = "톤앤매너", example = "전문적")
|
|
||||||
private String toneAndManner;
|
|
||||||
|
|
||||||
@Schema(description = "감정 강도", example = "보통")
|
|
||||||
private String emotionIntensity;
|
|
||||||
|
|
||||||
@Schema(description = "이벤트명", example = "신메뉴 출시 이벤트")
|
@Schema(description = "이벤트명", example = "신메뉴 출시 이벤트")
|
||||||
private String eventName;
|
private String eventName;
|
||||||
|
|
||||||
|
|||||||
@ -68,18 +68,6 @@ public class SnsContentCreateRequest {
|
|||||||
@Schema(description = "콘텐츠 타입", example = "SNS 게시물")
|
@Schema(description = "콘텐츠 타입", example = "SNS 게시물")
|
||||||
private String contentType;
|
private String contentType;
|
||||||
|
|
||||||
// @Schema(description = "톤앤매너",
|
|
||||||
// example = "친근함",
|
|
||||||
// allowableValues = {"친근함", "전문적", "유머러스", "감성적", "트렌디"})
|
|
||||||
// private String toneAndManner;
|
|
||||||
|
|
||||||
// @Schema(description = "감정 강도",
|
|
||||||
// example = "보통",
|
|
||||||
// allowableValues = {"약함", "보통", "강함"})
|
|
||||||
// private String emotionIntensity;
|
|
||||||
|
|
||||||
// ==================== 이벤트 정보 ====================
|
|
||||||
|
|
||||||
@Schema(description = "이벤트명 (이벤트 콘텐츠인 경우)",
|
@Schema(description = "이벤트명 (이벤트 콘텐츠인 경우)",
|
||||||
example = "신메뉴 출시 이벤트")
|
example = "신메뉴 출시 이벤트")
|
||||||
@Size(max = 200, message = "이벤트명은 200자 이하로 입력해주세요")
|
@Size(max = 200, message = "이벤트명은 200자 이하로 입력해주세요")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user