Files
Jarvis_java/src/main/java/cn/van/business/util/JDUtil.java
2025-04-08 16:18:14 +08:00

1201 lines
53 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.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.*;
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.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.chatRoom_BY;
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";
private static final String FANAN_COMMON = "\n复制到薇依次领券到J东APP结算\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<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 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);
if ("".equals(order)) {
// 设置状态为等待文案输入
String key = INTERACTION_STATE_PREFIX + fromWxid;
UserInteractionState state = loadOrCreateState(key);
state.setCurrentState(UserInteractionState.ProcessState.GIFT_MONEY_FLOW);
state.setCurrentStep(UserInteractionState.GiftMoneyStep.STEP_WAIT_FOR_CONTENT);
saveState(key, state);
// 提示用户输入文案
wxUtil.sendTextMessage(fromWxid, "请输入包含商品链接的文案:", 1, fromWxid, false);
} else {
handleUserInteraction(fromWxid, order);
}
}
/**
* 礼金创建流程 - 循环处理商品
*/
private void processGiftMoneyFlow(String wxid, String message, UserInteractionState state) {
try {
// 提取文案中的 u.jd.com 链接
List<String> urls = extractUJDUrls(message);
if (urls.isEmpty()) {
wxUtil.sendTextMessage(wxid, "未找到有效的商品链接,请重新输入包含商品链接的文案。", 1, wxid, false);
return;
}
// 查询商品信息,筛选有效商品
List<GoodsQueryResult> validProducts = new ArrayList<>();
for (String url : urls) {
try {
GoodsQueryResult productInfo = queryProductInfoByUJDUrl(url);
if (productInfo != null && productInfo.getCode() == 200 && productInfo.getTotalCount() > 0) {
validProducts.add(productInfo);
}
} catch (Exception e) {
logger.error("查询商品信息失败:{}", url, e);
}
}
if (validProducts.isEmpty()) {
wxUtil.sendTextMessage(wxid, "未找到有效的商品信息,请检查链接是否正确。", 1, wxid, false);
return;
}
// 缓存商品信息
cacheMap.put("validProducts" + wxid, JSON.toJSONString(validProducts));
// 更新状态
state.setCurrentStep(UserInteractionState.GiftMoneyStep.STEP_AMOUNT);
state.getCollectedFields().clear();
state.getCollectedFields().put("productIndex", "0"); // 当前处理的商品索引
state.getCollectedFields().put("totalProducts", String.valueOf(validProducts.size())); // 总商品数
saveState(INTERACTION_STATE_PREFIX + wxid, state);
// 提示用户输入金额
wxUtil.sendTextMessage(wxid, "请输入开通金额1-50元支持小数点后两位", 1, wxid, false);
} catch (Exception e) {
logger.error("处理礼金文案异常 - 用户: {}", wxid, e);
wxUtil.sendTextMessage(wxid, "处理请求时发生异常,请重试", 1, wxid, false);
resetState(wxid, state);
}
}
public void cjwlm(String message) {
logger.info("执行 cjwlm 方法order: {}", message);
if (message.contains("https://u.jd")) {
wxUtil.sendTextMessage(chatRoom_BY, "[爱心]---采集开始---[爱心] \n " + message, 1, chatRoom_BY, true);
// 2. 生成推广内容
HashMap<String, List<String>> contentResult = generatePromotionContent(message);
// 3. 发送文本内容
for (String text : contentResult.get("text")) {
wxUtil.sendTextMessage(chatRoom_BY, text, 1, chatRoom_BY, true);
}
// 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(chatRoom_BY, imageUrl);
}
}
}
wxUtil.sendTextMessage(chatRoom_BY, "[爱心]---采集完成---[爱心]", 1, chatRoom_BY, true);
}
// 具体逻辑
}
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 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};
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);
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, false);
}
}
}
/**
* 接收京粉指令指令
* 高级菜单
*/
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, 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;
}
//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;
//}
/**
* 消毒柜部分的业务逻辑
*/
@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());
}
});
}
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:
processGiftMoneyFlow(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");
wxUtil.sendTextMessage(wxid, "请输入推广方案(包含商品链接):", 1, wxid, false);
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);
}
}
/**
* 处理用户输入的推广方案内容
*
* @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, true);
}
// 4. 发送图片(如果有)
List<String> images = contentResult.get("images");
if (images != null) {
try {
Thread.sleep(1000);
} 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, 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 {
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, false);
} 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);
if (finalWenAn != null) {
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);
}
} else {
wxUtil.sendTextMessage(wxid, "未找到商品信息,请重新开始流程", 1, wxid, false);
}
// 清理状态和缓存
state.reset();
cacheMap.remove("productData" + wxid);
cacheMap.remove("finalWenAn" + wxid);
} else {
// 无效输入
wxUtil.sendTextMessage(wxid,
"请输入有效选项:\n" +
"回复 1 - 开通礼金\n" +
"回复 2 - 直接转链",
1, wxid, false);
}
} 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);
}
/**
* 生成转链和方案的方法
*
* @param message 方案内容,包含商品链接
* @return 处理后的方案,附带商品信息
*/
public HashMap<String, List<String>> generatePromotionContent(String message) {
HashMap<String, List<String>> finalMessage = 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("方案中未找到有效的商品链接,请检查格式是否正确。");
finalMessage.put("text", textList);
return finalMessage;
}
for (String url : urls) {
try {
// 查询商品信息
GoodsQueryResult productInfo = queryProductInfoByUJDUrl(url);
if (productInfo == null || productInfo.getCode() != 200) {
textList.add("链接查询失败:" + url);
continue;
}
long totalCount = productInfo.getTotalCount();
if (totalCount == 0) {
// 优惠券链接
textList.add("链接类型:优惠券\n" + url);
} else {
// 商品链接
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(productInfo.getData()[0]));
jsonObject.put("url", url);
dataList.add(jsonObject.toJSONString());
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());
String skuName = productInfo.getData()[0].getSkuName();
String cleanedSkuName = skuName.replaceAll("国家", "").replaceAll("补贴", "").replaceAll("15%", "").replaceAll("20%", "");
itemMap.put("skuName", cleanedSkuName);
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()));
StringBuilder couponInfo = new StringBuilder();
couponInfo.append("店铺:").append(itemMap.get("shopName")).append("\n")
.append("标题:").append(cleanedSkuName).append("\n")
.append("自营 POP").append(itemMap.get("owner").equals("g") ? " 自营 " : " POP ").append("\n")
.append("佣金:").append(itemMap.get("commission")).append("\n")
.append("佣金比例:").append(itemMap.get("commissionShare"));
textList.add(couponInfo.toString());
if (productInfo.getData()[0].getImageInfo() != null) {
for (UrlInfo image : productInfo.getData()[0].getImageInfo().getImageList()) {
imagesList.add(image.getUrl());
}
}
// 生成闲鱼的商品文案
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
textList.add("(教你买) " + cleanedSkuName + "\n" + WENAN_FANAN.replaceAll("更新", dateFormat.format(new Date()) + "更新"));
textList.add(cleanedSkuName + "\n" + WENAN_ZCXS);
}
} catch (Exception e) {
logger.error("处理商品链接时发生异常:{}", url, e);
textList.add("处理商品链接时发生异常:" + url);
}
}
// 生成最终文案
StringBuilder wenan = new StringBuilder(FANAN_COMMON);
for (String url : urls) {
wenan.append(message.replace(url, ""));
}
finalWenAn.add(wenan.toString());
finalMessage.put("text", textList);
finalMessage.put("images", imagesList);
finalMessage.put("data", dataList);
finalMessage.put("finalWenAn", finalWenAn);
return finalMessage;
}
/**
* 提取方案中的所有 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;
}
// 将状态存储迁移到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();
}
// 推荐使用枚举管理状态
public enum ProcessState {
INIT, GIFT_MONEY_FLOW, PRODUCT_PROMOTION
}
@Getter
public enum GiftMoneyStep {
STEP_WAIT_FOR_CONTENT(3),
STEP_CONFIRM_GIFT(0), // 显式指定序号
STEP_AMOUNT(1), STEP_QUANTITY(2);
private final int code;
GiftMoneyStep(int code) {
this.code = 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;// 违规佣金
}
}