fix: parse string datetime cells in external import
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.cms.externalimport;
|
package com.ruoyi.cms.externalimport;
|
||||||
|
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.CellType;
|
||||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||||
import org.apache.poi.ss.usermodel.DateUtil;
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
@@ -11,11 +12,14 @@ import org.apache.poi.ss.usermodel.WorkbookFactory;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析外部岗位模板。此解析器明确处理 Excel 日期单元格,替代旧导入逻辑中日期全部丢失的行为。
|
* 解析外部岗位模板。此解析器明确处理 Excel 日期单元格,替代旧导入逻辑中日期全部丢失的行为。
|
||||||
@@ -24,6 +28,7 @@ public class ExternalJobExcelParser {
|
|||||||
private static final String[] REQUIRED_HEADERS = {
|
private static final String[] REQUIRED_HEADERS = {
|
||||||
"Aca112", "Acb22a", "SalaryLow", "SalaryHight", "Aae397", "AAB004", "ORG", "ACE760", "Collect_time"
|
"Aca112", "Acb22a", "SalaryLow", "SalaryHight", "Aae397", "AAB004", "ORG", "ACE760", "Collect_time"
|
||||||
};
|
};
|
||||||
|
private static final Pattern TEXT_DATE_PATTERN = Pattern.compile("^\\s*(\\d{4})\\D+(\\d{1,2})\\D+(\\d{1,2}).*$");
|
||||||
private final DataFormatter formatter = new DataFormatter();
|
private final DataFormatter formatter = new DataFormatter();
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
|
||||||
@@ -114,11 +119,39 @@ public class ExternalJobExcelParser {
|
|||||||
if (cell == null) {
|
if (cell == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (DateUtil.isCellDateFormatted(cell)) {
|
if (isNumericDateCell(cell)) {
|
||||||
Date value = cell.getDateCellValue();
|
Date value = cell.getDateCellValue();
|
||||||
return value == null ? "" : dateFormat.format(value);
|
return value == null ? "" : dateFormat.format(value);
|
||||||
}
|
}
|
||||||
return formatter.formatCellValue(cell).trim();
|
return normalizeTextDate(formatter.formatCellValue(cell));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateUtil.isCellDateFormatted 会对 STRING 单元格读取数值,导致 POI 抛出类型不匹配异常。
|
||||||
|
* 只有数值型或数值结果公式才允许进入 Excel 日期格式判断。
|
||||||
|
*/
|
||||||
|
private boolean isNumericDateCell(Cell cell) {
|
||||||
|
CellType type = cell.getCellType();
|
||||||
|
if (type == CellType.NUMERIC) {
|
||||||
|
return DateUtil.isCellDateFormatted(cell);
|
||||||
|
}
|
||||||
|
return type == CellType.FORMULA
|
||||||
|
&& cell.getCachedFormulaResultType() == CellType.NUMERIC
|
||||||
|
&& DateUtil.isCellDateFormatted(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将 2026-07-14 00:00:00、2026/7/14、2026年7月14日 等文本统一为 ISO 日期。 */
|
||||||
|
private String normalizeTextDate(String value) {
|
||||||
|
String text = value == null ? "" : value.trim();
|
||||||
|
Matcher matcher = TEXT_DATE_PATTERN.matcher(text);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return LocalDate.of(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3))).toString();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBlank(Row row) {
|
private boolean isBlank(Row row) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
class ExternalJobExcelParserTest {
|
class ExternalJobExcelParserTest {
|
||||||
@Test
|
@Test
|
||||||
void parsesExcelDatesAndLocationFallback() throws Exception {
|
void parsesExcelDatesAndLocationFallback() throws Exception {
|
||||||
byte[] data = workbookBytes();
|
byte[] data = workbookBytes(false);
|
||||||
List<ExternalJobCandidate> rows = new ExternalJobExcelParser().parse(new ByteArrayInputStream(data));
|
List<ExternalJobCandidate> rows = new ExternalJobExcelParser().parse(new ByteArrayInputStream(data));
|
||||||
|
|
||||||
assertEquals(1, rows.size());
|
assertEquals(1, rows.size());
|
||||||
@@ -27,7 +27,16 @@ class ExternalJobExcelParserTest {
|
|||||||
assertEquals("Java 工程师", row.getJobTitle());
|
assertEquals("Java 工程师", row.getJobTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] workbookBytes() throws Exception {
|
@Test
|
||||||
|
void parsesStringDateTimeCellsWithoutTreatingThemAsNumericDates() throws Exception {
|
||||||
|
List<ExternalJobCandidate> rows = new ExternalJobExcelParser().parse(new ByteArrayInputStream(workbookBytes(true)));
|
||||||
|
|
||||||
|
assertEquals(1, rows.size());
|
||||||
|
assertEquals("2026-07-14", rows.get(0).getPostingDate());
|
||||||
|
assertEquals("2026-07-20", rows.get(0).getCollectDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] workbookBytes(boolean useStringDateTimes) throws Exception {
|
||||||
String[] headers = {"Aca112", "Acb22a", "SalaryLow", "SalaryHight", "Aae397", "AAB004", "ORG", "ACE760", "Collect_time", "City"};
|
String[] headers = {"Aca112", "Acb22a", "SalaryLow", "SalaryHight", "Aae397", "AAB004", "ORG", "ACE760", "Collect_time", "City"};
|
||||||
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||||
Row header = workbook.createSheet("合并数据").createRow(0);
|
Row header = workbook.createSheet("合并数据").createRow(0);
|
||||||
@@ -41,13 +50,21 @@ class ExternalJobExcelParserTest {
|
|||||||
data.createCell(1).setCellValue("负责平台开发");
|
data.createCell(1).setCellValue("负责平台开发");
|
||||||
data.createCell(2).setCellValue(5000);
|
data.createCell(2).setCellValue(5000);
|
||||||
data.createCell(3).setCellValue(10000);
|
data.createCell(3).setCellValue(10000);
|
||||||
|
if (useStringDateTimes) {
|
||||||
|
data.createCell(4).setCellValue("2026-07-14 00:00:00");
|
||||||
|
} else {
|
||||||
data.createCell(4).setCellValue(Date.from(LocalDate.of(2026, 7, 14).atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
|
data.createCell(4).setCellValue(Date.from(LocalDate.of(2026, 7, 14).atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
|
||||||
data.getCell(4).setCellStyle(dateStyle);
|
data.getCell(4).setCellStyle(dateStyle);
|
||||||
|
}
|
||||||
data.createCell(5).setCellValue("示例企业");
|
data.createCell(5).setCellValue("示例企业");
|
||||||
data.createCell(6).setCellValue("智联招聘");
|
data.createCell(6).setCellValue("智联招聘");
|
||||||
data.createCell(7).setCellValue("https://www.zhaopin.com/job/42");
|
data.createCell(7).setCellValue("https://www.zhaopin.com/job/42");
|
||||||
|
if (useStringDateTimes) {
|
||||||
|
data.createCell(8).setCellValue("2026-07-20 00:00:00");
|
||||||
|
} else {
|
||||||
data.createCell(8).setCellValue(Date.from(LocalDate.of(2026, 7, 20).atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
|
data.createCell(8).setCellValue(Date.from(LocalDate.of(2026, 7, 20).atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
|
||||||
data.getCell(8).setCellStyle(dateStyle);
|
data.getCell(8).setCellStyle(dateStyle);
|
||||||
|
}
|
||||||
data.createCell(9).setCellValue("石河子市");
|
data.createCell(9).setCellValue("石河子市");
|
||||||
workbook.write(output);
|
workbook.write(output);
|
||||||
return output.toByteArray();
|
return output.toByteArray();
|
||||||
|
|||||||
Reference in New Issue
Block a user