|
@@ -18,12 +18,9 @@ import cn.hutool.core.lang.Snowflake;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.core.util.RandomUtil;
|
|
import cn.hutool.core.util.RandomUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
-import cn.hutool.json.JSONUtil;
|
|
|
|
|
import cn.hutool.poi.excel.ExcelUtil;
|
|
import cn.hutool.poi.excel.ExcelUtil;
|
|
|
import cn.hutool.poi.excel.ExcelWriter;
|
|
import cn.hutool.poi.excel.ExcelWriter;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSON;
|
|
|
-import com.alibaba.excel.EasyExcel;
|
|
|
|
|
-import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
@@ -31,6 +28,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Lists;
|
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.collect.Maps;
|
|
|
|
|
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
|
import com.google.gson.internal.LinkedTreeMap;
|
|
import com.google.gson.internal.LinkedTreeMap;
|
|
|
import com.yami.shop.bean.app.dto.OrderCountData;
|
|
import com.yami.shop.bean.app.dto.OrderCountData;
|
|
|
import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
|
|
import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
|
|
@@ -42,13 +40,16 @@ import com.yami.shop.bean.event.SubmitOrderEvent;
|
|
|
import com.yami.shop.bean.event.SubmitScoreOrderEvent;
|
|
import com.yami.shop.bean.event.SubmitScoreOrderEvent;
|
|
|
import com.yami.shop.bean.model.*;
|
|
import com.yami.shop.bean.model.*;
|
|
|
import com.yami.shop.bean.param.*;
|
|
import com.yami.shop.bean.param.*;
|
|
|
|
|
+import com.yami.shop.bean.vo.ExportContext;
|
|
|
|
|
+import com.yami.shop.bean.vo.OrderCountVo;
|
|
|
import com.yami.shop.common.config.Constant;
|
|
import com.yami.shop.common.config.Constant;
|
|
|
import com.yami.shop.common.exception.GlobalException;
|
|
import com.yami.shop.common.exception.GlobalException;
|
|
|
import com.yami.shop.common.util.Arith;
|
|
import com.yami.shop.common.util.Arith;
|
|
|
-import com.yami.shop.common.util.Json;
|
|
|
|
|
import com.yami.shop.common.util.PageAdapter;
|
|
import com.yami.shop.common.util.PageAdapter;
|
|
|
import com.yami.shop.common.util.PageParam;
|
|
import com.yami.shop.common.util.PageParam;
|
|
|
|
|
+import com.yami.shop.common.util.R;
|
|
|
import com.yami.shop.dao.*;
|
|
import com.yami.shop.dao.*;
|
|
|
|
|
+import com.yami.shop.service.ExportTaskService;
|
|
|
import com.yami.shop.service.OrderItemService;
|
|
import com.yami.shop.service.OrderItemService;
|
|
|
import com.yami.shop.service.OrderService;
|
|
import com.yami.shop.service.OrderService;
|
|
|
import com.yami.shop.service.OrderSettlementService;
|
|
import com.yami.shop.service.OrderSettlementService;
|
|
@@ -58,30 +59,40 @@ import com.yami.shop.utils.HBSignUtil;
|
|
|
import com.yami.shop.wx.po.RefundInfoPo;
|
|
import com.yami.shop.wx.po.RefundInfoPo;
|
|
|
import com.yami.shop.wx.service.WxProviderService;
|
|
import com.yami.shop.wx.service.WxProviderService;
|
|
|
import lombok.AllArgsConstructor;
|
|
import lombok.AllArgsConstructor;
|
|
|
|
|
+import lombok.Data;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.ObjectUtils;
|
|
import org.apache.commons.lang3.ObjectUtils;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.apache.poi.ss.usermodel.*;
|
|
import org.apache.poi.ss.usermodel.*;
|
|
|
-import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
|
|
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
|
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
|
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
|
|
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
|
|
import org.springframework.beans.BeanUtils;
|
|
import org.springframework.beans.BeanUtils;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.cache.annotation.CacheEvict;
|
|
import org.springframework.cache.annotation.CacheEvict;
|
|
|
import org.springframework.cache.annotation.CachePut;
|
|
import org.springframework.cache.annotation.CachePut;
|
|
|
import org.springframework.cache.annotation.Cacheable;
|
|
import org.springframework.cache.annotation.Cacheable;
|
|
|
import org.springframework.context.ApplicationEventPublisher;
|
|
import org.springframework.context.ApplicationEventPublisher;
|
|
|
|
|
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
import javax.servlet.ServletOutputStream;
|
|
import javax.servlet.ServletOutputStream;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.io.BufferedOutputStream;
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
|
|
+import java.rmi.server.ExportException;
|
|
|
import java.text.SimpleDateFormat;
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
|
|
+import java.util.concurrent.Executors;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import static com.yami.shop.common.util.HttpUtil.post;
|
|
import static com.yami.shop.common.util.HttpUtil.post;
|
|
@@ -109,6 +120,18 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|
|
private Snowflake snowflake;
|
|
private Snowflake snowflake;
|
|
|
private final OrderRefundSkuMapper orderRefundSkuMapper;
|
|
private final OrderRefundSkuMapper orderRefundSkuMapper;
|
|
|
private final HBSignUtil hbSignUtil;
|
|
private final HBSignUtil hbSignUtil;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ExportTaskService exportTaskService;
|
|
|
|
|
+
|
|
|
|
|
+ //导出
|
|
|
|
|
+ // 线程池配置
|
|
|
|
|
+ private final ExecutorService exportExecutor = Executors.newFixedThreadPool(
|
|
|
|
|
+ 5,
|
|
|
|
|
+ new ThreadFactoryBuilder()
|
|
|
|
|
+ .setNameFormat("order-export-thread-%d")
|
|
|
|
|
+ .setDaemon(true)
|
|
|
|
|
+ .build()
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -1370,10 +1393,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|
|
* 导出正常订单
|
|
* 导出正常订单
|
|
|
*
|
|
*
|
|
|
* @param orderParam
|
|
* @param orderParam
|
|
|
- * @param response
|
|
|
|
|
|
|
+ * @param userId
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
- public void export (BackendOrderParam orderParam, HttpServletResponse response) {
|
|
|
|
|
|
|
+ public R<String> export (BackendOrderParam orderParam, Long userId) {
|
|
|
List<Order> orderList = orderMapper.findList(orderParam);
|
|
List<Order> orderList = orderMapper.findList(orderParam);
|
|
|
if (!orderList.isEmpty()) {
|
|
if (!orderList.isEmpty()) {
|
|
|
orderList.forEach(c -> {
|
|
orderList.forEach(c -> {
|
|
@@ -1383,22 +1406,80 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
if (orderList.isEmpty()) {
|
|
if (orderList.isEmpty()) {
|
|
|
- throw new GlobalException("该次导出未查询到数据,不允许导出");
|
|
|
|
|
|
|
+ return R.FAIL("未查询到数据不允许导出");
|
|
|
|
|
+ }
|
|
|
|
|
+ String taskId = UUID.randomUUID().toString();
|
|
|
|
|
+ ExportTask exportTask = new ExportTask();
|
|
|
|
|
+ exportTask.setUserId(userId);
|
|
|
|
|
+ exportTask.setExportStatus(0);//导出状态进行中
|
|
|
|
|
+ exportTask.setTaskId(taskId);
|
|
|
|
|
+ exportTask.setExportType(1);//导出类型
|
|
|
|
|
+ exportTask.setTaskName("正常订单导出");
|
|
|
|
|
+ exportTask.setExportMsg("导出中");
|
|
|
|
|
+ exportTask.setCreateTime(new Date());
|
|
|
|
|
+ exportTask.setUpdateTime(new Date());
|
|
|
|
|
+ exportTaskService.save(exportTask);
|
|
|
|
|
+ // 异步执行导出
|
|
|
|
|
+ CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
|
|
+ Thread currentThread = Thread.currentThread();
|
|
|
|
|
+ ExportContext context = new ExportContext(null, currentThread, false, 0);
|
|
|
|
|
+ exportTaskService.putTaskId(taskId,context);
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 记录任务开始
|
|
|
|
|
+ log.info("开始执行导出任务,用户ID: {}", userId);
|
|
|
|
|
+ exportNormalOrders(orderList,exportTask, "order",context);
|
|
|
|
|
+ // 记录任务完成
|
|
|
|
|
+ log.info("导出任务完成,用户ID: {}", userId);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("导出任务异常,用户ID: {}", userId, e);
|
|
|
|
|
+ exportTask.setExportStatus(2);
|
|
|
|
|
+ exportTask.setExportMsg("导出失败,"+e.getMessage());
|
|
|
|
|
+ exportTaskService.updateById(exportTask);
|
|
|
|
|
+ }finally {
|
|
|
|
|
+ exportTaskService.removeTaskId(taskId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, exportExecutor);
|
|
|
|
|
+ // 存储任务上下文
|
|
|
|
|
+ exportTaskService.putTaskId(taskId, new ExportContext(future, null, false, 0));
|
|
|
|
|
+ return R.SUCCESS("成功");
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public OrderCountVo orderCount(BackendOrderParam orderParam) {
|
|
|
|
|
+ OrderCountVo orderCountVo = orderMapper.orderCount(orderParam);
|
|
|
|
|
+ if (orderCountVo!=null){
|
|
|
|
|
+ if (orderCountVo.getOrderMoneyTotal()!=null&&orderCountVo.getCarriageMoneyTotal()!=null){
|
|
|
|
|
+ BigDecimal add = orderCountVo.getOrderMoneyTotal().add(orderCountVo.getCarriageMoneyTotal());
|
|
|
|
|
+ orderCountVo.setOrderMoneyTotal(add);
|
|
|
|
|
+ orderCountVo.setPayMoneyTotal(add);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (orderCountVo.getOrderTotal()!=0L
|
|
|
|
|
+ &&orderCountVo.getOrderMoneyTotal()!=null
|
|
|
|
|
+ &&orderCountVo.getOrderMoneyTotal().compareTo(BigDecimal.ZERO) != 0){
|
|
|
|
|
+ BigDecimal unitPrice = orderCountVo.getOrderMoneyTotal().divide(new BigDecimal(orderCountVo.getOrderTotal()), 2, RoundingMode.HALF_UP);
|
|
|
|
|
+ orderCountVo.setAverageOrderValueMoney(unitPrice);
|
|
|
}
|
|
}
|
|
|
- exportNormalOrders(orderList, response, "正常订单");
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ return orderCountVo;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
|
|
+ /**
|
|
|
* 导出正常订单数据到Excel(直接输出到HttpServletResponse)
|
|
* 导出正常订单数据到Excel(直接输出到HttpServletResponse)
|
|
|
*/
|
|
*/
|
|
|
- public void exportNormalOrders (List < Order > orderList,
|
|
|
|
|
- HttpServletResponse response,
|
|
|
|
|
- String fileName){
|
|
|
|
|
- // 设置响应头
|
|
|
|
|
- ExportUtils.setupResponse(response, fileName);
|
|
|
|
|
-
|
|
|
|
|
- try (Workbook workbook = new XSSFWorkbook()) {
|
|
|
|
|
|
|
+ public void exportNormalOrders (List < Order > orderList, ExportTask exportTask,
|
|
|
|
|
+ String fileName,ExportContext context){
|
|
|
|
|
+ // 2. 生成安全的文件名
|
|
|
|
|
+ String safeFileName = ExportUtils.generateSafeFileName(fileName);
|
|
|
|
|
+ // 3. 准备文件路径
|
|
|
|
|
+ try {
|
|
|
|
|
+ ExportUtils.prepareExportFile(safeFileName, exportTask);
|
|
|
|
|
+ } catch (ExportException e) {
|
|
|
|
|
+ exportTask.setExportStatus(2);//处理失败
|
|
|
|
|
+ exportTask.setExportMsg(e.getMessage());
|
|
|
|
|
+ exportTaskService.updateById(exportTask);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ File file = new File(exportTask.getFileUrl());
|
|
|
|
|
+ try (Workbook workbook = new SXSSFWorkbook(100)) {
|
|
|
// 创建Sheet
|
|
// 创建Sheet
|
|
|
Sheet sheet = workbook.createSheet("正常订单");
|
|
Sheet sheet = workbook.createSheet("正常订单");
|
|
|
|
|
|
|
@@ -1427,17 +1508,36 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|
|
ExportUtils.createColumnHeaders(sheet, headerStyle, headers);
|
|
ExportUtils.createColumnHeaders(sheet, headerStyle, headers);
|
|
|
|
|
|
|
|
// 填充数据
|
|
// 填充数据
|
|
|
- fillOrderData(sheet, orderList, dataStyle, numberStyle, dateStyle);
|
|
|
|
|
-
|
|
|
|
|
- // 写入响应流
|
|
|
|
|
- workbook.write(response.getOutputStream());
|
|
|
|
|
- response.getOutputStream().flush();
|
|
|
|
|
-
|
|
|
|
|
|
|
+ fillOrderData(sheet, orderList, dataStyle, numberStyle, dateStyle, context);
|
|
|
|
|
+ // 写入文件
|
|
|
|
|
+ try (FileOutputStream fos = new FileOutputStream(file);
|
|
|
|
|
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
|
|
|
|
|
+ workbook.write(bos);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查是否被中断
|
|
|
|
|
+ if (context.isCancelled() || Thread.currentThread().isInterrupted()) {
|
|
|
|
|
+ log.info("导出任务 {} 被中断,已处理 {} 行", exportTask.getTaskId(), context.getProcessedRows());
|
|
|
|
|
+ exportTask.setExportStatus(1); // 3-已取消
|
|
|
|
|
+ exportTask.setExportMsg("导出被用户取消,已处理" + context.getProcessedRows() + "行");
|
|
|
|
|
+ exportTaskService.updateById(exportTask);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
log.info("订单导出成功,文件:{},记录数:{}", fileName, orderList.size());
|
|
log.info("订单导出成功,文件:{},记录数:{}", fileName, orderList.size());
|
|
|
-
|
|
|
|
|
|
|
+ exportTask.setExportStatus(1);
|
|
|
|
|
+ exportTask.setExportMsg("导出成功");
|
|
|
|
|
+ exportTask.setUpdateTime(new Date());
|
|
|
|
|
+ exportTaskService.updateById(exportTask);
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ log.error("导出任务 {} 被中断", exportTask.getTaskId());
|
|
|
|
|
+ Thread.currentThread().interrupt(); // 重新设置中断状态
|
|
|
|
|
+ exportTask.setExportStatus(4);
|
|
|
|
|
+ exportTask.setExportMsg("导出被用户取消");
|
|
|
|
|
+ exportTaskService.updateById(exportTask);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("订单导出失败", e);
|
|
log.error("订单导出失败", e);
|
|
|
- throw new RuntimeException("导出失败:" + e.getMessage());
|
|
|
|
|
|
|
+ exportTask.setExportStatus(2);
|
|
|
|
|
+ exportTask.setExportMsg("导出失败:" + e.getMessage());
|
|
|
|
|
+ exportTaskService.updateById(exportTask);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1471,19 +1571,41 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|
|
* 填充订单数据
|
|
* 填充订单数据
|
|
|
*/
|
|
*/
|
|
|
private void fillOrderData (Sheet sheet, List < Order > orderList,
|
|
private void fillOrderData (Sheet sheet, List < Order > orderList,
|
|
|
- CellStyle dataStyle, CellStyle numberStyle, CellStyle dateStyle){
|
|
|
|
|
|
|
+ CellStyle dataStyle, CellStyle numberStyle, CellStyle dateStyle,ExportContext context) throws InterruptedException{
|
|
|
|
|
+ int batchSize = 100; // 每批处理1000条
|
|
|
|
|
+ int totalRows = orderList.size();
|
|
|
int rowNum = 2; // 从第3行开始(0-based索引)
|
|
int rowNum = 2; // 从第3行开始(0-based索引)
|
|
|
int indexNum = 1; //序号
|
|
int indexNum = 1; //序号
|
|
|
- for (Order order : orderList) {
|
|
|
|
|
- Row row = sheet.createRow(rowNum++);
|
|
|
|
|
- row.setHeightInPoints(18);
|
|
|
|
|
- // 订单编号
|
|
|
|
|
- ExportUtils.createCell(sheet, row, 0, order.getOrderItems().size(), rowNum, indexNum, dataStyle);
|
|
|
|
|
- fillOrderRowData(sheet, rowNum, row, order, dataStyle, numberStyle, dateStyle);
|
|
|
|
|
- rowNum += order.getOrderItems().size() - 1;
|
|
|
|
|
- indexNum++;
|
|
|
|
|
|
|
+ for (int i = 0; i < totalRows; i += batchSize) {
|
|
|
|
|
+ int end = Math.min(i + batchSize, totalRows);
|
|
|
|
|
+ List<Order> batch = orderList.subList(i, end);
|
|
|
|
|
+ for (Order order : batch) {
|
|
|
|
|
+ // 定期检查中断标志
|
|
|
|
|
+ if (context.isCancelled() || Thread.currentThread().isInterrupted()) {
|
|
|
|
|
+ context.setProcessedRows(indexNum);
|
|
|
|
|
+ break;
|
|
|
|
|
+// throw new InterruptedException("导出被中断");
|
|
|
|
|
+ }
|
|
|
|
|
+ Row row = sheet.createRow(rowNum++);
|
|
|
|
|
+ row.setHeightInPoints(18);
|
|
|
|
|
+ //序号
|
|
|
|
|
+ ExportUtils.createCell(sheet, row, 0, order.getOrderItems().size(), rowNum, indexNum, dataStyle);
|
|
|
|
|
+ fillOrderRowData(sheet, rowNum, row, order, dataStyle, numberStyle, dateStyle);
|
|
|
|
|
+ rowNum += order.getOrderItems().size() - 1;
|
|
|
|
|
+ indexNum++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 定期刷新到磁盘
|
|
|
|
|
+ if (rowNum % 100 == 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ((SXSSFSheet) sheet).flushRows(100);
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ log.warn("刷新行数据失败", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1613,4 +1735,4 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- }
|
|
|
|
|
|
|
+}
|