package cn.van.business.util; import cn.van.business.model.jd.OrderRow; import cn.van.business.model.jd.ProductOrder; import cn.van.business.repository.OrderRowRepository; import cn.van.business.repository.ProductOrderRepository; import com.alibaba.fastjson2.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.jd.open.api.sdk.DefaultJdClient; import com.jd.open.api.sdk.JdClient; import com.jd.open.api.sdk.domain.kplunion.CouponService.request.get.CreateGiftCouponReq; import com.jd.open.api.sdk.domain.kplunion.CouponService.request.stop.StopGiftCouponReq; import com.jd.open.api.sdk.domain.kplunion.GoodsService.request.query.BigFieldGoodsReq; import com.jd.open.api.sdk.domain.kplunion.GoodsService.request.query.GoodsReq; import com.jd.open.api.sdk.domain.kplunion.GoodsService.response.query.BigfieldQueryResult; import com.jd.open.api.sdk.domain.kplunion.GoodsService.response.query.GoodsQueryResult; import com.jd.open.api.sdk.domain.kplunion.GoodsService.response.query.UrlInfo; import com.jd.open.api.sdk.domain.kplunion.OrderService.request.query.OrderRowReq; import com.jd.open.api.sdk.domain.kplunion.promotionbysubunioni.PromotionService.request.get.PromotionCodeReq; import com.jd.open.api.sdk.request.kplunion.*; import com.jd.open.api.sdk.response.kplunion.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.*; 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.DISINFECTANT_CABINET; import static cn.van.business.util.JDUtil.UserInteractionState.ProcessState.INIT; import static cn.van.business.util.JDUtil.UserInteractionState.ProductOrderStep.*; import static cn.van.business.util.WXUtil.super_admins; import java.net.URL; /** * @author Leo * @version 1.0 * @create 2024/11/5 17:40 * @description: */ @Component public class JDUtil { static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); /** * 密钥配置 */ // van论坛 private static final String LPF_APP_KEY_WZ = "98e21c89ae5610240ec3f5f575f86a59"; private static final String LPF_SECRET_KEY_WZ = "3dcb6b23a1104639ac433fd07adb6dfb"; private static final String LPF_WZ_ID = "4101253066"; // 导购的 private static final String LPF_APP_KEY_DG = "faf410cb9587dc80dc7b31e321d7d322"; private static final String LPF_SECRET_KEY_DG = "a4fb15d7bedd4316b97b4e96e4effc1c"; private static final String LL_APP_KEY_DG = "9c2011409f0fc906b73432dd3687599d"; private static final String LL_SECRET_KEY_DG = "3ceddff403e544a8a2eacc727cf05dab"; private static final String SERVER_URL = "https://api.jd.com/routerjson"; //accessToken private static final String ACCESS_TOKEN = ""; 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 final StringRedisTemplate redisTemplate; private final OrderRowRepository orderRowRepository; private final ProductOrderRepository productOrderRepository; private final WXUtil wxUtil; private final OrderUtil orderUtil; // 添加ObjectMapper来序列化和反序列化UserInteractionState private final ObjectMapper objectMapper = new ObjectMapper(); // 构造函数中注入StringRedisTemplate @Autowired public JDUtil(StringRedisTemplate redisTemplate, ProductOrderRepository productOrderRepository, OrderRowRepository orderRowRepository, WXUtil wxUtil, OrderUtil orderUtil) { this.redisTemplate = redisTemplate; this.orderRowRepository = orderRowRepository; this.productOrderRepository = productOrderRepository; this.wxUtil = wxUtil; this.orderUtil = orderUtil; } private static List filterOrdersByDate(List orderRows, int daysBack) { LocalDate now = LocalDate.now(); return orderRows.stream().filter(order -> { // 将 Date 转换为 LocalDate LocalDate orderDate = order.getOrderTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // 计算是否在给定的天数内 return !orderDate.isBefore(now.minusDays(daysBack)) && !orderDate.isAfter(now); }).collect(Collectors.toList()); } private static Stream getStreamForWeiGui(List todayOrders) { return todayOrders.stream().filter(orderRow -> orderRow.getValidCode() == 13 || orderRow.getValidCode() == 25 || orderRow.getValidCode() == 26 || orderRow.getValidCode() == 27 || orderRow.getValidCode() == 28 || orderRow.getValidCode() == 29); } public void sendOrderToWxByOrderDefault(String order, String fromWxid) { logger.info("执行 sendOrderToWxByOrderDefault 方法,order: {}, fromWxid: {}", order, fromWxid); handleUserInteraction(fromWxid, order); // 具体逻辑 } private OrderStats calculateStats(List orders) { long paid = orders.stream().filter(o -> o.getValidCode() == 16).count(); long pending = orders.stream().filter(o -> o.getValidCode() == 15).count(); long canceled = orders.stream().filter(o -> o.getValidCode() != 16 && o.getValidCode() != 17).count(); long completed = orders.stream().filter(o -> o.getValidCode() == 17).count(); return new OrderStats(orders.size(), orders.size() - canceled, paid, orders.stream().filter(o -> o.getValidCode() == 16).mapToDouble(OrderRow::getEstimateFee).sum(), pending, orders.stream().filter(o -> o.getValidCode() == 15).mapToDouble(OrderRow::getEstimateFee).sum(), canceled, completed, orders.stream().filter(o -> o.getValidCode() == 17).mapToDouble(OrderRow::getEstimateFee).sum(), getStreamForWeiGui(orders).count(), getStreamForWeiGui(orders).mapToDouble(o -> o.getEstimateCosPrice() * o.getCommissionRate() * 0.01).sum()); } private StringBuilder buildStatsContent(String title, OrderStats stats) { StringBuilder content = new StringBuilder(); content//[爱心][Wow][Packet][Party][Broken][心碎][亲亲][色] .append("* ").append(title).append(" *\n").append("━━━━━━━━━━━━\n").append("[爱心] 订单总数:").append(stats.getTotalOrders()).append("\n") // [文件] .append("[Party] 有效订单:").append(stats.getValidOrders()).append("\n") // [OK] .append("[心碎]已取消:").append(stats.getCanceledOrders()).append("\n") // [禁止] .append("────────────\n").append("[爱心]已付款:").append(stats.getPaidOrders()).append("\n") // [钱袋] .append("[Packet] 已付款佣金:").append(String.format("%.2f", stats.getPaidCommission())).append("\n") // [钞票] .append("────────────\n").append("[Wow] 待付款:").append(stats.getPendingOrders()).append("\n") // [时钟] .append("[Packet] 待付款佣金:").append(String.format("%.2f", stats.getPendingCommission())).append("\n") // [钱] .append("────────────\n").append("[亲亲] 已完成:").append(stats.getCompletedOrders()).append("\n") // [旗帜] .append("[Packet] 已完成佣金:").append(String.format("%.2f", stats.getCompletedCommission())).append("\n") // [信用卡] .append("────────────\n").append("[Emm] 违规订单:").append(stats.getViolations()).append("\n") // [警告] .append("[Broken] 违规佣金:").append(String.format("%.2f", stats.getViolationCommission())).append("\n") // [炸弹] .append("━━━━━━━━━━━━"); return content; } /** * 接收京粉指令指令 */ public void sendOrderToWxByOrderJD(String order, String fromWxid) { int[] param = {-1}; WXUtil.SuperAdmin superAdmin = super_admins.get(fromWxid); String unionId = superAdmin.getUnionId(); List orderRows = orderRowRepository.findByValidCodeNotInOrderByOrderTimeDescAndUnionId(param, Long.valueOf(unionId)); /** * 菜单: * 今日统计 * 昨日统计 * 最近七天统计 * 最近一个月统计 * 今天订单 * 昨天订单 * */ List contents = new ArrayList<>(); StringBuilder content = new StringBuilder(); switch (order) { case "菜单": content.append("菜单:京+命令 \n 如: 京今日统计\r"); content.append("今日统计\r"); content.append("昨天统计\r"); content.append("七日统计\r"); content.append("一个月统计\r"); content.append("两个月统计\r"); content.append("三个月统计\r"); content.append("总统计\r\n"); content.append("这个月统计\r"); content.append("上个月统计\r\n"); content.append("今日订单\r"); content.append("昨日订单\r"); content.append("七日订单\r"); content.append("刷新7天\r"); contents.add(content); content = new StringBuilder(); content.append("高级菜单:京+高级+命令 \n 如: 京高级违规30\r"); content.append("京高级违规+整数(不传数字为365天)\r"); content.append("京高级SKU+sku\r"); content.append("京高级搜索+搜索标题(精准查询订单号+精准查询sku+模糊查询收件人+模糊查询地址),只返回最近100条\r"); contents.add(content); //content = new StringBuilder(); // //content.append("礼金\r"); //content.append("转链\r"); // //contents.add(content); break; case "测试指令": { //test01(); break; } case "今日统计": { // 订单总数,已付款,已取消,佣金总计 List todayOrders = filterOrdersByDate(orderRows, 0); OrderStats stats = calculateStats(todayOrders); contents.add(buildStatsContent("今日统计", stats)); break; } case "昨日统计": { List yesterdayOrders = filterOrdersByDate(orderRows, 1); OrderStats stats = calculateStats(yesterdayOrders); contents.add(buildStatsContent("昨日统计", stats)); break; } case "三日统计": { List last3DaysOrders = filterOrdersByDate(orderRows, 3); OrderStats stats = calculateStats(last3DaysOrders); contents.add(buildStatsContent("三日统计", stats)); break; } case "七日统计": { List last7DaysOrders = filterOrdersByDate(orderRows, 7); OrderStats stats = calculateStats(last7DaysOrders); contents.add(buildStatsContent("七日统计", stats)); break; } case "一个月统计": { List last30DaysOrders = filterOrdersByDate(orderRows, 30); OrderStats stats = calculateStats(last30DaysOrders); contents.add(buildStatsContent("一个月统计", stats)); break; } case "两个月统计": { List last60DaysOrders = filterOrdersByDate(orderRows, 60); OrderStats stats = calculateStats(last60DaysOrders); contents.add(buildStatsContent("两个月统计", stats)); break; } case "三个月统计": { List last90DaysOrders = filterOrdersByDate(orderRows, 90); OrderStats stats = calculateStats(last90DaysOrders); contents.add(buildStatsContent("三个月统计", stats)); break; } case "这个月统计": { // 计算出距离1号有几天 int days = LocalDate.now().getDayOfMonth(); List thisMonthOrders = filterOrdersByDate(orderRows, days); OrderStats stats = calculateStats(thisMonthOrders); contents.add(buildStatsContent("这个月统计", stats)); break; } case "上个月统计": { LocalDate lastMonth = LocalDate.now().minusMonths(1); int days = LocalDate.now().getDayOfMonth(); List lastMonthOrders = filterOrdersByDate(orderRows, lastMonth.lengthOfMonth() + days); List thisMonthOrders = filterOrdersByDate(orderRows, days); lastMonthOrders = lastMonthOrders.stream().filter(orderRow -> !thisMonthOrders.contains(orderRow)).collect(Collectors.toList()); OrderStats stats = calculateStats(lastMonthOrders); contents.add(buildStatsContent("上个月统计", stats)); break; } //总统计 case "总统计": { OrderStats stats = calculateStats(orderRows); contents.add(buildStatsContent("总统计", stats)); break; } case "今日订单": { List todayOrders = filterOrdersByDate(orderRows, 0); // 订单总数,已付款,已取消,佣金总计 OrderStats stats = calculateStats(todayOrders); contents.add(buildStatsContent("今日统计", stats)); if (!todayOrders.isEmpty()) { orderUtil.orderToWxBatch(todayOrders); } break; } case "昨日订单": { content = new StringBuilder(); List yesterdayOrders = filterOrdersByDate(orderRows, 1); List todayOrders = filterOrdersByDate(orderRows, 0); logger.info("原始订单数量:{}", orderRows.size()); logger.info("昨日过滤后数量:{}", yesterdayOrders.size()); yesterdayOrders.removeAll(todayOrders); logger.info("今日过滤后数量:{}", todayOrders.size()); logger.info("最终昨日订单数量:{}", yesterdayOrders.size()); OrderStats stats = calculateStats(yesterdayOrders); contents.add(buildStatsContent("昨日统计", stats)); if (!yesterdayOrders.isEmpty()) { orderUtil.orderToWxBatch(yesterdayOrders); } break; } case "七日订单": { List last7DaysOrders = filterOrdersByDate(orderRows, 1); List todayOrders = filterOrdersByDate(orderRows, 0); last7DaysOrders.removeAll(todayOrders); OrderStats stats = calculateStats(last7DaysOrders); contents.add(buildStatsContent("七日统计", stats)); if (!last7DaysOrders.isEmpty()) { orderUtil.orderToWxBatch(last7DaysOrders); } break; } default: sendOrderToWxByOrderJDAdvanced(order, fromWxid); } if (!contents.isEmpty()) { for (StringBuilder stringBuilder : contents) { wxUtil.sendTextMessage(fromWxid, stringBuilder.toString(), 1, fromWxid); } } } /** * 接收京粉指令指令 * 高级菜单 */ public void sendOrderToWxByOrderJDAdvanced(String order, String fromWxid) { int[] param = {-1}; WXUtil.SuperAdmin superAdmin = super_admins.get(fromWxid); String unionId = superAdmin.getUnionId(); List orderRows = orderRowRepository.findByValidCodeNotInOrderByOrderTimeDescAndUnionId(param, Long.valueOf(unionId)); List contents = new ArrayList<>(); StringBuilder content = new StringBuilder(); if (order.startsWith("高级")) { content = new StringBuilder(); order = order.replace("高级", ""); if (order.startsWith("违规")) { String days = order.replace("违规", ""); Integer daysInt = 365; if (Util.isNotEmpty(days)) { daysInt = Integer.parseInt(days); } List filterOrdersByDays = filterOrdersByDate(orderRows, daysInt); content.append("违规排行:"); content.append(daysInt).append("天").append("\r\n"); Map skuIdViolationCountMap = filterOrdersByDays.stream().filter(orderRow -> orderRow.getValidCode() == 27 || orderRow.getValidCode() == 28).filter(orderRow -> orderRow.getSkuName() != null).collect(Collectors.groupingBy(OrderRow::getSkuName, // ✅ 拼接SKU Collectors.counting())); Map> orderInfoMap = filterOrdersByDays.stream().filter(orderRow -> orderRow.getValidCode() == 27 || orderRow.getValidCode() == 28).filter(orderRow -> orderRow.getSkuName() != null).map(orderRow -> { OrderInfo info = new OrderInfo(); info.setSkuName(orderRow.getSkuName()); info.setOrderId(orderRow.getOrderId()); info.setOrderDate(orderRow.getOrderTime()); return info; }).collect(Collectors.groupingBy(OrderInfo::getSkuName)); List> sortedViolationCounts = skuIdViolationCountMap.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()); Integer num = 0; for (Map.Entry entry : sortedViolationCounts) { num++; String skuName = entry.getKey(); Long count = entry.getValue(); // 修改后直接使用已包含SKU信息的key content.append("\n").append(num).append(",商品:").append(entry.getKey()) // 这里已包含SKU信息 .append("\r\r").append(" 违规次数:").append(count).append("\r"); List infos = orderInfoMap.get(skuName); if (infos != null) { for (OrderInfo info : infos) { content.append("\n 订单:").append(info.getOrderId()).append("\r 下单:").append(info.getOrderDate()).append("\r"); } } } contents.add(content); } // 订单查询 if (order.startsWith("搜索")) { order = order.replace("搜索", ""); content = new StringBuilder(); // 精准查询订单号+精准查询sku+模糊查询收件人+模糊查询地址 content.append("精准查询订单号:\r"); List orderRowList = orderRowRepository.findByOrderId(Long.parseLong(order)); if (!orderRowList.isEmpty()) { OrderRow orderRow = orderRowList.get(0); if (orderRow.getUnionId().equals(Long.parseLong(unionId))) { content.append(orderUtil.getFormattedOrderInfo(orderRow, orderRow.getValidCode())); } else { content.append("订单不属于你,无法查询\r"); } } else { content.append("订单不存在\r"); } contents.add(content); content = new StringBuilder(); // 不统计已取消的订单 content.append("精准查询sku,不统计已取消的订单:\r"); int[] validCodes = {-1, 3}; List bySkuIdAndUnionId = orderRowRepository.findBySkuIdAndUnionId(validCodes, Long.parseLong(order), Long.parseLong(unionId)); int size = bySkuIdAndUnionId.size(); content.append("查询到").append(size).append("条订单\r"); // 切割成20条20条返回前100条 for (int i = 0; i < size; i += 20) { List subList = bySkuIdAndUnionId.subList(i, Math.min(i + 20, size)); content.append("第").append(i / 20 + 1).append("页:\r"); for (OrderRow orderRow : subList) { content.append(orderUtil.getFormattedOrderInfo(orderRow, orderRow.getValidCode())); contents.add(content); content = new StringBuilder(); } } content = new StringBuilder(); content.append("模糊查询收件人+模糊查询地址:\r"); //List orderRowList = orderRowRepository content.append("暂不支持"); contents.add(content); } if (order.startsWith("SKU")) { content = new StringBuilder(); order = order.replace("SKU", ""); String[] split = order.split("\r\n"); content.append("电脑端").append("\r\n"); for (String s : split) { content.append("https://item.jd.com/").append(s.trim()).append(".html").append("\r\n"); } wxUtil.sendTextMessage(fromWxid, content.toString(), 1, fromWxid); content = new StringBuilder(); content.append("手机端").append("\r\n"); for (String s : split) { content.append("https://item.m.jd.com/product/").append(s.trim()).append(".html").append("\r\n"); } wxUtil.sendTextMessage(fromWxid, content.toString(), 1, fromWxid); content = new StringBuilder(); contents.add(content); } // 转链 if (order.startsWith("转链")) { content = new StringBuilder(); order = order.replace("转链", ""); String jsonString; try { GoodsQueryResult goodsQueryResult = queryProductInfoByUJDUrl(order); jsonString = JSON.toJSONString(goodsQueryResult); content.append(jsonString); } catch (Exception e) { content.append(e); throw new RuntimeException(e); } contents.add(content); } } else { try { sendOrderToWxByOrderJD("菜单", fromWxid); } catch (Exception e) { throw new RuntimeException(e); } } if (!contents.isEmpty()) { for (StringBuilder stringBuilder : contents) { wxUtil.sendTextMessage(fromWxid, stringBuilder.toString(), 1, fromWxid); } } } /** * 获取订单列表 * * @param start 开始时间 * @param end 结束时间 * @return * @throws Exception */ public UnionOpenOrderRowQueryResponse getUnionOpenOrderRowQueryResponse(LocalDateTime start, LocalDateTime end, Integer pageIndex, String appKey, String secretKey) throws Exception { String startTime = start.format(DATE_TIME_FORMATTER); String endTime = end.format(DATE_TIME_FORMATTER); // 模拟 API 调用 //System.out.println("调用API - 从 " + startTime // + " 到 " + endTime); // 实际的 API 调用逻辑应在这里进行 JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, appKey, secretKey); UnionOpenOrderRowQueryRequest request = new UnionOpenOrderRowQueryRequest(); OrderRowReq orderReq = new OrderRowReq(); orderReq.setPageIndex(pageIndex); orderReq.setPageSize(200); orderReq.setStartTime(startTime); orderReq.setEndTime(endTime); orderReq.setType(1); request.setOrderReq(orderReq); request.setVersion("1.0"); request.setSignmethod("md5"); // 时间戳,格式为yyyy-MM-dd HH:mm:ss,时区为GMT+8。API服务端允许客户端请求最大时间误差为10分钟 Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); request.setTimestamp(simpleDateFormat.format(date)); return client.execute(request); } /** * 接口描述:通过商品链接、领券链接、活动链接获取普通推广链接或优惠券二合一推广链接 * jd.union.open.promotion.bysubunionid.get */ String transfer(String url) { JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, LPF_APP_KEY_WZ, LPF_SECRET_KEY_WZ); UnionOpenPromotionBysubunionidGetRequest request=new UnionOpenPromotionBysubunionidGetRequest(); PromotionCodeReq promotionCodeReq=new PromotionCodeReq(); promotionCodeReq.setSceneId(1); promotionCodeReq.setMaterialId(url); request.setPromotionCodeReq(promotionCodeReq); request.setVersion("1.0"); //logger.info("transfer - request {}", JSON.toJSONString(request)); UnionOpenPromotionBysubunionidGetResponse response; try { response = client.execute(request); //logger.info("transfer - response {}", JSON.toJSONString(response)); } catch (Exception e) { throw new RuntimeException(e); } /** * { * "jd_union_open_promotion_bysubunionid_get_responce": { * "getResult": { * "code": "200", * "data": { * "clickURL": "https://union-click.jd.com/jdc?e=XXXXXX p=XXXXXXXXXXX", * "weChatShortLink": "#小程序://京小街/****", * "jShortCommand": "短口令", * "shortURL": "https://u.jd.com/XXXXX", * "jCommand": "6.0复制整段话 http://JhT7V5wlKygHDK京口令内容#J6UFE5iMn***" * }, * "message": "success" * } * } * } * */ String result = ""; if (Util.isNotEmpty(response)) { if (response.getCode().equals("0") && response.getGetResult().getCode() == 200) { result = response.getGetResult().getData().getShortURL(); } } else { result = null; } return result; } /** * 消毒柜部分的业务逻辑 */ @Scheduled(fixedRate = 60000) // 每分钟执行一次 public void cleanUpTimeoutStates() { LocalDateTime now = LocalDateTime.now(); redisTemplate.keys(INTERACTION_STATE_PREFIX + "*").forEach(key -> { String stateJson = redisTemplate.opsForValue().get(key); try { UserInteractionState state = objectMapper.readValue(stateJson, UserInteractionState.class); LocalDateTime lastInteractionTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER); if (ChronoUnit.MINUTES.between(lastInteractionTime, now) > TIMEOUT_MINUTES) { redisTemplate.delete(key); logger.debug("Deleted timeout state for key: {}", key); } } catch (Exception e) { logger.error("Error parsing interaction state: {}", e.getMessage()); } }); } //public UnionOpenGoodsBigfieldQueryResponse getUnionOpenGoodsBigfieldQueryResponse(){ // JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, APP_KEY, SECRET_KEY); // // UnionOpenGoodsBigfieldQueryRequest request=new UnionOpenGoodsBigfieldQueryRequest(); // BigFieldGoodsReq goodsReq=new BigFieldGoodsReq(); // goodsReq.setSkuIds(); // request.setGoodsReq(goodsReq); // request.setVersion("1.0"); // UnionOpenGoodsBigfieldQueryResponse response= null; // try { // response = client.execute(request); // } catch (Exception e) { // throw new RuntimeException(e); // } // return response; //} private void handleUserInteraction(String fromWxid, String message) { String key = INTERACTION_STATE_PREFIX + fromWxid; String stateJson = redisTemplate.opsForValue().get(key); UserInteractionState state; if (stateJson == null) { state = new UserInteractionState(); logger.debug("New interaction state created for user: {}", fromWxid); } else { try { state = objectMapper.readValue(stateJson, UserInteractionState.class); // 检查是否超时 LocalDateTime now = LocalDateTime.now(); LocalDateTime lastInteractionTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER); if (ChronoUnit.MINUTES.between(lastInteractionTime, now) > TIMEOUT_MINUTES) { redisTemplate.delete(key); logger.debug("Deleted timeout state for user: {}", fromWxid); state = new UserInteractionState(); } } catch (Exception e) { logger.error("Error parsing interaction state: {}", e.getMessage()); state = new UserInteractionState(); } } state.updateLastInteractionTime(); switch (state.getCurrentState()) { case INIT: if ("转链".equals(message)) { state.setCurrentState(UserInteractionState.ProcessState.PRODUCT_PROMOTION); state.setCurrentField("content"); wxUtil.sendTextMessage(fromWxid, "请输入推广文案(包含商品链接):", 1, fromWxid); logger.info("进入转链流程 - 文案输入步骤"); } else if ("礼金".equals(message)) { state.setCurrentState(UserInteractionState.ProcessState.PRODUCT_PROMOTION); state.setCurrentField("content"); wxUtil.sendTextMessage(fromWxid, "请输入推广文案(包含商品链接):", 1, fromWxid); logger.info("进入转链流程 - 文案输入步骤"); } else if ("登记".equals(message)) { state.setCurrentState(DISINFECTANT_CABINET); state.setCurrentField("orderId"); wxUtil.sendTextMessage(fromWxid, "请输入订单号:", 1, fromWxid); logger.debug("User {} entered DISINFECTANT_CABINET state", fromWxid); } break; case PRODUCT_PROMOTION: if ("content".equals(state.getCurrentField())) { // 第一次输入文案,直接生成内容 HashMap> messagesAll = generatePromotionContent(message); List messages = messagesAll.get("text") ;// 不需要图片和SKU名称 for (String s : messages) { wxUtil.sendTextMessage(fromWxid, s, 1, fromWxid); } if (messagesAll.get("images") != null){ for (String s : messagesAll.get("images")) { wxUtil.sendImageMessage(fromWxid, s); } } // 询问是否需要继续转链 state.setCurrentField("confirm"); wxUtil.sendTextMessage(fromWxid, "是否需要继续进行转链?\n回复 1 - 是\n回复 2 - 否", 1, fromWxid); logger.info("转链流程 - 等待用户确认是否继续转链"); } else if ("confirm".equals(state.getCurrentField())) { if ("1".equals(message)) { // 继续转链逻辑 String transferredContent = transferContentWithLinks(message); wxUtil.sendTextMessage(fromWxid, "转链完成:\n" + transferredContent, 1, fromWxid); } else if ("2".equals(message)) { wxUtil.sendTextMessage(fromWxid, "转链流程已取消", 1, fromWxid); } else { wxUtil.sendTextMessage(fromWxid, "无效的选择,请重新输入:\n回复 1 - 是\n回复 2 - 否", 1, fromWxid); } state.reset(); // 重置状态 } break; case GIFT_MONEY_FLOW: handleGiftMoneyFlow(fromWxid, message, state); break; case PRODUCT_ORDER_REGISTRATION: handleProductOrderRegistration(fromWxid, message, state); break; default: wxUtil.sendTextMessage(fromWxid, "无效的状态,请重新开始对话", 1, fromWxid); state.setCurrentState(INIT); logger.debug("User {} reset to INIT state due to invalid state", fromWxid); break; } try { redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(state), TIMEOUT_MINUTES, TimeUnit.MINUTES); logger.debug("Saved interaction state for user {}: {}", fromWxid, state); } catch (Exception e) { logger.error("Error saving interaction state: {}", e.getMessage()); } } // 新增方法:处理文案中的链接并进行转链 private String transferContentWithLinks(String message) { List urls = extractUJDUrls(message); if (urls.isEmpty()) { return "文案中未找到有效的商品链接,请检查格式是否正确。\n" + message; } StringBuilder enrichedContent = new StringBuilder(); for (String url : urls) { String transferUrl = transfer(url); if (transferUrl != null) { enrichedContent.append("原链接:").append(url).append("\n") .append("转链后链接:").append(transferUrl).append("\n"); } else { enrichedContent.append("链接转链失败:").append(url).append("\n"); } } return enrichedContent.toString(); } // 新增礼金流程处理方法 private void handleGiftMoneyFlow(String fromWxid, String message, UserInteractionState state) { if (state.getCurrentStep() == null) { state.setCurrentStep(STEP_PRODUCT_LINK); wxUtil.sendTextMessage(fromWxid, "流程异常,已重置。请输入商品链接:", 1, fromWxid); return; } if ("礼金".equals(message)) { state.reset(); logger.debug("用户 {} 重置礼金流程", fromWxid); // 新增 wxUtil.sendTextMessage(fromWxid, "流程已重置,请重新开始", 1, fromWxid); return; } try { String skuId = ""; double amount = 0.0; String owner = "pop"; switch (state.getCurrentStep()) { case STEP_PRODUCT_LINK: skuId = parseSkuFromUrl(message); logger.debug("用户 {} 输入商品链接:{}, 解析出SKU: {}", fromWxid, message, skuId); // 新增 if (skuId == null) { wxUtil.sendTextMessage(fromWxid, "❌ 商品链接格式错误,请重新输入", 1, fromWxid); return; } //BigfieldQueryResult queryResult = queryProductInfo(skuId); //logger.debug("商品ID {} 查询结果:{}", skuId, queryResult); // 新增 //if (queryResult == null) { // wxUtil.sendTextMessage(fromWxid, "⚠️ 商品信息查询失败,可能链接无效", 1, fromWxid); // return; //} //BigFieldGoodsResp productInfo = queryResult.getData()[0]; //owner = productInfo.getOwner(); //state.getCollectedFields().put("skuId", skuId); //state.getCollectedFields().put("productInfo", productInfo.getBaseBigFieldInfo().getWdis()); //state.getCollectedFields().put("owner", owner); // //logger.debug("商品信息已收集:SKU={}, Owner={}, WDIS={}", skuId, owner, productInfo.getBaseBigFieldInfo().getWdis()); // 新增 String productInfo = skuId; state.getCollectedFields().put("skuId", skuId); state.setCurrentStep(STEP_AMOUNT); String prompt = String.format("商品SKU:\n %s\n 请输入开通金额(元):", productInfo); wxUtil.sendTextMessage(fromWxid, prompt, 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; } // 转换并保留两位小数 amount = Double.parseDouble(message); String formattedAmount = String.format("%.2f", amount); state.getCollectedFields().put("amount", formattedAmount); state.setCurrentStep(STEP_QUANTITY); wxUtil.sendTextMessage(fromWxid, "请输入数量(1-1000):", 1, fromWxid); 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 > 1000) { wxUtil.sendTextMessage(fromWxid, "❌ 数量需在1-1000之间", 1, fromWxid); return; } logger.debug("礼金参数准备完成:SKU={},金额={}元,数量={},Owner={}", state.getCollectedFields().get("skuId"), amount, quantity, state.getCollectedFields().get("owner")); // 新增 String giftKey = createGiftCoupon(state.getCollectedFields().get("skuId"), Double.parseDouble(state.getCollectedFields().get("amount")), quantity, owner); if (giftKey == null) { logger.error("用户 {} 礼金创建失败:SKU={}, 金额={}, 数量={}, Owner={}", fromWxid, skuId, amount, quantity, owner); // 新增 wxUtil.sendTextMessage(fromWxid, "❌ 礼金创建失败,请检查商品是否符合要求", 1, fromWxid); state.reset(); return; } logger.info("用户 {} 礼金创建成功:批次ID={}, 参数:SKU={}, 金额={}元,数量={}, Owner={}", fromWxid, giftKey, skuId, amount, quantity, owner); // 新增关键成功日志 state.getCollectedFields().put("giftKey", giftKey); break; } } catch (Exception e) { logger.error("礼金流程异常,用户 {} 当前步骤:{}", fromWxid, state.getCurrentStep(), e); // 新增 wxUtil.sendTextMessage(fromWxid, "❌ 系统异常,请稍后重试", 1, fromWxid); state.reset(); } } /** * 生成转链和文案的方法 * * @param message 文案内容,包含商品链接 * @return 处理后的文案,附带商品信息 */ public HashMap> generatePromotionContent(String message) { HashMap> finallMessage = new HashMap<>(); List textList = new ArrayList<>(); List imagesList = new ArrayList<>(); // 提取文案中的所有 u.jd.com 链接 List urls = extractUJDUrls(message); if (urls.isEmpty()) { textList.add("文案中未找到有效的商品链接,请检查格式是否正确。"); finallMessage.put("text", textList); return finallMessage; } /** * { * "jd_union_open_goods_query_responce": { * "code": "0", * "queryResult": { * "code": 200, * "data": [ * { * "brandCode": "16407", * "brandName": "松下(Panasonic)", * "categoryInfo": { * "cid1": 737, * "cid1Name": "家用电器", * "cid2": 794, * "cid2Name": "大 家 电", * "cid3": 880, * "cid3Name": "洗衣机" * }, * "comments": 10000, * "commissionInfo": { * "commission": 599.95, * "commissionShare": 5, * "couponCommission": 592.95, * "endTime": 1743609599000, * "isLock": 1, * "plusCommissionShare": 5, * "startTime": 1742486400000 * }, * "couponInfo": { * "couponList": [ * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 100, * "getEndTime": 1746028799000, * "getStartTime": 1743436800000, * "isBest": 1, * "isInputCoupon": 1, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoeVJzBK6Q6mfry4nz4ylRYHXsp_NnipDSrReNwGZGTxXhj94SGi9SkzR_7xaWEXLpO2boqow", * "platformType": 0, * "quota": 5000, * "useEndTime": 1746028799000, * "useStartTime": 1743436800000 * }, * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 50, * "getEndTime": 1746028799000, * "getStartTime": 1743436800000, * "hotValue": 0, * "isBest": 0, * "isInputCoupon": 0, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoervD9YaNNXlsj6OBbZoWSFI1sZXw31PSjK04AH6tFP_Iu0YJlePWBT6sZfNvp14W0QK2K6A", * "platformType": 0, * "quota": 5000, * "useEndTime": 1746028799000, * "useStartTime": 1743436800000 * }, * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 40, * "getEndTime": 1746028799000, * "getStartTime": 1743436800000, * "hotValue": 0, * "isBest": 0, * "isInputCoupon": 0, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoeqQdmhZbv1TiAn0QqI5gnT4g_zAgV5887llN8j6TatV-zpDfReipMr-hKkwQasE9NNUV2uQ", * "platformType": 0, * "quota": 500, * "useEndTime": 1746028799000, * "useStartTime": 1743436800000 * }, * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 20, * "getEndTime": 1746028799000, * "getStartTime": 1743436800000, * "hotValue": 0, * "isBest": 0, * "isInputCoupon": 0, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoepukjRCuLpbwceODRbBw5HpNLRTVe5Olp99d34Izdo0s9lDtTIdyYZ-uJRCEXnc5N3OZ5LA", * "platformType": 0, * "quota": 2000, * "useEndTime": 1746028799000, * "useStartTime": 1743436800000 * }, * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 10, * "getEndTime": 1746028799000, * "getStartTime": 1743436800000, * "hotValue": 0, * "isBest": 0, * "isInputCoupon": 0, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoeKIIbuvk-VsfJj_m1o3PRHqRaEBIbXHwooEMMt3z44T3cNQTkQsS8TjGnEeIz1obKp_t_mQ", * "platformType": 0, * "quota": 1000, * "useEndTime": 1746028799000, * "useStartTime": 1743436800000 * } * ] * }, * "deliveryType": 1, * "eliteType": [], * "forbidTypes": [ * 0 * ], * "goodCommentsShare": 99, * "imageInfo": { * "imageList": [ * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/273873/17/14072/113544/67ebd215Feef3f56b/fc5156bf59a25ba3.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/273802/16/14273/84419/67ebd224F72dbcc8d/8ffe6f99aeeeb8fd.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/278931/40/12819/81341/67ea0227F41ffc604/ab1c6727e5d4f224.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/276989/12/13440/79310/67ea0226F68c2ed40/8acdeda05aa3596b.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/284503/25/12384/59498/67ea0225F8be60c83/b29ea34abc64346e.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/284667/29/12481/56118/67ea0225F97d9c729/f4c81d77064957bd.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/270959/35/13852/50552/67ea0224F911b8f00/248f9a7751549db7.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/274981/3/13326/48019/67ea0224Fe64bca69/b062218fc8db9b29.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/283691/1/12478/82267/67ea0223Fd4ecb0f5/2c32de327d8aa440.jpg" * }, * { * "url": "https://img14.360buyimg.com/pop/jfs/t1/281457/31/12476/94335/67ea0222Fde76593a/cdddcd59b0b20c9a.jpg" * } * ] * }, * "inOrderComm30Days": 633170.8, * "inOrderCount30Days": 3000, * "inOrderCount30DaysSku": 100, * "isHot": 1, * "isJdSale": 1, * "isOversea": 0, * "itemId": "BsrqLq5CfIziE7BSl3ItPp8q_3DFaNVYJqfkRRLc7HR", * "jxFlags": [], * "materialUrl": "jingfen.jd.com/detail/BsrqLq5CfIziE7BSl3ItPp8q_3DFaNVYJqfkRRLc7HR.html", * "oriItemId": "BMrqLq5CfIz9X04KC3ItPp8q_3DFaNVYJqfkRRLc7HR", * "owner": "g", * "pinGouInfo": {}, * "pingGouInfo": {}, * "priceInfo": { * "lowestCouponPrice": 11899, * "lowestPrice": 11999, * "lowestPriceType": 1, * "price": 11999 * }, * "purchasePriceInfo": { * "basisPriceType": 1, * "code": 200, * "couponList": [ * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 100, * "isBest": 0, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoeVJzBK6Q6mfry4nz4ylRYHXsp_NnipDSrReNwGZGTxXhj94SGi9SkzR_7xaWEXLpO2boqow", * "platformType": 0, * "quota": 5000 * }, * { * "bindType": 1, * "couponStatus": 0, * "couponStyle": 0, * "discount": 40, * "isBest": 0, * "link": "https://coupon.m.jd.com/coupons/show.action?linkKey=AAROH_xIpeffAs_-naABEFoeqQdmhZbv1TiAn0QqI5gnT4g_zAgV5887llN8j6TatV-zpDfReipMr-hKkwQasE9NNUV2uQ", * "platformType": 0, * "quota": 500 * } * ], * "message": "success", * "purchaseNum": 1, * "purchasePrice": 11859, * "thresholdPrice": 11999 * }, * "shopInfo": { * "shopId": 1000001741, * "shopLabel": "0", * "shopLevel": 4.9, * "shopName": "松下洗衣机京东自营旗舰店" * }, * "skuName": "松下(Panasonic)白月光4.0Ultra 洗烘套装 10kg滚筒洗衣机+变频热泵烘干机 除毛升级2.0 水氧SPA护理 8532N+8532NR", * "skuTagList": [ * { * "index": 1, * "name": "自营", * "type": 11 * }, * { * "index": 1, * "name": "plus", * "type": 6 * }, * { * "index": 3, * "name": "7天无理由退货", * "type": 12 * } * ], * "spuid": 100137629936, * "videoInfo": {} * } * ], * "message": "success", * "requestId": "o_0b721560_m8yp5pqj_88394507", * "totalCount": 1 * } * } * } * * */ // 如果需要图片和SKU名称,则代表要把图片下载发过去,还有对应的skuName StringBuilder couponInfo = new StringBuilder(); ArrayList> resultList = new ArrayList<>(); for (String url : urls) { try { // 查询商品信息 GoodsQueryResult productInfo = queryProductInfoByUJDUrl(url); if (productInfo == null || productInfo.getCode() != 200) { couponInfo.append("链接查询失败:").append(url).append("\n"); continue; } long totalCount = productInfo.getTotalCount(); couponInfo.append("链接查询成功:\n").append(" ").append(url).append("\n"); if (totalCount == 0) { couponInfo.append("链接类型:优惠券"); } else { couponInfo.append("链接类型:商品"); //"materialUrl": "jingfen.jd.com/detail/BsrqLq5CfIziE7BSl3ItPp8q_3DFaNVYJqfkRRLc7HR.html", //"oriItemId": "BMrqLq5CfIz9X04KC3ItPp8q_3DFaNVYJqfkRRLc7HR", //"owner": "g", //"shopInfo": { // "shopId": 1000001741, // "shopLabel": "0", // "shopLevel": 4.9, // "shopName": "松下洗衣机京东自营旗舰店" //}, //"skuName": "松下(Panasonic)白月光4.0Ultra 洗烘套装 10kg滚筒洗衣机+变频热泵烘干机 除毛升级2.0 水氧SPA护理 8532N+8532NR", //"spuid": 100137629936, HashMap itemMap = new HashMap<>(); itemMap.put("url", url); itemMap.put("materialUrl", productInfo.getData()[0].getMaterialUrl()); itemMap.put("oriItemId", productInfo.getData()[0].getOriItemId()); itemMap.put("owner", productInfo.getData()[0].getOwner()); itemMap.put("shopId", String.valueOf(productInfo.getData()[0].getShopInfo().getShopId())); itemMap.put("shopName", productInfo.getData()[0].getShopInfo().getShopName()); itemMap.put("skuName", productInfo.getData()[0].getSkuName()); itemMap.put("spuid", String.valueOf(productInfo.getData()[0].getSpuid())); couponInfo.append("商品信息:\n"); for (HashMap.Entry entry : itemMap.entrySet()) { couponInfo.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } StringBuilder images = new StringBuilder(); if (productInfo.getData()[0].getImageInfo() != null) { images.append(" ").append("图片信息:\n"); int index = 1; for (UrlInfo image : productInfo.getData()[0].getImageInfo().getImageList()) { images.append("图片 ").append(index++).append("\n").append(image.getUrl()).append("\n"); imagesList.add(image.getUrl()); } } textList.add(String.valueOf(images)); resultList.add(itemMap); } textList.add(String.valueOf(couponInfo)); } catch (Exception e) { logger.error("处理商品链接时发生异常:{}", url, e); couponInfo.append(" 处理商品链接时发生异常:").append(url).append("\n"); textList.add(String.valueOf(couponInfo)); finallMessage.put("text", textList); } } StringBuilder wenan = new StringBuilder().append("文案生成: \n"); // 完成转链后替换链接为u.jd.com链接,文案不修改就返回 for (HashMap stringStringHashMap : resultList) { String transferUrl = transfer(stringStringHashMap.get("materialUrl")); wenan = new StringBuilder(message.replace(stringStringHashMap.get("url"), transferUrl)); } textList.add(String.valueOf(wenan)); finallMessage.put("text", textList); finallMessage.put("images", imagesList); return finallMessage; } private String downloadImage(String imageUrl, String 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) { fileOutputStream.write(dataBuffer, 0, bytesRead); } return destinationFile; } catch (IOException e) { logger.error("Error downloading image from URL: {}", imageUrl, e); return null; } } /** * 提取文案中的所有 u.jd.com 链接 * * @param message 文案内容 * @return 包含所有 u.jd.com 链接的列表 */ private List extractUJDUrls(String message) { List urls = new ArrayList<>(); Pattern pattern = Pattern.compile("https://u\\.jd\\.com/\\S+"); Matcher matcher = pattern.matcher(message); while (matcher.find()) { urls.add(matcher.group()); } return urls; } /** * 通过这个可以将u.jd.*** 查询到对应的商品信息 和 商品图片,甚至可以查询到是不是自营的商品 */ public UnionOpenGoodsQueryResponse getUnionOpenGoodsQueryRequest(String uJDUrl) throws Exception { JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, LPF_APP_KEY_WZ, LPF_SECRET_KEY_WZ); UnionOpenGoodsQueryRequest request = new UnionOpenGoodsQueryRequest(); GoodsReq goodsReq = new GoodsReq(); //goodsReq.setSkuIds(List.of(Long.parseLong(skuId)).toArray(new Long[0])); // 传入skuId集合 /** * 查询域集合,不填写则查询全部,目目前支持:categoryInfo(类目信息),imageInfo(图片信息), * baseBigFieldInfo(基础大字段信息),bookBigFieldInfo(图书大字段信息), * videoBigFieldInfo(影音大字段信息),detailImages(商详图) * */ //goodsReq.setFields(new String[]{"categoryInfo", "imageInfo", "baseBigFieldInfo"}); // 设置需要查询的字段 goodsReq.setKeyword(uJDUrl); goodsReq.setSceneId(1); request.setGoodsReqDTO(goodsReq); request.setVersion("1.0"); request.setSignmethod("md5"); // 时间戳,格式为yyyy-MM-dd HH:mm:ss,时区为GMT+8。API服务端允许客户端请求最大时间误差为10分钟 Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); request.setTimestamp(simpleDateFormat.format(date)); //logger.debug("getUnionOpenGoodsQueryRequest 查询商品信息请求:{}", JSON.toJSONString(request)); UnionOpenGoodsQueryResponse execute = client.execute(request); //logger.debug("getUnionOpenGoodsQueryRequest 查询商品信息响应:{}", JSON.toJSONString(execute)); return execute; } public GoodsQueryResult queryProductInfoByUJDUrl(String uJDUrl) throws Exception { UnionOpenGoodsQueryResponse response = getUnionOpenGoodsQueryRequest(uJDUrl); if (response == null || response.getQueryResult() == null) { return null; } GoodsQueryResult queryResult = response.getQueryResult(); if (queryResult.getCode() != 200) { return null; } return queryResult; } // 在JDUtil类中新增方法实现商品详情查询接口 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(); BigFieldGoodsReq goodsReq = new BigFieldGoodsReq(); goodsReq.setSkuIds(List.of(Long.parseLong(skuId)).toArray(new Long[0])); // 传入skuId集合 /** * 查询域集合,不填写则查询全部,目目前支持:categoryInfo(类目信息),imageInfo(图片信息), * baseBigFieldInfo(基础大字段信息),bookBigFieldInfo(图书大字段信息), * videoBigFieldInfo(影音大字段信息),detailImages(商详图)*/ //goodsReq.setFields(new String[]{"categoryInfo", "imageInfo", "baseBigFieldInfo"}); // 设置需要查询的字段 goodsReq.setSceneId(2); // 设置场景ID request.setGoodsReq(goodsReq); request.setVersion("1.0"); request.setSignmethod("md5"); request.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); return client.execute(request); } // 修改parseSkuFromUrl方法提取SKU private String parseSkuFromUrl(String url) { Matcher m = Pattern.compile("item.jd.com/(\\d+).html").matcher(url); return m.find() ? m.group(1) : null; } public BigfieldQueryResult queryProductInfo(String skuId) throws Exception { UnionOpenGoodsBigfieldQueryResponse response = getUnionOpenGoodsBigfieldQueryResponse(skuId); if (response == null || response.getQueryResult() == null) { return null; } BigfieldQueryResult queryResult = response.getQueryResult(); if (queryResult.getCode() != 200) { return null; } return queryResult; } // 新增礼金创建方法 public String createGiftCoupon(String skuId, double amount, int quantity, String owner) throws Exception { logger.debug("准备创建礼金:SKU={}, 金额={}元,数量={}, Owner={}", skuId, amount, quantity, owner); // 参数校验 if (skuId == null || amount <= 0 || quantity <= 0) { logger.error("礼金创建失败:参数错误,SKU={}, 金额={}元,数量={}", skuId, amount, quantity); return null; } // 设置默认值 owner = (owner != null) ? owner : "自营"; JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, LPF_APP_KEY_WZ, LPF_SECRET_KEY_WZ); UnionOpenCouponGiftGetRequest request = new UnionOpenCouponGiftGetRequest(); CreateGiftCouponReq couponReq = new CreateGiftCouponReq(); couponReq.setSkuMaterialId(skuId); // 使用SKU或链接 couponReq.setDiscount(amount); couponReq.setAmount(quantity); // 自营的只能设置一天,pop的只能设置7天,默认为自营 String startTime; String endTime; 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")); } else { startTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH")); endTime = LocalDateTime.now().plusDays(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH")); } couponReq.setReceiveStartTime(startTime); couponReq.setReceiveEndTime(endTime); couponReq.setEffectiveDays(7); couponReq.setIsSpu(1); couponReq.setExpireType(1); couponReq.setShare(-1); couponReq.setCouponTitle(skuId); couponReq.setContentMatch(1); request.setCouponReq(couponReq); request.setVersion("1.0"); logger.debug("请求参数:{}", JSON.toJSONString(request)); UnionOpenCouponGiftGetResponse response = client.execute(request); logger.debug("API响应:{}", JSON.toJSONString(response)); if ("200".equals(response.getCode())) { String giftKey = response.getGetResult().getData().getGiftCouponKey(); logger.debug("礼金创建成功:批次ID={}, 返回数据:{}", giftKey, response.getGetResult().getData()); return giftKey; } else { logger.error("礼金创建失败:错误码={}, 错误信息={}", response.getCode(), response.getMsg()); } return null; } // 修改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; } // 新增礼金停止方法(可选) public boolean stopGiftCoupon(String giftKey) throws Exception { JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, LPF_APP_KEY_WZ, LPF_SECRET_KEY_WZ); UnionOpenCouponGiftStopRequest request = new UnionOpenCouponGiftStopRequest(); StopGiftCouponReq couponReq = new StopGiftCouponReq(); couponReq.setGiftCouponKey(giftKey); request.setCouponReq(couponReq); request.setVersion("1.0"); request.setSignmethod("md5"); request.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); UnionOpenCouponGiftStopResponse response = client.execute(request); return "200".equals(response.getCode()); } private boolean isValidAmount(String input) { // 新增:直接处理 null 或空字符串 if (input == null || input.trim().isEmpty()) { return false; } try { double amount = Double.parseDouble(input); return amount >= 1 && amount <= 50; } catch (NumberFormatException e) { return false; } } private boolean isValidQuantity(String input) { return input.matches("^\\d+$"); } private void handleProductOrderRegistration(String fromWxid, String message, UserInteractionState state) { switch (state.getCurrentProductOrderStep()) { case STEP_ORDER_ID: if (!message.matches("^\\d{10,20}$")) { wxUtil.sendTextMessage(fromWxid, "⚠️ 订单号格式错误(需10-20位数字)", 1, fromWxid); return; } state.getCollectedFields().put("orderId", message); state.setCurrentProductOrderStep(STEP_PRODUCT_INFO); wxUtil.sendTextMessage(fromWxid, "请输入商品信息(格式:商品名称-类型编号)\n类型对照:1-家电 2-数码 3-服饰\n示例:格力空调-1", 1, fromWxid); break; case STEP_PRODUCT_INFO: String[] productInfo = message.split("-"); if (productInfo.length != 2 || !productInfo[1].matches("[1-3]")) { wxUtil.sendTextMessage(fromWxid, "❌ 格式错误或类型编号无效", 1, fromWxid); return; } state.getCollectedFields().put("skuName", productInfo[0]); state.getCollectedFields().put("skuType", productInfo[1]); state.setCurrentProductOrderStep(UserInteractionState.ProductOrderStep.STEP_RECIPIENT_INFO); wxUtil.sendTextMessage(fromWxid, "请输入收件信息(格式:姓名-电话-地址)\n示例:张三-13812345678-北京市朝阳区", 1, fromWxid); break; case STEP_RECIPIENT_INFO: String[] recipientInfo = message.split("-"); if (recipientInfo.length < 3) { wxUtil.sendTextMessage(fromWxid, "❌ 格式错误,请按示例格式输入", 1, fromWxid); return; } state.getCollectedFields().put("recipientName", recipientInfo[0]); state.getCollectedFields().put("recipientPhone", recipientInfo[1]); state.getCollectedFields().put("recipientAddress", String.join("-", Arrays.copyOfRange(recipientInfo, 2, recipientInfo.length))); // 生成确认信息 String confirmMsg = buildConfirmMessage(state); wxUtil.sendTextMessage(fromWxid, confirmMsg, 1, fromWxid); state.setCurrentProductOrderStep(STEP_REVIEW_CONFIRM); break; case STEP_REVIEW_CONFIRM: if ("确认".equals(message)) { boolean success = saveFullProductOrder(state, fromWxid); wxUtil.sendTextMessage(fromWxid, success ? "✅ 订单登记成功!" : "❌ 保存失败,请联系管理员", 1, fromWxid); state.reset(); } else { state.setCurrentProductOrderStep(STEP_ORDER_ID); wxUtil.sendTextMessage(fromWxid, "请重新输入订单号:", 1, fromWxid); } break; } } private String buildConfirmMessage(UserInteractionState state) { return "📋 请确认登记信息:\n" + "────────────────\n" + "▪ 订单号:" + state.getCollectedFields().get("orderId") + "\n" + "▪ 商品名称:" + state.getCollectedFields().get("skuName") + "\n" + "▪ 商品类型:" + getTypeDesc(state.getCollectedFields().get("skuType")) + "\n" + "▪ 收件人:" + state.getCollectedFields().get("recipientName") + "\n" + "▪ 联系方式:" + state.getCollectedFields().get("recipientPhone") + "\n" + "▪ 收货地址:" + state.getCollectedFields().get("recipientAddress") + "\n" + "────────────────\n" + "回复【确认】提交,其他内容重新开始"; } private boolean saveFullProductOrder(UserInteractionState state, String fromWxid) { try { ProductOrder order = new ProductOrder(); order.setOrderId(state.getCollectedFields().get("orderId")); order.setSkuName(state.getCollectedFields().get("skuName")); order.setSkuType(Integer.valueOf(state.getCollectedFields().get("skuType"))); order.setRecipientName(state.getCollectedFields().get("recipientName")); order.setRecipientPhone(state.getCollectedFields().get("recipientPhone")); order.setRecipientAddress(state.getCollectedFields().get("recipientAddress")); order.setOrderTime(new Date()); // 设置下单时间为当前时间 order.setWhoOrder(state.getCollectedFields().get("recipientName")); // 关联管理员信息 order.setIsReviewed(false); // 默认是否晒图登记 order.setIsCashbackReceived(false); // 默认是否返现到账 order.setFromWxid(fromWxid); // 设置当前交互的wxid productOrderRepository.save(order); logger.info("订单登记成功:{}", order); return true; } catch (Exception e) { logger.error("订单保存异常:{}", e.getMessage()); return false; } } private String getTypeDesc(String skuType) { return switch (skuType) { case "1" -> "家电"; case "2" -> "数码"; case "3" -> "服饰"; default -> "未知类型"; }; } // 定义一个内部类来存储用户交互状态 @Getter @Setter static class UserInteractionState { private GiftMoneyStep currentStep; // 新增当前步骤字段 private String lastInteractionTime; private ProcessState currentState; private Map collectedFields; // 用于存储收集到的字段值 private String currentField; // 当前正在询问的字段 private ProductOrderStep currentProductOrderStep; public UserInteractionState() { this.lastInteractionTime = LocalDateTime.now().format(DATE_TIME_FORMATTER); this.currentState = INIT; this.collectedFields = new HashMap<>(); this.currentField = null; } public void updateLastInteractionTime() { this.lastInteractionTime = LocalDateTime.now().format(DATE_TIME_FORMATTER); } public void reset() { this.currentState = INIT; this.collectedFields.clear(); this.currentStep = STEP_PRODUCT_LINK; // 明确重置步骤 this.currentProductOrderStep = null; updateLastInteractionTime(); } // 推荐使用枚举管理状态 public enum ProcessState { INIT, GIFT_MONEY_FLOW, DISINFECTANT_CABINET, PRODUCT_ORDER_REGISTRATION, PRODUCT_PROMOTION } public enum GiftMoneyStep { STEP_PRODUCT_LINK, STEP_AMOUNT, STEP_QUANTITY } // 在UserInteractionState类中新增步骤枚举 public enum ProductOrderStep { STEP_ORDER_ID, STEP_PRODUCT_INFO, STEP_RECIPIENT_INFO, STEP_REVIEW_CONFIRM, STEP_CASHBACK_TRACK } } // 限流异常类(需自定义) public static class RateLimitExceededException extends RuntimeException { public RateLimitExceededException(String message) { super(message); } } @Setter @Getter public static class OrderInfo { private String skuName; private Long count; private Long orderId; private Date orderDate; } // 统计指标DTO @Getter @AllArgsConstructor class OrderStats { private long totalOrders; // 总订单数 private long validOrders; // 有效订单数(不含取消) private long paidOrders; // 已付款订单 private double paidCommission; // 已付款佣金 private long pendingOrders; // 待付款订单 private double pendingCommission; // 待付款佣金 private long canceledOrders; // 已取消订单 private long completedOrders; // 已完成订单 private double completedCommission;// 已完成佣金 private long violations; // 违规订单数 private double violationCommission;// 违规佣金 } }