feat(performance): 优化绩效表合并导出功能

- 移除ZIP压缩功能,改为直接返回合并的Excel文件
- 新增MERGED_EXPORT_CELL_ADDRESSES常量定义合并导出单元格地址
- 添加正则表达式用于匹配和替换被考核人姓名
- 实现基于模板的合并导出功能,支持模板缓存机制
- 添加导出失败时的降级处理和空白页生成
- 实现被考核人姓名的自动填充和单元格值的安全读取
- 优化合并工作簿构建逻辑,移除重复的文件内容加载
- 改进下载文件命名规则,去除编号前缀并调整格式
This commit is contained in:
dk
2026-07-09 16:23:56 +08:00
parent b6c2311749
commit 5b2c6dd94c
2 changed files with 221 additions and 77 deletions

View File

@@ -25,4 +25,11 @@ public final class PerformanceExcelCellConstants {
"K6", "K7", "K8", "K9", "K10", "K6", "K7", "K8", "K9", "K10",
"L6", "L7", "L8", "L9", "L10" "L6", "L7", "L8", "L9", "L10"
); );
public static final List<String> MERGED_EXPORT_CELL_ADDRESSES = List.of(
"I6", "I7", "I8", "I9",
"J6", "J7", "J8", "J9", "J10",
"K6", "K7", "K8", "K9", "K10",
"L6", "L7", "L8", "L9", "L10"
);
} }

View File

@@ -93,9 +93,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.zip.CRC32; import java.util.regex.Matcher;
import java.util.zip.ZipEntry; import java.util.regex.Pattern;
import java.util.zip.ZipOutputStream;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
@@ -105,7 +104,8 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
public class PerformanceSheetServiceImpl implements PerformanceSheetService { public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private static final String EXCEL_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private static final String EXCEL_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static final String ZIP_CONTENT_TYPE = "application/zip"; private static final Pattern ASSESSED_EMPLOYEE_TEXT_PATTERN = Pattern.compile("(被考核人\\s*[:]\\s*)(.+)");
private static final long CURRENT_TEMPLATE_CACHE_KEY = -1L;
@Resource @Resource
private PerformanceSheetMapper performanceSheetMapper; private PerformanceSheetMapper performanceSheetMapper;
@@ -263,7 +263,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
for (Long id : ids) { for (Long id : ids) {
sheets.add(validateReadableSheet(id)); sheets.add(validateReadableSheet(id));
} }
return zipSheetsWithMergedWorkbook("绩效表_" + LocalDate.now() + ".zip", sheets); return buildMergedWorkbookDownload(sheets);
} }
@Override @Override
@@ -282,7 +282,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
employeeIds = List.of(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUserId())); employeeIds = List.of(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUserId()));
} }
List<PerformanceSheetDO> sheets = performanceSheetMapper.selectExportList(reqVO, employeeIds); List<PerformanceSheetDO> sheets = performanceSheetMapper.selectExportList(reqVO, employeeIds);
return zipSheetsWithMergedWorkbook("绩效表_" + LocalDate.now() + ".zip", sheets); return buildMergedWorkbookDownload(sheets);
} }
@Override @Override
@@ -530,69 +530,40 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
return respVO; return respVO;
} }
private PerformanceDownloadFile zipSheetsWithMergedWorkbook(String filename, List<PerformanceSheetDO> sheets) { private PerformanceDownloadFile buildMergedWorkbookDownload(List<PerformanceSheetDO> sheets) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try {
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { return new PerformanceDownloadFile(buildMergedWorkbookDownloadName(sheets), EXCEL_CONTENT_TYPE,
Map<String, Integer> filenameCounts = new HashMap<>(); buildMergedWorkbook(sheets));
Map<PerformanceSheetDO, byte[]> fileContents = loadFileContents(sheets);
if (sheets != null && !sheets.isEmpty()) {
byte[] mergedWorkbookContent = buildMergedWorkbook(sheets, fileContents);
putStoredZipEntry(zipOutputStream, buildMergedWorkbookEntryName(sheets), mergedWorkbookContent);
}
for (PerformanceSheetDO sheet : sheets) {
byte[] content = fileContents.get(sheet);
String entryName = uniqueFilename(defaultDownloadName(sheet), filenameCounts);
putStoredZipEntry(zipOutputStream, entryName, content);
}
zipOutputStream.finish();
return new PerformanceDownloadFile(filename, ZIP_CONTENT_TYPE, outputStream.toByteArray());
} catch (Exception ex) { } catch (Exception ex) {
throw new IllegalStateException("打包绩效表失败", ex); throw new IllegalStateException("导出绩效汇总 Excel 失败", ex);
} }
} }
private Map<PerformanceSheetDO, byte[]> loadFileContents(List<PerformanceSheetDO> sheets) { private byte[] buildMergedWorkbook(List<PerformanceSheetDO> sheets) {
Map<PerformanceSheetDO, byte[]> fileContents = new IdentityHashMap<>();
if (sheets == null || sheets.isEmpty()) {
return fileContents;
}
for (PerformanceSheetDO sheet : sheets) {
fileContents.put(sheet, readFileContent(sheet));
}
return fileContents;
}
private void putStoredZipEntry(ZipOutputStream zipOutputStream, String entryName, byte[] content) throws Exception {
CRC32 crc32 = new CRC32();
crc32.update(content);
ZipEntry zipEntry = new ZipEntry(entryName);
zipEntry.setMethod(ZipEntry.STORED);
zipEntry.setSize(content.length);
zipEntry.setCompressedSize(content.length);
zipEntry.setCrc(crc32.getValue());
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(content);
zipOutputStream.closeEntry();
}
private byte[] buildMergedWorkbook(List<PerformanceSheetDO> sheets, Map<PerformanceSheetDO, byte[]> fileContents) {
try (XSSFWorkbook targetWorkbook = new XSSFWorkbook(); try (XSSFWorkbook targetWorkbook = new XSSFWorkbook();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
if (sheets == null || sheets.isEmpty()) {
Sheet emptySheet = targetWorkbook.createSheet("绩效表汇总");
Row row = emptySheet.createRow(0);
row.createCell(0).setCellValue("暂无绩效数据");
} else {
Set<String> usedSheetNames = new HashSet<>(); Set<String> usedSheetNames = new HashSet<>();
Map<Long, PerformanceTemplateDO> templateCache = new HashMap<>();
Map<Long, byte[]> templateContentCache = new HashMap<>();
for (PerformanceSheetDO performanceSheet : sheets) { for (PerformanceSheetDO performanceSheet : sheets) {
byte[] sourceBytes = fileContents.get(performanceSheet);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(sourceBytes);
Workbook sourceWorkbook = WorkbookFactory.create(inputStream)) {
if (!(sourceWorkbook instanceof XSSFWorkbook)) {
throw new IllegalStateException("绩效汇总导出仅支持 .xlsx 文件");
}
if (sourceWorkbook.getNumberOfSheets() <= 0) {
continue;
}
Sheet sourceSheet = sourceWorkbook.getSheetAt(0);
String targetSheetName = sanitizeSheetName(buildMergedSheetName(performanceSheet), usedSheetNames); String targetSheetName = sanitizeSheetName(buildMergedSheetName(performanceSheet), usedSheetNames);
Sheet targetSheet = targetWorkbook.createSheet(targetSheetName); Sheet targetSheet = targetWorkbook.createSheet(targetSheetName);
copySheet(sourceWorkbook, sourceSheet, targetWorkbook, targetSheet); try {
PerformanceTemplateDO template = resolveTemplateForMergedExport(performanceSheet, templateCache);
copyTemplateSheetToTarget(targetWorkbook, targetSheet, template, templateContentCache);
} catch (Exception ex) {
log.warn("复制绩效汇总模板失败改为空白页继续导出。sheetId={}, employeeId={}, periodMonth={}, sourceFileId={}, sourceFileName={}, reason={}",
performanceSheet.getId(), performanceSheet.getEmployeeId(), performanceSheet.getPeriodMonth(),
performanceSheet.getFileId(), performanceSheet.getFileName(), ex.getMessage(), ex);
writeFallbackSheetHeader(targetSheet, performanceSheet);
}
fillAssessedEmployeeName(targetSheet, performanceSheet.getEmployeeName());
writeMergedExportCellValues(targetSheet, readMergedExportCellValuesSafely(performanceSheet));
} }
} }
targetWorkbook.setForceFormulaRecalculation(true); targetWorkbook.setForceFormulaRecalculation(true);
@@ -603,7 +574,10 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
} }
} }
private String buildMergedWorkbookEntryName(List<PerformanceSheetDO> sheets) { private String buildMergedWorkbookDownloadName(List<PerformanceSheetDO> sheets) {
if (sheets == null || sheets.isEmpty()) {
return "绩效表汇总_" + LocalDate.now() + ".xlsx";
}
Set<String> periodMonths = new LinkedHashSet<>(); Set<String> periodMonths = new LinkedHashSet<>();
for (PerformanceSheetDO sheet : sheets) { for (PerformanceSheetDO sheet : sheets) {
if (sheet != null && StringUtils.hasText(sheet.getPeriodMonth())) { if (sheet != null && StringUtils.hasText(sheet.getPeriodMonth())) {
@@ -611,9 +585,181 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
} }
} }
if (periodMonths.size() == 1) { if (periodMonths.size() == 1) {
return "0_绩效表汇总_" + periodMonths.iterator().next() + "月.xlsx"; return "绩效表汇总_" + periodMonths.iterator().next() + "月.xlsx";
} }
return "0_绩效表汇总.xlsx"; return "绩效表汇总_" + LocalDate.now() + ".xlsx";
}
private PerformanceTemplateDO resolveTemplateForMergedExport(PerformanceSheetDO performanceSheet,
Map<Long, PerformanceTemplateDO> templateCache) {
PerformanceTemplateDO template = null;
Long templateId = performanceSheet.getTemplateId();
if (templateId != null) {
template = templateCache.get(templateId);
if (!templateCache.containsKey(templateId)) {
template = performanceTemplateMapper.selectById(templateId);
templateCache.put(templateId, template);
}
}
if (template == null) {
template = templateCache.get(CURRENT_TEMPLATE_CACHE_KEY);
if (!templateCache.containsKey(CURRENT_TEMPLATE_CACHE_KEY)) {
template = performanceTemplateMapper.selectCurrent();
templateCache.put(CURRENT_TEMPLATE_CACHE_KEY, template);
}
if (template != null && templateId != null) {
log.warn("绩效汇总导出未找到原模板已回退当前启用模板。sheetId={}, templateId={}",
performanceSheet.getId(), templateId);
}
}
if (template == null) {
throw exception(ErrorCodeConstants.PERFORMANCE_TEMPLATE_CURRENT_NOT_EXISTS);
}
if (template.getFileId() == null) {
throw exception(ErrorCodeConstants.PERFORMANCE_FILE_NOT_EXISTS);
}
return template;
}
private void copyTemplateSheetToTarget(Workbook targetWorkbook, Sheet targetSheet, PerformanceTemplateDO template,
Map<Long, byte[]> templateContentCache) throws Exception {
byte[] templateContent = templateContentCache.get(template.getId());
if (templateContent == null) {
templateContent = readTemplateFileContent(template);
templateContentCache.put(template.getId(), templateContent);
}
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(templateContent);
Workbook templateWorkbook = WorkbookFactory.create(inputStream)) {
if (templateWorkbook.getNumberOfSheets() <= 0) {
return;
}
copySheet(templateWorkbook, templateWorkbook.getSheetAt(0), targetWorkbook, targetSheet);
}
}
private void writeFallbackSheetHeader(Sheet targetSheet, PerformanceSheetDO performanceSheet) {
Row firstRow = targetSheet.getRow(0);
if (firstRow == null) {
firstRow = targetSheet.createRow(0);
}
firstRow.createCell(0).setCellValue("被考核人:" + defaultText(performanceSheet.getEmployeeName()));
Row secondRow = targetSheet.getRow(1);
if (secondRow == null) {
secondRow = targetSheet.createRow(1);
}
secondRow.createCell(0).setCellValue("绩效月份:" + defaultText(performanceSheet.getPeriodMonth()));
Row fourthRow = targetSheet.getRow(3);
if (fourthRow == null) {
fourthRow = targetSheet.createRow(3);
}
fourthRow.createCell(0).setCellValue("说明:模板复制失败,本页仅保留汇总字段。");
}
private void fillAssessedEmployeeName(Sheet targetSheet, String employeeName) {
if (!StringUtils.hasText(employeeName)) {
return;
}
int firstRowNum = targetSheet.getFirstRowNum();
int lastRowNum = targetSheet.getLastRowNum();
for (int rowIndex = firstRowNum; rowIndex <= lastRowNum; rowIndex++) {
Row row = targetSheet.getRow(rowIndex);
if (row == null) {
continue;
}
short firstCellNum = row.getFirstCellNum();
short lastCellNum = row.getLastCellNum();
if (firstCellNum < 0 || lastCellNum < 0) {
continue;
}
for (int cellIndex = firstCellNum; cellIndex < lastCellNum; cellIndex++) {
Cell cell = row.getCell(cellIndex);
if (cell == null || cell.getCellType() != org.apache.poi.ss.usermodel.CellType.STRING) {
continue;
}
String cellValue = cell.getStringCellValue();
if (!StringUtils.hasText(cellValue)) {
continue;
}
Matcher matcher = ASSESSED_EMPLOYEE_TEXT_PATTERN.matcher(cellValue);
if (!matcher.find()) {
continue;
}
cell.setCellValue(matcher.replaceFirst("$1" + Matcher.quoteReplacement(defaultText(employeeName))));
}
}
}
private byte[] readTemplateFileContent(PerformanceTemplateDO template) {
CommonResult<byte[]> result = fileApi.getFileContent(template.getFileId());
byte[] content = result == null ? null : result.getCheckedData();
if (content == null || content.length == 0) {
throw exception(ErrorCodeConstants.PERFORMANCE_FILE_NOT_EXISTS);
}
return content;
}
private Map<String, String> readMergedExportCellValues(PerformanceSheetDO performanceSheet) throws Exception {
return readSelectedCellValues(readFileContent(performanceSheet), PerformanceExcelCellConstants.MERGED_EXPORT_CELL_ADDRESSES);
}
private Map<String, String> readMergedExportCellValuesSafely(PerformanceSheetDO performanceSheet) {
try {
return readMergedExportCellValues(performanceSheet);
} catch (Exception ex) {
log.warn("读取绩效汇总导出单元格失败已跳过该绩效表的数据回填。sheetId={}, employeeId={}, periodMonth={}, sourceFileId={}, sourceFileName={}, reason={}",
performanceSheet.getId(), performanceSheet.getEmployeeId(), performanceSheet.getPeriodMonth(),
performanceSheet.getFileId(), performanceSheet.getFileName(), ex.getMessage(), ex);
return Map.of();
}
}
private void writeMergedExportCellValues(Sheet targetSheet, Map<String, String> cellValues) {
if (cellValues == null || cellValues.isEmpty()) {
return;
}
for (Map.Entry<String, String> entry : cellValues.entrySet()) {
String cellAddress = entry.getKey();
String cellValue = entry.getValue();
if (!StringUtils.hasText(cellAddress) || cellValue == null) {
continue;
}
CellReference reference = new CellReference(cellAddress);
Row row = targetSheet.getRow(reference.getRow());
if (row == null) {
row = targetSheet.createRow(reference.getRow());
}
Cell cell = row.getCell(reference.getCol());
if (cell == null) {
cell = row.createCell(reference.getCol());
}
if (cell.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) {
cell.removeFormula();
}
BigDecimal scoreValue = parseScoreCellValue(cellAddress, cellValue);
if (scoreValue != null) {
cell.setCellValue(scoreValue.doubleValue());
} else {
cell.setCellValue(cellValue);
}
}
}
private BigDecimal parseScoreCellValue(String cellAddress, String cellValue) {
if (!isScoreCellAddress(cellAddress) || !StringUtils.hasText(cellValue)) {
return null;
}
String normalized = cellValue.trim().replace(",", "");
try {
return new BigDecimal(normalized);
} catch (NumberFormatException ex) {
return null;
}
}
private boolean isScoreCellAddress(String cellAddress) {
return PerformanceExcelCellConstants.ACTUAL_SCORE_CELLS.contains(cellAddress)
|| PerformanceExcelCellConstants.BASE_SCORE_CELLS.contains(cellAddress)
|| PerformanceExcelCellConstants.EXTRA_SCORE_CELLS.contains(cellAddress);
} }
private String buildMergedSheetName(PerformanceSheetDO sheet) { private String buildMergedSheetName(PerformanceSheetDO sheet) {
@@ -936,20 +1082,11 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
return content; return content;
} }
private String uniqueFilename(String filename, Map<String, Integer> filenameCounts) { private Map<String, String> readPrefillCellValues(PerformanceSheetDO sheet) throws Exception {
Integer count = filenameCounts.merge(filename, 1, Integer::sum); return readSelectedCellValues(readFileContent(sheet), PerformanceExcelCellConstants.PREFILL_CELL_ADDRESSES);
if (count == 1) {
return filename;
}
int dotIndex = filename.lastIndexOf('.');
if (dotIndex <= 0) {
return filename + "_" + count;
}
return filename.substring(0, dotIndex) + "_" + count + filename.substring(dotIndex);
} }
private Map<String, String> readPrefillCellValues(PerformanceSheetDO sheet) throws Exception { private Map<String, String> readSelectedCellValues(byte[] content, List<String> cellAddresses) throws Exception {
byte[] content = readFileContent(sheet);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(content); try (ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
Workbook workbook = WorkbookFactory.create(inputStream)) { Workbook workbook = WorkbookFactory.create(inputStream)) {
if (workbook.getNumberOfSheets() <= 0) { if (workbook.getNumberOfSheets() <= 0) {
@@ -959,7 +1096,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
DataFormatter formatter = new DataFormatter(); DataFormatter formatter = new DataFormatter();
Map<String, String> cellValues = new LinkedHashMap<>(); Map<String, String> cellValues = new LinkedHashMap<>();
for (String cellAddress : PerformanceExcelCellConstants.PREFILL_CELL_ADDRESSES) { for (String cellAddress : cellAddresses) {
String value = readCellDisplayValue(sourceSheet, evaluator, formatter, cellAddress); String value = readCellDisplayValue(sourceSheet, evaluator, formatter, cellAddress);
if (value != null) { if (value != null) {
cellValues.put(cellAddress, value); cellValues.put(cellAddress, value);