diff --git a/src/main/java/cn/van/business/controller/SocialMediaPromptController.java b/src/main/java/cn/van/business/controller/SocialMediaPromptController.java new file mode 100644 index 0000000..0efed7e --- /dev/null +++ b/src/main/java/cn/van/business/controller/SocialMediaPromptController.java @@ -0,0 +1,234 @@ +package cn.van.business.controller; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 小红书/抖音提示词模板配置Controller + * + * @author System + */ +@Slf4j +@RestController +@RequestMapping("/jarvis/social-media/prompt") +public class SocialMediaPromptController { + + @Autowired(required = false) + private StringRedisTemplate redisTemplate; + + // Redis Key 前缀 + private static final String REDIS_KEY_PREFIX = "social_media:prompt:"; + + // 模板键名列表 + private static final String[] TEMPLATE_KEYS = { + "keywords", + "content:xhs", + "content:douyin", + "content:both" + }; + + // 模板说明 + private static final Map TEMPLATE_DESCRIPTIONS = new HashMap() {{ + put("keywords", "关键词提取提示词模板\n占位符:%s - 商品名称"); + put("content:xhs", "小红书文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息"); + put("content:douyin", "抖音文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息"); + put("content:both", "通用文案生成提示词模板\n占位符:%s - 商品名称,%s - 价格信息,%s - 关键词信息"); + }}; + + /** + * 获取所有提示词模板 + * + * GET /jarvis/social-media/prompt/list + */ + @GetMapping("/list") + public JSONObject listTemplates() { + JSONObject response = new JSONObject(); + try { + Map templates = new HashMap<>(); + + for (String key : TEMPLATE_KEYS) { + Map templateInfo = new HashMap<>(); + templateInfo.put("key", key); + templateInfo.put("description", TEMPLATE_DESCRIPTIONS.get(key)); + + String template = getTemplate(key); + templateInfo.put("template", template); + templateInfo.put("isDefault", template == null); + + templates.put(key, templateInfo); + } + + response.put("code", 200); + response.put("msg", "操作成功"); + response.put("data", templates); + + } catch (Exception e) { + log.error("获取提示词模板列表失败", e); + response.put("code", 500); + response.put("msg", "获取失败: " + e.getMessage()); + } + return response; + } + + /** + * 获取单个提示词模板 + * + * GET /jarvis/social-media/prompt/{key} + */ + @GetMapping("/{key}") + public JSONObject getTemplate(@PathVariable String key) { + JSONObject response = new JSONObject(); + try { + if (!isValidKey(key)) { + response.put("code", 400); + response.put("msg", "无效的模板键名"); + return response; + } + + String template = getTemplate(key); + Map data = new HashMap<>(); + data.put("key", key); + data.put("description", TEMPLATE_DESCRIPTIONS.get(key)); + data.put("template", template); + data.put("isDefault", template == null); + + response.put("code", 200); + response.put("msg", "操作成功"); + response.put("data", data); + + } catch (Exception e) { + log.error("获取提示词模板失败", e); + response.put("code", 500); + response.put("msg", "获取失败: " + e.getMessage()); + } + return response; + } + + /** + * 保存提示词模板 + * + * POST /jarvis/social-media/prompt/save + * + * { + * "key": "keywords", + * "template": "提示词模板内容..." + * } + */ + @PostMapping("/save") + public JSONObject saveTemplate(@RequestBody Map request) { + JSONObject response = new JSONObject(); + try { + String key = (String) request.get("key"); + String template = (String) request.get("template"); + + if (!isValidKey(key)) { + response.put("code", 400); + response.put("msg", "无效的模板键名"); + return response; + } + + if (StrUtil.isBlank(template)) { + response.put("code", 400); + response.put("msg", "模板内容不能为空"); + return response; + } + + if (redisTemplate == null) { + response.put("code", 500); + response.put("msg", "Redis未配置,无法保存模板"); + return response; + } + + String redisKey = REDIS_KEY_PREFIX + key; + redisTemplate.opsForValue().set(redisKey, template); + + log.info("保存提示词模板成功: {}", key); + + response.put("code", 200); + response.put("msg", "保存成功"); + + } catch (Exception e) { + log.error("保存提示词模板失败", e); + response.put("code", 500); + response.put("msg", "保存失败: " + e.getMessage()); + } + return response; + } + + /** + * 删除提示词模板(恢复默认) + * + * DELETE /jarvis/social-media/prompt/{key} + */ + @DeleteMapping("/{key}") + public JSONObject deleteTemplate(@PathVariable String key) { + JSONObject response = new JSONObject(); + try { + if (!isValidKey(key)) { + response.put("code", 400); + response.put("msg", "无效的模板键名"); + return response; + } + + if (redisTemplate == null) { + response.put("code", 500); + response.put("msg", "Redis未配置,无法删除模板"); + return response; + } + + String redisKey = REDIS_KEY_PREFIX + key; + redisTemplate.delete(redisKey); + + log.info("删除提示词模板成功: {}", key); + + response.put("code", 200); + response.put("msg", "删除成功,已恢复默认模板"); + + } catch (Exception e) { + log.error("删除提示词模板失败", e); + response.put("code", 500); + response.put("msg", "删除失败: " + e.getMessage()); + } + return response; + } + + /** + * 从 Redis 获取模板 + */ + private String getTemplate(String key) { + if (redisTemplate == null) { + return null; + } + + try { + String redisKey = REDIS_KEY_PREFIX + key; + return redisTemplate.opsForValue().get(redisKey); + } catch (Exception e) { + log.warn("读取Redis模板失败: {}", key, e); + return null; + } + } + + /** + * 验证模板键名是否有效 + */ + private boolean isValidKey(String key) { + if (StrUtil.isBlank(key)) { + return false; + } + for (String validKey : TEMPLATE_KEYS) { + if (validKey.equals(key)) { + return true; + } + } + return false; + } +} + diff --git a/src/main/java/cn/van/business/service/SocialMediaService.java b/src/main/java/cn/van/business/service/SocialMediaService.java index fb526f7..6eafcb4 100644 --- a/src/main/java/cn/van/business/service/SocialMediaService.java +++ b/src/main/java/cn/van/business/service/SocialMediaService.java @@ -4,6 +4,7 @@ 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.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -27,6 +28,60 @@ public class SocialMediaService { @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请直接生成文案内容,不要添加其他说明:"; + /** * 提取商品标题关键词 * @@ -43,15 +98,9 @@ public class SocialMediaService { } try { - String prompt = String.format( - "请从以下商品标题中提取3-5个最核心的关键词,这些关键词要能突出商品的核心卖点和特色。\n" + - "要求:\n" + - "1. 每个关键词2-4个字\n" + - "2. 关键词要能吸引小红书/抖音用户\n" + - "3. 用逗号分隔,只返回关键词,不要其他内容\n" + - "商品标题:%s", - productName - ); + // 从 Redis 读取提示词模板,如果没有则使用默认模板 + String promptTemplate = getPromptTemplate("keywords", DEFAULT_KEYWORDS_PROMPT); + String prompt = String.format(promptTemplate, productName); String response = deepSeekClientUtil.getDeepSeekResponse(prompt); @@ -115,45 +164,32 @@ public class SocialMediaService { } 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"); + // 构建价格信息 + StringBuilder priceInfo = new StringBuilder(); if (originalPrice != null && originalPrice > 0) { - prompt.append("原价:¥").append(String.format("%.0f", originalPrice)).append("\n"); + priceInfo.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"); + priceInfo.append("到手价:¥").append(String.format("%.0f", finalPrice)).append("\n"); } - prompt.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 = deepSeekClientUtil.getDeepSeekResponse(prompt.toString()); @@ -257,6 +293,35 @@ public class SocialMediaService { 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; + } + } + /** * 生成简单文案(降级方案) */