From 7fda3da9ed7e54de07cdb77c507f9ef6b4a7f42c Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 29 Nov 2025 22:47:41 +0800 Subject: [PATCH] 1 --- .../controller/SocialMediaController.java | 176 +++++++++++ .../business/service/SocialMediaService.java | 282 ++++++++++++++++++ 2 files changed, 458 insertions(+) create mode 100644 src/main/java/cn/van/business/controller/SocialMediaController.java create mode 100644 src/main/java/cn/van/business/service/SocialMediaService.java diff --git a/src/main/java/cn/van/business/controller/SocialMediaController.java b/src/main/java/cn/van/business/controller/SocialMediaController.java new file mode 100644 index 0000000..7a5a869 --- /dev/null +++ b/src/main/java/cn/van/business/controller/SocialMediaController.java @@ -0,0 +1,176 @@ +package cn.van.business.controller; + +import cn.van.business.service.SocialMediaService; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 小红书/抖音内容生成控制器 + * + * @author System + */ +@Slf4j +@RestController +@RequestMapping("/jarvis/social-media") +public class SocialMediaController { + + @Autowired + private SocialMediaService socialMediaService; + + /** + * 提取关键词 + * + * POST /jarvis/social-media/extract-keywords + * + * { + * "productName": "商品名称" + * } + */ + @PostMapping("/extract-keywords") + public JSONObject extractKeywords(@RequestBody Map request) { + JSONObject response = new JSONObject(); + try { + String productName = (String) request.get("productName"); + + if (productName == null || productName.trim().isEmpty()) { + response.put("code", 400); + response.put("msg", "商品名称不能为空"); + return response; + } + + Map result = socialMediaService.extractKeywords(productName); + + response.put("code", 200); + response.put("msg", "操作成功"); + response.put("data", result); + + } catch (Exception e) { + log.error("提取关键词失败", e); + response.put("code", 500); + response.put("msg", "提取关键词失败: " + e.getMessage()); + } + return response; + } + + /** + * 生成文案 + * + * POST /jarvis/social-media/generate-content + * + * { + * "productName": "商品名称", + * "originalPrice": 499.0, + * "finalPrice": 199.0, + * "keywords": "关键词1、关键词2", + * "style": "xhs" // xhs/douyin/both + * } + */ + @PostMapping("/generate-content") + public JSONObject generateContent(@RequestBody Map request) { + JSONObject response = new JSONObject(); + try { + String productName = (String) request.get("productName"); + Object originalPriceObj = request.get("originalPrice"); + Object finalPriceObj = request.get("finalPrice"); + String keywords = (String) request.get("keywords"); + String style = (String) request.getOrDefault("style", "both"); + + if (productName == null || productName.trim().isEmpty()) { + response.put("code", 400); + response.put("msg", "商品名称不能为空"); + return response; + } + + Double originalPrice = parseDouble(originalPriceObj); + Double finalPrice = parseDouble(finalPriceObj); + + Map result = socialMediaService.generateContent( + productName, originalPrice, finalPrice, keywords, style + ); + + response.put("code", 200); + response.put("msg", "操作成功"); + response.put("data", result); + + } catch (Exception e) { + log.error("生成文案失败", e); + response.put("code", 500); + response.put("msg", "生成文案失败: " + e.getMessage()); + } + return response; + } + + /** + * 一键生成完整内容(关键词 + 文案 + 图片) + * + * POST /jarvis/social-media/generate-complete + * + * { + * "productImageUrl": "商品主图URL", + * "productName": "商品名称", + * "originalPrice": 499.0, + * "finalPrice": 199.0, + * "style": "xhs" + * } + */ + @PostMapping("/generate-complete") + public JSONObject generateComplete(@RequestBody Map request) { + JSONObject response = new JSONObject(); + try { + String productImageUrl = (String) request.get("productImageUrl"); + String productName = (String) request.get("productName"); + Object originalPriceObj = request.get("originalPrice"); + Object finalPriceObj = request.get("finalPrice"); + String style = (String) request.getOrDefault("style", "both"); + + if (productName == null || productName.trim().isEmpty()) { + response.put("code", 400); + response.put("msg", "商品名称不能为空"); + return response; + } + + Double originalPrice = parseDouble(originalPriceObj); + Double finalPrice = parseDouble(finalPriceObj); + + Map result = socialMediaService.generateCompleteContent( + productImageUrl, productName, originalPrice, finalPrice, style + ); + + response.put("code", 200); + response.put("msg", "操作成功"); + response.put("data", result); + + } catch (Exception e) { + log.error("生成完整内容失败", e); + response.put("code", 500); + response.put("msg", "生成完整内容失败: " + e.getMessage()); + } + return response; + } + + /** + * 解析Double值 + */ + private Double parseDouble(Object value) { + if (value == null) { + return null; + } + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + try { + return Double.parseDouble(value.toString()); + } catch (Exception e) { + return null; + } + } +} + diff --git a/src/main/java/cn/van/business/service/SocialMediaService.java b/src/main/java/cn/van/business/service/SocialMediaService.java new file mode 100644 index 0000000..fb526f7 --- /dev/null +++ b/src/main/java/cn/van/business/service/SocialMediaService.java @@ -0,0 +1,282 @@ +package cn.van.business.service; + +import cn.hutool.core.util.StrUtil; +import cn.van.business.util.ds.DeepSeekClientUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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 DeepSeekClientUtil deepSeekClientUtil; + + @Autowired + private MarketingImageService marketingImageService; + + /** + * 提取商品标题关键词 + * + * @param productName 商品名称 + * @return 关键词列表 + */ + public Map extractKeywords(String productName) { + Map result = new HashMap<>(); + + if (StrUtil.isBlank(productName)) { + result.put("success", false); + result.put("error", "商品名称不能为空"); + return result; + } + + try { + String prompt = String.format( + "请从以下商品标题中提取3-5个最核心的关键词,这些关键词要能突出商品的核心卖点和特色。\n" + + "要求:\n" + + "1. 每个关键词2-4个字\n" + + "2. 关键词要能吸引小红书/抖音用户\n" + + "3. 用逗号分隔,只返回关键词,不要其他内容\n" + + "商品标题:%s", + productName + ); + + String response = deepSeekClientUtil.getDeepSeekResponse(prompt); + + if (StrUtil.isNotBlank(response)) { + // 解析关键词 + String[] keywords = response.trim() + .replaceAll("[,,]", ",") + .split(","); + + List 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 generateContent(String productName, Double originalPrice, + Double finalPrice, String keywords, String style) { + Map result = new HashMap<>(); + + if (StrUtil.isBlank(productName)) { + result.put("success", false); + result.put("error", "商品名称不能为空"); + return result; + } + + try { + // 构建提示词 + StringBuilder prompt = new StringBuilder(); + + if ("xhs".equals(style)) { + prompt.append("请为小红书平台生成一篇商品推广文案,要求:\n"); + prompt.append("1. 风格:真实、种草、有温度\n"); + prompt.append("2. 开头:用emoji或感叹句吸引注意\n"); + prompt.append("3. 内容:突出商品亮点、使用场景、性价比\n"); + prompt.append("4. 结尾:引导行动(如:快冲、闭眼入等)\n"); + prompt.append("5. 长度:150-300字\n"); + prompt.append("6. 适当使用emoji和换行\n"); + } else if ("douyin".equals(style)) { + prompt.append("请为抖音平台生成一篇商品推广文案,要求:\n"); + prompt.append("1. 风格:直接、有冲击力、吸引眼球\n"); + prompt.append("2. 开头:用疑问句或对比句抓住注意力\n"); + prompt.append("3. 内容:强调价格优势、限时优惠、稀缺性\n"); + prompt.append("4. 结尾:制造紧迫感,引导立即行动\n"); + prompt.append("5. 长度:100-200字\n"); + prompt.append("6. 使用短句,节奏感强\n"); + } else { + prompt.append("请生成一篇适合小红书和抖音平台的商品推广文案,要求:\n"); + prompt.append("1. 风格:真实、有吸引力\n"); + prompt.append("2. 突出商品亮点和价格优势\n"); + prompt.append("3. 长度:150-250字\n"); + } + + prompt.append("\n商品信息:\n"); + prompt.append("商品名称:").append(productName).append("\n"); + if (originalPrice != null && originalPrice > 0) { + prompt.append("原价:¥").append(String.format("%.0f", originalPrice)).append("\n"); + } + if (finalPrice != null && finalPrice > 0) { + prompt.append("到手价:¥").append(String.format("%.0f", finalPrice)).append("\n"); + } + if (StrUtil.isNotBlank(keywords)) { + prompt.append("关键词:").append(keywords).append("\n"); + } + + prompt.append("\n请直接生成文案内容,不要添加其他说明:"); + + String content = deepSeekClientUtil.getDeepSeekResponse(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 generateCompleteContent(String productImageUrl, String productName, + Double originalPrice, Double finalPrice, String style) { + Map result = new HashMap<>(); + + try { + // 1. 提取关键词 + Map keywordResult = extractKeywords(productName); + List keywords = (List) keywordResult.get("keywords"); + String keywordsText = (String) keywordResult.get("keywordsText"); + + // 2. 生成文案 + Map 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 simpleExtractKeywords(String productName) { + List 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; + } + + /** + * 生成简单文案(降级方案) + */ + 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(); + } +} +