基本完成转链。富贵指日可待!

This commit is contained in:
Leo
2025-04-05 01:45:09 +08:00
parent 79775dde63
commit 82c54f409c
3 changed files with 566 additions and 262 deletions

View File

@@ -0,0 +1,22 @@
package cn.van.business.util;
import static cn.van.business.util.JDUtil.UserInteractionState.GiftMoneyStep.STEP_QUANTITY;
/**
* @author Leo
* @version 1.0
* @create 2025/4/4 20:46
* @description
*/
// 实现金额处理步骤
class AmountStepHandler implements FlowStepHandler {
public void handle(JDUtil util, String wxid, String input, JDUtil.UserInteractionState state) {
if (!util.isValidAmount(input)) {
util.wxUtil.sendTextMessage(wxid, "金额格式错误", 1, wxid);
return;
}
state.getCollectedFields().put("amount", input);
state.setCurrentStep(STEP_QUANTITY);
}
}

View File

@@ -0,0 +1,11 @@
package cn.van.business.util;
/**
* @author Leo
* @version 1.0
* @create 2025/4/4 20:45
* @description
*/
public interface FlowStepHandler {
void handle(JDUtil util, String wxid, String input, JDUtil.UserInteractionState state);
}

View File

@@ -33,6 +33,7 @@ import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -41,13 +42,14 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.van.business.util.JDUtil.UserInteractionState.GiftMoneyStep.*;
import static cn.van.business.util.JDUtil.UserInteractionState.ProcessState.*;
import static cn.van.business.util.JDUtil.UserInteractionState.ProcessState.INIT;
import static cn.van.business.util.WXUtil.super_admins;
/**
@@ -78,16 +80,50 @@ public class JDUtil {
private static final Logger logger = LoggerFactory.getLogger(JDUtil.class);
private static final String INTERACTION_STATE_PREFIX = "interaction_state:";
private static final long TIMEOUT_MINUTES = 1;
private static final String WENAN_FANAN = "提供方法自己下单\n" +
"全程都是自己的账号下单\n" +
"标价就是下单的到手价\n" +
"(不包含教程费)\n" +
"本人有耐心,会一步一步提供教程\n" +
"以后有什么质量问题都是用自己的账号走京东售后\n" +
"\n" +
"更新\n" +
"\n" +
"用你自己的账号下单\n" +
"官方店铺 提供方法自己下单\n" +
"不含教程费,价格不对不收费\n" +
"教程费用私聊我问价\n" +
"——————————————————————————————————————————\n" +
"同行可长久合作,可提供神级家电线报\n" +
"\n" +
"配合家电线报可以自己下单,不用找代购和代下,订单和利润都掌握在自己手中。\n" +
"\n" +
"一次入会永久使用,包含家电帮,雷神价,韭菜帮,河南&湖南帮,各种暗号帮后返等内部独家家电线报\n" +
"\n" +
"JD采销采购不定时发放独家优惠券\n" +
"\n" +
"基本上你能看到的京东家电低价都是从这些渠道里面出来。\n" +
"\n" +
"2025年家电项目新方向配合家电线报下单秒省1K+。";
private static final String WENAN_ZCXS = "\n" +
"购买后,两小时内出库,物流会电话联系您,同时生成京东官方安装单。送装一体,无需担心。\n" +
"\n" +
"\n" +
"1:全新正品,原包装未拆封(京东商城代购,就近直发)\n" +
"2:可提供下单运单号与电子发票(发票在收到货后找我要)。\n" +
"3:收货时查看是否有质量或运损问题。可拍照让京东免费申请换新。\n" +
"4:下单后非质量问题不支持退款退货强制退扣100元。\n" +
"5:价格有浮动,不支持补差价,谢谢理解。\n" +
"6:全国联保,全国统一安装标准。支持官方 400服务号查询假一赔十。\n";
final WXUtil wxUtil;
private final StringRedisTemplate redisTemplate;
private final OrderRowRepository orderRowRepository;
private final WXUtil wxUtil;
private final OrderUtil orderUtil;
// 添加ObjectMapper来序列化和反序列化UserInteractionState
private final ObjectMapper objectMapper = new ObjectMapper();
private final ConcurrentHashMap<String, UserInteractionState> userInteractionStates = new ConcurrentHashMap<>();
private HashMap<String, String> cacheMap = new HashMap<>();
// 构造函数中注入StringRedisTemplate
@Autowired
public JDUtil(StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, WXUtil wxUtil, OrderUtil orderUtil) {
@@ -569,8 +605,7 @@ public class JDUtil {
* */
String result = "";
if (Util.isNotEmpty(response)) {
if (response.getCode().equals("0") &&
response.getGetResult().getCode() == 200) {
if (response.getCode().equals("0") && response.getGetResult().getCode() == 200) {
result = response.getGetResult().getData().getShortURL();
}
} else {
@@ -617,249 +652,449 @@ public class JDUtil {
// return response;
//}
private void handleUserInteraction(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = userInteractionStates.get(key);
private void handleUserInteraction(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = loadOrCreateState(key);
if (state == null) {
state = new UserInteractionState();
logger.debug("New interaction state created for user: {}", fromWxid);
} else {
LocalDateTime now = LocalDateTime.now();
LocalDateTime lastInteractionTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER);
if (ChronoUnit.MINUTES.between(lastInteractionTime, now) > TIMEOUT_MINUTES) {
userInteractionStates.remove(key);
logger.debug("Deleted timeout state for user: {}", fromWxid);
try {
switch (state.getCurrentState()) {
case INIT:
handleInitState(fromWxid, message, state);
break;
case PRODUCT_PROMOTION:
handlePromotionState(fromWxid, message, state);
break;
case GIFT_MONEY_FLOW:
handleGiftMoneyFlow(fromWxid, message, state);
break;
default:
resetState(fromWxid, state);
wxUtil.sendTextMessage(fromWxid, "流程异常,请重新开始", 1, fromWxid);
}
} catch (Exception e) {
logger.error("流程处理异常 - 用户: {}, 状态: {}", fromWxid, state, e);
resetState(fromWxid, state);
wxUtil.sendTextMessage(fromWxid, "处理异常,请重新开始", 1, fromWxid);
} finally {
saveState(key, state);
}
userInteractionStates.put(key, state);
logger.debug("Updated interaction state for user {}: {}", fromWxid, JSON.toJSONString(state));
}
private UserInteractionState loadOrCreateState(String key) {
UserInteractionState state = loadState(key);
if (state == null) {
state = new UserInteractionState();
logger.debug("创建新交互状态: {}", key);
} else if (isStateExpired(state)) {
redisTemplate.delete(key);
state = new UserInteractionState();
logger.debug("状态已过期,重置: {}", key);
}
return state;
}
private boolean isStateExpired(UserInteractionState state) {
LocalDateTime lastTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER);
return ChronoUnit.MINUTES.between(lastTime, LocalDateTime.now()) > TIMEOUT_MINUTES;
}
private void handleInitState(String wxid, String message, UserInteractionState state) {
if ("".equals(message)) {
state.setCurrentState(UserInteractionState.ProcessState.PRODUCT_PROMOTION);
state.setCurrentField("content");
wxUtil.sendTextMessage(wxid, "请输入推广方案(包含商品链接):", 1, wxid);
logger.info("进入转链流程 - 用户: {}", wxid);
}
}
state.updateLastInteractionTime();
userInteractionStates.put(key, state); // 确保状态保存
switch (state.getCurrentState()) {
case INIT:
if ("转链".equals(message)) {
state.setCurrentState(UserInteractionState.ProcessState.PRODUCT_PROMOTION);
state.setCurrentField("content");
wxUtil.sendTextMessage(fromWxid, "请输入推广文案(包含商品链接):", 1, fromWxid);
logger.info("进入转链流程 - 文案输入步骤");
}
break;
case PRODUCT_PROMOTION:
if ("content".equals(state.getCurrentField())) {
cacheMap.remove("text" + fromWxid);
// 第一次输入文案,直接生成内容
HashMap<String, List<String>> messagesAll = generatePromotionContent(message);
List<String> messages = messagesAll.get("text");// 不需要图片和SKU名称
for (String s : messages) {
wxUtil.sendTextMessage(fromWxid, s, 1, fromWxid);
}
try {
List<String> images = messagesAll.get("images");
if (images != null) {
for (String s : images) {
if (s != null) {
wxUtil.sendImageMessage(fromWxid, s);
}
}
}
} catch (Exception e) {
logger.error("Error generating promotion content: {}", e.getMessage());
}
cacheMap.put("text" + fromWxid, messagesAll.get("data").get(0));
// 直接进入确认开通礼金的步骤
state.setCurrentField("confirm");
wxUtil.sendTextMessage(fromWxid, "是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
logger.info("转链流程 - 等待用户确认是否需要开通礼金");
} else if ("confirm".equals(state.getCurrentField())) {
logger.info("转链流程 - 用户确认是否需要开通礼金:{}", message);
if ("1".equals(message)) {
// ▼▼▼ 关键修改:明确设置初始步骤 ▼▼▼
state.setCurrentState(UserInteractionState.ProcessState.GIFT_MONEY_FLOW);
state.setCurrentStep(UserInteractionState.GiftMoneyStep.STEP_AMOUNT);
state.getCollectedFields().clear();
userInteractionStates.put(key, state); // 立即持久化状态
wxUtil.sendTextMessage(fromWxid, "请输入开通金额(元):", 1, fromWxid);
} else if ("2".equals(message)) {
wxUtil.sendTextMessage(fromWxid, "开通礼金流程已取消", 1, fromWxid);
cacheMap.remove("text" + fromWxid);
} else {
wxUtil.sendTextMessage(fromWxid, "无效的选择,请重新输入:\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
}
state.reset(); // 重置状态
userInteractionStates.put(key, state); // 在外部保存
}
break;
case GIFT_MONEY_FLOW:
createPromotionWithGift(fromWxid, message);
break;
default:
wxUtil.sendTextMessage(fromWxid, "无效的状态,请重新开始对话", 1, fromWxid);
state.setCurrentState(INIT);
logger.debug("User {} reset to INIT state due to invalid state", fromWxid);
break;
}
userInteractionStates.put(key, state);
logger.debug("Updated interaction state for user {}: {}", fromWxid, JSON.toJSONString(state));
}
private void createPromotionWithGift(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = userInteractionStates.get(key);
// 修改点3在createPromotionWithGift方法开始处增加状态校验
if (state == null || state.getCurrentStep() == null) {
logger.warn("非法状态访问: {}", fromWxid);
wxUtil.sendTextMessage(fromWxid, "⚠️ 会话超时,请重新开始流程", 1, fromWxid);
return;
} else {
LocalDateTime now = LocalDateTime.now();
LocalDateTime lastInteractionTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER);
if (ChronoUnit.MINUTES.between(lastInteractionTime, now) > TIMEOUT_MINUTES) {
userInteractionStates.remove(key);
logger.debug("Deleted timeout state for user: {}", fromWxid);
state = new UserInteractionState();
private void handlePromotionState(String wxid, String message, UserInteractionState state) {
if ("content".equals(state.getCurrentField())) {
processContentInput(wxid, message, state);
} else if ("confirm".equals(state.getCurrentField())) {
handleGiftMoneyConfirmation(wxid, message, state);
}
}
state.updateLastInteractionTime();
userInteractionStates.put(key, state); // 确保状态保存
private void handleGiftMoneyFlow(String wxid, String message, UserInteractionState state) {
if (!state.validateStep(state.getCurrentStep())) {
wxUtil.sendTextMessage(wxid, "流程顺序异常,请重新开始", 1, wxid);
return;
}
try {
switch (state.getCurrentStep()) {
case STEP_CONFIRM_GIFT:
if ("1".equals(message)) {
state.setCurrentStep(STEP_AMOUNT);
wxUtil.sendTextMessage(fromWxid, "请输入开通金额(元):", 1, fromWxid);
} else if ("2".equals(message)) {
// 不开通礼金,直接生成转链
String cachedData = cacheMap.get("text" + fromWxid);
if (cachedData != null) {
JSONObject jsonObject = JSONObject.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String transferUrl = transfer(skuId, null);
wxUtil.sendTextMessage(fromWxid, "转链后的链接:\n" + transferUrl, 1, fromWxid);
} else {
wxUtil.sendTextMessage(fromWxid, "未找到缓存的商品链接,请重新开始流程", 1, fromWxid);
}
state.reset();
userInteractionStates.put(key, state); // 在外部保存
} else {
wxUtil.sendTextMessage(fromWxid, "无效的选择,请重新输入:\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
}
break;
case STEP_AMOUNT:
logger.debug("用户 {} 输入金额:{}", fromWxid, message);
if (message == null || message.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "金额不能为空请输入数字100.00", 1, fromWxid);
return;
}
if (!isValidAmount(message)) {
wxUtil.sendTextMessage(fromWxid, "金额格式错误请输入数字100.00", 1, fromWxid);
return;
}
double amount = Double.parseDouble(message);
String formattedAmount = String.format("%.2f", amount);
state.getCollectedFields().put("amount", formattedAmount);
state.setCurrentStep(STEP_QUANTITY);
userInteractionStates.put(key, state); // ▼▼▼ 保存点2 ▼▼▼
wxUtil.sendTextMessage(fromWxid, "请输入数量1-100", 1, fromWxid);
processAmountInput(wxid, message, state);
break;
case STEP_QUANTITY:
logger.debug("用户 {} 输入数量:{}", fromWxid, message); // 新增
if (!isValidQuantity(message)) {
wxUtil.sendTextMessage(fromWxid, "数量格式错误,请输入整数", 1, fromWxid);
return;
}
int quantity = Integer.parseInt(message);
if (quantity < 1 || quantity > 100) {
wxUtil.sendTextMessage(fromWxid, "数量需在1-100之间", 1, fromWxid);
return;
}
// 从缓存中获取 amount
String amountStr = state.getCollectedFields().get("amount");
if (amountStr == null || amountStr.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "未找到金额,请重新开始流程", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
return;
}
amount = Double.parseDouble(amountStr);
logger.debug("礼金参数准备完成:金额={}元,数量={}", amount, quantity); // 新增
String cachedData = cacheMap.get("text" + fromWxid);
if (cachedData != null) {
JSONObject jsonObject = JSONObject.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String giftKey = createGiftCoupon(skuId, amount, quantity, "pop");
if (giftKey == null) {
logger.error("用户 {} 礼金创建失败SKU={}, 金额={}元,数量={}", fromWxid, skuId, amount, quantity); // 新增
wxUtil.sendTextMessage(fromWxid, "❌ 礼金创建失败,请检查商品是否符合要求", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
return;
}
logger.info("用户 {} 礼金创建成功批次ID={}, 参数SKU={}, 金额={}元,数量={}", fromWxid, giftKey, skuId, amount, quantity); // 新增关键成功日志
state.getCollectedFields().put("giftKey", giftKey);
// 生成转链
String transferUrl = transfer(skuId, giftKey);
wxUtil.sendTextMessage(fromWxid, "附带礼金的链接:\n" + transferUrl, 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
} else {
wxUtil.sendTextMessage(fromWxid, "未找到缓存的商品链接,请重新开始流程", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
}
processQuantityInput(wxid, message, state);
break;
default:
state.setCurrentStep(STEP_CONFIRM_GIFT);
wxUtil.sendTextMessage(fromWxid, "是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
break;
state.setCurrentStep(STEP_AMOUNT);
wxUtil.sendTextMessage(wxid, "请输入金额1-50元", 1, wxid);
}
} catch (Exception e) {
logger.error("转链和礼金流程异常,用户 {} 当前步骤:{}", fromWxid, state.getCurrentStep(), e);
wxUtil.sendTextMessage(fromWxid, "❌ 系统异常,请稍后重试", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
}
}
private void processAmountInput(String wxid, String message, UserInteractionState state) {
if (!isValidAmount(message)) {
wxUtil.sendTextMessage(wxid, "金额格式错误请输入1-50元", 1, wxid);
return;
}
state.getCollectedFields().put("amount", message);
state.setCurrentStep(STEP_QUANTITY);
wxUtil.sendTextMessage(wxid, "请输入数量1-100", 1, wxid);
}
private void processQuantityInput(String wxid, String message, UserInteractionState state) {
if (!isValidQuantity(message)) {
wxUtil.sendTextMessage(wxid, "数量格式错误请输入1-100的整数", 1, wxid);
return;
}
String cachedData = cacheMap.get("productData" + wxid);
String finalWenAn = cacheMap.get("finalWenAn" + wxid);
if (cachedData == null) {
wxUtil.sendTextMessage(wxid, "数据丢失,请重新开始流程", 1, wxid);
resetState(wxid, state);
return;
}
try {
JSONObject jsonObject = JSON.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String owner = jsonObject.getString("owner");
String skuName = jsonObject.getString("skuName");
double amount = Double.parseDouble(state.getCollectedFields().get("amount"));
int quantity = Integer.parseInt(message);
String giftKey = createGiftCoupon(skuId, amount, quantity, owner, skuName);
if (giftKey == null) {
wxUtil.sendTextMessage(wxid, "礼金创建失败,请重试", 1, wxid);
return;
}
// 记录成功日志
logger.info("礼金创建成功 - 用户: {}, SKU: {}, 金额: {}, 数量: {}",
wxid, skuId, amount, quantity);
// 生成转链
String transferUrl = transfer(skuId, giftKey);
wxUtil.sendTextMessage(wxid, "附带礼金的链接:\n" + transferUrl, 1, wxid);
wxUtil.sendTextMessage(wxid, "附带礼金的方案:\n", 1, wxid);
wxUtil.sendTextMessage(wxid, finalWenAn.replaceAll(jsonObject.getString("url"), transferUrl), 1, wxid);
} catch (Exception e) {
logger.error("礼金处理异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理异常,请重试", 1, wxid);
} finally {
resetState(wxid, state);
cacheMap.remove("text" + wxid);
cacheMap.remove("finalWenAn" + wxid);
}
}
/**
* 处理用户输入的推广方案内容
*
* @param wxid 用户微信ID
* @param message 用户输入的方案内容
* @param state 当前交互状态
*/
private void processContentInput(String wxid, String message, UserInteractionState state) {
try {
// 1. 清除旧缓存
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
// 2. 生成推广内容
HashMap<String, List<String>> contentResult = generatePromotionContent(message);
// 3. 发送文本内容
for (String text : contentResult.get("text")) {
wxUtil.sendTextMessage(wxid, text, 1, wxid);
}
// 4. 发送图片(如果有)
List<String> images = contentResult.get("images");
if (images != null) {
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
for (String imageUrl : images) {
if (imageUrl != null) {
wxUtil.sendImageMessage(wxid, imageUrl);
}
}
}
// 5. 缓存商品数据
if (!contentResult.get("data").isEmpty()) {
String productData = contentResult.get("data").get(0);
cacheMap.put("productData" + wxid, productData);
cacheMap.put("finalWenAn" + wxid, contentResult.get("finalWenAn").get(0));
// 6. 进入确认礼金步骤
state.setCurrentField("confirm");
wxUtil.sendTextMessage(wxid,
"是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否",
1, wxid);
} else {
wxUtil.sendTextMessage(wxid, "未获取到商品数据,请检查链接格式", 1, wxid);
state.reset();
}
} catch (Exception e) {
logger.error("处理推广内容异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理内容时发生异常,请重试", 1, wxid);
state.reset();
}
}
/**
* 处理用户对礼金开通的确认
*
* @param wxid 用户微信ID
* @param message 用户回复1或2
* @param state 当前交互状态
*/
private void handleGiftMoneyConfirmation(String wxid, String message, UserInteractionState state) {
try {
if ("1".equals(message)) {
// 用户选择开通礼金
state.setCurrentState(UserInteractionState.ProcessState.GIFT_MONEY_FLOW);
state.setCurrentStep(UserInteractionState.GiftMoneyStep.STEP_AMOUNT);
state.getCollectedFields().clear();
wxUtil.sendTextMessage(wxid,
"请输入开通金额1-50元支持小数点后两位\n" +
"示例20.50",
1, wxid);
} else if ("2".equals(message)) {
// 用户选择不开通礼金
String cachedData = cacheMap.get("productData" + wxid);
if (cachedData != null) {
JSONObject productInfo = JSON.parseObject(cachedData);
String skuUrl = productInfo.getString("materialUrl");
String transferUrl = transfer(skuUrl, null);
String finalWenAn = cacheMap.get("finalWenAn" + wxid);
finalWenAn = (finalWenAn.replace(productInfo.getString("url"), transferUrl));
wxUtil.sendTextMessage(wxid,
"不开礼金,只转链的方案:\n",
1, wxid);
wxUtil.sendTextMessage(wxid,
finalWenAn,
1, wxid);
} else {
wxUtil.sendTextMessage(wxid, "未找到商品信息,请重新开始流程", 1, wxid);
}
state.reset();
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
} else {
// 无效输入
wxUtil.sendTextMessage(wxid,
"请输入有效选项:\n" +
"回复 1 - 开通礼金\n" +
"回复 2 - 直接转链",
1, wxid);
}
} catch (Exception e) {
logger.error("处理礼金确认异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理请求时发生异常,请重试", 1, wxid);
state.reset();
}
}
private void resetState(String wxid, UserInteractionState state) {
state.reset();
saveState(INTERACTION_STATE_PREFIX + wxid, state);
}
// 在发送提示信息时增加进度指示
private void sendStepPrompt(String wxid, int step, int totalSteps) {
String progress = String.format("[%d/%d] ", step, totalSteps);
String message = progress + "请输入礼金金额示例20.50";
wxUtil.sendTextMessage(wxid, message, 1, wxid);
}
private void createPromotionWithGift(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = userInteractionStates.get(key);
// 修改createPromotionWithGift方法中的校验逻辑
if (!state.validateStep(STEP_CONFIRM_GIFT)) {
logger.warn("状态校验失败,预期步骤:{} 实际步骤:{}", STEP_CONFIRM_GIFT, state.getCurrentStep());
wxUtil.sendTextMessage(fromWxid, "流程顺序异常,请重新开始", 1, fromWxid);
return;
}
// 修改点3在createPromotionWithGift方法开始处增加状态校验
if (state == null || state.getCurrentStep() == null) {
logger.warn("非法状态访问: {}", fromWxid);
wxUtil.sendTextMessage(fromWxid, "⚠️ 会话超时,请重新开始流程", 1, fromWxid);
return;
} else {
LocalDateTime now = LocalDateTime.now();
LocalDateTime lastInteractionTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER);
if (ChronoUnit.MINUTES.between(lastInteractionTime, now) > TIMEOUT_MINUTES) {
userInteractionStates.remove(key);
logger.debug("Deleted timeout state for user: {}", fromWxid);
state = new UserInteractionState();
}
}
state.updateLastInteractionTime();
userInteractionStates.put(key, state); // 确保状态保存
try {
switch (state.getCurrentStep()) {
case STEP_CONFIRM_GIFT:
if ("1".equals(message)) {
state.setCurrentStep(STEP_AMOUNT);
wxUtil.sendTextMessage(fromWxid, "请输入开通金额(元):", 1, fromWxid);
} else if ("2".equals(message)) {
// 不开通礼金,直接生成转链
String cachedData = cacheMap.get("productData" + fromWxid);
if (cachedData != null) {
JSONObject jsonObject = JSONObject.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String transferUrl = transfer(skuId, null);
wxUtil.sendTextMessage(fromWxid, "转链后的链接:\n" + transferUrl, 1, fromWxid);
} else {
wxUtil.sendTextMessage(fromWxid, "未找到缓存的商品链接,请重新开始流程", 1, fromWxid);
}
state.reset();
userInteractionStates.put(key, state); // 在外部保存
} else {
wxUtil.sendTextMessage(fromWxid, "无效的选择,请重新输入:\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
}
break;
case STEP_AMOUNT:
logger.debug("用户 {} 输入金额:{}", fromWxid, message);
if (message == null || message.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "金额不能为空请输入数字100.00", 1, fromWxid);
return;
}
if (!isValidAmount(message)) {
wxUtil.sendTextMessage(fromWxid, "金额格式错误请输入数字100.00", 1, fromWxid);
return;
}
double amount = Double.parseDouble(message);
String formattedAmount = String.format("%.2f", amount);
state.getCollectedFields().put("amount", formattedAmount);
state.setCurrentStep(STEP_QUANTITY);
userInteractionStates.put(key, state); // ▼▼▼ 保存点2 ▼▼▼
wxUtil.sendTextMessage(fromWxid, "请输入数量1-100", 1, fromWxid);
sendStepPrompt(fromWxid, 1, 3);
break;
case STEP_QUANTITY:
logger.debug("用户 {} 输入数量:{}", fromWxid, message); // 新增
if (!isValidQuantity(message)) {
wxUtil.sendTextMessage(fromWxid, "数量格式错误,请输入整数", 1, fromWxid);
return;
}
int quantity = Integer.parseInt(message);
if (quantity < 1 || quantity > 100) {
wxUtil.sendTextMessage(fromWxid, "数量需在1-100之间", 1, fromWxid);
return;
}
// 从缓存中获取 amount
String amountStr = state.getCollectedFields().get("amount");
if (amountStr == null || amountStr.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "未找到金额,请重新开始流程", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
return;
}
amount = Double.parseDouble(amountStr);
logger.debug("礼金参数准备完成:金额={}元,数量={}", amount, quantity); // 新增
String cachedData = cacheMap.get("productData" + fromWxid);
String finalWenAn = cacheMap.get("finalWenAn" + fromWxid);
if (cachedData != null) {
JSONObject jsonObject = JSONObject.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String owner = jsonObject.getString("owner");
String skuName = jsonObject.getString("skuName");
String giftKey = createGiftCoupon(skuId, amount, quantity, owner, skuName);
if (giftKey == null) {
logger.error("用户 {} 礼金创建失败SKU={}, 金额={}元,数量={}", fromWxid, skuId, amount, quantity); // 新增
wxUtil.sendTextMessage(fromWxid, "❌ 礼金创建失败,请检查商品是否符合要求", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
return;
}
logger.info("用户 {} 礼金创建成功批次ID={}, 参数SKU={}, 金额={}元,数量={}", fromWxid, giftKey, skuId, amount, quantity); // 新增关键成功日志
state.getCollectedFields().put("giftKey", giftKey);
// 生成转链
String transferUrl = transfer(skuId, giftKey);
wxUtil.sendTextMessage(fromWxid, "附带礼金的链接:\n" + transferUrl, 1, fromWxid);
wxUtil.sendTextMessage(fromWxid, "附带礼金的方案:\n" + finalWenAn.replaceAll(jsonObject.getString("url"), transferUrl), 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
} else {
wxUtil.sendTextMessage(fromWxid, "未找到缓存的商品链接,请重新开始流程", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
}
break;
default:
state.setCurrentStep(STEP_CONFIRM_GIFT);
wxUtil.sendTextMessage(fromWxid, "是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
break;
}
} catch (Exception e) {
logger.error("转链和礼金流程异常,用户 {} 当前步骤:{}", fromWxid, state.getCurrentStep(), e);
wxUtil.sendTextMessage(fromWxid, "❌ 系统异常,请稍后重试", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
}
}
/**
* 生成转链和案的方法
* 生成转链和案的方法
*
* @param message 案内容,包含商品链接
* @return 处理后的案,附带商品信息
* @param message 案内容,包含商品链接
* @return 处理后的案,附带商品信息
*/
public HashMap<String, List<String>> generatePromotionContent(String message) {
HashMap<String, List<String>> finallMessage = new HashMap<>();
List<String> textList = new ArrayList<>();
List<String> imagesList = new ArrayList<>();
List<String> dataList = new ArrayList<>();
// 最终的方案
List<String> finalWenAn = new ArrayList<>();
// 提取文案中的所有 u.jd.com 链接
// 提取方案中的所有 u.jd.com 链接
List<String> urls = extractUJDUrls(message);
if (urls.isEmpty()) {
textList.add("案中未找到有效的商品链接,请检查格式是否正确。");
textList.add("案中未找到有效的商品链接,请检查格式是否正确。");
finallMessage.put("text", textList);
return finallMessage;
}
@@ -1133,11 +1368,11 @@ private void createPromotionWithGift(String fromWxid, String message) {
//},
//"skuName": "松下Panasonic白月光4.0Ultra 洗烘套装 10kg滚筒洗衣机+变频热泵烘干机 除毛升级2.0 水氧SPA护理 8532N+8532NR",
//"spuid": 100137629936,
dataList.add(JSONObject.toJSONString(productInfo.getData()[0]));
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(productInfo.getData()[0]));
jsonObject.put("url", url);
dataList.add(JSONObject.toJSONString(jsonObject));
finallMessage.put("data", dataList);
HashMap<String, String> itemMap = new HashMap<>();
itemMap.put("url", url);
itemMap.put("materialUrl", productInfo.getData()[0].getMaterialUrl());
@@ -1148,12 +1383,13 @@ private void createPromotionWithGift(String fromWxid, String message) {
itemMap.put("skuName", productInfo.getData()[0].getSkuName());
String replaceAll = itemMap.get("skuName").replaceAll("国家", "").replaceAll("补贴", "");
itemMap.put("spuid", String.valueOf(productInfo.getData()[0].getSpuid()));
itemMap.put("commission", String.valueOf(productInfo.getData()[0].getCommissionInfo().getCommission()));
itemMap.put("commissionShare", String.valueOf(productInfo.getData()[0].getCommissionInfo().getCommissionShare()));
//for (HashMap.Entry<String, String> entry : itemMap.entrySet()) {
//couponInfo.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
//}
couponInfo.append(" ").append("店铺:\n").append(itemMap.get("shopName")).append("\n")
.append("标题\n").append(replaceAll).append("\n")
.append("自营 POP\n").append(itemMap.get("owner"));
couponInfo.append(" ").append("店铺:\n").append(itemMap.get("shopName")).append("\n").append(" 标题:\n").append(replaceAll).append("\n").append("自营 POP\n").append(itemMap.get("owner").equals("g") ? " 自营 " : " POP ")
.append("佣金\n").append(itemMap.get("commission")).append("\n").append("佣金比例:\n").append(itemMap.get("commissionShare"));
//StringBuilder images = new StringBuilder();
@@ -1168,6 +1404,17 @@ private void createPromotionWithGift(String fromWxid, String message) {
//textList.add(String.valueOf(images));
resultList.add(itemMap);
/*直接生成闲鱼的商品文案*/
StringBuilder sb1 = new StringBuilder();
// 新建格式好日期
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
sb1.append(replaceAll).append("\n").append(WENAN_FANAN.replaceAll("更新", dateFormat.format(new Date()) + "更新"));
textList.add("闲鱼方案的文案:\n");
textList.add(String.valueOf(sb1));
StringBuilder sb2 = new StringBuilder();
sb2.append(replaceAll).append("\n").append(WENAN_ZCXS);
textList.add("闲鱼正常销售:\n");
textList.add(String.valueOf(sb2));
}
textList.add(String.valueOf(couponInfo));
@@ -1179,25 +1426,29 @@ private void createPromotionWithGift(String fromWxid, String message) {
finallMessage.put("text", textList);
}
}
StringBuilder wenan = new StringBuilder().append("文案生成: \n");
// 完成转链后替换链接为u.jd.com链接文案不修改就返回
for (HashMap<String, String> stringStringHashMap : resultList) {
String transferUrl = transfer(stringStringHashMap.get("materialUrl"), null);
wenan = new StringBuilder(message.replace(stringStringHashMap.get("url"), transferUrl));
/**
* 因为在这里转链返回,没有什么意义,后面走转链不转链,会重新发方案
* */
StringBuilder wenan = new StringBuilder();
// 完成转链后替换链接为u.jd.com链接方案不修改就返回
//for (HashMap<String, String> stringStringHashMap : resultList) {
// String transferUrl = transfer(stringStringHashMap.get("materialUrl"), null);
// wenan = new StringBuilder(message.replace(stringStringHashMap.get("url"), transferUrl));
//}
wenan = new StringBuilder(message);
//textList.add(String.valueOf(wenan));
finalWenAn.add(String.valueOf(wenan));
}
textList.add(String.valueOf(wenan));
finallMessage.put("text", textList);
finallMessage.put("images", imagesList);
finallMessage.put("finalWenAn", finalWenAn);
return finallMessage;
}
private String downloadImage(String imageUrl, String destinationFile) {
try (BufferedInputStream in = new BufferedInputStream(new URL(imageUrl).openStream());
FileOutputStream fileOutputStream = new FileOutputStream(destinationFile)) {
try (BufferedInputStream in = new BufferedInputStream(new URL(imageUrl).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(destinationFile)) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
@@ -1211,9 +1462,9 @@ private void createPromotionWithGift(String fromWxid, String message) {
}
/**
* 提取案中的所有 u.jd.com 链接
* 提取案中的所有 u.jd.com 链接
*
* @param message 案内容
* @param message 案内容
* @return 包含所有 u.jd.com 链接的列表
*/
private List<String> extractUJDUrls(String message) {
@@ -1273,7 +1524,8 @@ private void createPromotionWithGift(String fromWxid, String message) {
}
// 在JDUtil类中新增方法实现商品详情查询接口
public UnionOpenGoodsBigfieldQueryResponse getUnionOpenGoodsBigfieldQueryResponse(String skuId) throws Exception {
public UnionOpenGoodsBigfieldQueryResponse getUnionOpenGoodsBigfieldQueryResponse(String skuId) throws
Exception {
JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, LPF_APP_KEY_WZ, LPF_SECRET_KEY_WZ);
UnionOpenGoodsBigfieldQueryRequest request = new UnionOpenGoodsBigfieldQueryRequest();
@@ -1315,7 +1567,7 @@ private void createPromotionWithGift(String fromWxid, String message) {
// 新增礼金创建方法
public String createGiftCoupon(String skuId, double amount, int quantity, String owner) throws Exception {
public String createGiftCoupon(String skuId, double amount, int quantity, String owner, String skuName) throws Exception {
logger.debug("准备创建礼金SKU={}, 金额={}元,数量={}, Owner={}", skuId, amount, quantity, owner);
// 参数校验
@@ -1341,18 +1593,28 @@ private void createPromotionWithGift(String fromWxid, String message) {
if ("pop".equals(owner)) {
startTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
endTime = LocalDateTime.now().plusDays(6).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
couponReq.setEffectiveDays(7);
} else {
startTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
endTime = LocalDateTime.now().plusDays(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
couponReq.setEffectiveDays(1);
}
couponReq.setReceiveStartTime(startTime);
couponReq.setReceiveEndTime(endTime);
couponReq.setEffectiveDays(7);
couponReq.setIsSpu(1);
couponReq.setExpireType(1);
couponReq.setShare(-1);
couponReq.setCouponTitle(skuId);
if (skuName.length() >= 25) {
skuName = skuName.substring(0, 25);
}
// 新建格式日期
DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
skuName = skuName + " " + dateFormat.format(new Date());
couponReq.setCouponTitle(skuName);
couponReq.setContentMatch(1);
request.setCouponReq(couponReq);
@@ -1363,7 +1625,7 @@ private void createPromotionWithGift(String fromWxid, String message) {
UnionOpenCouponGiftGetResponse response = client.execute(request);
logger.debug("API响应{}", JSON.toJSONString(response));
if ("200".equals(response.getCode())) {
if ("0".equals(response.getCode()) && response.getGetResult().getCode() == 200) {
String giftKey = response.getGetResult().getData().getGiftCouponKey();
logger.debug("礼金创建成功批次ID={}, 返回数据:{}", giftKey, response.getGetResult().getData());
return giftKey;
@@ -1376,15 +1638,6 @@ private void createPromotionWithGift(String fromWxid, String message) {
// 修改activateGiftMoney方法调用真实接口
private boolean activateGiftMoney(String skuId, double amount, int quantity, String owner) {
try {
String giftKey = createGiftCoupon(skuId, amount, quantity, owner);
if (giftKey != null) {
// 可在此处保存礼金批次ID到用户状态
return true;
}
} catch (Exception e) {
logger.error("礼金创建失败", e);
}
return false;
}
@@ -1407,7 +1660,7 @@ private void createPromotionWithGift(String fromWxid, String message) {
}
private boolean isValidAmount(String input) {
boolean isValidAmount(String input) {
// 新增:直接处理 null 或空字符串
if (input == null || input.trim().isEmpty()) {
return false;
@@ -1439,6 +1692,16 @@ private void createPromotionWithGift(String fromWxid, String message) {
}
}
// 将状态存储迁移到Redis
public void saveState(String wxid, UserInteractionState state) {
String key = INTERACTION_STATE_PREFIX + wxid;
redisTemplate.opsForValue().set(key, JSON.toJSONString(state), 10, TimeUnit.MINUTES);
}
public UserInteractionState loadState(String wxid) {
String json = redisTemplate.opsForValue().get(INTERACTION_STATE_PREFIX + wxid);
return JSON.parseObject(json, UserInteractionState.class);
}
// 定义一个内部类来存储用户交互状态
@Getter
@@ -1466,32 +1729,42 @@ private void createPromotionWithGift(String fromWxid, String message) {
public void reset() {
this.currentState = INIT;
this.collectedFields.clear();
this.currentStep = STEP_CONFIRM_GIFT ; // 明确重置步骤
this.currentStep = STEP_CONFIRM_GIFT; // 明确重置步骤
updateLastInteractionTime();
}
// 在UserInteractionState类中增加状态校验方法
public boolean validateStep(GiftMoneyStep expectedStep) {
return this.currentStep != null && this.currentStep.getCode() == expectedStep.getCode();
}
// 在UserInteractionState中增加恢复方法
public void restoreState(Map<String, String> backup) {
this.collectedFields.putAll(backup);
this.lastInteractionTime = LocalDateTime.now().format(DATE_TIME_FORMATTER);
}
// 推荐使用枚举管理状态
public enum ProcessState {
INIT, GIFT_MONEY_FLOW, PRODUCT_PROMOTION
}
public enum GiftMoneyStep {
STEP_CONFIRM_GIFT(0), // 显式指定序号
STEP_AMOUNT(1),
STEP_QUANTITY(2);
private final int code;
public enum GiftMoneyStep {
STEP_CONFIRM_GIFT(0), // 显式指定序号
STEP_AMOUNT(1), STEP_QUANTITY(2);
private final int code;
GiftMoneyStep(int code) {
this.code = code;
}
GiftMoneyStep(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
public int getCode() {
return code;
}
}
}
@@ -1512,7 +1785,6 @@ public enum GiftMoneyStep {
}
// 统计指标DTO
@Getter
@AllArgsConstructor
@@ -1529,5 +1801,4 @@ public enum GiftMoneyStep {
private long violations; // 违规订单数
private double violationCommission;// 违规佣金
}
}