feat: Marketing Tip Summary

This commit is contained in:
yuhalog
2025-06-17 14:05:21 +09:00
parent f9a5007cfd
commit 14c5164c41
2 changed files with 157 additions and 14 deletions
@@ -142,23 +142,167 @@ public class MarketingTipService implements MarketingTipUseCase {
}
/**
* 마케팅 팁 요약 생성 (첫 50자 또는 첫 번째 문장)
* 마케팅 팁 요약 생성 (핵심 마케팅 팁 섹션에서 첫 번째 문장 추출)
*
* @param fullContent AI로 생성된 전체 마케팅 팁 HTML 콘텐츠
* @return 핵심 마케팅 팁의 첫 번째 문장
*/
private String generateTipSummary(String fullContent) {
if (fullContent == null || fullContent.trim().isEmpty()) {
return "마케팅 팁이 생성되었습니다.";
}
// 첫 번째 문장으로 요약 (마침표 기준)
String[] sentences = fullContent.split("[.!?]");
String firstSentence = sentences.length > 0 ? sentences[0].trim() : fullContent;
try {
// 1. "✨ 핵심 마케팅 팁" 섹션 추출
String coreSection = extractCoreMarketingTipSection(fullContent);
// 50자 제한
if (firstSentence.length() > 50) {
return firstSentence.substring(0, 47) + "...";
if (coreSection != null && !coreSection.trim().isEmpty()) {
// 2. HTML 태그 제거
String cleanText = removeHtmlTags(coreSection);
// 3. 첫 번째 의미있는 문장 추출
String summary = extractFirstMeaningfulSentence(cleanText);
// 4. 길이 제한 (100자 이내)
if (summary.length() > 100) {
summary = summary.substring(0, 97) + "...";
}
return summary;
}
// 핵심 팁 섹션을 찾지 못한 경우 fallback 처리
return extractFallbackSummary(fullContent);
} catch (Exception e) {
log.warn("마케팅 팁 요약 생성 중 오류 발생, 기본 메시지 반환: {}", e.getMessage());
return "맞춤형 마케팅 팁이 생성되었습니다.";
}
}
/**
* "✨ 핵심 마케팅 팁" 섹션 추출
*/
private String extractCoreMarketingTipSection(String fullContent) {
// 핵심 마케팅 팁 섹션 시작 패턴들
String[] corePatterns = {
"✨ 핵심 마케팅 팁",
"<h3>✨ 핵심 마케팅 팁</h3>",
"핵심 마케팅 팁"
};
// 다음 섹션 시작 패턴들
String[] nextSectionPatterns = {
"🚀 실행 방법",
"<h3>🚀 실행 방법</h3>",
"💰 예상 비용",
"<h3>💰 예상 비용"
};
for (String pattern : corePatterns) {
int startIndex = fullContent.indexOf(pattern);
if (startIndex != -1) {
// 패턴 뒤부터 시작
int contentStart = startIndex + pattern.length();
// 다음 섹션까지의 내용 추출
int endIndex = fullContent.length();
for (String nextPattern : nextSectionPatterns) {
int nextIndex = fullContent.indexOf(nextPattern, contentStart);
if (nextIndex != -1 && nextIndex < endIndex) {
endIndex = nextIndex;
}
}
return fullContent.substring(contentStart, endIndex).trim();
}
}
return firstSentence;
return null;
}
/**
* HTML 태그 제거
*/
private String removeHtmlTags(String htmlText) {
if (htmlText == null) return "";
return htmlText
.replaceAll("<[^>]+>", "") // HTML 태그 제거
.replaceAll("&nbsp;", " ") // HTML 엔티티 처리
.replaceAll("&lt;", "<")
.replaceAll("&gt;", ">")
.replaceAll("&amp;", "&")
.replaceAll("\\s+", " ") // 연속된 공백을 하나로
.trim();
}
/**
* 첫 번째 의미있는 문장 추출
*/
private String extractFirstMeaningfulSentence(String cleanText) {
if (cleanText == null || cleanText.trim().isEmpty()) {
return "마케팅 팁이 생성되었습니다.";
}
// 문장 분할 (마침표, 느낌표, 물음표 기준)
String[] sentences = cleanText.split("[.!?]");
for (String sentence : sentences) {
String trimmed = sentence.trim();
// 의미있는 문장인지 확인 (10자 이상, 특수문자만으로 구성되지 않음)
if (trimmed.length() >= 10 &&
!trimmed.matches("^[\\s\\p{Punct}]*$") && // 공백과 구두점만으로 구성되지 않음
!isOnlyEmojisOrSymbols(trimmed)) { // 이모지나 기호만으로 구성되지 않음
// 문장 끝에 마침표 추가 (없는 경우)
if (!trimmed.endsWith(".") && !trimmed.endsWith("!") && !trimmed.endsWith("?")) {
trimmed += ".";
}
return trimmed;
}
}
// 의미있는 문장을 찾지 못한 경우 원본의 처음 50자 반환
if (cleanText.length() > 50) {
return cleanText.substring(0, 47) + "...";
}
return cleanText;
}
/**
* 이모지나 기호만으로 구성되었는지 확인
*/
private boolean isOnlyEmojisOrSymbols(String text) {
// 한글, 영문, 숫자가 포함되어 있으면 의미있는 텍스트로 판단
return !text.matches(".*[\\p{L}\\p{N}].*");
}
/**
* 핵심 팁 섹션을 찾지 못한 경우 대체 요약 생성
*/
private String extractFallbackSummary(String fullContent) {
// HTML 태그 제거 후 첫 번째 의미있는 문장 찾기
String cleanContent = removeHtmlTags(fullContent);
// 첫 번째 문단에서 의미있는 문장 추출
String[] paragraphs = cleanContent.split("\\n\\n");
for (String paragraph : paragraphs) {
String trimmed = paragraph.trim();
if (trimmed.length() >= 20) { // 충분히 긴 문단
String summary = extractFirstMeaningfulSentence(trimmed);
if (summary.length() >= 10) {
return summary;
}
}
}
// 모든 방법이 실패한 경우 기본 메시지
return "개인화된 마케팅 팁이 생성되었습니다.";
}
/**