1
This commit is contained in:
@@ -297,9 +297,7 @@ public class SocialMediaController extends BaseController
|
|||||||
public AjaxResult generateXianyuWenan(@RequestBody Map<String, Object> request)
|
public AjaxResult generateXianyuWenan(@RequestBody Map<String, Object> request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
String title = (String) request.get("title");
|
Map<String, Object> result = socialMediaService.generateXianyuWenan(request);
|
||||||
String remark = (String) request.get("remark");
|
|
||||||
Map<String, Object> result = socialMediaService.generateXianyuWenan(title, remark);
|
|
||||||
if (Boolean.TRUE.equals(result.get("success"))) {
|
if (Boolean.TRUE.equals(result.get("success"))) {
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,13 +65,17 @@ public interface ISocialMediaService
|
|||||||
com.ruoyi.common.core.domain.AjaxResult deletePromptTemplate(String key);
|
com.ruoyi.common.core.domain.AjaxResult deletePromptTemplate(String key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据标题(+可选型号备注)生成闲鱼文案(代下单、教你下单),不依赖JD接口
|
* 生成闲鱼文案(代下单、教你下单),并可选生成种草文案。
|
||||||
*
|
*
|
||||||
* @param title 商品标题(必填)
|
* request 常用字段:
|
||||||
* @param remark 型号/备注(可选)
|
* - title/remark(兼容旧版手动入口)
|
||||||
* @return 包含代下单、教你下单两种文案的 Map
|
* - generateSeedNote(boolean) 是否额外生成种草文案
|
||||||
|
* - goods_title/goods_model/goods_type(种草文案必传)
|
||||||
|
* - goods_brand/channel_source/official_price/sell_price/warranty/delivery_install(可选)
|
||||||
|
*
|
||||||
|
* @return 包含代下单、教你下单,以及可选 seedNote 的 Map
|
||||||
*/
|
*/
|
||||||
Map<String, Object> generateXianyuWenan(String title, String remark);
|
Map<String, Object> generateXianyuWenan(Map<String, Object> request);
|
||||||
|
|
||||||
/** 列出多套大模型接入配置及当前激活的 id(与 Jarvis 共用 Redis) */
|
/** 列出多套大模型接入配置及当前激活的 id(与 Jarvis 共用 Redis) */
|
||||||
com.ruoyi.common.core.domain.AjaxResult listLlmProfiles();
|
com.ruoyi.common.core.domain.AjaxResult listLlmProfiles();
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
"content:both",
|
"content:both",
|
||||||
"xianyu:wenan_base",
|
"xianyu:wenan_base",
|
||||||
"xianyu:jiaonixiadan_extra",
|
"xianyu:jiaonixiadan_extra",
|
||||||
"xianyu:title_clean_regex"
|
"xianyu:title_clean_regex",
|
||||||
|
"xianyu:seed_note_prompt"
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模板说明
|
// 模板说明
|
||||||
@@ -62,6 +63,7 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
put("xianyu:wenan_base", "闲鱼文案·正文基础说明\n用于「一键代下」与「教你下单」两版文案中,紧接在标题/型号行之后(纯文本,无占位符)");
|
put("xianyu:wenan_base", "闲鱼文案·正文基础说明\n用于「一键代下」与「教你下单」两版文案中,紧接在标题/型号行之后(纯文本,无占位符)");
|
||||||
put("xianyu:jiaonixiadan_extra", "闲鱼文案·教你下单版尾部附加说明\n接在「更新日期:yyyy-MM-dd」之后(纯文本)");
|
put("xianyu:jiaonixiadan_extra", "闲鱼文案·教你下单版尾部附加说明\n接在「更新日期:yyyy-MM-dd」之后(纯文本)");
|
||||||
put("xianyu:title_clean_regex", "闲鱼文案·标题/型号清洗正则\n从标题与型号备注中移除营销敏感片段;须为 Java 正则,匹配到的内容会被删除");
|
put("xianyu:title_clean_regex", "闲鱼文案·标题/型号清洗正则\n从标题与型号备注中移除营销敏感片段;须为 Java 正则,匹配到的内容会被删除");
|
||||||
|
put("xianyu:seed_note_prompt", "闲鱼文案·种草文案提示词模板\n占位符:{{goods_title}} {{goods_model}} {{goods_type}} {{goods_brand}} {{channel_source}} {{official_price}} {{sell_price}} {{warranty}} {{delivery_install}}");
|
||||||
}};
|
}};
|
||||||
|
|
||||||
/** 多套大模型 JSON 存储,与 Jarvis_java SocialMediaLlmClient 一致 */
|
/** 多套大模型 JSON 存储,与 Jarvis_java SocialMediaLlmClient 一致 */
|
||||||
@@ -88,6 +90,50 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
+ "优先回复你合适的下单渠道和详细步骤,让你安全省钱地完成下单。";
|
+ "优先回复你合适的下单渠道和详细步骤,让你安全省钱地完成下单。";
|
||||||
/** 标题/型号清洗:去掉营销敏感词 */
|
/** 标题/型号清洗:去掉营销敏感词 */
|
||||||
private static final String DEFAULT_XIANYU_TITLE_CLEAN_REGEX = "以旧|政府|换新|领取|国家|补贴|15%|20%|国补|立减|【|】";
|
private static final String DEFAULT_XIANYU_TITLE_CLEAN_REGEX = "以旧|政府|换新|领取|国家|补贴|15%|20%|国补|立减|【|】";
|
||||||
|
/** 闲鱼种草文案提示词模板(Redis 未配置时使用) */
|
||||||
|
private static final String DEFAULT_XIANYU_SEED_NOTE_PROMPT =
|
||||||
|
"# 角色设定\n"
|
||||||
|
+ "你是拥有10年家电行业经验的资深渠道商,同时是闲鱼平台家电类目TOP级种草文案达人,精通闲鱼家电用户的消费心理、搜索流量规则、高转化文案写作逻辑,擅长用专业且亲切的口吻,为全新未拆封的正品家电,写出强信任、高种草、高成交的闲鱼转卖文案,拒绝冰冷的官方参数堆砌、浮夸的营销硬广、二手闲置话术。\n\n"
|
||||||
|
+ "# 核心任务\n"
|
||||||
|
+ "严格基于我提供的家电商品信息,生成一段完全适配闲鱼平台、符合全新正品家电属性、有强吸引力、高转化的种草文案,全程遵循以下所有规则。\n\n"
|
||||||
|
+ "# 家电商品输入信息(接口传入)\n"
|
||||||
|
+ "【必传核心信息】\n"
|
||||||
|
+ "1. 商品完整标题:{{goods_title}}\n"
|
||||||
|
+ "2. 商品精准完整型号:{{goods_model}}\n"
|
||||||
|
+ "3. 家电品类类型:{{goods_type}}\n\n"
|
||||||
|
+ "【可选补充信息(无则按默认兜底规则生成)】\n"
|
||||||
|
+ "4. 商品品牌:{{goods_brand}}\n"
|
||||||
|
+ "5. 渠道来源:{{channel_source}}\n"
|
||||||
|
+ "6. 官方指导价:{{official_price}}\n"
|
||||||
|
+ "7. 闲鱼售卖价格:{{sell_price}}\n"
|
||||||
|
+ "8. 官方质保政策:{{warranty}}\n"
|
||||||
|
+ "9. 配送安装政策:{{delivery_install}}\n\n"
|
||||||
|
+ "# 字段优先级&兜底规则(必须严格遵守)\n"
|
||||||
|
+ "1. 型号优先级:必须以「{{goods_model}}」为唯一精准型号,商品标题里的型号简写仅做补充,禁止写错、简写型号\n"
|
||||||
|
+ "2. 品类优先级:必须以「{{goods_type}}」为唯一品类标准,所有场景、卖点、话术必须围绕该家电品类展开\n"
|
||||||
|
+ "3. 品牌兜底:若{{goods_brand}}为空,自动从{{goods_title}}中提取品牌名,提取不到则不强制体现\n"
|
||||||
|
+ "4. 渠道兜底:若{{channel_source}}为空,默认使用「品牌授权经销商直供」,禁止使用二手、个人闲置相关渠道描述\n"
|
||||||
|
+ "5. 价格兜底:若{{official_price}}为空,默认使用「比官方旗舰店优惠力度大」相关话术;若{{sell_price}}为空,不写固定售价,引导用户私信询价\n"
|
||||||
|
+ "6. 售后兜底:若{{warranty}}为空,默认使用「品牌官方全国联保,和官方旗舰店享受同等售后权益」\n"
|
||||||
|
+ "7. 配送兜底:若{{delivery_install}}为空,默认使用「全国包邮送货入户,乡镇可达」\n\n"
|
||||||
|
+ "# 核心字段强制融合规则(必须100%严格执行,解决生硬堆砌问题)\n"
|
||||||
|
+ "1. 标题植入规则:生成的闲鱼标题,必须同时包含「{{goods_type}}+{{goods_model}}+品牌+全新正品」4个核心元素,同时适配闲鱼家电用户搜索习惯,字数控制在25-35字\n"
|
||||||
|
+ "2. 正文植入规则:\n"
|
||||||
|
+ " - 开篇前30字,必须同时出现「{{goods_type}}+{{goods_model}}」,强化型号和品类,同时给出正品信任背书\n"
|
||||||
|
+ " - 正文卖点模块,必须围绕「{{goods_type}}」的品类核心需求展开,同时绑定{{goods_model}}型号,禁止脱离品类写通用卖点\n"
|
||||||
|
+ " - 正文至少3次自然提及{{goods_model}}完整型号,适配闲鱼搜索权重规则,禁止只出现1次\n"
|
||||||
|
+ " - 正文所有场景化描述,必须贴合{{goods_type}}的使用场景,比如空调对应卧室/客厅制冷、冰箱对应食材囤货/嵌入装修、洗衣机对应家庭大容量/除菌等\n"
|
||||||
|
+ "3. 结尾植入规则:结尾行动引导和话题标签,必须包含「{{goods_type}}」相关流量词,标签数量4-6个,必须有1个标签带{{goods_model}}型号\n"
|
||||||
|
+ "4. 卖点提取规则:核心卖点自动从{{goods_title}}中提取,优先提取能效、容量、变频、智能、超薄、除菌等家电核心决策卖点,转化为场景化话术,禁止参数堆砌\n\n"
|
||||||
|
+ "# 创作必须严格遵守的通用规则\n"
|
||||||
|
+ "1. 全程使用第一人称「我/我们」写作,贴合家电渠道商的专业且亲切的身份,像给朋友推荐靠谱好货,绝对不能出现官方旗舰店硬广话术、二手个人闲置转手话术,全程围绕「全新未拆封正品家电」核心属性创作\n"
|
||||||
|
+ "2. 文案100%基于接口传入的信息创作,不得凭空捏造与输入信息不符的功能、参数、质保、服务政策,核心卖点必须转化为用户可感知的场景化好处\n"
|
||||||
|
+ "3. 必须完整包含以下核心模块,顺序不可调换,缺一不可:吸睛搜索标题、开篇信任背书、正品保障、卖点拆解、价格优势、配送安装、售后承诺、结尾行动和标签\n"
|
||||||
|
+ "4. 文案排版:短句为主,段落清晰,每段不超过3行,手机阅读友好\n"
|
||||||
|
+ "5. 字数要求:正文控制在400-600字\n"
|
||||||
|
+ "6. 合规要求:严格符合闲鱼平台规则和3C家电宣传规范,不使用极限词,不夸大宣传\n\n"
|
||||||
|
+ "# 输出格式\n"
|
||||||
|
+ "直接输出最终完整文案,不要额外解释。";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis 无记录时与 Jarvis_java SocialMediaService 使用的默认模板一致,供接口回显到前端参考。
|
* Redis 无记录时与 Jarvis_java SocialMediaService 使用的默认模板一致,供接口回显到前端参考。
|
||||||
@@ -139,6 +185,7 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
put("xianyu:wenan_base", DEFAULT_XIANYU_WENAN_BASE);
|
put("xianyu:wenan_base", DEFAULT_XIANYU_WENAN_BASE);
|
||||||
put("xianyu:jiaonixiadan_extra", DEFAULT_XIANYU_JIAONIXIADAN_EXTRA);
|
put("xianyu:jiaonixiadan_extra", DEFAULT_XIANYU_JIAONIXIADAN_EXTRA);
|
||||||
put("xianyu:title_clean_regex", DEFAULT_XIANYU_TITLE_CLEAN_REGEX);
|
put("xianyu:title_clean_regex", DEFAULT_XIANYU_TITLE_CLEAN_REGEX);
|
||||||
|
put("xianyu:seed_note_prompt", DEFAULT_XIANYU_SEED_NOTE_PROMPT);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -933,11 +980,18 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据标题(+可选型号备注)生成闲鱼文案(代下单、教你下单),不依赖JD接口
|
* 生成闲鱼文案:默认输出「代下单 + 教你下单」,可选额外生成「种草文案」。
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> generateXianyuWenan(String title, String remark) {
|
public Map<String, Object> generateXianyuWenan(Map<String, Object> request) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
Map<String, Object> req = request == null ? new HashMap<String, Object>() : request;
|
||||||
|
String title = asString(req.get("title"));
|
||||||
|
String remark = asString(req.get("remark"));
|
||||||
|
if (StringUtils.isEmpty(title)) {
|
||||||
|
// 兼容只传新字段的调用方式
|
||||||
|
title = asString(req.get("goods_title"));
|
||||||
|
}
|
||||||
if (StringUtils.isEmpty(title) || StringUtils.isEmpty(title.trim())) {
|
if (StringUtils.isEmpty(title) || StringUtils.isEmpty(title.trim())) {
|
||||||
result.put("success", false);
|
result.put("success", false);
|
||||||
result.put("error", "商品标题不能为空");
|
result.put("error", "商品标题不能为空");
|
||||||
@@ -973,9 +1027,121 @@ public class SocialMediaServiceImpl implements ISocialMediaService
|
|||||||
result.put("success", true);
|
result.put("success", true);
|
||||||
result.put("daixiadan", daixiadanBuilder.toString());
|
result.put("daixiadan", daixiadanBuilder.toString());
|
||||||
result.put("jiaonixiadan", jiaonixiadanBuilder.toString());
|
result.put("jiaonixiadan", jiaonixiadanBuilder.toString());
|
||||||
|
|
||||||
|
boolean generateSeed = isTrue(req.get("generateSeedNote"));
|
||||||
|
if (generateSeed) {
|
||||||
|
String seedError = validateSeedRequiredFields(req);
|
||||||
|
if (seedError != null) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", seedError);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String seedPrompt = buildSeedPrompt(req);
|
||||||
|
String seedNote = callJarvisLlm(seedPrompt, asString(req.get("profileId")));
|
||||||
|
if (StringUtils.isEmpty(seedNote)) {
|
||||||
|
throw new RuntimeException("种草文案生成返回为空");
|
||||||
|
}
|
||||||
|
result.put("seedNote", seedNote.trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成闲鱼种草文案失败", e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("error", "种草文案生成失败: " + e.getMessage());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildSeedPrompt(Map<String, Object> request) {
|
||||||
|
String template = getPromptTemplateWithDefault("xianyu:seed_note_prompt", DEFAULT_XIANYU_SEED_NOTE_PROMPT);
|
||||||
|
|
||||||
|
String goodsTitle = asString(request.get("goods_title"));
|
||||||
|
String goodsModel = asString(request.get("goods_model"));
|
||||||
|
String goodsType = asString(request.get("goods_type"));
|
||||||
|
|
||||||
|
String goodsBrand = asString(request.get("goods_brand"));
|
||||||
|
String channelSource = asString(request.get("channel_source"));
|
||||||
|
String officialPrice = asString(request.get("official_price"));
|
||||||
|
String sellPrice = asString(request.get("sell_price"));
|
||||||
|
String warranty = asString(request.get("warranty"));
|
||||||
|
String deliveryInstall = asString(request.get("delivery_install"));
|
||||||
|
|
||||||
|
String channelFinal = StringUtils.isNotEmpty(channelSource) ? channelSource : "品牌授权经销商直供";
|
||||||
|
String warrantyFinal = StringUtils.isNotEmpty(warranty) ? warranty : "品牌官方全国联保,和官方旗舰店享受同等售后权益";
|
||||||
|
String deliveryFinal = StringUtils.isNotEmpty(deliveryInstall) ? deliveryInstall : "全国包邮送货入户,乡镇可达";
|
||||||
|
String officialFinal = StringUtils.isNotEmpty(officialPrice) ? officialPrice : "比官方旗舰店优惠力度大";
|
||||||
|
String sellFinal = StringUtils.isNotEmpty(sellPrice) ? sellPrice : "私信询价(到手更划算)";
|
||||||
|
|
||||||
|
return template
|
||||||
|
.replace("{{goods_title}}", goodsTitle)
|
||||||
|
.replace("{{goods_model}}", goodsModel)
|
||||||
|
.replace("{{goods_type}}", goodsType)
|
||||||
|
.replace("{{goods_brand}}", StringUtils.isNotEmpty(goodsBrand) ? goodsBrand : "")
|
||||||
|
.replace("{{channel_source}}", channelFinal)
|
||||||
|
.replace("{{official_price}}", officialFinal)
|
||||||
|
.replace("{{sell_price}}", sellFinal)
|
||||||
|
.replace("{{warranty}}", warrantyFinal)
|
||||||
|
.replace("{{delivery_install}}", deliveryFinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String validateSeedRequiredFields(Map<String, Object> request) {
|
||||||
|
if (request == null) {
|
||||||
|
return "请求体不能为空";
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(asString(request.get("goods_title")))) {
|
||||||
|
return "生成种草文案时,goods_title 不能为空";
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(asString(request.get("goods_model")))) {
|
||||||
|
return "生成种草文案时,goods_model 不能为空";
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(asString(request.get("goods_type")))) {
|
||||||
|
return "生成种草文案时,goods_type 不能为空";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String callJarvisLlm(String message, String profileId) {
|
||||||
|
String url = jarvisBaseUrl + "/jarvis/social-media/llm/test";
|
||||||
|
JSONObject body = new JSONObject();
|
||||||
|
body.put("message", message);
|
||||||
|
if (StringUtils.isNotEmpty(profileId)) {
|
||||||
|
body.put("profileId", profileId);
|
||||||
|
}
|
||||||
|
String resp = HttpUtils.sendJsonPost(url, body.toJSONString());
|
||||||
|
if (StringUtils.isEmpty(resp)) {
|
||||||
|
throw new RuntimeException("Jarvis 返回为空,请检查服务可达性");
|
||||||
|
}
|
||||||
|
Object parsed = JSON.parse(resp);
|
||||||
|
if (!(parsed instanceof JSONObject)) {
|
||||||
|
throw new RuntimeException("Jarvis 响应格式错误");
|
||||||
|
}
|
||||||
|
JSONObject jo = (JSONObject) parsed;
|
||||||
|
if (!Integer.valueOf(200).equals(jo.getInteger("code"))) {
|
||||||
|
throw new RuntimeException(jo.getString("msg"));
|
||||||
|
}
|
||||||
|
Object dataObj = jo.get("data");
|
||||||
|
if (!(dataObj instanceof JSONObject)) {
|
||||||
|
throw new RuntimeException("Jarvis data 结构异常");
|
||||||
|
}
|
||||||
|
JSONObject data = (JSONObject) dataObj;
|
||||||
|
if (!Boolean.TRUE.equals(data.getBoolean("success"))) {
|
||||||
|
throw new RuntimeException(data.getString("error"));
|
||||||
|
}
|
||||||
|
return data.getString("reply");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTrue(Object value) {
|
||||||
|
return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String asString(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return StringUtils.trim(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清洗标题/型号中的敏感词(正则来自可配置模板 xianyu:title_clean_regex)
|
* 清洗标题/型号中的敏感词(正则来自可配置模板 xianyu:title_clean_regex)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user