1
This commit is contained in:
@@ -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<String, String> TEMPLATE_DESCRIPTIONS = new HashMap<String, String>() {{
|
||||||
|
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<String, Object> templates = new HashMap<>();
|
||||||
|
|
||||||
|
for (String key : TEMPLATE_KEYS) {
|
||||||
|
Map<String, Object> 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<String, Object> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import cn.van.business.util.ds.DeepSeekClientUtil;
|
import cn.van.business.util.ds.DeepSeekClientUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -27,6 +28,60 @@ public class SocialMediaService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MarketingImageService marketingImageService;
|
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 {
|
try {
|
||||||
String prompt = String.format(
|
// 从 Redis 读取提示词模板,如果没有则使用默认模板
|
||||||
"请从以下商品标题中提取3-5个最核心的关键词,这些关键词要能突出商品的核心卖点和特色。\n" +
|
String promptTemplate = getPromptTemplate("keywords", DEFAULT_KEYWORDS_PROMPT);
|
||||||
"要求:\n" +
|
String prompt = String.format(promptTemplate, productName);
|
||||||
"1. 每个关键词2-4个字\n" +
|
|
||||||
"2. 关键词要能吸引小红书/抖音用户\n" +
|
|
||||||
"3. 用逗号分隔,只返回关键词,不要其他内容\n" +
|
|
||||||
"商品标题:%s",
|
|
||||||
productName
|
|
||||||
);
|
|
||||||
|
|
||||||
String response = deepSeekClientUtil.getDeepSeekResponse(prompt);
|
String response = deepSeekClientUtil.getDeepSeekResponse(prompt);
|
||||||
|
|
||||||
@@ -115,45 +164,32 @@ public class SocialMediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建提示词
|
// 构建价格信息
|
||||||
StringBuilder prompt = new StringBuilder();
|
StringBuilder priceInfo = 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) {
|
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) {
|
if (finalPrice != null && finalPrice > 0) {
|
||||||
prompt.append("到手价:¥").append(String.format("%.0f", finalPrice)).append("\n");
|
priceInfo.append("到手价:¥").append(String.format("%.0f", finalPrice)).append("\n");
|
||||||
}
|
|
||||||
if (StrUtil.isNotBlank(keywords)) {
|
|
||||||
prompt.append("关键词:").append(keywords).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());
|
String content = deepSeekClientUtil.getDeepSeekResponse(prompt.toString());
|
||||||
|
|
||||||
@@ -257,6 +293,35 @@ public class SocialMediaService {
|
|||||||
return keywords;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成简单文案(降级方案)
|
* 生成简单文案(降级方案)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user