package cn.van.business.util;
import cn.van.business.model.jd.OrderRow;
import cn.van.business.repository.OrderRowRepository;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
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.GoodsService.request.query.GoodsReq;
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.promotionbysubunioni.PromotionService.request.get.PromotionCodeReq;
import com.jd.open.api.sdk.request.kplunion.UnionOpenCouponGiftGetRequest;
import com.jd.open.api.sdk.request.kplunion.UnionOpenGoodsQueryRequest;
import com.jd.open.api.sdk.request.kplunion.UnionOpenPromotionBysubunionidGetRequest;
import com.jd.open.api.sdk.response.kplunion.UnionOpenCouponGiftGetResponse;
import com.jd.open.api.sdk.response.kplunion.UnionOpenGoodsQueryResponse;
import com.jd.open.api.sdk.response.kplunion.UnionOpenPromotionBysubunionidGetResponse;
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.text.DateFormat;
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.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.INIT;
import static cn.van.business.util.WXUtil.*;
/**
* @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 = 10;
private static final String WENAN_FANAN_LQD = "提供方法自己下单\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_FANAN_HG = "手把手教你实现超值下单,无需依赖他人!全程使用个人专属账号操作,所见即所得,页面标价即为最终到手价。别担心操作难题,我会全程贴心指导,每一步都细致讲解,助你轻松下单。后续若出现任何质量问题,凭借个人账号就能直接对接JD官方售后,售后无忧。\n" + "更新\n" + "采用自主账号下单模式,官方店铺商品随心购,专业方法全程提供!\n" + "——————————————————————————————————————————\n" + "诚邀同行建立长期合作关系,海量独家家电优惠线报倾囊相授!\n" + "借助这些优质家电线报,无需寻求代购代下服务,自己就能轻松下单,订单信息与收益牢牢掌握在手中。\n" + "一次加入,终身受益!涵盖家电帮、雷神价、韭菜帮、河南 & 湖南帮等众多渠道,还有各类暗号帮后返等内部专属家电优惠信息一网打尽。\n" + "JD采销团队会不定时发放独家隐藏优惠券,市面上那些令人心动的JD家电低价好物,大多都源自这些渠道!\n" + "2025 年家电选购新趋势,依托线报下单,轻松省下千元开支,开启超值购物之旅!";
private static final String WENAN_ZCXS = "\n" + "购买后,两小时内出库,物流会电话联系您,同时生成京东官方安装单。送装一体,无需担心。\n" + "\n" + "\n" + "1:全新正品,原包装未拆封(京东商城代购,就近直发)\n" + "2:可提供下单运单号与电子发票(发票在收到货后找我要)。\n" + "3:收货时查看是否有质量或运损问题。可拍照让京东免费申请换新。\n" + "4:下单后非质量问题不支持退款退货,强制退扣100元。\n" + "5:价格有浮动,不支持补差价,谢谢理解。\n" + "6:全国联保,全国统一安装标准。支持官方 400,服务号查询,假一赔十。\n";
private static final String FANAN_COMMON = "\n复制到薇,依次领券,到J东APP结算\n" + "换新选生活电器-除螨仪,可免回收\n";
/**
* 内部单号:
* 分销标记(标记用,勿改):
* 型号:
*
* 价格:
*
* 后返:
*
* 地址:
*
* 物流单号:
*
* 订单号:
*/
private static final String WENAN_D = "单:\n" + "{单号} \n" + "分销标记(标记用,勿改):{分销标记}\n" + "型号:\n" + "{型号}" + "\n" + "链接:\n" + "{链接}" + "\n" + "下单付款:\n" + "\n" + "后返金额:\n" + "\n" + "地址:\n" + "{地址}" + "\n" + "物流链接:\n" + "\n" + "订单号:\n" + "\n" + "下单人:\n" + "\n";
final WXUtil wxUtil;
private final StringRedisTemplate redisTemplate;
private final OrderRowRepository orderRowRepository;
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) {
this.redisTemplate = redisTemplate;
this.orderRowRepository = orderRowRepository;
this.wxUtil = wxUtil;
this.orderUtil = orderUtil;
}
private 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 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 String convertAddress(String input) {
try {
// 正则表达式分解输入内容
Pattern pattern = Pattern.compile("^(.+?)\\[(\\d+)\\]\\s+(\\d+)\\s+(.+)\\[\\d+]$");
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
// 提取各部分内容
String name = matcher.group(1);
String code = matcher.group(2);
String phone = matcher.group(3);
String address = matcher.group(4).trim();
// 构造新地址格式
return String.format("%s 13068923963 %s联系客户%s转%s", name, address, phone, code);
}
} catch (Exception ignored) {
return "地址格式不匹配";
}
return "地址格式不匹配";
}
public void sendOrderToWxByOrderDefault(String order, String fromWxid) {
logger.info("执行 sendOrderToWxByOrderDefault 方法,order: {}, fromWxid: {}", order, fromWxid);
handleUserInteraction(fromWxid, order);
// 具体逻辑
}
public void xb(String message, String fromWxid) {
logger.info("执行 线报处理 方法,order: {}", message);
if (message.contains("https://u.jd")) {
String chatRoomRemark = chatRoom_xb.get(fromWxid);
wxUtil.sendTextMessage(chatRoom_BY, "线报来源 \n " + chatRoomRemark + "\n\n " + message, 1, chatRoom_BY, true);
// 2. 生成推广内容
HashMap> contentResult = generatePromotionContent(message, true);
// 3. 发送文本内容
for (String text : contentResult.get("text")) {
wxUtil.sendTextMessage(chatRoom_BY, text, 1, chatRoom_BY, true);
}
}
// 具体逻辑
}
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 sendOrderToWxByOrderPDD(String order, String fromWxid) {
String convertAddress = convertAddress(order);
wxUtil.sendTextMessage(fromWxid, convertAddress, 0, fromWxid, false);
}
/**
* 接收京粉指令指令
*/
public void sendOrderToWxByOrderJD(String order, String fromWxid) {
int[] param = {-1};
List superAdmins = getSuperAdmins(fromWxid);
List unionIds = new ArrayList<>();
for (WXUtil.SuperAdmin superAdmin : superAdmins) {
String unionId = superAdmin.getUnionId();
unionIds.add(Long.valueOf(unionId));
}
List orderRows = orderRowRepository.findByValidCodeNotInAndUnionIdIn(param, unionIds);
/**
* 菜单:
* 今日统计
* 昨日统计
* 最近七天统计
* 最近一个月统计
* 今天订单
* 昨天订单
* */
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, false);
}
}
}
/**
* 接收京粉指令指令
* 高级菜单
*/
public void sendOrderToWxByOrderJDAdvanced(String order, String fromWxid) {
int[] param = {-1};
List superAdmins = getSuperAdmins(fromWxid);
List unionIds = new ArrayList<>();
for (WXUtil.SuperAdmin superAdmin : superAdmins) {
String unionId = superAdmin.getUnionId();
unionIds.add(Long.valueOf(unionId));
}
List orderRows = orderRowRepository.findByValidCodeNotInAndUnionIdIn(param, unionIds);
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())).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 (unionIds.contains(orderRow.getUnionId())) {
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.findBySkuIdAndUnionIdIn(validCodes, Long.parseLong(order), unionIds);
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, false);
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, false);
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, false);
}
}
}
/**
* 接口描述:通过商品链接、领券链接、活动链接获取普通推广链接或优惠券二合一推广链接
* jd.union.open.promotion.bysubunionid.get
*/
String transfer(String url, String giftCouponKey) {
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);
if (giftCouponKey != null) {
promotionCodeReq.setGiftCouponKey(giftCouponKey);
}
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 = 10000) // 每10分钟执行一次
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());
}
});
}
private void handleUserInteraction(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = loadOrCreateState(key);
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, false);
}
} catch (Exception e) {
logger.error("流程处理异常 - 用户: {}, 状态: {}", fromWxid, state, e);
resetState(fromWxid, state);
wxUtil.sendTextMessage(fromWxid, "处理异常,请重新开始", 1, fromWxid, false);
} 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—withOutPic");
wxUtil.sendTextMessage(wxid, "请输入推广方案 \n " + "(指令: 转 不返回图文 \n 文案 带图文 ):", 1, wxid, false);
logger.info("进入转链流程 - 用户: {}", wxid);
} else if ("文案".equals(message)) {
state.setCurrentState(UserInteractionState.ProcessState.PRODUCT_PROMOTION);
state.setCurrentField("content—withPic");
wxUtil.sendTextMessage(wxid, "请输入推广方案 \n " + "(指令: 转 不返回图文 \n 文案 带图文 ):", 1, wxid, false);
logger.info("进入转链流程 - 用户: {}", wxid);
}
}
private void handlePromotionState(String wxid, String message, UserInteractionState state) {
if ("content—withOutPic".equals(state.getCurrentField())) {
processContentInput(wxid, message, state, false);
} else if ("content—withPic".equals(state.getCurrentField())) {
processContentInput(wxid, message, state, true);
} else if ("confirm".equals(state.getCurrentField())) {
handleGiftMoneyConfirmation(wxid, message, state);
}
}
private void handleGiftMoneyFlow(String wxid, String message, UserInteractionState state) {
if (!state.validateStep(state.getCurrentStep())) {
wxUtil.sendTextMessage(wxid, "流程顺序异常,请重新开始", 1, wxid, false);
return;
}
switch (state.getCurrentStep()) {
case STEP_AMOUNT:
processAmountInput(wxid, message, state);
break;
case STEP_QUANTITY:
processQuantityInput(wxid, message, state);
break;
default:
state.setCurrentStep(STEP_AMOUNT);
wxUtil.sendTextMessage(wxid, "[Packet] 请输入金额(1-50元):", 1, wxid, false);
}
}
private void processAmountInput(String wxid, String message, UserInteractionState state) {
if (!isValidAmount(message)) {
wxUtil.sendTextMessage(wxid, "金额格式错误,请输入1-50元", 1, wxid, false);
return;
}
state.getCollectedFields().put("amount", message);
state.setCurrentStep(STEP_QUANTITY);
wxUtil.sendTextMessage(wxid, "请输入数量(1-100):", 1, wxid, false);
}
private void processQuantityInput(String wxid, String message, UserInteractionState state) {
if (!isValidQuantity(message)) {
wxUtil.sendTextMessage(wxid, "数量格式错误,请输入1-100的整数", 1, wxid, false);
return;
}
// 从缓存获取商品数据
String productDataJson = cacheMap.get("productData" + wxid);
String finalWenAn = cacheMap.get("finalWenAn" + wxid);
if (productDataJson == null || finalWenAn == null) {
logger.error("数据丢失 - productData: {}, finalWenAn: {}", productDataJson != null, finalWenAn != null);
wxUtil.sendTextMessage(wxid, "数据丢失,请重新开始流程", 1, wxid, false);
resetState(wxid, state);
return;
}
try {
// 解析商品数据
/**
* // 取出来的每一个String都是JSONObject
* List data = contentResult.get("data");
* JSONArray jsonObjectArr = new JSONArray(data);
*
*
* cacheMap.put("productData" + wxid, jsonObjectArr.toJSONString());*/
List productList = JSON.parseArray(productDataJson, String.class);
double amount = Double.parseDouble(state.getCollectedFields().get("amount"));
int quantity = Integer.parseInt(message);
StringBuilder result = new StringBuilder();
String updatedContent = finalWenAn;
for (String productJSONString : productList) {
// 序列化好了的
JSONObject product = JSON.parseObject(productJSONString);
String skuId = product.getString("materialUrl");
String owner = product.getString("owner");
String skuName = product.getString("skuName");
String originalUrl = product.getString("url");
String giftKey = createGiftCoupon(skuId, amount, quantity, owner, skuName);
if (giftKey == null) {
result.append(" ").append(skuName).append(" 礼金创建失败\n");
continue;
}
String transferUrl = transfer(skuId, giftKey);
if (transferUrl != null) {
updatedContent = updatedContent.replace(originalUrl, transferUrl);
result.append(" ").append(skuName).append(" 礼金创建成功\n");
} else {
result.append(" ").append(skuName).append(" 转链失败\n");
}
}
wxUtil.sendTextMessage(wxid, result.toString(), 1, wxid, false);
wxUtil.sendTextMessage(wxid, updatedContent, 1, wxid, true);
} catch (Exception e) {
logger.error("礼金处理异常", e);
wxUtil.sendTextMessage(wxid, "处理异常:" + e.getMessage(), 1, wxid, false);
} finally {
resetState(wxid, state);
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
}
}
/**
* 处理用户输入的推广方案内容
*
* @param wxid 用户微信ID
* @param message 用户输入的方案内容
* @param state 当前交互状态
* @param b
*/
private void processContentInput(String wxid, String message, UserInteractionState state, boolean withPic) {
try {
// 1. 清除旧缓存
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
// 2. 生成推广内容
HashMap> contentResult = generatePromotionContent(message, false);
if (withPic) {
// 3. 发送文本内容
for (String text : contentResult.get("text")) {
wxUtil.sendTextMessage(wxid, text, 1, wxid, true);
}
// 4. 发送图片(如果有)
List images = contentResult.get("images");
if (images != null) {
for (String imageUrl : images) {
if (imageUrl != null) {
wxUtil.sendImageMessage(wxid, imageUrl);
}
}
}
} else {
// 3. 发送文本内容
for (String text : contentResult.get("text")) {
if (text.contains("链接类型")) {
wxUtil.sendTextMessage(wxid, text, 1, wxid, true);
}
}
}
// 5. 缓存商品数据
if (!contentResult.get("data").isEmpty()) {
// 取出来的每一个String都是JSONObject
List data = contentResult.get("data");
JSONArray jsonObjectArr = new JSONArray(data);
cacheMap.put("productData" + wxid, jsonObjectArr.toJSONString());
cacheMap.put("finalWenAn" + wxid, contentResult.get("finalWenAn").get(0));
state.setCurrentField("confirm");
wxUtil.sendTextMessage(wxid, "检测到" + jsonObjectArr.size() + "个商品\n" + "是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否", 1, wxid, false);
} else {
wxUtil.sendTextMessage(wxid, "未获取到商品数据,请检查链接格式", 1, wxid, false);
state.reset();
}
} catch (Exception e) {
logger.error("处理推广内容异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理内容时发生异常,请重试", 1, wxid, false);
state.reset();
}
}
/**
* 处理用户对礼金开通的确认
*
* @param wxid 用户微信ID
* @param message 用户回复(1或2)
* @param state 当前交互状态
*/
private void handleGiftMoneyConfirmation(String wxid, String message, UserInteractionState state) {
try {
String cachedData = cacheMap.get("productData" + wxid);
JSONArray jsonObjectArr = JSON.parseArray(cachedData);
if ("1".equals(message)) {
// 用户选择开通礼金
state.setCurrentState(UserInteractionState.ProcessState.GIFT_MONEY_FLOW);
state.setCurrentStep(STEP_AMOUNT);
state.getCollectedFields().clear();
wxUtil.sendTextMessage(wxid, "当前选择" + jsonObjectArr.size() + "个商品\n" + "请输入开通金额(1-50元,支持小数点后两位):\n" + "示例:20.50", 1, wxid, false);
} else if ("2".equals(message)) {
// 用户选择不开通礼金
//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, false);
// wxUtil.sendTextMessage(wxid, finalWenAn, 1, wxid, true);
//} else {
// wxUtil.sendTextMessage(wxid, "未找到商品信息,请重新开始流程", 1, wxid, false);
//}
state.reset();
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
wxUtil.sendTextMessage(wxid, "已清除商品信息缓存,可输入新的文案", 1, wxid, false);
// 进入输入方案流程
state.setCurrentState(INIT);
state.setCurrentField("content");
} else {
wxUtil.sendTextMessage(wxid, "处理请求时发生异常,请重试", 1, wxid, false);
state.reset();
}
} catch (Exception e) {
logger.error("处理礼金确认异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理请求时发生异常,请重试", 1, wxid, false);
state.reset();
}
}
private void resetState(String wxid, UserInteractionState state) {
state.reset();
saveState(INTERACTION_STATE_PREFIX + wxid, state);
}
public static List extractPrices(String text) {
List prices = new ArrayList<>();
if (text.contains("🔥")) {
// 处理包含 🔥 的文本
Pattern pattern = Pattern.compile("\\d+(\\.\\d+)?💰");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String priceStr = matcher.group().replace("💰", "");
prices.add(priceStr);
}
} else {
// 处理不包含 🔥 的文本
Pattern pattern = Pattern.compile("💰(\\d+)");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
prices.add(matcher.group(1));
}
}
return prices;
}
/**
* 生成转链和方案的方法
*
* @param message 方案内容,包含商品链接
* @return 处理后的方案,附带商品信息
*/
public synchronized HashMap> generatePromotionContent(String message, Boolean isCj) {
HashMap> finallyMessage = new HashMap<>();
List textList = new ArrayList<>();
List imagesList = new ArrayList<>();
List dataList = new ArrayList<>();
// 最终的方案
List finalWenAn = new ArrayList<>();
// 提取方案中的所有 u.jd.com 链接
List urlList = new ArrayList<>();
List priceList = new ArrayList<>();
// 提取方案中的所有 u.jd.com 链接
List urls = extractUJDUrls(message);
if (urls.isEmpty()) {
textList.add("方案中未找到有效的商品链接,请检查格式是否正确。");
finallyMessage.put("text", textList);
return finallyMessage;
}
/**
* {
* "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 = null;
ArrayList> resultList = new ArrayList<>();
String format = null;
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
if (!isCj) {
try {
priceList = extractPrices(message);
finallyMessage.put("priceList", priceList);
} catch (Exception ignored) {
}
}
for (String url : urls) {
try {
// 重置。不然会一直追加文本内容
couponInfo = new StringBuilder();
// 新建格式好日期
format = dateFormat.format(new Date());
// 查询商品信息
GoodsQueryResult productInfo = queryProductInfoByUJDUrl(url);
if (productInfo == null || productInfo.getCode() != 200) {
couponInfo.append("链接查询失败:").append(url).append("\n");
continue;
}
long totalCount = productInfo.getTotalCount();
couponInfo.append(" ").append(url).append("\n");
if (totalCount == 0) {
couponInfo.append("链接类型:优惠券\n\n");
} else {
urlList.add(url);
couponInfo.append("链接类型:商品\n\n");
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(productInfo.getData()[0]));
jsonObject.put("url", url);
dataList.add(jsonObject.toString());
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());
String replaceAll = itemMap.get("skuName").replaceAll("以旧|政府|换新|领取|国家|补贴|15%|20%|国补|立减|【|】", "");
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(itemMap.get("shopName")).append("\n").append("标题: ").append(replaceAll).append("\n").append("自营 POP: ").append(itemMap.get("owner").equals("g") ? " 自营 " : " POP ").append("\n").append("佣金比例: ").append(itemMap.get("commissionShare")).append("\n").append("佣金: ").append(itemMap.get("commission")).append("\n");
//StringBuilder images = new StringBuilder();
if (productInfo.getData()[0].getImageInfo() != null) {
//images.append(" ").append("图片信息:\n");
//int index = 1;
if (!isCj) {
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);
String title = "";
if (!isCj) {
try {
if (!message.equals(url)) {
String[] lines = message.split("\\r?\\n");
if (lines.length > 0) {
title = lines[0];
// 有的换行了
if (lines[1].length() > 3 && !lines[1].contains("u.jd")) {
title = title + lines[1];
}
logger.info("文案首行 {}", title);
title = title.replaceAll("@|所有人", "");
}
}
} catch (Exception e) {
logger.error("文案首行异常", e);
}
/*直接生成闲鱼的商品文案*/
StringBuilder sb1 = new StringBuilder();
sb1.append("(教你买) ").append(title).append(replaceAll).append("\n").append(WENAN_FANAN_LQD.replaceAll("更新", format + "更新"));
//textList.add("闲鱼方案的文案:\n");
textList.add(String.valueOf(sb1));
StringBuilder sb2 = new StringBuilder();
sb2.append("(一键代下) ").append(title).append(replaceAll).append("\n").append(WENAN_ZCXS);
//textList.add("闲鱼正常销售:\n");
textList.add(String.valueOf(sb2));
StringBuilder sb3 = new StringBuilder();
sb3.append("(教你买) ").append(title).append(replaceAll).append("\n").append(WENAN_FANAN_HG.replaceAll("更新", format + "更新"));
textList.add(String.valueOf(sb3));
}
}
textList.add(String.valueOf(couponInfo));
finallyMessage.put("data", dataList);
finallyMessage.put("urlList", urlList);
} catch (Exception e) {
logger.error("处理商品链接时发生异常:{}", url, e);
couponInfo.append(" 处理商品链接时发生异常:").append(url).append("\n");
textList.add(String.valueOf(couponInfo));
finallyMessage.put("text", textList);
}
}
/**
* 因为在这里转链返回,没有什么意义,后面走转链不转链,会重新发方案
* */
StringBuilder wenan;
wenan = new StringBuilder().append(format).append(FANAN_COMMON).append(message);
finalWenAn.add(String.valueOf(wenan));
finallyMessage.put("text", textList);
finallyMessage.put("images", imagesList);
finallyMessage.put("finalWenAn", finalWenAn);
return finallyMessage;
}
/**
* 提取方案中的所有 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;
}
// 新增礼金创建方法
public String createGiftCoupon(String skuId, double amount, int quantity, String owner, String skuName) 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 : "g";
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"));
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.setIsSpu(1);
couponReq.setExpireType(1);
couponReq.setShare(-1);
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);
request.setVersion("1.0");
logger.debug("请求参数:{}", JSON.toJSONString(request));
UnionOpenCouponGiftGetResponse response = client.execute(request);
logger.debug("API响应:{}", JSON.toJSONString(response));
if ("0".equals(response.getCode()) && response.getGetResult().getCode() == 200) {
String giftKey = response.getGetResult().getData().getGiftCouponKey();
logger.debug("礼金创建成功:批次ID={}, 返回数据:{}", giftKey, response.getGetResult().getData());
return giftKey;
} else {
logger.error("礼金创建失败:错误码={}, 错误信息={}", response.getCode(), response.getMsg());
}
return null;
}
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) {
if (input == null || input.trim().isEmpty()) {
logger.error("数量为空或无效: {}", input);
return false;
}
try {
int quantity = Integer.parseInt(input);
if (quantity < 1 || quantity > 100) {
logger.error("数量超出范围: {}", quantity);
return false;
}
return true;
} catch (NumberFormatException e) {
logger.error("数量格式错误: {}", input, e);
return false;
}
}
// 将状态存储迁移到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);
}
public void sendOrderToWxByOrderD(String order, String fromWxid) {
if (order == null || order.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "输入格式为:\n 单\n分销标记\n型号\n转链链接\n数量\n地址", 1, fromWxid, false);
return;
}
String[] split = order.split("\n");
logger.info("sendOrderToWxByOrderD.split 数组长度{}", split.length);
for (String s : split) {
logger.info("sendOrderToWxByOrderD.split 内容 {}", s);
}
if (split.length != 6) {
wxUtil.sendTextMessage(fromWxid, "输入格式为:\n 单\n分销标记\n型号\n转链链接\n数量\n地址", 1, fromWxid, false);
return;
}
String temp = WENAN_D;
// 今天的日期
String today = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd "));
String redisKey = "order_count:" + today;
Integer count;
try {
// 从 Redis 获取当前日期的订单计数器
String s = redisTemplate.opsForValue().get(redisKey);
count = s != null ? Integer.parseInt(s) : 1;
logger.info("sendOrderToWxByOrderD.count beforeFor {}", count);
Integer num = 1;
try {
num = Integer.valueOf(split[4].trim());
} catch (NumberFormatException e) {
logger.error("sendOrderToWxByOrderD 订单数量格式错误,请输入正确的数字", e);
}
for (int i = 0; i < num; i++) {
count++; // 递增计数器
logger.info("sendOrderToWxByOrderD.count inFor {}", count);
// 将新的计数器值保存回 Redis
redisTemplate.opsForValue().set(redisKey, String.valueOf(count), 1, TimeUnit.DAYS);
// 生成订单号
String orderID = today + String.format("%03d", count); // 格式化为 3 位数字,不足补零
// 替换模板中的占位符
temp = temp.replace("{单号}", orderID);
temp = temp.replace("{分销标记}", split[1]);
temp = temp.replace("{链接}", split[3]);
temp = temp.replace("{地址}", split[5]);
temp = temp.replace("{型号}", split[2]);
temp = temp.replaceAll("[|]", "");
// 发送订单信息到微信
wxUtil.sendTextMessage(fromWxid, temp, 1, fromWxid, true);
}
} catch (Exception e) {
logger.error("生成订单号时发生异常 - 用户: {}, 日期: {}", fromWxid, today, e);
wxUtil.sendTextMessage(fromWxid, "生成订单号时发生异常,请重试", 1, fromWxid, false);
}
}
public void sendOrderToWxByOrderP(String order, String fromWxid) {
// 检查是否命中“评”指令
if ("".equals(order)) {
// 初始化用户交互状态
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = loadOrCreateState(key);
try {
// 设置当前状态为生成评论流程
state.setCurrentState(UserInteractionState.ProcessState.COMMENT_GENERATION);
state.setCurrentField("commentTypeSelection");
// 提示用户选择评论类型
wxUtil.sendTextMessage(fromWxid, "请选择要生成的评论类型:\n回复 1 - 消毒柜评论\n回复 2 - 油烟机评论", 1, fromWxid, false);
logger.info("进入生成评论流程 - 用户: {}", fromWxid);
} catch (Exception e) {
logger.error("生成评论流程初始化异常 - 用户: {}, 状态: {}", fromWxid, state, e);
wxUtil.sendTextMessage(fromWxid, "处理异常,请重新开始", 1, fromWxid, false);
resetState(fromWxid, state);
} finally {
saveState(key, state);
}
} else {
// 如果未命中“评”指令,检查是否在生成评论流程中
handleCommentInteraction(fromWxid, order);
}
}
/**
* 处理生成评论流程中的用户交互
*/
private void handleCommentInteraction(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = loadOrCreateState(key);
try {
// 检查当前状态是否为生成评论流程
if (!"commentTypeSelection".equals(state.getCurrentField())) {
return;
}
// 根据用户输入生成对应的评论
switch (message) {
case "1":
generateComment(fromWxid, "消毒柜");
break;
case "2":
generateComment(fromWxid, "油烟机");
break;
default:
wxUtil.sendTextMessage(fromWxid, "无效的选择,请回复 1 或 2", 1, fromWxid, false);
return;
}
} catch (Exception e) {
logger.error("生成评论流程处理异常 - 用户: {}, 状态: {}", fromWxid, state, e);
wxUtil.sendTextMessage(fromWxid, "处理异常,请重新开始", 1, fromWxid, false);
resetState(fromWxid, state);
} finally {
saveState(key, state);
}
}
/**
* 生成评论内容
*/
private void generateComment(String fromWxid, String productType) {
// 这里可以调用缓存或AI生成文案,目前先返回固定模板
String commentTemplate = "这是一条关于%s的评论:\n" + "1. 性能非常出色,使用体验极佳。\n" + "2. 安装方便,操作简单。\n" + "3. 售后服务到位,值得信赖。\n" + "感谢您的购买与支持!";
String comment = String.format(commentTemplate, productType);
wxUtil.sendTextMessage(fromWxid, comment, 1, fromWxid, false);
// 重置状态
resetState(fromWxid, loadOrCreateState(INTERACTION_STATE_PREFIX + fromWxid));
}
// 定义一个内部类来存储用户交互状态
@Getter
@Setter
static class UserInteractionState {
private List selectedSkuIds; // 新增字段,存储多个商品SKU ID
private List selectedSkuNames; // 新增字段,存储多个商品名称
private GiftMoneyStep currentStep; // 新增当前步骤字段
private String lastInteractionTime;
private ProcessState currentState;
private Map collectedFields; // 用于存储收集到的字段值
private String currentField; // 当前正在询问的字段
public UserInteractionState() {
this.currentStep = STEP_CONFIRM_GIFT; // 初始
this.lastInteractionTime = LocalDateTime.now().format(DATE_TIME_FORMATTER);
this.currentState = INIT;
this.collectedFields = new HashMap<>();
this.currentField = null;
this.selectedSkuIds = new ArrayList<>();
this.selectedSkuNames = new ArrayList<>();
}
public void updateLastInteractionTime() {
this.lastInteractionTime = LocalDateTime.now().format(DATE_TIME_FORMATTER);
}
public void reset() {
this.currentState = INIT;
this.collectedFields.clear();
this.currentStep = STEP_CONFIRM_GIFT; // 明确重置步骤
updateLastInteractionTime();
}
// 在UserInteractionState类中增加状态校验方法
public boolean validateStep(GiftMoneyStep expectedStep) {
return this.currentStep != null && this.currentStep.getCode() == expectedStep.getCode();
}
// 推荐使用枚举管理状态
public enum ProcessState {
INIT, GIFT_MONEY_FLOW, PRODUCT_PROMOTION, COMMENT_GENERATION
}
public enum GiftMoneyStep {
STEP_CONFIRM_GIFT(0), // 显式指定序号
STEP_AMOUNT(1), STEP_QUANTITY(2);
private final int code;
GiftMoneyStep(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
}
// 限流异常类(需自定义)
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;// 违规佣金
}
}