增加导出功能

This commit is contained in:
wuweihua
2026-10-21 18:06:21 +08:00
parent c04191c316
commit 92000956fc
15 changed files with 1181 additions and 21 deletions
@@ -0,0 +1,47 @@
package com.bms.price.domain;
import java.io.Serializable;
/** Async order/billing list Excel export progress (Redis). */
public class PrcOrderBillExportTask implements Serializable
{
private static final long serialVersionUID = 1L;
public static final String STATUS_PENDING = "PENDING";
public static final String STATUS_RUNNING = "RUNNING";
public static final String STATUS_SUCCESS = "SUCCESS";
public static final String STATUS_FAIL = "FAIL";
public static final String TYPE_ORDER = "ORDER";
public static final String TYPE_BILLING = "BILLING";
private String taskId;
private String status;
private int percent;
private String message;
private String error;
private String exportType;
private String fileName;
/** Relative path under download root, or empty until success. */
private String storageKey;
private int rowCount;
public String getTaskId() { return taskId; }
public void setTaskId(String taskId) { this.taskId = taskId; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public int getPercent() { return percent; }
public void setPercent(int percent) { this.percent = percent; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getError() { return error; }
public void setError(String error) { this.error = error; }
public String getExportType() { return exportType; }
public void setExportType(String exportType) { this.exportType = exportType; }
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public String getStorageKey() { return storageKey; }
public void setStorageKey(String storageKey) { this.storageKey = storageKey; }
public int getRowCount() { return rowCount; }
public void setRowCount(int rowCount) { this.rowCount = rowCount; }
}
@@ -0,0 +1,66 @@
package com.bms.price.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.bms.common.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/** Billing-list Excel export row. */
@Data
public class PrcBillingBillListExportRow implements Serializable
{
private static final long serialVersionUID = 1L;
@Excel(name = "订单号", width = 22)
private String orderNo;
@Excel(name = "运单号", width = 20)
private String trackingNo;
@Excel(name = "转单号", width = 20)
private String transferNo;
@Excel(name = "客户", width = 20)
private String customerName;
@Excel(name = "客户代码", width = 14)
private String customerCode;
@Excel(name = "产品代码", width = 14)
private String productCode;
@Excel(name = "产品名称", width = 22)
private String productName;
@Excel(name = "国家代码", width = 12)
private String countryCode;
@Excel(name = "国家/地区", width = 16)
private String countryName;
@Excel(name = "件数", width = 10)
private Integer pieces;
@Excel(name = "计费重", scale = 3, width = 12)
private BigDecimal chargeWeight;
@Excel(name = "原计费重", scale = 3, width = 12)
private BigDecimal weight;
@Excel(name = "收货重", scale = 3, width = 12)
private BigDecimal receiveWeight;
@Excel(name = "应收总额", scale = 2, width = 14)
private BigDecimal incomeTotal;
@Excel(name = "应付总额", scale = 2, width = 14)
private BigDecimal payTotal;
@Excel(name = "计价来源", width = 16)
private String matchSourceLabel;
@Excel(name = "计费状态", width = 14)
private String billingFailStatusLabel;
@Excel(name = "客户状态", width = 12)
private String customerActiveStatusLabel;
@Excel(name = "额度欠费", width = 12)
private String creditArrearsStatusLabel;
@Excel(name = "计费时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bizDate;
@Excel(name = "下单时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date orderTime;
@Excel(name = "销售员", width = 12)
private String belongSaler;
@Excel(name = "结算员", width = 12)
private String settlementMan;
@Excel(name = "客服员", width = 12)
private String serviceMan;
}
@@ -0,0 +1,63 @@
package com.bms.price.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.bms.common.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/** Order-list Excel export row. */
@Data
public class PrcOrderBillListExportRow implements Serializable
{
private static final long serialVersionUID = 1L;
@Excel(name = "订单号", width = 22)
private String orderNo;
@Excel(name = "运单号", width = 20)
private String trackingNo;
@Excel(name = "转单号", width = 20)
private String transferNo;
@Excel(name = "客户", width = 20)
private String customerName;
@Excel(name = "客户代码", width = 14)
private String customerCode;
@Excel(name = "产品代码", width = 14)
private String productCode;
@Excel(name = "产品名称", width = 22)
private String productName;
@Excel(name = "国家代码", width = 12)
private String countryCode;
@Excel(name = "国家/地区", width = 16)
private String countryName;
@Excel(name = "件数", width = 10)
private Integer pieces;
@Excel(name = "计费重", scale = 3, width = 12)
private BigDecimal weight;
@Excel(name = "体积重", scale = 3, width = 12)
private BigDecimal volumeWeight;
@Excel(name = "实重", scale = 3, width = 12)
private BigDecimal receiveWeight;
@Excel(name = "收件人邮编", width = 14)
private String destPostcode;
@Excel(name = "订单状态", width = 12)
private String orderStatusLabel;
@Excel(name = "计费状态", width = 16)
private String billingFailStatusLabel;
@Excel(name = "计费时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bizDate;
@Excel(name = "下单时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date orderTime;
@Excel(name = "提货时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date pickupDate;
@Excel(name = "销售员", width = 12)
private String belongSaler;
@Excel(name = "结算员", width = 12)
private String settlementMan;
@Excel(name = "客服员", width = 12)
private String serviceMan;
}
@@ -2,7 +2,9 @@ package com.bms.price.service;
import java.math.BigDecimal;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.PrcOrderBillExportTask;
import com.bms.price.domain.BatchAddIncomeBillFeeTask;
import com.bms.price.domain.ChargeWeightImportTask;
import com.bms.price.domain.vo.AddIncomeBillFeeRequest;
@@ -157,4 +159,18 @@ public interface IPrcOrderBillService
/** Data-scoped count for local rebill. */
int countBillIdsForLocalRebill(PrcOrderBill query);
/**
* Submit async Excel export for order list or billing list.
* Requires at least one meaningful filter; returns taskId for poll/download.
*
* @param exportType {@link PrcOrderBillExportTask#TYPE_ORDER} or {@link PrcOrderBillExportTask#TYPE_BILLING}
*/
String submitOrderBillExport(String exportType, PrcOrderBill query);
/** Poll Redis progress for order/billing list export task. */
PrcOrderBillExportTask getOrderBillExportTask(String taskId);
/** Stream SUCCESS export file to response by taskId. */
void downloadOrderBillExport(String taskId, HttpServletResponse response) throws Exception;
}
@@ -2,6 +2,9 @@ package com.bms.price.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
@@ -12,10 +15,13 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -27,11 +33,13 @@ import com.bms.ar.enums.ArOrderBillStatus;
import com.bms.common.annotation.DataScope;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.exception.ServiceException;
import com.bms.common.config.BmsConfig;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.DictUtils;
import com.bms.common.utils.MessageUtils;
import com.bms.common.utils.SecurityUtils;
import com.bms.common.utils.StringUtils;
import com.bms.common.utils.file.FileUtils;
import com.bms.common.utils.sign.Md5Utils;
import com.bms.common.utils.uuid.IdUtils;
import com.bms.customer.customerinfo.domain.BmsCustomer;
@@ -41,6 +49,7 @@ import com.bms.system.service.IBmsBizRuleService;
import com.bms.price.domain.PrcIncomeBill;
import com.bms.price.domain.PrcIncomeBillFee;
import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.PrcOrderBillExportTask;
import com.bms.price.domain.PrcOrderBillExt;
import com.bms.price.domain.PrcPayBill;
import com.bms.price.domain.PrcPayBillFee;
@@ -58,8 +67,10 @@ import com.bms.price.domain.vo.UpdateIncomeBillFeeRequest;
import com.bms.price.task.PrcBatchAddIncomeFeeAsyncRunner;
import com.bms.price.task.PrcChargeWeightImportAsyncRunner;
import com.bms.price.task.PrcChargeWeightImportAsyncRunner.ChargeWeightImportWorkItem;
import com.bms.price.task.PrcOrderBillExportAsyncRunner;
import com.bms.price.task.PrcOrderBillLocalRebillAsyncRunner;
import com.bms.price.task.PrcRecalcBillPriceAsyncRunner;
import com.bms.price.service.support.OrderBillExportSupport;
import com.bms.price.domain.vo.TmsPriceBillRequest;
import com.bms.price.domain.vo.TmsOrderBillStatusResult;
import com.bms.price.domain.vo.TmsPriceQuoteFeeLine;
@@ -152,6 +163,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
@Autowired
private PrcOrderBillLocalRebillAsyncRunner localRebillAsyncRunner;
@Autowired
private PrcOrderBillExportAsyncRunner orderBillExportAsyncRunner;
@Autowired
private IBmsBizRuleService bizRuleService;
@@ -161,6 +175,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
private static final int RECALC_PRICE_MAX = 5000;
/** Soft max for one local-rebill job (id-only load + paged parallel). */
private static final int LOCAL_REBILL_MAX = 100000;
/** Soft max for one order/billing list Excel export. */
private static final int ORDER_BILL_EXPORT_MAX = 50000;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -843,6 +859,84 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
return localRebillAsyncRunner.get(taskId);
}
@Override
public String submitOrderBillExport(String exportType, PrcOrderBill query)
{
String type = StringUtils.nvl(exportType, "").trim().toUpperCase();
if (!PrcOrderBillExportTask.TYPE_ORDER.equals(type)
&& !PrcOrderBillExportTask.TYPE_BILLING.equals(type))
{
type = PrcOrderBillExportTask.TYPE_ORDER;
}
PrcOrderBill q = query == null ? new PrcOrderBill() : query;
if (!OrderBillExportSupport.hasExportFilter(q))
{
throw new ServiceException(MessageUtils.message("price.bill.export.filterRequired"));
}
int total = self.countBillIdsForLocalRebill(q);
if (total <= 0)
{
throw new ServiceException(MessageUtils.message("price.bill.export.noMatch"));
}
if (total > ORDER_BILL_EXPORT_MAX)
{
throw new ServiceException(MessageUtils.message("price.bill.export.tooMany",
Integer.valueOf(ORDER_BILL_EXPORT_MAX)));
}
String prefix = PrcOrderBillExportTask.TYPE_BILLING.equals(type)
? "\u8ba1\u8d39\u5217\u8868"
: "\u8ba2\u5355\u5217\u8868";
String fileName = prefix + "_" + DateUtils.dateTimeNow() + ".xlsx";
String taskId = IdUtils.fastSimpleUUID();
orderBillExportAsyncRunner.put(taskId, PrcOrderBillExportTask.STATUS_PENDING, 0, "queued", null,
type, fileName, null, 0);
// Pass request Authentication into async thread for @DataScope on selectBillList.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
orderBillExportAsyncRunner.run(taskId, type, q, fileName, authentication);
return taskId;
}
@Override
public PrcOrderBillExportTask getOrderBillExportTask(String taskId)
{
if (StringUtils.isEmpty(taskId) || orderBillExportAsyncRunner == null)
{
return null;
}
return orderBillExportAsyncRunner.get(taskId);
}
@Override
public void downloadOrderBillExport(String taskId, HttpServletResponse response) throws Exception
{
PrcOrderBillExportTask task = getOrderBillExportTask(taskId);
if (task == null)
{
throw new ServiceException(MessageUtils.message("price.bill.export.taskNotExists"));
}
if (!PrcOrderBillExportTask.STATUS_SUCCESS.equals(task.getStatus())
|| StringUtils.isEmpty(task.getStorageKey()))
{
throw new ServiceException(MessageUtils.message("price.bill.export.notReady"));
}
if (!FileUtils.checkAllowDownload(task.getStorageKey()))
{
throw new ServiceException(MessageUtils.message("price.bill.export.fileMissing"));
}
Path downloadRoot = Paths.get(BmsConfig.getDownloadPath()).toAbsolutePath().normalize();
Path filePath = downloadRoot.resolve(task.getStorageKey()).normalize();
if (!filePath.startsWith(downloadRoot) || !Files.isRegularFile(filePath))
{
throw new ServiceException(MessageUtils.message("price.bill.export.fileMissing"));
}
String fileName = StringUtils.isNotEmpty(task.getFileName())
? task.getFileName()
: (taskId + ".xlsx");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
FileUtils.setAttachmentResponseHeader(response, fileName);
FileUtils.writeBytes(filePath.toString(), response.getOutputStream());
}
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
public List<String> selectBillIdsForLocalRebill(PrcOrderBill query)
@@ -0,0 +1,297 @@
package com.bms.price.service.support;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletResponse;
import com.bms.common.utils.StringUtils;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.vo.PrcBillingBillListExportRow;
import com.bms.price.domain.vo.PrcOrderBillListExportRow;
/** Map PrcOrderBill rows to Excel export VOs and write response. */
public final class OrderBillExportSupport
{
private OrderBillExportSupport()
{
}
/**
* True when query has at least one meaningful filter.
* Default orderStatuses alone does not count.
*/
public static boolean hasExportFilter(PrcOrderBill query)
{
if (query == null)
{
return false;
}
if (hasTexts(query.getOrderIdentifiers()) || hasTexts(query.getOrderNos())
|| hasTexts(query.getTrackingNos()) || hasTexts(query.getTransferNos())
|| hasTexts(query.getCustomerCodes()) || hasTexts(query.getProductCodes())
|| hasTexts(query.getBillIds()))
{
return true;
}
if (StringUtils.isNotEmpty(query.getCreditArrearsStatus())
|| StringUtils.isNotEmpty(query.getCountryCode())
|| StringUtils.isNotEmpty(query.getCustomerCode())
|| StringUtils.isNotEmpty(query.getOrderNo())
|| StringUtils.isNotEmpty(query.getTrackingNo())
|| StringUtils.isNotEmpty(query.getTransferNo())
|| StringUtils.isNotEmpty(query.getProductCode()))
{
return true;
}
if (query.getBillingFailStatuses() != null && !query.getBillingFailStatuses().isEmpty())
{
return true;
}
if (query.getBillingFailStatus() != null)
{
return true;
}
Map<String, Object> params = query.getParams();
if (params != null)
{
if (notBlank(params.get("beginBizDate")) || notBlank(params.get("endBizDate"))
|| notBlank(params.get("beginPickupDate")) || notBlank(params.get("endPickupDate")))
{
return true;
}
}
return false;
}
private static boolean hasTexts(List<?> list)
{
if (list == null || list.isEmpty())
{
return false;
}
for (Object item : list)
{
if (item != null && StringUtils.isNotEmpty(String.valueOf(item).trim()))
{
return true;
}
}
return false;
}
private static boolean notBlank(Object v)
{
return v != null && StringUtils.isNotEmpty(String.valueOf(v).trim());
}
/** Export order-list Excel by current query result. */
public static void exportOrderList(HttpServletResponse response, List<PrcOrderBill> list)
{
ExcelUtil.export(response, toOrderListRows(list), PrcOrderBillListExportRow.class, "\u8ba2\u5355\u5217\u8868");
}
/** Export billing-list Excel by current query result. */
public static void exportBillingList(HttpServletResponse response, List<PrcOrderBill> list)
{
ExcelUtil.export(response, toBillingListRows(list), PrcBillingBillListExportRow.class, "\u8ba1\u8d39\u5217\u8868");
}
public static List<PrcOrderBillListExportRow> toOrderListRows(List<PrcOrderBill> list)
{
List<PrcOrderBillListExportRow> rows = new ArrayList<PrcOrderBillListExportRow>();
if (list == null)
{
return rows;
}
for (PrcOrderBill bill : list)
{
if (bill == null)
{
continue;
}
PrcOrderBillListExportRow row = new PrcOrderBillListExportRow();
row.setOrderNo(bill.getOrderNo());
row.setTrackingNo(bill.getTrackingNo());
row.setTransferNo(bill.getTransferNo());
row.setCustomerName(bill.getCustomerName());
row.setCustomerCode(bill.getCustomerCode());
row.setProductCode(bill.getProductCode());
row.setProductName(bill.getProductName());
row.setCountryCode(bill.getCountryCode());
row.setCountryName(bill.getCountryName());
row.setPieces(bill.getPieces());
row.setWeight(bill.getWeight());
row.setVolumeWeight(bill.getVolumeWeight());
row.setReceiveWeight(bill.getReceiveWeight());
row.setDestPostcode(bill.getDestPostcode());
row.setOrderStatusLabel(orderStatusLabel(bill.getOrderStatus()));
row.setBillingFailStatusLabel(billingFailStatusLabel(bill.getBillingFailStatus()));
row.setBizDate(bill.getBizDate());
row.setOrderTime(bill.getOrderTime());
row.setPickupDate(bill.getPickupDate());
row.setBelongSaler(bill.getBelongSaler());
row.setSettlementMan(bill.getSettlementMan());
row.setServiceMan(bill.getServiceMan());
rows.add(row);
}
return rows;
}
public static List<PrcBillingBillListExportRow> toBillingListRows(List<PrcOrderBill> list)
{
List<PrcBillingBillListExportRow> rows = new ArrayList<PrcBillingBillListExportRow>();
if (list == null)
{
return rows;
}
for (PrcOrderBill bill : list)
{
if (bill == null)
{
continue;
}
PrcBillingBillListExportRow row = new PrcBillingBillListExportRow();
row.setOrderNo(bill.getOrderNo());
row.setTrackingNo(bill.getTrackingNo());
row.setTransferNo(bill.getTransferNo());
row.setCustomerName(bill.getCustomerName());
row.setCustomerCode(bill.getCustomerCode());
row.setProductCode(bill.getProductCode());
row.setProductName(bill.getProductName());
row.setCountryCode(bill.getCountryCode());
row.setCountryName(bill.getCountryName());
row.setPieces(bill.getPieces());
row.setChargeWeight(bill.getChargeWeight());
row.setWeight(bill.getWeight());
row.setReceiveWeight(bill.getReceiveWeight());
row.setIncomeTotal(bill.getIncomeTotal());
row.setPayTotal(bill.getPayTotal());
row.setMatchSourceLabel(matchSourceLabel(bill.getMatchSource()));
row.setBillingFailStatusLabel(billingFailStatusLabel(bill.getBillingFailStatus()));
row.setCustomerActiveStatusLabel(customerActiveLabel(bill.getCustomerActiveStatus()));
row.setCreditArrearsStatusLabel(creditArrearsLabel(bill.getCreditArrearsStatus()));
row.setBizDate(bill.getBizDate());
row.setOrderTime(bill.getOrderTime());
row.setBelongSaler(bill.getBelongSaler());
row.setSettlementMan(bill.getSettlementMan());
row.setServiceMan(bill.getServiceMan());
rows.add(row);
}
return rows;
}
public static String orderStatusLabel(Integer status)
{
if (status == null)
{
return "";
}
switch (status.intValue())
{
case 0: return "草稿订单";
case 1: return "确认订单";
case 2: return "已中转";
case 3: return "已收货";
case 4: return "已装箱";
case 5: return "已出库";
case 6: return "转运中";
case 7: return "已上网";
case 8: return "已签收";
case 9: return "已退件";
case 10: return "已取消";
case 11: return "已复核";
default: return String.valueOf(status);
}
}
public static String billingFailStatusLabel(Integer status)
{
if (status == null)
{
return "计费成功";
}
switch (status.intValue())
{
case 0: return "计费成功";
case 1: return "未匹配产品";
case 2: return "未匹配国家";
case 3: return "未匹配重量段";
case 4: return "未绑定套餐";
case 5: return "无生效套餐版本";
case 6: return "已生成应收账单";
case 7: return "产品档案缺失";
case 8: return "未命中价格";
case 9: return "分区无价格";
case 10: return "邮编必填";
case 11: return "邮编未匹配分区";
case 12: return "上游订单不存在";
case 13: return "上游订单号为空";
case 14: return "上游客户为空";
case 15: return "上游产品为空";
case 16: return "上游重量无效";
case 17: return "计费时间为空";
case 18: return "退费订单无法解析";
case 19: return "客户代码为空";
case 20: return "客户已停用";
case 21: return "运费为空";
case 22: return "未知失败";
default: return String.valueOf(status);
}
}
public static String matchSourceLabel(String source)
{
if (StringUtils.isEmpty(source))
{
return "";
}
if ("PACKAGE_FIXED".equals(source) || "FIXED".equals(source))
{
return "客户固定价套餐";
}
if ("PACKAGE_DISCOUNT".equals(source) || "DISCOUNT".equals(source))
{
return "客户折扣套餐";
}
if ("PRODUCT_DEFAULT".equals(source))
{
return "产品默认价";
}
if ("BILLING_FAIL".equals(source))
{
return "计费失败";
}
return source;
}
public static String customerActiveLabel(String status)
{
if ("ACTIVE".equals(status))
{
return "正常";
}
if ("MISSING".equals(status))
{
return "不存在";
}
if ("DISABLED".equals(status))
{
return "停用";
}
return status == null ? "" : status;
}
public static String creditArrearsLabel(String status)
{
if ("NORMAL".equals(status))
{
return "正常";
}
if ("ARREARS".equals(status))
{
return "欠费";
}
return status == null ? "" : status;
}
}
@@ -0,0 +1,157 @@
package com.bms.price.task;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.bms.common.config.BmsConfig;
import com.bms.common.core.redis.RedisCache;
import com.bms.common.utils.StringUtils;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.PrcOrderBillExportTask;
import com.bms.price.domain.vo.PrcBillingBillListExportRow;
import com.bms.price.domain.vo.PrcOrderBillListExportRow;
import com.bms.price.service.IPrcOrderBillService;
import com.bms.price.service.support.OrderBillExportSupport;
/**
* Async Excel export for order list / billing list.
* Writes under download path; poll task then download by taskId.
*/
@Component
public class PrcOrderBillExportAsyncRunner
{
private static final Logger log = LoggerFactory.getLogger(PrcOrderBillExportAsyncRunner.class);
public static final String CACHE_KEY_PREFIX = "price:billing:export:task:";
public static final int TTL_MINUTES = 60;
public static final String REL_DIR = "orderBillExport";
@Autowired
@Lazy
private IPrcOrderBillService orderBillService;
@Autowired
private RedisCache redisCache;
/**
* @param authentication login from request thread; required because {@code selectBillList} uses {@code @DataScope}
*/
@Async("threadPoolTaskExecutor")
public void run(String taskId, String exportType, PrcOrderBill query, String fileName,
Authentication authentication)
{
Path file = null;
SecurityContext previous = SecurityContextHolder.getContext();
try
{
// Async pool threads have no JWT filter; restore caller login for DataScope.
if (authentication != null)
{
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
put(taskId, PrcOrderBillExportTask.STATUS_RUNNING, 5, "querying", null,
exportType, fileName, null, 0);
List<PrcOrderBill> list = orderBillService.selectBillList(query == null ? new PrcOrderBill() : query);
int rows = list == null ? 0 : list.size();
put(taskId, PrcOrderBillExportTask.STATUS_RUNNING, 40, "building excel", null,
exportType, fileName, null, rows);
Path dir = Paths.get(BmsConfig.getDownloadPath(), REL_DIR).toAbsolutePath().normalize();
Files.createDirectories(dir);
file = dir.resolve(taskId + ".xlsx").normalize();
if (!file.startsWith(dir))
{
throw new IllegalStateException("invalid export path");
}
try (OutputStream out = Files.newOutputStream(file))
{
if (PrcOrderBillExportTask.TYPE_BILLING.equals(exportType))
{
List<PrcBillingBillListExportRow> exportRows = OrderBillExportSupport.toBillingListRows(list);
new ExcelUtil<PrcBillingBillListExportRow>(PrcBillingBillListExportRow.class)
.exportExcelToStream(exportRows, "\u8ba1\u8d39\u5217\u8868", out);
}
else
{
List<PrcOrderBillListExportRow> exportRows = OrderBillExportSupport.toOrderListRows(list);
new ExcelUtil<PrcOrderBillListExportRow>(PrcOrderBillListExportRow.class)
.exportExcelToStream(exportRows, "\u8ba2\u5355\u5217\u8868", out);
}
}
String storageKey = REL_DIR + "/" + taskId + ".xlsx";
put(taskId, PrcOrderBillExportTask.STATUS_SUCCESS, 100, "done", null,
exportType, fileName, storageKey, rows);
}
catch (Exception e)
{
log.error("order bill export failed taskId={}", taskId, e);
if (file != null)
{
try { Files.deleteIfExists(file); } catch (Exception ignore) { }
}
put(taskId, PrcOrderBillExportTask.STATUS_FAIL, 100, "failed", errorMessage(e),
exportType, fileName, null, 0);
}
finally
{
if (previous == null)
{
SecurityContextHolder.clearContext();
}
else
{
SecurityContextHolder.setContext(previous);
}
}
}
public void put(String taskId, String status, int percent, String message, String error,
String exportType, String fileName, String storageKey, int rowCount)
{
PrcOrderBillExportTask task = new PrcOrderBillExportTask();
task.setTaskId(taskId);
task.setStatus(status);
task.setPercent(percent);
task.setMessage(message);
task.setError(error);
task.setExportType(exportType);
task.setFileName(fileName);
task.setStorageKey(storageKey);
task.setRowCount(rowCount);
redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES);
}
public PrcOrderBillExportTask get(String taskId)
{
return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId);
}
private static String errorMessage(Exception e)
{
String msg = e.getMessage();
if (StringUtils.isEmpty(msg))
{
msg = e.getClass().getSimpleName();
}
if (msg.length() > 500)
{
msg = msg.substring(0, 500);
}
return msg;
}
}