Files
Jarvis_java/src/main/java/cn/van/business/util/JDUtil.java

1810 lines
86 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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.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.net.URL;
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.super_admins;
/**
* @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 static final String WENAN_FANAN = "提供方法自己下单\n" +
"全程都是自己的账号下单\n" +
"标价就是下单的到手价\n" +
"(不包含教程费)\n" +
"本人有耐心,会一步一步提供教程\n" +
"以后有什么质量问题都是用自己的账号走京东售后\n" +
"\n" +
"更新\n" +
"\n" +
"用你自己的账号下单\n" +
"官方店铺 提供方法自己下单\n" +
"不含教程费,价格不对不收费\n" +
"教程费用私聊我问价\n" +
"——————————————————————————————————————————\n" +
"同行可长久合作,可提供神级家电线报\n" +
"\n" +
"配合家电线报可以自己下单,不用找代购和代下,订单和利润都掌握在自己手中。\n" +
"\n" +
"一次入会永久使用,包含家电帮,雷神价,韭菜帮,河南&湖南帮,各种暗号帮后返等内部独家家电线报\n" +
"\n" +
"JD采销采购不定时发放独家优惠券\n" +
"\n" +
"基本上你能看到的京东家电低价都是从这些渠道里面出来。\n" +
"\n" +
"2025年家电项目新方向配合家电线报下单秒省1K+。";
private static final String WENAN_ZCXS = "\n" +
"购买后,两小时内出库,物流会电话联系您,同时生成京东官方安装单。送装一体,无需担心。\n" +
"\n" +
"\n" +
"1:全新正品,原包装未拆封(京东商城代购,就近直发)\n" +
"2:可提供下单运单号与电子发票(发票在收到货后找我要)。\n" +
"3:收货时查看是否有质量或运损问题。可拍照让京东免费申请换新。\n" +
"4:下单后非质量问题不支持退款退货强制退扣100元。\n" +
"5:价格有浮动,不支持补差价,谢谢理解。\n" +
"6:全国联保,全国统一安装标准。支持官方 400服务号查询假一赔十。\n";
final WXUtil wxUtil;
private final StringRedisTemplate redisTemplate;
private final OrderRowRepository orderRowRepository;
private final OrderUtil orderUtil;
// 添加ObjectMapper来序列化和反序列化UserInteractionState
private final ObjectMapper objectMapper = new ObjectMapper();
private final ConcurrentHashMap<String, UserInteractionState> userInteractionStates = new ConcurrentHashMap<>();
private HashMap<String, String> cacheMap = new HashMap<>();
// 构造函数中注入StringRedisTemplate
@Autowired
public JDUtil(StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, WXUtil wxUtil, OrderUtil orderUtil) {
this.redisTemplate = redisTemplate;
this.orderRowRepository = orderRowRepository;
this.wxUtil = wxUtil;
this.orderUtil = orderUtil;
}
private static List<OrderRow> filterOrdersByDate(List<OrderRow> 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<OrderRow> getStreamForWeiGui(List<OrderRow> 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<OrderRow> 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<OrderRow> orderRows = orderRowRepository.findByValidCodeNotInOrderByOrderTimeDescAndUnionId(param, Long.valueOf(unionId));
/**
* 菜单:
* 今日统计
* 昨日统计
* 最近七天统计
* 最近一个月统计
* 今天订单
* 昨天订单
* */
List<StringBuilder> 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<OrderRow> todayOrders = filterOrdersByDate(orderRows, 0);
OrderStats stats = calculateStats(todayOrders);
contents.add(buildStatsContent("今日统计", stats));
break;
}
case "昨日统计": {
List<OrderRow> yesterdayOrders = filterOrdersByDate(orderRows, 1);
OrderStats stats = calculateStats(yesterdayOrders);
contents.add(buildStatsContent("昨日统计", stats));
break;
}
case "三日统计": {
List<OrderRow> last3DaysOrders = filterOrdersByDate(orderRows, 3);
OrderStats stats = calculateStats(last3DaysOrders);
contents.add(buildStatsContent("三日统计", stats));
break;
}
case "七日统计": {
List<OrderRow> last7DaysOrders = filterOrdersByDate(orderRows, 7);
OrderStats stats = calculateStats(last7DaysOrders);
contents.add(buildStatsContent("七日统计", stats));
break;
}
case "一个月统计": {
List<OrderRow> last30DaysOrders = filterOrdersByDate(orderRows, 30);
OrderStats stats = calculateStats(last30DaysOrders);
contents.add(buildStatsContent("一个月统计", stats));
break;
}
case "两个月统计": {
List<OrderRow> last60DaysOrders = filterOrdersByDate(orderRows, 60);
OrderStats stats = calculateStats(last60DaysOrders);
contents.add(buildStatsContent("两个月统计", stats));
break;
}
case "三个月统计": {
List<OrderRow> last90DaysOrders = filterOrdersByDate(orderRows, 90);
OrderStats stats = calculateStats(last90DaysOrders);
contents.add(buildStatsContent("三个月统计", stats));
break;
}
case "这个月统计": {
// 计算出距离1号有几天
int days = LocalDate.now().getDayOfMonth();
List<OrderRow> 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<OrderRow> lastMonthOrders = filterOrdersByDate(orderRows, lastMonth.lengthOfMonth() + days);
List<OrderRow> 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<OrderRow> todayOrders = filterOrdersByDate(orderRows, 0);
// 订单总数,已付款,已取消,佣金总计
OrderStats stats = calculateStats(todayOrders);
contents.add(buildStatsContent("今日统计", stats));
if (!todayOrders.isEmpty()) {
orderUtil.orderToWxBatch(todayOrders);
}
break;
}
case "昨日订单": {
content = new StringBuilder();
List<OrderRow> yesterdayOrders = filterOrdersByDate(orderRows, 1);
List<OrderRow> 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<OrderRow> last7DaysOrders = filterOrdersByDate(orderRows, 1);
List<OrderRow> 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<OrderRow> orderRows = orderRowRepository.findByValidCodeNotInOrderByOrderTimeDescAndUnionId(param, Long.valueOf(unionId));
List<StringBuilder> 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<OrderRow> filterOrdersByDays = filterOrdersByDate(orderRows, daysInt);
content.append("违规排行:");
content.append(daysInt).append("").append("\r\n");
Map<String, Long> 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<String, List<OrderInfo>> 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<Map.Entry<String, Long>> sortedViolationCounts = skuIdViolationCountMap.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList());
Integer num = 0;
for (Map.Entry<String, Long> 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<OrderInfo> 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<OrderRow> 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<OrderRow> 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<OrderRow> 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<OrderRow> 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, 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 = 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;
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);
}
} catch (Exception e) {
logger.error("流程处理异常 - 用户: {}, 状态: {}", fromWxid, state, e);
resetState(fromWxid, state);
wxUtil.sendTextMessage(fromWxid, "处理异常,请重新开始", 1, fromWxid);
} finally {
saveState(key, state);
}
userInteractionStates.put(key, state);
logger.debug("Updated interaction state for user {}: {}", fromWxid, JSON.toJSONString(state));
}
private UserInteractionState loadOrCreateState(String key) {
UserInteractionState state = loadState(key);
if (state == null) {
state = new UserInteractionState();
logger.debug("创建新交互状态: {}", key);
} else if (isStateExpired(state)) {
redisTemplate.delete(key);
state = new UserInteractionState();
logger.debug("状态已过期,重置: {}", key);
}
return state;
}
private boolean isStateExpired(UserInteractionState state) {
LocalDateTime lastTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER);
return ChronoUnit.MINUTES.between(lastTime, LocalDateTime.now()) > TIMEOUT_MINUTES;
}
private void handleInitState(String wxid, String message, UserInteractionState state) {
if ("".equals(message)) {
state.setCurrentState(UserInteractionState.ProcessState.PRODUCT_PROMOTION);
state.setCurrentField("content");
wxUtil.sendTextMessage(wxid, "请输入推广方案(包含商品链接):", 1, wxid);
logger.info("进入转链流程 - 用户: {}", wxid);
}
}
private void handlePromotionState(String wxid, String message, UserInteractionState state) {
if ("content".equals(state.getCurrentField())) {
processContentInput(wxid, message, state);
} else if ("confirm".equals(state.getCurrentField())) {
handleGiftMoneyConfirmation(wxid, message, state);
}
}
private void handleGiftMoneyFlow(String wxid, String message, UserInteractionState state) {
if (!state.validateStep(state.getCurrentStep())) {
wxUtil.sendTextMessage(wxid, "流程顺序异常,请重新开始", 1, wxid);
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, "请输入金额1-50元", 1, wxid);
}
}
private void processAmountInput(String wxid, String message, UserInteractionState state) {
if (!isValidAmount(message)) {
wxUtil.sendTextMessage(wxid, "金额格式错误请输入1-50元", 1, wxid);
return;
}
state.getCollectedFields().put("amount", message);
state.setCurrentStep(STEP_QUANTITY);
wxUtil.sendTextMessage(wxid, "请输入数量1-100", 1, wxid);
}
private void processQuantityInput(String wxid, String message, UserInteractionState state) {
if (!isValidQuantity(message)) {
wxUtil.sendTextMessage(wxid, "数量格式错误请输入1-100的整数", 1, wxid);
return;
}
String cachedData = cacheMap.get("productData" + wxid);
String finalWenAn = cacheMap.get("finalWenAn" + wxid);
if (cachedData == null) {
wxUtil.sendTextMessage(wxid, "数据丢失,请重新开始流程", 1, wxid);
resetState(wxid, state);
return;
}
try {
JSONObject jsonObject = JSON.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String owner = jsonObject.getString("owner");
String skuName = jsonObject.getString("skuName");
double amount = Double.parseDouble(state.getCollectedFields().get("amount"));
int quantity = Integer.parseInt(message);
String giftKey = createGiftCoupon(skuId, amount, quantity, owner, skuName);
if (giftKey == null) {
wxUtil.sendTextMessage(wxid, "礼金创建失败,请重试", 1, wxid);
return;
}
// 记录成功日志
logger.info("礼金创建成功 - 用户: {}, SKU: {}, 金额: {}, 数量: {}",
wxid, skuId, amount, quantity);
// 生成转链
String transferUrl = transfer(skuId, giftKey);
wxUtil.sendTextMessage(wxid, "附带礼金的链接:\n" + transferUrl, 1, wxid);
wxUtil.sendTextMessage(wxid, "附带礼金的方案:\n", 1, wxid);
wxUtil.sendTextMessage(wxid, finalWenAn.replaceAll(jsonObject.getString("url"), transferUrl), 1, wxid);
} catch (Exception e) {
logger.error("礼金处理异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理异常,请重试", 1, wxid);
} finally {
resetState(wxid, state);
cacheMap.remove("text" + wxid);
cacheMap.remove("finalWenAn" + wxid);
}
}
/**
* 处理用户输入的推广方案内容
*
* @param wxid 用户微信ID
* @param message 用户输入的方案内容
* @param state 当前交互状态
*/
private void processContentInput(String wxid, String message, UserInteractionState state) {
try {
// 1. 清除旧缓存
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
// 2. 生成推广内容
HashMap<String, List<String>> contentResult = generatePromotionContent(message);
// 3. 发送文本内容
for (String text : contentResult.get("text")) {
wxUtil.sendTextMessage(wxid, text, 1, wxid);
}
// 4. 发送图片(如果有)
List<String> images = contentResult.get("images");
if (images != null) {
try {
Thread.sleep(6000);
} catch (InterruptedException ignored) {
}
for (String imageUrl : images) {
if (imageUrl != null) {
wxUtil.sendImageMessage(wxid, imageUrl);
}
}
}
// 5. 缓存商品数据
if (!contentResult.get("data").isEmpty()) {
String productData = contentResult.get("data").get(0);
cacheMap.put("productData" + wxid, productData);
cacheMap.put("finalWenAn" + wxid, contentResult.get("finalWenAn").get(0));
// 6. 进入确认礼金步骤
state.setCurrentField("confirm");
try {
Thread.sleep(5000);
}catch (InterruptedException ignored){
}
wxUtil.sendTextMessage(wxid,
"是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否",
1, wxid);
} else {
wxUtil.sendTextMessage(wxid, "未获取到商品数据,请检查链接格式", 1, wxid);
state.reset();
}
} catch (Exception e) {
logger.error("处理推广内容异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理内容时发生异常,请重试", 1, wxid);
state.reset();
}
}
/**
* 处理用户对礼金开通的确认
*
* @param wxid 用户微信ID
* @param message 用户回复1或2
* @param state 当前交互状态
*/
private void handleGiftMoneyConfirmation(String wxid, String message, UserInteractionState state) {
try {
if ("1".equals(message)) {
// 用户选择开通礼金
state.setCurrentState(UserInteractionState.ProcessState.GIFT_MONEY_FLOW);
state.setCurrentStep(UserInteractionState.GiftMoneyStep.STEP_AMOUNT);
state.getCollectedFields().clear();
wxUtil.sendTextMessage(wxid,
"请输入开通金额1-50元支持小数点后两位\n" +
"示例20.50",
1, wxid);
} else if ("2".equals(message)) {
// 用户选择不开通礼金
String cachedData = cacheMap.get("productData" + wxid);
if (cachedData != null) {
JSONObject productInfo = JSON.parseObject(cachedData);
String skuUrl = productInfo.getString("materialUrl");
String transferUrl = transfer(skuUrl, null);
String finalWenAn = cacheMap.get("finalWenAn" + wxid);
finalWenAn = (finalWenAn.replace(productInfo.getString("url"), transferUrl));
wxUtil.sendTextMessage(wxid,
"不开礼金,只转链的方案:\n",
1, wxid);
wxUtil.sendTextMessage(wxid,
finalWenAn,
1, wxid);
} else {
wxUtil.sendTextMessage(wxid, "未找到商品信息,请重新开始流程", 1, wxid);
}
state.reset();
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
} else {
// 无效输入
wxUtil.sendTextMessage(wxid,
"请输入有效选项:\n" +
"回复 1 - 开通礼金\n" +
"回复 2 - 直接转链",
1, wxid);
}
} catch (Exception e) {
logger.error("处理礼金确认异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理请求时发生异常,请重试", 1, wxid);
state.reset();
}
}
private void resetState(String wxid, UserInteractionState state) {
state.reset();
saveState(INTERACTION_STATE_PREFIX + wxid, state);
}
// 在发送提示信息时增加进度指示
private void sendStepPrompt(String wxid, int step, int totalSteps) {
String progress = String.format("[%d/%d] ", step, totalSteps);
String message = progress + "请输入礼金金额示例20.50";
wxUtil.sendTextMessage(wxid, message, 1, wxid);
}
private void createPromotionWithGift(String fromWxid, String message) {
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = userInteractionStates.get(key);
// 修改createPromotionWithGift方法中的校验逻辑
if (!state.validateStep(STEP_CONFIRM_GIFT)) {
logger.warn("状态校验失败,预期步骤:{} 实际步骤:{}", STEP_CONFIRM_GIFT, state.getCurrentStep());
wxUtil.sendTextMessage(fromWxid, "流程顺序异常,请重新开始", 1, fromWxid);
return;
}
// 修改点3在createPromotionWithGift方法开始处增加状态校验
if (state == null || state.getCurrentStep() == null) {
logger.warn("非法状态访问: {}", fromWxid);
wxUtil.sendTextMessage(fromWxid, "⚠️ 会话超时,请重新开始流程", 1, fromWxid);
return;
} else {
LocalDateTime now = LocalDateTime.now();
LocalDateTime lastInteractionTime = LocalDateTime.parse(state.getLastInteractionTime(), DATE_TIME_FORMATTER);
if (ChronoUnit.MINUTES.between(lastInteractionTime, now) > TIMEOUT_MINUTES) {
userInteractionStates.remove(key);
logger.debug("Deleted timeout state for user: {}", fromWxid);
state = new UserInteractionState();
}
}
state.updateLastInteractionTime();
userInteractionStates.put(key, state); // 确保状态保存
try {
switch (state.getCurrentStep()) {
case STEP_CONFIRM_GIFT:
if ("1".equals(message)) {
state.setCurrentStep(STEP_AMOUNT);
wxUtil.sendTextMessage(fromWxid, "请输入开通金额(元):", 1, fromWxid);
} else if ("2".equals(message)) {
// 不开通礼金,直接生成转链
String cachedData = cacheMap.get("productData" + fromWxid);
if (cachedData != null) {
JSONObject jsonObject = JSONObject.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String transferUrl = transfer(skuId, null);
wxUtil.sendTextMessage(fromWxid, "转链后的链接:\n" + transferUrl, 1, fromWxid);
} else {
wxUtil.sendTextMessage(fromWxid, "未找到缓存的商品链接,请重新开始流程", 1, fromWxid);
}
state.reset();
userInteractionStates.put(key, state); // 在外部保存
} else {
wxUtil.sendTextMessage(fromWxid, "无效的选择,请重新输入:\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
}
break;
case STEP_AMOUNT:
logger.debug("用户 {} 输入金额:{}", fromWxid, message);
if (message == null || message.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "金额不能为空请输入数字100.00", 1, fromWxid);
return;
}
if (!isValidAmount(message)) {
wxUtil.sendTextMessage(fromWxid, "金额格式错误请输入数字100.00", 1, fromWxid);
return;
}
double amount = Double.parseDouble(message);
String formattedAmount = String.format("%.2f", amount);
state.getCollectedFields().put("amount", formattedAmount);
state.setCurrentStep(STEP_QUANTITY);
userInteractionStates.put(key, state); // ▼▼▼ 保存点2 ▼▼▼
wxUtil.sendTextMessage(fromWxid, "请输入数量1-100", 1, fromWxid);
sendStepPrompt(fromWxid, 1, 3);
break;
case STEP_QUANTITY:
logger.debug("用户 {} 输入数量:{}", fromWxid, message); // 新增
if (!isValidQuantity(message)) {
wxUtil.sendTextMessage(fromWxid, "数量格式错误,请输入整数", 1, fromWxid);
return;
}
int quantity = Integer.parseInt(message);
if (quantity < 1 || quantity > 100) {
wxUtil.sendTextMessage(fromWxid, "数量需在1-100之间", 1, fromWxid);
return;
}
// 从缓存中获取 amount
String amountStr = state.getCollectedFields().get("amount");
if (amountStr == null || amountStr.trim().isEmpty()) {
wxUtil.sendTextMessage(fromWxid, "未找到金额,请重新开始流程", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
return;
}
amount = Double.parseDouble(amountStr);
logger.debug("礼金参数准备完成:金额={}元,数量={}", amount, quantity); // 新增
String cachedData = cacheMap.get("productData" + fromWxid);
String finalWenAn = cacheMap.get("finalWenAn" + fromWxid);
if (cachedData != null) {
JSONObject jsonObject = JSONObject.parseObject(cachedData);
String skuId = jsonObject.getString("materialUrl");
String owner = jsonObject.getString("owner");
String skuName = jsonObject.getString("skuName");
String giftKey = createGiftCoupon(skuId, amount, quantity, owner, skuName);
if (giftKey == null) {
logger.error("用户 {} 礼金创建失败SKU={}, 金额={}元,数量={}", fromWxid, skuId, amount, quantity); // 新增
wxUtil.sendTextMessage(fromWxid, "❌ 礼金创建失败,请检查商品是否符合要求", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
return;
}
logger.info("用户 {} 礼金创建成功批次ID={}, 参数SKU={}, 金额={}元,数量={}", fromWxid, giftKey, skuId, amount, quantity); // 新增关键成功日志
state.getCollectedFields().put("giftKey", giftKey);
// 生成转链
String transferUrl = transfer(skuId, giftKey);
wxUtil.sendTextMessage(fromWxid, "附带礼金的链接:\n" + transferUrl, 1, fromWxid);
wxUtil.sendTextMessage(fromWxid, "附带礼金的方案:\n" + finalWenAn.replaceAll(jsonObject.getString("url"), transferUrl), 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
} else {
wxUtil.sendTextMessage(fromWxid, "未找到缓存的商品链接,请重新开始流程", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
}
break;
default:
state.setCurrentStep(STEP_CONFIRM_GIFT);
wxUtil.sendTextMessage(fromWxid, "是否需要开通礼金?\n回复 1 - 是\n回复 2 - 否", 1, fromWxid);
break;
}
} catch (Exception e) {
logger.error("转链和礼金流程异常,用户 {} 当前步骤:{}", fromWxid, state.getCurrentStep(), e);
wxUtil.sendTextMessage(fromWxid, "❌ 系统异常,请稍后重试", 1, fromWxid);
state.reset();
userInteractionStates.put(key, state); // 在外部保存
}
}
/**
* 生成转链和方案的方法
*
* @param message 方案内容,包含商品链接
* @return 处理后的方案,附带商品信息
*/
public HashMap<String, List<String>> generatePromotionContent(String message) {
HashMap<String, List<String>> finallMessage = new HashMap<>();
List<String> textList = new ArrayList<>();
List<String> imagesList = new ArrayList<>();
List<String> dataList = new ArrayList<>();
// 最终的方案
List<String> finalWenAn = new ArrayList<>();
// 提取方案中的所有 u.jd.com 链接
List<String> 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<HashMap<String, String>> 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("链接类型:优惠券\n");
} else {
couponInfo.append("链接类型:商品\n");
//"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,
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(productInfo.getData()[0]));
jsonObject.put("url", url);
dataList.add(JSONObject.toJSONString(jsonObject));
finallMessage.put("data", dataList);
HashMap<String, String> itemMap = new HashMap<>();
itemMap.put("url", url);
itemMap.put("materialUrl", productInfo.getData()[0].getMaterialUrl());
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("国家", "").replaceAll("补贴", "");
itemMap.put("spuid", String.valueOf(productInfo.getData()[0].getSpuid()));
itemMap.put("commission", String.valueOf(productInfo.getData()[0].getCommissionInfo().getCommission()));
itemMap.put("commissionShare", String.valueOf(productInfo.getData()[0].getCommissionInfo().getCommissionShare()));
//for (HashMap.Entry<String, String> entry : itemMap.entrySet()) {
//couponInfo.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
//}
couponInfo.append(" ").append("店铺:\n").append(itemMap.get("shopName")).append("\n").append(" 标题:\n").append(replaceAll).append("\n").append("自营 POP\n").append(itemMap.get("owner").equals("g") ? " 自营 " : " POP ")
.append("佣金:\n").append(itemMap.get("commission")).append("\n").append("佣金比例:\n").append(itemMap.get("commissionShare"));
//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);
/*直接生成闲鱼的商品文案*/
StringBuilder sb1 = new StringBuilder();
// 新建格式好日期
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
sb1.append(replaceAll).append("\n").append(WENAN_FANAN.replaceAll("更新", dateFormat.format(new Date()) + "更新"));
textList.add("闲鱼方案的文案:\n");
textList.add(String.valueOf(sb1));
StringBuilder sb2 = new StringBuilder();
sb2.append(replaceAll).append("\n").append(WENAN_ZCXS);
textList.add("闲鱼正常销售:\n");
textList.add(String.valueOf(sb2));
}
textList.add(String.valueOf(couponInfo));
} 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();
// 完成转链后替换链接为u.jd.com链接方案不修改就返回
//for (HashMap<String, String> stringStringHashMap : resultList) {
// String transferUrl = transfer(stringStringHashMap.get("materialUrl"), null);
// wenan = new StringBuilder(message.replace(stringStringHashMap.get("url"), transferUrl));
//}
wenan = new StringBuilder(message);
//textList.add(String.valueOf(wenan));
finalWenAn.add(String.valueOf(wenan));
finallMessage.put("text", textList);
finallMessage.put("images", imagesList);
finallMessage.put("finalWenAn", finalWenAn);
return finallMessage;
}
private String downloadImage(String imageUrl, String destinationFile) {
try (BufferedInputStream in = new BufferedInputStream(new URL(imageUrl).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(destinationFile)) {
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<String> extractUJDUrls(String message) {
List<String> 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, 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;
}
// 修改activateGiftMoney方法调用真实接口
private boolean activateGiftMoney(String skuId, double amount, int quantity, String owner) {
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());
}
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);
}
// 定义一个内部类来存储用户交互状态
@Getter
@Setter
static class UserInteractionState {
private GiftMoneyStep currentStep; // 新增当前步骤字段
private String lastInteractionTime;
private ProcessState currentState;
private Map<String, String> collectedFields; // 用于存储收集到的字段值
private String currentField; // 当前正在询问的字段
public UserInteractionState() {
this.currentStep = GiftMoneyStep.STEP_CONFIRM_GIFT; // 初始
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_CONFIRM_GIFT; // 明确重置步骤
updateLastInteractionTime();
}
// 在UserInteractionState类中增加状态校验方法
public boolean validateStep(GiftMoneyStep expectedStep) {
return this.currentStep != null && this.currentStep.getCode() == expectedStep.getCode();
}
// 在UserInteractionState中增加恢复方法
public void restoreState(Map<String, String> backup) {
this.collectedFields.putAll(backup);
this.lastInteractionTime = LocalDateTime.now().format(DATE_TIME_FORMATTER);
}
// 推荐使用枚举管理状态
public enum ProcessState {
INIT, GIFT_MONEY_FLOW, PRODUCT_PROMOTION
}
public enum GiftMoneyStep {
STEP_CONFIRM_GIFT(0), // 显式指定序号
STEP_AMOUNT(1), STEP_QUANTITY(2);
private final int code;
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;// 违规佣金
}
}