feat(performance): 优化绩效表合并导出功能
- 移除ZIP压缩功能,改为直接返回合并的Excel文件 - 新增MERGED_EXPORT_CELL_ADDRESSES常量定义合并导出单元格地址 - 添加正则表达式用于匹配和替换被考核人姓名 - 实现基于模板的合并导出功能,支持模板缓存机制 - 添加导出失败时的降级处理和空白页生成 - 实现被考核人姓名的自动填充和单元格值的安全读取 - 优化合并工作簿构建逻辑,移除重复的文件内容加载 - 改进下载文件命名规则,去除编号前缀并调整格式
This commit is contained in:
@@ -25,4 +25,11 @@ public final class PerformanceExcelCellConstants {
|
||||
"K6", "K7", "K8", "K9", "K10",
|
||||
"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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,9 +93,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
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 {
|
||||
|
||||
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
|
||||
private PerformanceSheetMapper performanceSheetMapper;
|
||||
@@ -263,7 +263,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||
for (Long id : ids) {
|
||||
sheets.add(validateReadableSheet(id));
|
||||
}
|
||||
return zipSheetsWithMergedWorkbook("绩效表_" + LocalDate.now() + ".zip", sheets);
|
||||
return buildMergedWorkbookDownload(sheets);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,7 +282,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||
employeeIds = List.of(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUserId()));
|
||||
}
|
||||
List<PerformanceSheetDO> sheets = performanceSheetMapper.selectExportList(reqVO, employeeIds);
|
||||
return zipSheetsWithMergedWorkbook("绩效表_" + LocalDate.now() + ".zip", sheets);
|
||||
return buildMergedWorkbookDownload(sheets);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -530,69 +530,40 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private PerformanceDownloadFile zipSheetsWithMergedWorkbook(String filename, List<PerformanceSheetDO> sheets) {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||
Map<String, Integer> filenameCounts = new HashMap<>();
|
||||
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());
|
||||
private PerformanceDownloadFile buildMergedWorkbookDownload(List<PerformanceSheetDO> sheets) {
|
||||
try {
|
||||
return new PerformanceDownloadFile(buildMergedWorkbookDownloadName(sheets), EXCEL_CONTENT_TYPE,
|
||||
buildMergedWorkbook(sheets));
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("打包绩效表失败", ex);
|
||||
throw new IllegalStateException("导出绩效汇总 Excel 失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<PerformanceSheetDO, byte[]> loadFileContents(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) {
|
||||
private byte[] buildMergedWorkbook(List<PerformanceSheetDO> sheets) {
|
||||
try (XSSFWorkbook targetWorkbook = new XSSFWorkbook();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Set<String> usedSheetNames = new HashSet<>();
|
||||
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);
|
||||
if (sheets == null || sheets.isEmpty()) {
|
||||
Sheet emptySheet = targetWorkbook.createSheet("绩效表汇总");
|
||||
Row row = emptySheet.createRow(0);
|
||||
row.createCell(0).setCellValue("暂无绩效数据");
|
||||
} else {
|
||||
Set<String> usedSheetNames = new HashSet<>();
|
||||
Map<Long, PerformanceTemplateDO> templateCache = new HashMap<>();
|
||||
Map<Long, byte[]> templateContentCache = new HashMap<>();
|
||||
for (PerformanceSheetDO performanceSheet : sheets) {
|
||||
String targetSheetName = sanitizeSheetName(buildMergedSheetName(performanceSheet), usedSheetNames);
|
||||
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);
|
||||
@@ -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<>();
|
||||
for (PerformanceSheetDO sheet : sheets) {
|
||||
if (sheet != null && StringUtils.hasText(sheet.getPeriodMonth())) {
|
||||
@@ -611,9 +585,181 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -936,20 +1082,11 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||
return content;
|
||||
}
|
||||
|
||||
private String uniqueFilename(String filename, Map<String, Integer> filenameCounts) {
|
||||
Integer count = filenameCounts.merge(filename, 1, Integer::sum);
|
||||
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 {
|
||||
return readSelectedCellValues(readFileContent(sheet), PerformanceExcelCellConstants.PREFILL_CELL_ADDRESSES);
|
||||
}
|
||||
|
||||
private Map<String, String> readPrefillCellValues(PerformanceSheetDO sheet) throws Exception {
|
||||
byte[] content = readFileContent(sheet);
|
||||
private Map<String, String> readSelectedCellValues(byte[] content, List<String> cellAddresses) throws Exception {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
|
||||
Workbook workbook = WorkbookFactory.create(inputStream)) {
|
||||
if (workbook.getNumberOfSheets() <= 0) {
|
||||
@@ -959,7 +1096,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
|
||||
DataFormatter formatter = new DataFormatter();
|
||||
Map<String, String> cellValues = new LinkedHashMap<>();
|
||||
for (String cellAddress : PerformanceExcelCellConstants.PREFILL_CELL_ADDRESSES) {
|
||||
for (String cellAddress : cellAddresses) {
|
||||
String value = readCellDisplayValue(sourceSheet, evaluator, formatter, cellAddress);
|
||||
if (value != null) {
|
||||
cellValues.put(cellAddress, value);
|
||||
|
||||
Reference in New Issue
Block a user