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

This commit is contained in:
yuhalog 2025-06-18 17:00:08 +09:00
commit 9004f7b726
10 changed files with 78 additions and 202 deletions

View File

@ -35,24 +35,24 @@ podTemplate(
echo "Team ID: ${props.teamid}"
}
stage("Check Changes") {
script {
def changes = sh(
script: "git diff --name-only HEAD~1 HEAD",
returnStdout: true
).trim()
echo "Changed files: ${changes}"
if (!changes.contains("smarketing-ai/")) {
echo "No changes in smarketing-ai, skipping build"
currentBuild.result = 'SUCCESS'
error("Stopping pipeline - no changes detected")
}
echo "Changes detected in smarketing-ai, proceeding with build"
}
}
// stage("Check Changes") {
// script {
// def changes = sh(
// script: "git diff --name-only HEAD~1 HEAD",
// returnStdout: true
// ).trim()
//
// echo "Changed files: ${changes}"
//
// if (!changes.contains("smarketing-ai/")) {
// echo "No changes in smarketing-ai, skipping build"
// currentBuild.result = 'SUCCESS'
// error("Stopping pipeline - no changes detected")
// }
//
// echo "Changes detected in smarketing-ai, proceeding with build"
// }
// }
stage("Setup AKS") {
container('azure-cli') {

View File

@ -1380,7 +1380,7 @@ class SnsContentService:
'call_to_action': ['방문', '예약', '문의', '공감', '이웃추가'],
'image_placement_strategy': [
'매장 외관 → 인테리어 → 메뉴판 → 음식 → 분위기',
'텍스트 2-3문장마다 이미지 배치',
##'텍스트 2-3문장마다 이미지 배치',
'이미지 설명은 간결하고 매력적으로',
'마지막에 대표 이미지로 마무리'
]
@ -1713,6 +1713,11 @@ class SnsContentService:
5. 줄바꿈을 활용하여 가독성 향상
6. 해시태그는 본문과 자연스럽게 연결되도록 배치
**이미지 태그 사용법:**
- [IMAGE_1]: 번째 이미지 배치 위치
- [IMAGE_2]: 번째 이미지 배치 위치
- 이미지 태그 다음 줄에 이미지 설명 문구 작성
**필수 요구사항:**
{request.requirement} or '고객의 관심을 끌고 방문을 유도하는 매력적인 게시물'
@ -1777,21 +1782,14 @@ class SnsContentService:
1. 검색자의 궁금증을 해결하는 정보 중심 작성
2. 구체적인 가격, 위치, 운영시간 실용 정보 포함
3. 개인적인 경험과 솔직한 후기 작성
4. 섹션마다 적절한 위치에 [IMAGE_X] 태그로 이미지 배치 위치 표시
5. 이미지마다 간단한 설명 문구 추가
6. 지역 정보와 접근성 정보 포함
**이미지 태그 사용법:**
- [IMAGE_1]: 번째 이미지 배치 위치
- [IMAGE_2]: 번째 이미지 배치 위치
- 이미지 태그 다음 줄에 이미지 설명 문구 작성
4. 이미지마다 간단한 설명 문구 추가
5. 지역 정보와 접근성 정보 포함
**필수 요구사항:**
{request.requirement} or '유용한 정보를 제공하여 방문을 유도하는 신뢰성 있는 후기'
네이버 검색에서 상위 노출되고, 실제로 도움이 되는 정보를 제공하는 블로그 포스트를 작성해주세요.
필수 요구사항을 반드시 참고하여 작성해주세요.
이미지 배치 위치를 [IMAGE_X] 태그로 명확히 표시해주세요.
"""
return prompt

View File

@ -43,7 +43,11 @@ public class SecurityConfig {
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
.requestMatchers("/api/auth/**", "/api/member/register", "/api/member/check-duplicate/**",
"/api/member/validate-password", "/swagger-ui/**", "/v3/api-docs/**",
"/swagger-resources/**", "/webjars/**", "/actuator/**", "/health/**", "/error"
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

View File

@ -43,22 +43,22 @@ podTemplate(
echo "Image Org: ${props.image_org}"
}
stage("Check Changes") {
script {
def changes = sh(
script: "git diff --name-only HEAD~1 HEAD",
returnStdout: true
).trim()
if (!changes.contains("smarketing-java/")) {
echo "No changes in smarketing-java, skipping build"
currentBuild.result = 'SUCCESS'
error("Stopping pipeline - no changes detected")
}
echo "Changes detected in smarketing-java, proceeding with build"
}
}
// stage("Check Changes") {
// script {
// def changes = sh(
// script: "git diff --name-only HEAD~1 HEAD",
// returnStdout: true
// ).trim()
//
// if (!changes.contains("smarketing-java/")) {
// echo "No changes in smarketing-java, skipping build"
// currentBuild.result = 'SUCCESS'
// error("Stopping pipeline - no changes detected")
// }
//
// echo "Changes detected in smarketing-java, proceeding with build"
// }
// }
stage("Setup AKS") {
container('azure-cli') {

View File

@ -182,29 +182,6 @@ spec:
name: common-secret
- secretRef:
name: member-secret
startupProbe:
tcpSocket:
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
livenessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
apiVersion: apps/v1
@ -248,29 +225,7 @@ spec:
name: common-secret
- secretRef:
name: store-secret
startupProbe:
tcpSocket:
port: 8082
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
livenessProbe:
httpGet:
path: /actuator/health
port: 8082
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health
port: 8082
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
apiVersion: apps/v1
@ -314,29 +269,7 @@ spec:
name: common-secret
- secretRef:
name: marketing-content-secret
startupProbe:
tcpSocket:
port: 8083
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
livenessProbe:
httpGet:
path: /actuator/health
port: 8083
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health
port: 8083
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
apiVersion: apps/v1
@ -380,29 +313,7 @@ spec:
name: common-secret
- secretRef:
name: ai-recommend-secret
startupProbe:
tcpSocket:
port: 8084
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
livenessProbe:
httpGet:
path: /actuator/health
port: 8084
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health
port: 8084
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
# Services
@ -462,84 +373,44 @@ spec:
type: ClusterIP
---
# deploy.yaml.template의 Ingress 부분 - 완전한 설정
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: smarketing-backend
namespace: ${namespace}
name: smarketing-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/use-regex: "true"
# nginx.ingress.kubernetes.io/rewrite-target: /$2 # 이 줄 제거
# nginx.ingress.kubernetes.io/use-regex: "true" # 이 줄 제거
spec:
ingressClassName: nginx
rules:
- host: smarketing.20.249.184.228.nip.io
http:
paths:
# Member 서비스 - 인증 관련
- path: /api/auth(/|$)(.*)
pathType: ImplementationSpecific
- path: /api/auth
pathType: Prefix
backend:
service:
name: member
name: auth-service
port:
number: 80
# Member 서비스 - 회원 관리 (누락된 경로!)
- path: /api/member(/|$)(.*)
pathType: ImplementationSpecific
- path: /api/store
pathType: Prefix
backend:
service:
name: member
name: store-service
port:
number: 80
# Store 서비스
- path: /api/store(/|$)(.*)
pathType: ImplementationSpecific
- path: /api/content
pathType: Prefix
backend:
service:
name: store
name: content-service
port:
number: 80
# Store 서비스 - 매출 관련
- path: /api/sales(/|$)(.*)
pathType: ImplementationSpecific
- path: /api/recommend
pathType: Prefix
backend:
service:
name: store
port:
number: 80
# Store 서비스 - 메뉴 관련
- path: /api/menu(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: store
port:
number: 80
# Store 서비스 - 이미지 업로드
- path: /api/images(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: store
port:
number: 80
# Marketing Content 서비스
- path: /api/content(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: marketing-content
port:
number: 80
# AI Recommend 서비스
- path: /api/recommend(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: ai-recommend
name: recommend-service
port:
number: 80

View File

@ -9,7 +9,7 @@ image_org=smarketing
# Application Settings
replicas=1
allowed_origins=http://20.249.171.38
allowed_origins=http://20.249.154.194
# Security Settings
jwt_secret_key=8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ

View File

@ -84,7 +84,7 @@ public class PosterContentService implements PosterContentUseCase {
// 콘텐츠 엔티티 생성
Content content = Content.builder()
.contentType(ContentType.POSTER)
.platform(Platform.GENERAL)
.platform(Platform.POSTER)
.title(request.getTitle())
.content(request.getContent())
.images(request.getImages())

View File

@ -14,6 +14,7 @@ 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.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -34,6 +35,9 @@ public class SnsContentService implements SnsContentUseCase {
private final AiContentGenerator aiContentGenerator;
private final BlobStorageService blobStorageService;
@Value("${azure.storage.container.poster-images:content-images}")
private String contentImageContainer;
/**
* SNS 콘텐츠 생성
*
@ -44,8 +48,10 @@ public class SnsContentService implements SnsContentUseCase {
@Transactional
public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request, List<MultipartFile> files) {
//파일들 주소 가져옴
List<String> urls = blobStorageService.uploadImage(files, "containerName");
if(files != null) {
List<String> urls = blobStorageService.uploadImage(files, contentImageContainer);
request.setImages(urls);
}
// AI를 사용하여 SNS 콘텐츠 생성
String content = aiContentGenerator.generateSnsContent(request);
@ -67,8 +73,6 @@ public class SnsContentService implements SnsContentUseCase {
CreationConditions conditions = CreationConditions.builder()
.category(request.getCategory())
.requirement(request.getRequirement())
//.toneAndManner(request.getToneAndManner())
//.emotionIntensity(request.getEmotionIntensity())
.eventName(request.getEventName())
.startDate(request.getStartDate())
.endDate(request.getEndDate())
@ -76,7 +80,6 @@ public class SnsContentService implements SnsContentUseCase {
// 콘텐츠 엔티티 생성 저장
Content content = Content.builder()
// .contentType(ContentType.SNS_POST)
.platform(Platform.fromString(request.getPlatform()))
.title(request.getTitle())
.content(request.getContent())

View File

@ -17,7 +17,7 @@ public enum Platform {
FACEBOOK("페이스북"),
KAKAO_STORY("카카오스토리"),
YOUTUBE("유튜브"),
GENERAL("일반");
POSTER("포스터");
private final String displayName;

View File

@ -46,7 +46,7 @@ public class ContentController {
@Operation(summary = "SNS 게시물 생성", description = "AI를 활용하여 SNS 게시물을 생성합니다.")
@PostMapping(path = "/sns/generate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ApiResponse<SnsContentCreateResponse>> generateSnsContent(@Valid @RequestPart("request") String requestJson,
@Valid @RequestPart("files") List<MultipartFile> images) throws JsonProcessingException {
@Valid @RequestPart(name = "files", required = false) List<MultipartFile> images) throws JsonProcessingException {
SnsContentCreateRequest request = objectMapper.readValue(requestJson, SnsContentCreateRequest.class);
SnsContentCreateResponse response = snsContentUseCase.generateSnsContent(request, images);
return ResponseEntity.ok(ApiResponse.success(response, "SNS 콘텐츠가 성공적으로 생성되었습니다."));