public void exportErrorOrderList(HttpServletResponse response, String exportKey){
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
Locale currentLocale = Optional.of(LocaleContextHolder.getLocale()).orElse(Locale.ENGLISH);
String langCode = LangEnum.getByCode(currentLocale.getLanguage()).getCode();
//订单导入模板类型
String countryCode = ContextUtils.getLoginAccount().getCountryCode();
OmsImportTemplateConfig.CountryImportConfig countryImportConfig = omsImportTemplateConfig.getCountryImportConfig(countryCode);
if (countryImportConfig == null) {
countryImportConfig = omsImportTemplateConfig.getImportConfig().get("GH");
}
String templateFileName = countryImportConfig.getTemplate().get(langCode);
response.setHeader("Content-disposition", "attachment;filename=" + URLUtil.encode(templateFileName, "utf-8"));
String redisKey = RedisConstant.OMS_ORDER_ERROR_LIST_EXPORT_KEY + exportKey;
// 导出错误的订单
RBucket<List<WarehouseExcelImportDto>> bucket = redissionLock.getRedissonClient().getBucket(redisKey, new JsonJacksonCodec());
if (!bucket.isExists()) {
log.error("exportErrorOrderList.bucket.notExist.redisKey:{}", redisKey);
throw new BussinessCodeException(INTERNAL_SERVER_ERROR);
}
List<WarehouseExcelImportDto> orderExcelDtoList = bucket.get();
if (CollectionUtils.isEmpty(orderExcelDtoList)) {
throw new BussinessCodeException(INTERNAL_SERVER_ERROR);
}
// 复制到导出 DTO。确保 WarehouseErrorExcelExportDto 中有 errorDesc 字段,
List<WarehouseErrorExcelExportDto> exportDtos = orderExcelDtoList.stream()
.map(dto -> {
WarehouseErrorExcelExportDto exportDto = BeanExUtil.copyProperties(dto, WarehouseErrorExcelExportDto.class);
// 组装错误描述:多个错误用分号拼起来
if (!CollectionUtils.isEmpty(dto.getErrorMessageList())) {
String desc = String.join(";", dto.getErrorMessageList());
exportDto.setErrorMessageList(desc);
}
return exportDto;
})
.collect(Collectors.toList());
// 模板存放位置
Path templateFileDir = Paths.get(templatePath).resolve(templateFileName);
// 注意:如果模板已更新(新增了“错误描述”列),必须删除本地旧模板或强制覆盖,否则不会生效。
// 这里简单处理:每次导出都重新下载模板。如果导出频繁,建议改用模板版本号或判断最后修改时间。
try {
Files.deleteIfExists(templateFileDir);
} catch (IOException e) {
log.warn("delete.old.template.failed, path:{}", templateFileDir, e);
}
try (OSSObject ossObject = FileSystemClient.getPrivateClient().getObject(templateFileName);
InputStream inputStream = ossObject.getObjectContent()) {
// 确保目录存在
Files.createDirectories(templateFileDir.getParent());
// 复制文件(自动处理缓冲区)
Files.copy(inputStream, templateFileDir, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
log.error("download.templateFile.error", e);
throw new BussinessCodeException(INTERNAL_SERVER_ERROR);
}
// 错误订单写入模板 excel
try (InputStream templateStream = Files.newInputStream(templateFileDir)) {
Workbook workbook = WorkbookFactory.create(templateStream);
Sheet sheet = workbook.getSheetAt(0);
int insertCol = 0; // 在第 0 列(A列)前插入
// 1. 先处理合并区域:把所有起始列 >= 0 的合并区域整体右移 1 列
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
// 先记录再清除再重建,避免遍历时修改报错
List<CellRangeAddress> newMerged = new ArrayList<>(mergedRegions.size());
for (CellRangeAddress region : mergedRegions) {
int firstCol = region.getFirstColumn();
int lastCol = region.getLastColumn();
if (firstCol >= insertCol) {
// 右移一列
newMerged.add(new CellRangeAddress(
region.getFirstRow(), region.getLastRow(),
firstCol + 1, lastCol + 1));
} else {
newMerged.add(region); // 不涉及,保持原样
}
}
// 清除旧的合并
for (int i = 0; i < mergedRegions.size(); i++) {
sheet.removeMergedRegion(0); // 每次移除第 0 个,下标自动前移
}
// 加回新的
newMerged.forEach(sheet::addMergedRegion);
// 2. 两行表头都右移一列
for (int rowIdx = 0; rowIdx <= 1; rowIdx++) {
Row row = sheet.getRow(rowIdx);
if (row == null) {
row = sheet.createRow(rowIdx);
}
// 从最后一列往前移,避免覆盖
int lastCol = row.getLastCellNum(); // 注意:getLastCellNum 是下一个空位
for (int i = lastCol; i >= 0; i--) {
Cell old = row.getCell(i);
if (old != null) {
Cell newCell = row.createCell(i + 1);
newCell.setCellValue(old.getStringCellValue());
newCell.setCellStyle(old.getCellStyle());
// 清空原位置
row.removeCell(old);
}
}
}
// 3. 写入 A 列表头(两行)
Row headRow0 = sheet.getRow(0);
Row headRow1 = sheet.getRow(1);
if (headRow0 == null) headRow0 = sheet.createRow(0);
if (headRow1 == null) headRow1 = sheet.createRow(1);
Cell a0 = headRow0.createCell(0);
Cell a1 = headRow1.createCell(0);
String headText = MessageUtil.getMessageWithLangAndParam(MessageConstant.INCORRECT_DESCRIPTION, langCode);
a0.setCellValue(headText);
a1.setCellValue(""); // 第二行留空,或与第一行合并
// 让 A 列的两行合并成一个表头单元格
sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 0));
// 4. 写回字节流,交给 EasyExcel
ByteArrayOutputStream bos = new ByteArrayOutputStream();
workbook.write(bos);
workbook.close();
try (ByteArrayInputStream modifiedTemplate = new ByteArrayInputStream(bos.toByteArray())) {
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.withTemplate(modifiedTemplate)
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().sheetNo(0).build();
excelWriter.write(exportDtos, writeSheet);
excelWriter.finish();
}
} catch (Exception e) {
log.error("exportExcel.error", e);
throw new BussinessCodeException(INTERNAL_SERVER_ERROR);
}
}