package cn.van.business.util; import cn.van.business.enums.ValidCodeConverter; import cn.van.business.model.jd.OrderRow; import cn.van.business.repository.OrderRowRepository; import lombok.AllArgsConstructor; import lombok.Getter; 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.Async; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import static cn.van.business.util.WXUtil.*; /** * @author Leo * @version 1.0 * @create 2024/11/29 11:43 * @description: */ @Service public class OrderUtil { private static final Logger logger = LoggerFactory.getLogger(OrderUtil.class); @Autowired private StringRedisTemplate redisTemplate; @Autowired private OrderRowRepository orderRowRepository; @Autowired private WXUtil wxUtil; //标记唯一订单行:订单+sku维度的唯一标识 private static final String ORDER_ROW_KEY = "jd:order:row:"; private static final String ORDER_ROW_JB_KEY = "jd:order:row:jb:"; /** * 手动调用 将订单发送到微信 */ @Async("threadPoolTaskExecutor") public void orderToWx(OrderRow orderRow, Boolean isAutoFlush, Boolean isAutoFlushJB) { try { // 获取订单当前状态 Integer newValidCode = orderRow.getValidCode(); String oldValidCode = getFromRedis(ORDER_ROW_KEY + orderRow.getId()); // 检查Redis中是否有旧的状态码,没有的话赋予默认值 Integer lastValidCode = oldValidCode != null ? Integer.parseInt(oldValidCode) : -100; // 先更新 Redis 状态,防止消息发送失败导致重复触发 redisTemplate.opsForValue().set(ORDER_ROW_KEY + orderRow.getId(), String.valueOf(orderRow.getValidCode())); // 订单变化:当 isAutoFlush == false 或状态确实有变化时,进行消息发送 if (!isAutoFlush || !lastValidCode.equals(newValidCode)) { String content = getFormattedOrderInfo(orderRow); String wxId = getWxidFromJdid(orderRow.getUnionId().toString()); if (Util.isNotEmpty(wxId)) { wxUtil.sendTextMessage(wxId, content, 1, wxId, true); if (newValidCode != 17) { // 发送今日统计信息 sendDailyStats(wxId); } } } // 处理价保逻辑 BigDecimal newProPriceAmount = Optional.ofNullable(orderRow.getProPriceAmount()).map(BigDecimal::new).orElse(BigDecimal.ZERO); // 获取Redis中的旧价保金额和通知状态 String redisKey = ORDER_ROW_JB_KEY + orderRow.getId(); String redisValue = getFromRedis(redisKey); // 解析Redis值,格式为 "金额:通知状态",例如 "100.00:true" String[] parts = redisValue != null ? redisValue.split(":") : new String[0]; BigDecimal oldProPriceAmount = parts.length > 0 ? new BigDecimal(parts[0]) : BigDecimal.ZERO; boolean hasNotified = parts.length > 1 && Boolean.parseBoolean(parts[1]); // 判断是否需要发送通知:金额不为零且金额变化了或者未发送过通知 boolean shouldNotify = newProPriceAmount.compareTo(BigDecimal.ZERO) > 0 && (newProPriceAmount.compareTo(oldProPriceAmount) != 0 || !hasNotified); if (isAutoFlushJB) { shouldNotify = true; } if (shouldNotify) { String wxId = getWxidFromJdid(orderRow.getUnionId().toString()); if (Util.isNotEmpty(wxId)) { String content = getFormattedOrderInfoForJB(orderRow); String alertMsg = "[爱心] 价保了 " + newProPriceAmount + " [爱心] \n" + content; try { // 先发送通知 wxUtil.sendTextMessage(wxId, alertMsg, 1, wxId, true); // 通知成功后更新Redis,格式为 "金额:true" if (!isAutoFlushJB) { String newRedisValue = newProPriceAmount + ":true"; redisTemplate.opsForValue().set(redisKey, newRedisValue); } } catch (Exception e) { logger.error("发送价保通知失败: {}", e.getMessage(), e); // 通知失败,不更新Redis,下次继续尝试 } } } } catch (Exception e) { logger.error("处理订单微信通知失败: {}", e.getMessage(), e); // 可加入重试机制或上报监控 } } private void sendDailyStats(String wxId) { List superAdmins = getSuperAdmins(wxId); if (superAdmins.isEmpty()) return; List unionIds = superAdmins.stream().map(admin -> Long.valueOf(admin.getUnionId())).collect(Collectors.toList()); List orderRows = orderRowRepository.findByValidCodeNotInAndUnionIdIn(new int[]{-1}, unionIds); List todayOrders = filterOrdersByDate(orderRows, 0); if (todayOrders.isEmpty()) return; // 按照 unionId 分组统计 Map> grouped = todayOrders.stream().collect(Collectors.groupingBy(o -> o.getUnionId().toString())); StringBuilder resultContent = new StringBuilder(); for (Map.Entry> entry : grouped.entrySet()) { String unionId = entry.getKey(); OrderStats stats = calculateStats(entry.getValue()); resultContent.append(buildStatsContent("京粉 : " + getRemarkFromJdid(unionId), stats)); } OrderStats totalStats = calculateStats(todayOrders); String totalMsg = buildStatsContent("今日总统计 : ", totalStats) + " \n详:\n━━━━━━━━━━━━\n" + resultContent; wxUtil.sendTextMessage(wxId, totalMsg, 1, wxId, true); } private String getFromRedis(String key) { try { return redisTemplate.opsForValue().get(key); } catch (Exception e) { logger.warn("Redis get 失败 key={}", key, e); return null; } } /** * 手动调用 将订单发送到微信 批量 */ @Async("threadPoolTaskExecutor") public void orderToWxBatch(List orderRowList) { if (!orderRowList.isEmpty()) { int i = 1; String wxId = getWxidFromJdid(orderRowList.get(0).getUnionId().toString()); StringBuilder content = new StringBuilder(); content.append("批量订单:\n\r ").append(" 共 ").append(orderRowList.size()).append("单 \r"); List filterList = orderRowList.stream().filter(orderRow -> orderRow.getValidCode() != 2 && orderRow.getValidCode() != 3).toList(); content.append("移除 拆单或者取消 的订单, 共 ").append(filterList.size()).append("单: \n\r"); for (OrderRow orderRow : filterList) { content.append("\r\n"); content.append(i++).append("、"); content.append(getFormattedOrderInfoBatch(orderRow)); } if (Util.isNotEmpty(wxId)) { wxUtil.sendTextMessage(wxId, content.toString(), 1, wxId, false); } } } /** * 将数据库订单转化成微信所需要文本 */ public String getFormattedOrderInfo(OrderRow orderRow) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ValidCodeConverter converter = new ValidCodeConverter(); Long unionId = orderRow.getUnionId(); String remarkFromJdid = getRemarkFromJdid(String.valueOf(unionId)); StringBuilder orderInfo = new StringBuilder().append(" ").append(getEmjoy(orderRow.getValidCode())).append(" ").append(converter.getCodeDescription(orderRow.getValidCode())).append("\r"); //if (oldValidCode != -100 && !oldValidCode.equals(orderRow.getValidCode())) { // orderInfo.insert(0, "从 :" + getEmjoy(oldValidCode) + " " // + converter.getCodeDescription(oldValidCode) + "\r变成 " // + getEmjoy(orderRow.getValidCode()) + " " // + converter.getCodeDescription(orderRow.getValidCode()) + "\r\n"); //} orderInfo //+ "订单+sku:" + orderRow.getId() + "\r" .append("京粉:").append(remarkFromJdid).append("\r").append("订单:").append(orderRow.getOrderId()).append(" (").append(orderRow.getPlus() == 1 ? "plus" : "非plus").append(")\r").append("名称:").append(orderRow.getSkuName()).append("\r").append("\r") //+ "商品单价:" + orderRow.getPrice() + "\r" //+ "商品数量:" + orderRow.getSkuNum() + "\r" //+ "商品总价:" + (orderRow.getPrice() * orderRow.getSkuNum()) + "\r" .append("计佣金额:").append(orderRow.getEstimateCosPrice()).append("\r") //+ "金额:" + orderRow.getActualCosPrice() + "\r" .append("比例:").append(orderRow.getCommissionRate()).append("\r").append("[Packet] 佣金:").append(orderRow.getEstimateFee()).append("\r").append("下单:").append(formatter.format(orderRow.getOrderTime())).append("\r").append("完成:").append(orderRow.getFinishTime() != null ? formatter.format(orderRow.getFinishTime()) : "未完成"); return orderInfo.toString(); } // 价保 public String getFormattedOrderInfoForJB(OrderRow orderRow) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ValidCodeConverter converter = new ValidCodeConverter(); Long unionId = orderRow.getUnionId(); String remarkFromJdid = getRemarkFromJdid(String.valueOf(unionId)); StringBuilder orderInfo = new StringBuilder().append(" ").append(getEmjoy(orderRow.getValidCode())).append(" ").append(converter.getCodeDescription(orderRow.getValidCode())).append("\r"); orderInfo //+ "订单+sku:" + orderRow.getId() + "\r" .append("京粉:").append(remarkFromJdid).append("\r").append("订单:").append(orderRow.getOrderId()).append(" (").append(orderRow.getPlus() == 1 ? "plus" : "非plus").append(")\r").append("名称:").append(orderRow.getSkuName()).append("\r").append("\r").append("下单:").append(formatter.format(orderRow.getOrderTime())).append("\r").append("完成:").append(orderRow.getFinishTime() != null ? formatter.format(orderRow.getFinishTime()) : "未完成"); return orderInfo.toString(); } /** * 手动调用 将订单发送到微信 批量 */ @Async("threadPoolTaskExecutor") public void orderToWxBatchForJB(List orderRowList) { if (!orderRowList.isEmpty()) { int i = 1; String wxId = getWxidFromJdid(orderRowList.get(0).getUnionId().toString()); StringBuilder content = new StringBuilder(); content.append("批量订单:\n\r ").append(" 共 ").append(orderRowList.size()).append("单 \r"); List filterList = orderRowList.stream().filter(orderRow -> orderRow.getValidCode() != 2 && orderRow.getValidCode() != 3).toList(); content.append("移除 拆单或者取消 的订单, 共 ").append(filterList.size()).append("单: \n\r"); for (OrderRow orderRow : filterList) { content.append("\r\n"); content.append(i++).append("、"); content.append(getFormattedOrderInfoBatchForJB(orderRow)); } if (Util.isNotEmpty(wxId)) { wxUtil.sendTextMessage(wxId, content.toString(), 1, wxId, false); } } } public String getEmjoy(Integer status) { return switch (status) { //[爱心]已付款 case 16 -> "[爱心]"; //[Wow] 待付款 case 15 -> "[Wow]"; //[心碎]已取消 case 2, 3 -> "[心碎]"; //[亲亲] 已完成 case 17 -> "[亲亲]"; //[Broken] 违规 case 27, 28 -> "[Broken]"; default -> ""; }; } /** * 将数据库订单转化成微信所需要文本 简洁版 */ public String getFormattedOrderInfoBatch(OrderRow orderRow) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ValidCodeConverter converter = new ValidCodeConverter(); Long unionId = orderRow.getUnionId(); String remarkFromJdid = getRemarkFromJdid(String.valueOf(unionId)); return "订单:" + orderRow.getOrderId() + " (" + (orderRow.getPlus() == 1 ? "plus" : "非plus") + ")\r" + "走的京粉:" + remarkFromJdid + "\r" + "状态:" + (converter.getCodeDescription(orderRow.getValidCode())) + "\r" + "标题:" + orderRow.getSkuName() + "\r\n" + "\r\n" //+ "商品单价:" + orderRow.getPrice() + "\r" //+ "商品数量:" + orderRow.getSkuNum() + "\r" //+ "商品总价:" + (orderRow.getPrice() * orderRow.getSkuNum()) + "\r" + "计佣金额:" + orderRow.getEstimateCosPrice() + "\r" //+ "金额:" + orderRow.getActualCosPrice() + "\r" //+ "比例:" + orderRow.getCommissionRate() + "\r" + "佣金:" + orderRow.getEstimateFee() + "\r\n" + "下单:" + formatter.format(orderRow.getOrderTime()) + "\r" + "完成:" + (orderRow.getFinishTime() != null ? formatter.format(orderRow.getFinishTime()) : "未完成") + "\r"; } /** * 将数据库订单转化成微信所需要文本 简洁版 */ public String getFormattedOrderInfoBatchForJB(OrderRow orderRow) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); StringBuilder orderInfo = new StringBuilder().append("\n").append("价保了:").append(orderRow.getProPriceAmount()).append("\n"); orderInfo.append("订单:").append(orderRow.getOrderId()).append(" (").append(orderRow.getPlus() == 1 ? "plus" : "非plus").append(")\r").append("名称:").append(orderRow.getSkuName()).append("\r").append("\r").append("下单:").append(formatter.format(orderRow.getOrderTime())).append("\r").append("完成:").append(orderRow.getFinishTime() != null ? formatter.format(orderRow.getFinishTime()) : "未完成"); return orderInfo.toString(); } /** * JDUtil拷贝的方法,避免循环注入 */ private List filterOrdersByDate(List orderRows, int daysBack) { LocalDate now = LocalDate.now(); return orderRows.stream().filter(order -> { // 将 Date 转换为 LocalDate LocalDate orderDate = order.getOrderTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // 计算是否在给定的天数内 return !orderDate.isBefore(now.minusDays(daysBack)) && !orderDate.isAfter(now); }).collect(Collectors.toList()); } private OrderStats calculateStats(List orders) { long paid = orders.stream().filter(o -> o.getValidCode() == 16).count(); long pending = orders.stream().filter(o -> o.getValidCode() == 15).count(); long canceled = orders.stream().filter(o -> o.getValidCode() != 16 && o.getValidCode() != 17).count(); long completed = orders.stream().filter(o -> o.getValidCode() == 17).count(); return new OrderStats(orders.size(), orders.size() - canceled, paid, orders.stream().filter(o -> o.getValidCode() == 16).mapToDouble(OrderRow::getEstimateFee).sum(), pending, orders.stream().filter(o -> o.getValidCode() == 15).mapToDouble(OrderRow::getEstimateFee).sum(), canceled, completed, orders.stream().filter(o -> o.getValidCode() == 17).mapToDouble(OrderRow::getEstimateFee).sum(), getStreamForWeiGui(orders).count(), getStreamForWeiGui(orders).mapToDouble(o -> o.getEstimateCosPrice() * o.getCommissionRate() * 0.01).sum()); } private String buildStatsContent(String title, OrderStats stats) { StringBuilder content = new StringBuilder(); content//[爱心][Wow][Packet][Party][Broken][心碎][亲亲][色] .append(title).append(" \n").append("[爱心] 订单总数:").append(stats.getTotalOrders()).append("\n") // [文件] .append("[Party] 有效订单:").append(stats.getValidOrders()).append("\n") // [OK] .append("[心碎]已取消:").append(stats.getCanceledOrders()).append("\n") // [禁止] .append("[爱心]已付款:").append(stats.getPaidOrders()).append("\n") // [钱袋] .append("[Packet] 已付款佣金:").append(String.format("%.2f", stats.getPaidCommission())).append("\n") // [钞票] .append("[Wow] 待付款:").append(stats.getPendingOrders()).append("\n") // [时钟] .append("[Packet] 待付款佣金:").append(String.format("%.2f", stats.getPendingCommission())).append("\n").append("━━━━━━━━━━━━\n"); return content.toString(); } private Stream getStreamForWeiGui(List todayOrders) { return todayOrders.stream().filter(orderRow -> orderRow.getValidCode() == 13 || orderRow.getValidCode() == 25 || orderRow.getValidCode() == 26 || orderRow.getValidCode() == 27 || orderRow.getValidCode() == 28 || orderRow.getValidCode() == 29); } // 统计指标DTO @Getter @AllArgsConstructor static 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;// 违规佣金 } }