Files
Jarvis_java/src/main/java/cn/van/business/service/SocialMediaService.java
2026-04-04 16:20:42 +08:00

348 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cn.van.business.service;
import cn.hutool.core.util.StrUtil;
import cn.van.business.util.ds.SocialMediaLlmClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 小红书/抖音内容生成服务
* 提供关键词提取、文案生成等功能
*
* @author System
*/
@Slf4j
@Service
public class SocialMediaService {
@Autowired
private SocialMediaLlmClient socialMediaLlmClient;
@Autowired
private MarketingImageService marketingImageService;
@Autowired(required = false)
private StringRedisTemplate redisTemplate;
// Redis Key 前缀
private static final String REDIS_KEY_PREFIX = "social_media:prompt:";
// 默认提示词模板
private static final String DEFAULT_KEYWORDS_PROMPT =
"请从以下商品标题中提取3-5个最核心的关键词这些关键词要能突出商品的核心卖点和特色。\n" +
"要求:\n" +
"1. 每个关键词2-4个字\n" +
"2. 关键词要能吸引小红书/抖音用户\n" +
"3. 用逗号分隔,只返回关键词,不要其他内容\n" +
"商品标题:%s";
private static final String DEFAULT_CONTENT_PROMPT_XHS =
"请为小红书平台生成一篇商品推广文案,要求:\n" +
"1. 风格:真实、种草、有温度\n" +
"2. 开头用emoji或感叹句吸引注意\n" +
"3. 内容:突出商品亮点、使用场景、性价比\n" +
"4. 结尾:引导行动(如:快冲、闭眼入等)\n" +
"5. 长度150-300字\n" +
"6. 适当使用emoji和换行\n" +
"\n商品信息\n" +
"商品名称:%s\n" +
"%s" + // 价格信息
"%s" + // 关键词
"\n请直接生成文案内容不要添加其他说明";
private static final String DEFAULT_CONTENT_PROMPT_DOUYIN =
"请为抖音平台生成一篇商品推广文案,要求:\n" +
"1. 风格:直接、有冲击力、吸引眼球\n" +
"2. 开头:用疑问句或对比句抓住注意力\n" +
"3. 内容:强调价格优势、限时优惠、稀缺性\n" +
"4. 结尾:制造紧迫感,引导立即行动\n" +
"5. 长度100-200字\n" +
"6. 使用短句,节奏感强\n" +
"\n商品信息\n" +
"商品名称:%s\n" +
"%s" + // 价格信息
"%s" + // 关键词
"\n请直接生成文案内容不要添加其他说明";
private static final String DEFAULT_CONTENT_PROMPT_BOTH =
"请生成一篇适合小红书和抖音平台的商品推广文案,要求:\n" +
"1. 风格:真实、有吸引力\n" +
"2. 突出商品亮点和价格优势\n" +
"3. 长度150-250字\n" +
"\n商品信息\n" +
"商品名称:%s\n" +
"%s" + // 价格信息
"%s" + // 关键词
"\n请直接生成文案内容不要添加其他说明";
/**
* 提取商品标题关键词
*
* @param productName 商品名称
* @return 关键词列表
*/
public Map<String, Object> extractKeywords(String productName) {
Map<String, Object> result = new HashMap<>();
if (StrUtil.isBlank(productName)) {
result.put("success", false);
result.put("error", "商品名称不能为空");
return result;
}
try {
// 从 Redis 读取提示词模板,如果没有则使用默认模板
String promptTemplate = getPromptTemplate("keywords", DEFAULT_KEYWORDS_PROMPT);
String prompt = String.format(promptTemplate, productName);
String response = socialMediaLlmClient.getResponse(prompt);
if (StrUtil.isNotBlank(response)) {
// 解析关键词
String[] keywords = response.trim()
.replaceAll("[,]", ",")
.split(",");
List<String> keywordList = new ArrayList<>();
for (String keyword : keywords) {
String cleaned = keyword.trim();
if (StrUtil.isNotBlank(cleaned) && cleaned.length() <= 6) {
keywordList.add(cleaned);
}
}
// 限制数量
if (keywordList.size() > 5) {
keywordList = keywordList.subList(0, 5);
}
result.put("success", true);
result.put("keywords", keywordList);
result.put("keywordsText", String.join("", keywordList));
log.info("提取关键词成功: {} -> {}", productName, keywordList);
} else {
throw new Exception("AI返回结果为空");
}
} catch (Exception e) {
log.error("提取关键词失败", e);
result.put("success", false);
result.put("error", "提取关键词失败: " + e.getMessage());
// 降级方案:简单提取
result.put("keywords", simpleExtractKeywords(productName));
result.put("keywordsText", String.join("", simpleExtractKeywords(productName)));
}
return result;
}
/**
* 生成小红书/抖音文案
*
* @param productName 商品名称
* @param originalPrice 原价
* @param finalPrice 到手价
* @param keywords 关键词(可选)
* @param style 文案风格xhs小红书、douyin抖音、both通用
* @return 生成的文案
*/
public Map<String, Object> generateContent(String productName, Double originalPrice,
Double finalPrice, String keywords, String style) {
Map<String, Object> result = new HashMap<>();
if (StrUtil.isBlank(productName)) {
result.put("success", false);
result.put("error", "商品名称不能为空");
return result;
}
try {
// 构建价格信息
StringBuilder priceInfo = new StringBuilder();
if (originalPrice != null && originalPrice > 0) {
priceInfo.append("原价:¥").append(String.format("%.0f", originalPrice)).append("\n");
}
if (finalPrice != null && finalPrice > 0) {
priceInfo.append("到手价:¥").append(String.format("%.0f", finalPrice)).append("\n");
}
// 构建关键词信息
String keywordsInfo = "";
if (StrUtil.isNotBlank(keywords)) {
keywordsInfo = "关键词:" + keywords + "\n";
}
// 从 Redis 读取提示词模板,如果没有则使用默认模板
String promptTemplate;
if ("xhs".equals(style)) {
promptTemplate = getPromptTemplate("content:xhs", DEFAULT_CONTENT_PROMPT_XHS);
} else if ("douyin".equals(style)) {
promptTemplate = getPromptTemplate("content:douyin", DEFAULT_CONTENT_PROMPT_DOUYIN);
} else {
promptTemplate = getPromptTemplate("content:both", DEFAULT_CONTENT_PROMPT_BOTH);
}
String prompt = String.format(promptTemplate, productName, priceInfo.toString(), keywordsInfo);
String content = socialMediaLlmClient.getResponse(prompt.toString());
if (StrUtil.isNotBlank(content)) {
result.put("success", true);
result.put("content", content.trim());
log.info("生成文案成功: {}", productName);
} else {
throw new Exception("AI返回结果为空");
}
} catch (Exception e) {
log.error("生成文案失败", e);
result.put("success", false);
result.put("error", "生成文案失败: " + e.getMessage());
// 降级方案:生成简单文案
result.put("content", generateSimpleContent(productName, originalPrice, finalPrice));
}
return result;
}
/**
* 一键生成完整内容(关键词 + 文案 + 图片)
*
* @param productImageUrl 商品主图URL
* @param productName 商品名称
* @param originalPrice 原价
* @param finalPrice 到手价
* @param style 文案风格
* @return 完整内容
*/
public Map<String, Object> generateCompleteContent(String productImageUrl, String productName,
Double originalPrice, Double finalPrice, String style) {
Map<String, Object> result = new HashMap<>();
try {
// 1. 提取关键词
Map<String, Object> keywordResult = extractKeywords(productName);
List<String> keywords = (List<String>) keywordResult.get("keywords");
String keywordsText = (String) keywordResult.get("keywordsText");
// 2. 生成文案
Map<String, Object> contentResult = generateContent(
productName, originalPrice, finalPrice, keywordsText, style
);
String content = (String) contentResult.get("content");
// 3. 生成营销图片
String imageBase64 = null;
if (StrUtil.isNotBlank(productImageUrl)) {
try {
// 使用提取的关键词作为商品名称显示
String displayName = keywords != null && !keywords.isEmpty()
? keywords.get(0)
: productName;
imageBase64 = marketingImageService.generateMarketingImage(
productImageUrl, originalPrice, finalPrice, displayName
);
} catch (Exception e) {
log.warn("生成营销图片失败", e);
}
}
result.put("success", true);
result.put("keywords", keywords);
result.put("keywordsText", keywordsText);
result.put("content", content);
result.put("imageBase64", imageBase64);
} catch (Exception e) {
log.error("生成完整内容失败", e);
result.put("success", false);
result.put("error", "生成完整内容失败: " + e.getMessage());
}
return result;
}
/**
* 简单提取关键词(降级方案)
*/
private List<String> simpleExtractKeywords(String productName) {
List<String> keywords = new ArrayList<>();
// 移除常见规格信息
String cleaned = productName
.replaceAll("\\s*XL|L|M|S|XXL\\s*", "")
.replaceAll("\\s*\\d+/\\d+[A-Z]?\\s*", "")
.replaceAll("\\s*【.*?】\\s*", "")
.replaceAll("\\s*\\(.*?\\)\\s*", "");
// 提取前几个词
String[] words = cleaned.split("\\s+");
for (String word : words) {
if (word.length() >= 2 && word.length() <= 6 && keywords.size() < 5) {
keywords.add(word);
}
}
return keywords;
}
/**
* 从 Redis 获取提示词模板,如果没有则返回默认模板
*
* @param templateKey 模板键名keywords, content:xhs, content:douyin, content:both
* @param defaultTemplate 默认模板
* @return 提示词模板
*/
private String getPromptTemplate(String templateKey, String defaultTemplate) {
if (redisTemplate == null) {
log.debug("Redis未配置使用默认模板: {}", templateKey);
return defaultTemplate;
}
try {
String redisKey = REDIS_KEY_PREFIX + templateKey;
String template = redisTemplate.opsForValue().get(redisKey);
if (StrUtil.isNotBlank(template)) {
log.debug("从Redis读取模板: {}", templateKey);
return template;
} else {
log.debug("Redis中未找到模板使用默认模板: {}", templateKey);
return defaultTemplate;
}
} catch (Exception e) {
log.warn("读取Redis模板失败使用默认模板: {}", templateKey, e);
return defaultTemplate;
}
}
/**
* 生成简单文案(降级方案)
*/
private String generateSimpleContent(String productName, Double originalPrice, Double finalPrice) {
StringBuilder content = new StringBuilder();
content.append("🔥 ").append(productName).append("\n\n");
if (originalPrice != null && finalPrice != null && originalPrice > finalPrice) {
content.append("💰 原价:¥").append(String.format("%.0f", originalPrice)).append("\n");
content.append("💸 到手价:¥").append(String.format("%.0f", finalPrice)).append("\n");
double discount = ((originalPrice - finalPrice) / originalPrice) * 100;
content.append("✨ 立省:¥").append(String.format("%.0f", originalPrice - finalPrice))
.append("").append(String.format("%.0f", discount)).append("%\n\n");
}
content.append("💡 超值好物,不容错过!\n");
content.append("🎁 限时优惠,先到先得!");
return content.toString();
}
}