369 lines
18 KiB
Java
369 lines
18 KiB
Java
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<WXUtil.SuperAdmin> superAdmins = getSuperAdmins(wxId);
|
||
if (superAdmins.isEmpty()) return;
|
||
|
||
List<Long> unionIds = superAdmins.stream().map(admin -> Long.valueOf(admin.getUnionId())).collect(Collectors.toList());
|
||
|
||
List<OrderRow> orderRows = orderRowRepository.findByValidCodeNotInAndUnionIdIn(new int[]{-1}, unionIds);
|
||
List<OrderRow> todayOrders = filterOrdersByDate(orderRows, 0);
|
||
|
||
if (todayOrders.isEmpty()) return;
|
||
|
||
// 按照 unionId 分组统计
|
||
Map<String, List<OrderRow>> grouped = todayOrders.stream().collect(Collectors.groupingBy(o -> o.getUnionId().toString()));
|
||
|
||
StringBuilder resultContent = new StringBuilder();
|
||
for (Map.Entry<String, List<OrderRow>> 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<OrderRow> 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<OrderRow> 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<OrderRow> 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<OrderRow> 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<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 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 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<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);
|
||
}
|
||
|
||
// 统计指标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;// 违规佣金
|
||
}
|
||
}
|