From 82c54f409c801c113b30f90d70054effa6612cd5 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 5 Apr 2025 01:45:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90=E8=BD=AC?= =?UTF-8?q?=E9=93=BE=E3=80=82=E5=AF=8C=E8=B4=B5=E6=8C=87=E6=97=A5=E5=8F=AF?= =?UTF-8?q?=E5=BE=85=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../van/business/util/AmountStepHandler.java | 22 + .../cn/van/business/util/FlowStepHandler.java | 11 + .../java/cn/van/business/util/JDUtil.java | 795 ++++++++++++------ 3 files changed, 566 insertions(+), 262 deletions(-) create mode 100644 src/main/java/cn/van/business/util/AmountStepHandler.java create mode 100644 src/main/java/cn/van/business/util/FlowStepHandler.java diff --git a/src/main/java/cn/van/business/util/AmountStepHandler.java b/src/main/java/cn/van/business/util/AmountStepHandler.java new file mode 100644 index 0000000..9e753a0 --- /dev/null +++ b/src/main/java/cn/van/business/util/AmountStepHandler.java @@ -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); + } +} diff --git a/src/main/java/cn/van/business/util/FlowStepHandler.java b/src/main/java/cn/van/business/util/FlowStepHandler.java new file mode 100644 index 0000000..352a896 --- /dev/null +++ b/src/main/java/cn/van/business/util/FlowStepHandler.java @@ -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); +} diff --git a/src/main/java/cn/van/business/util/JDUtil.java b/src/main/java/cn/van/business/util/JDUtil.java index fb549d8..a0e8879 100644 --- a/src/main/java/cn/van/business/util/JDUtil.java +++ b/src/main/java/cn/van/business/util/JDUtil.java @@ -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 userInteractionStates = new ConcurrentHashMap<>(); private HashMap 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> messagesAll = generatePromotionContent(message); - List messages = messagesAll.get("text");// 不需要图片和SKU名称 - for (String s : messages) { - wxUtil.sendTextMessage(fromWxid, s, 1, fromWxid); - } - try { - List 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> contentResult = generatePromotionContent(message); + + // 3. 发送文本内容 + for (String text : contentResult.get("text")) { + wxUtil.sendTextMessage(wxid, text, 1, wxid); + } + + // 4. 发送图片(如果有) + List 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> generatePromotionContent(String message) { HashMap> finallMessage = new HashMap<>(); List textList = new ArrayList<>(); List imagesList = new ArrayList<>(); List dataList = new ArrayList<>(); + // 最终的方案 + List finalWenAn = new ArrayList<>(); - // 提取文案中的所有 u.jd.com 链接 + + // 提取方案中的所有 u.jd.com 链接 List 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 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 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 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 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 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 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;// 违规佣金 } - }