This commit is contained in:
van
2026-04-04 16:39:46 +08:00
parent bce83f680c
commit 6fbfecf690
4 changed files with 184 additions and 118 deletions

View File

@@ -15,6 +15,8 @@ import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* 小红书/抖音内容生成Service业务层处理
@@ -41,7 +43,10 @@ public class SocialMediaServiceImpl implements ISocialMediaService
"keywords",
"content:xhs",
"content:douyin",
"content:both"
"content:both",
"xianyu:wenan_base",
"xianyu:jiaonixiadan_extra",
"xianyu:title_clean_regex"
};
// 模板说明
@@ -50,6 +55,9 @@ public class SocialMediaServiceImpl implements ISocialMediaService
put("content:xhs", "小红书文案生成提示词模板\n占位符%s - 商品名称,%s - 价格信息,%s - 关键词信息");
put("content:douyin", "抖音文案生成提示词模板\n占位符%s - 商品名称,%s - 价格信息,%s - 关键词信息");
put("content:both", "通用文案生成提示词模板\n占位符%s - 商品名称,%s - 价格信息,%s - 关键词信息");
put("xianyu:wenan_base", "闲鱼文案·正文基础说明\n用于「一键代下」与「教你下单」两版文案中紧接在标题/型号行之后(纯文本,无占位符)");
put("xianyu:jiaonixiadan_extra", "闲鱼文案·教你下单版尾部附加说明\n接在「更新日期yyyy-MM-dd」之后纯文本");
put("xianyu:title_clean_regex", "闲鱼文案·标题/型号清洗正则\n从标题与型号备注中移除营销敏感片段须为 Java 正则,匹配到的内容会被删除");
}};
/** 与 Jarvis_java SocialMediaLlmClient 使用相同 Redis 键 */
@@ -61,19 +69,19 @@ public class SocialMediaServiceImpl implements ISocialMediaService
private static final String LLM_MODE_OLLAMA = "ollama";
private static final String LLM_MODE_OPENAI = "openai";
/** 闲鱼文案-通用基础说明 */
private static final String WENAN_BASE =
/** 闲鱼文案-通用基础说明Redis 未配置时使用) */
private static final String DEFAULT_XIANYU_WENAN_BASE =
"全新未拆封正品,包邮包安装,支持查验后再签收。\n"
+ "运损可免费换新。\n"
+ "售后全部支持全国联保。";
/** 闲鱼文案-教你下单附加说明(不含更新日期) */
private static final String WENAN_JIAONIXIADAN_EXTRA =
private static final String DEFAULT_XIANYU_JIAONIXIADAN_EXTRA =
"\n无偿提供下单方案包价格成立\n"
+ "只要告诉我【需要下单的型号 + 收货地址】,\n"
+ "我会根据你所在地区和需求,\n"
+ "优先回复你合适的下单渠道和详细步骤,让你安全省钱地完成下单。";
/** 标题/型号清洗:去掉营销敏感词 */
private static final String TITLE_CLEAN_REGEX = "以旧|政府|换新|领取|国家|补贴|15%|20%|国补|立减|【|】";
private static final String DEFAULT_XIANYU_TITLE_CLEAN_REGEX = "以旧|政府|换新|领取|国家|补贴|15%|20%|国补|立减|【|】";
/**
* 提取商品标题关键词
@@ -352,6 +360,13 @@ public class SocialMediaServiceImpl implements ISocialMediaService
if (StringUtils.isEmpty(templateValue)) {
return AjaxResult.error("模板内容不能为空");
}
if ("xianyu:title_clean_regex".equals(key)) {
try {
Pattern.compile(templateValue);
} catch (PatternSyntaxException e) {
return AjaxResult.error("正则表达式无效: " + e.getMessage());
}
}
redisTemplate.opsForValue().set(redisKey, templateValue);
log.info("保存提示词模板成功: {}", key);
@@ -407,6 +422,12 @@ public class SocialMediaServiceImpl implements ISocialMediaService
}
}
/** Redis 无配置时返回 defaultTemplate */
private String getPromptTemplateWithDefault(String templateKey, String defaultTemplate) {
String fromRedis = getTemplateFromRedis(templateKey);
return StringUtils.isNotEmpty(fromRedis) ? fromRedis : defaultTemplate;
}
/**
* 验证模板键名是否有效
*/
@@ -571,6 +592,9 @@ public class SocialMediaServiceImpl implements ISocialMediaService
String cleanTitle = cleanTitleOrRemark(title.trim());
String cleanRemark = StringUtils.isNotEmpty(remark) ? cleanTitleOrRemark(remark.trim()) : "";
String wenanBase = getPromptTemplateWithDefault("xianyu:wenan_base", DEFAULT_XIANYU_WENAN_BASE);
String jiaonixiadanExtra = getPromptTemplateWithDefault("xianyu:jiaonixiadan_extra", DEFAULT_XIANYU_JIAONIXIADAN_EXTRA);
// 标题行
StringBuilder daixiadanBuilder = new StringBuilder();
daixiadanBuilder.append("(一键代下) ").append(cleanTitle).append("\n");
@@ -578,7 +602,7 @@ public class SocialMediaServiceImpl implements ISocialMediaService
if (StringUtils.isNotEmpty(cleanRemark)) {
daixiadanBuilder.append("型号:").append(cleanRemark).append("\n");
}
daixiadanBuilder.append(WENAN_BASE);
daixiadanBuilder.append(wenanBase);
// 教你下单版
StringBuilder jiaonixiadanBuilder = new StringBuilder();
@@ -586,11 +610,11 @@ public class SocialMediaServiceImpl implements ISocialMediaService
if (StringUtils.isNotEmpty(cleanRemark)) {
jiaonixiadanBuilder.append("型号:").append(cleanRemark).append("\n");
}
jiaonixiadanBuilder.append(WENAN_BASE).append("\n\n");
jiaonixiadanBuilder.append(wenanBase).append("\n\n");
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(new java.util.Date());
jiaonixiadanBuilder.append("更新日期:").append(dateStr).append(WENAN_JIAONIXIADAN_EXTRA);
jiaonixiadanBuilder.append("更新日期:").append(dateStr).append(jiaonixiadanExtra);
result.put("success", true);
result.put("daixiadan", daixiadanBuilder.toString());
@@ -599,13 +623,19 @@ public class SocialMediaServiceImpl implements ISocialMediaService
}
/**
* 清洗标题/型号中的敏感词
* 清洗标题/型号中的敏感词(正则来自可配置模板 xianyu:title_clean_regex
*/
private static String cleanTitleOrRemark(String text) {
private String cleanTitleOrRemark(String text) {
if (text == null) {
return "";
}
return text.replaceAll(TITLE_CLEAN_REGEX, "");
String regex = getPromptTemplateWithDefault("xianyu:title_clean_regex", DEFAULT_XIANYU_TITLE_CLEAN_REGEX);
try {
return text.replaceAll(regex, "");
} catch (Exception e) {
log.warn("标题清洗正则执行失败,使用内置默认: {}", e.getMessage());
return text.replaceAll(DEFAULT_XIANYU_TITLE_CLEAN_REGEX, "");
}
}
}

View File

@@ -218,24 +218,39 @@ public class TencentDocServiceImpl implements ITencentDocService {
for (int i = 0; i < headerCells.size(); i++) {
String cellText = headerCells.getString(i);
if (cellText != null) {
if (TencentDocDataParser.isJdPlaceOrderNoHeader(cellText)) {
if (jdPlaceOrderNoColumn == null) {
jdPlaceOrderNoColumn = i;
}
} else if (cellText.contains("日期")) dateColumn = i;
else if (cellText.contains("公司")) companyColumn = i;
else if (cellText.contains("单号") && !cellText.contains("物流")
&& !TencentDocDataParser.isJdPlaceOrderNoHeader(cellText)) orderNoColumn = i;
else if (cellText.contains("")) modelColumn = i;
else if (cellText.contains("数量")) quantityColumn = i;
else if (cellText.contains("姓名")) nameColumn = i;
else if (cellText.contains("电话")) phoneColumn = i;
else if (cellText.contains("地址")) addressColumn = i;
else if (cellText.contains("价格")) priceColumn = i;
else if (cellText.contains("备注")) remarkColumn = i;
else if (cellText.contains("是否安排") || cellText.contains("安排")) arrangedColumn = i;
else if (cellText.contains("物流")) logisticsColumn = i;
if (cellText == null) {
continue;
}
if (jdPlaceOrderNoColumn == null && TencentDocDataParser.isJdPlaceOrderNoHeader(cellText)) {
jdPlaceOrderNoColumn = i;
} else if (cellText.contains("日期")) {
dateColumn = i;
} else if (cellText.contains("公司")) {
companyColumn = i;
} else if (TencentDocDataParser.headerEquals(cellText, "")) {
orderNoColumn = i;
} else if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellText, "第三方单号")) {
orderNoColumn = i;
} else if (cellText.contains("型号")) {
modelColumn = i;
} else if (cellText.contains("数量")) {
quantityColumn = i;
} else if (cellText.contains("姓名")) {
nameColumn = i;
} else if (cellText.contains("电话")) {
phoneColumn = i;
} else if (cellText.contains("地址")) {
addressColumn = i;
} else if (cellText.contains("价格")) {
priceColumn = i;
} else if (TencentDocDataParser.headerEquals(cellText, "备注")) {
remarkColumn = i;
} else if (TencentDocDataParser.headerEquals(cellText, "是否安排")) {
arrangedColumn = i;
} else if (TencentDocDataParser.headerEquals(cellText, "物流单号")) {
logisticsColumn = i;
} else if (logisticsColumn == null && TencentDocDataParser.headerEquals(cellText, "物流链接")) {
logisticsColumn = i;
}
}

View File

@@ -215,20 +215,36 @@ public class TencentDocDataParser {
}
/**
* 表头是否为「京东下单订单号」列(与第三方「单号」等列区分
* 表头规范化:去 BOM、首尾空白、不间断空格与全角空格后去掉中间空白再比较与腾讯表格展示列名一致
*/
public static String normalizeTencentDocHeader(String raw) {
if (raw == null) {
return "";
}
String t = raw.trim();
if (t.startsWith("\uFEFF")) {
t = t.substring(1).trim();
}
t = t.replace('\u00A0', ' ').replace('\u3000', ' ');
t = t.replaceAll("\\s+", "");
return t;
}
/**
* 表头是否与期望列名完全一致(规范化后)
*/
public static boolean headerEquals(String raw, String expectedName) {
if (expectedName == null) {
return false;
}
return expectedName.equals(normalizeTencentDocHeader(raw));
}
/**
* 表头是否为「京东下单订单号」列(列名须完全一致)
*/
public static boolean isJdPlaceOrderNoHeader(String cellValueTrim) {
if (cellValueTrim == null) {
return false;
}
String t = cellValueTrim.trim();
if (t.isEmpty()) {
return false;
}
if (t.contains("京东下单订单号")) {
return true;
}
return t.contains("京东") && t.contains("下单") && t.contains("订单号");
return headerEquals(cellValueTrim, "京东下单订单号");
}
}