feat: Implement external job import functionality

- Add ExternalJobSourceAliasRequest class for managing source alias requests.
- Create ExternalUploadClient to interact with external Excel upload system.
- Introduce ExternalUploadFileMetadata to represent metadata of uploaded files.
- Define IExternalJobImportService interface for external job import operations.
- Update JobDataTrendServiceImpl to support dynamic source code filtering.
- Modify PublicJobFairMapper.xml to ensure proper query handling.
- Add unit tests for ExternalJobExcelParser and ExternalJobFingerprint.
- Create script for applying external job import migrations.
- Implement SQL migration for external job import, including new tables and columns.
This commit is contained in:
2026-07-21 14:56:47 +08:00
parent fab6718a83
commit d206b25fcf
24 changed files with 2312 additions and 10 deletions

View File

@@ -0,0 +1,56 @@
package com.ruoyi.cms.externalimport;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ExternalJobExcelParserTest {
@Test
void parsesExcelDatesAndLocationFallback() throws Exception {
byte[] data = workbookBytes();
List<ExternalJobCandidate> rows = new ExternalJobExcelParser().parse(new ByteArrayInputStream(data));
assertEquals(1, rows.size());
ExternalJobCandidate row = rows.get(0);
assertEquals("2026-07-14", row.getPostingDate());
assertEquals("2026-07-20", row.getCollectDate());
assertEquals("石河子市", row.getLocation());
assertEquals("Java 工程师", row.getJobTitle());
}
private byte[] workbookBytes() throws Exception {
String[] headers = {"Aca112", "Acb22a", "SalaryLow", "SalaryHight", "Aae397", "AAB004", "ORG", "ACE760", "Collect_time", "City"};
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
Row header = workbook.createSheet("合并数据").createRow(0);
for (int i = 0; i < headers.length; i++) {
header.createCell(i).setCellValue(headers[i]);
}
Row data = workbook.getSheetAt(0).createRow(1);
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat("yyyy-mm-dd"));
data.createCell(0).setCellValue("Java 工程师");
data.createCell(1).setCellValue("负责平台开发");
data.createCell(2).setCellValue(5000);
data.createCell(3).setCellValue(10000);
data.createCell(4).setCellValue(Date.from(LocalDate.of(2026, 7, 14).atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
data.getCell(4).setCellStyle(dateStyle);
data.createCell(5).setCellValue("示例企业");
data.createCell(6).setCellValue("智联招聘");
data.createCell(7).setCellValue("https://www.zhaopin.com/job/42");
data.createCell(8).setCellValue(Date.from(LocalDate.of(2026, 7, 20).atStartOfDay(java.time.ZoneId.systemDefault()).toInstant()));
data.getCell(8).setCellStyle(dateStyle);
data.createCell(9).setCellValue("石河子市");
workbook.write(output);
return output.toByteArray();
}
}
}

View File

@@ -0,0 +1,42 @@
package com.ruoyi.cms.externalimport;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class ExternalJobFingerprintTest {
@Test
void identityFingerprintIgnoresTrackingParametersAndMutableContent() {
ExternalJobCandidate first = candidate("https://www.zhaopin.com/job/42/?utm_source=test&from=feed", "5000", "岗位描述一");
ExternalJobCandidate changed = candidate("https://www.zhaopin.com/job/42?utm_campaign=summer", "7000", "岗位描述二");
assertEquals(ExternalJobFingerprint.identity(first), ExternalJobFingerprint.identity(changed));
assertNotEquals(ExternalJobFingerprint.content(first), ExternalJobFingerprint.content(changed));
}
@Test
void identityFingerprintSeparatesDifferentSources() {
ExternalJobCandidate zhaopin = candidate("https://www.zhaopin.com/job/42", "5000", "岗位描述");
ExternalJobCandidate liepin = candidate("https://www.zhaopin.com/job/42", "5000", "岗位描述");
liepin.setSourceCode("liepin");
assertNotEquals(ExternalJobFingerprint.identity(zhaopin), ExternalJobFingerprint.identity(liepin));
}
private ExternalJobCandidate candidate(String url, String salary, String description) {
ExternalJobCandidate candidate = new ExternalJobCandidate();
candidate.setSourceCode("zhaopin");
candidate.setCompanyName("石河子示例企业");
candidate.setJobTitle("Java 工程师");
candidate.setLocation("石河子市");
candidate.setJobUrl(url);
candidate.setSalaryLow(salary);
candidate.setSalaryHigh("10000");
candidate.setDescription(description);
candidate.setEducationCode("3");
candidate.setExperienceCode("4");
candidate.setPostingDate("2026-07-20");
return candidate;
}
}