feat: refresh es after external import commit

This commit is contained in:
2026-07-21 17:43:04 +08:00
parent a75e83e9e7
commit e0fc7c87b5
7 changed files with 149 additions and 1 deletions

View File

@@ -0,0 +1,31 @@
package com.ruoyi.cms.externalimport;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* 外部岗位导入后置任务的独立执行器。
*
* 全量重建会删除并创建 job_document 索引,必须串行执行,不能占用通用业务线程池。
*/
@Configuration
@EnableAsync
public class ExternalJobImportAsyncConfig {
@Bean(name = "externalJobImportEsRefreshExecutor")
public ThreadPoolTaskExecutor externalJobImportEsRefreshExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(10);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("external-job-es-refresh-");
// 队列满时在提交后的监听线程执行,确保成功批次最终一定会请求索引刷新。
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.cms.externalimport;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
/**
* 在外部岗位导入事务提交后安排 ES 全量刷新。
*/
@Component
public class ExternalJobImportEsRefreshListener {
private final ExternalJobImportEsRefreshService refreshService;
public ExternalJobImportEsRefreshListener(ExternalJobImportEsRefreshService refreshService) {
this.refreshService = refreshService;
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onImportSucceeded(ExternalJobImportSucceededEvent event) {
refreshService.refreshAfterCommitAsync(event.getBatchId());
}
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.cms.externalimport;
import com.ruoyi.cms.service.IESJobSearchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* 外部导入提交后的 ES 全量重建执行器。
*/
@Service
public class ExternalJobImportEsRefreshService {
private static final Logger LOGGER = LoggerFactory.getLogger(ExternalJobImportEsRefreshService.class);
private final IESJobSearchService esJobSearchService;
public ExternalJobImportEsRefreshService(IESJobSearchService esJobSearchService) {
this.esJobSearchService = esJobSearchService;
}
@Async("externalJobImportEsRefreshExecutor")
public void refreshAfterCommitAsync(String batchId) {
LOGGER.info("外部岗位导入批次 {} 已提交,开始异步全量重建 ES 岗位索引", batchId);
try {
esJobSearchService.resetTextCache();
LOGGER.info("外部岗位导入批次 {} 的 ES 岗位索引异步重建完成", batchId);
} catch (RuntimeException ex) {
LOGGER.error("外部岗位导入批次 {} 的 ES 岗位索引异步重建失败", batchId, ex);
}
}
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.cms.externalimport;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder; import org.springframework.jdbc.support.KeyHolder;
@@ -40,16 +41,19 @@ public class ExternalJobImportServiceImpl implements IExternalJobImportService {
private final ExternalUploadClient uploadClient; private final ExternalUploadClient uploadClient;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final ApplicationEventPublisher eventPublisher;
private final ExternalJobExcelParser parser = new ExternalJobExcelParser(); private final ExternalJobExcelParser parser = new ExternalJobExcelParser();
public ExternalJobImportServiceImpl(ExternalJobImportProperties properties, public ExternalJobImportServiceImpl(ExternalJobImportProperties properties,
ExternalUploadClient uploadClient, ExternalUploadClient uploadClient,
JdbcTemplate jdbcTemplate, JdbcTemplate jdbcTemplate,
ObjectMapper objectMapper) { ObjectMapper objectMapper,
ApplicationEventPublisher eventPublisher) {
this.properties = properties; this.properties = properties;
this.uploadClient = uploadClient; this.uploadClient = uploadClient;
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.eventPublisher = eventPublisher;
} }
@Override @Override
@@ -158,6 +162,8 @@ public class ExternalJobImportServiceImpl implements IExternalJobImportService {
result.setStatus("SUCCESS"); result.setStatus("SUCCESS");
result.setMessage("外部岗位全量快照发布成功"); result.setMessage("外部岗位全量快照发布成功");
updateBatch(batchId, "SUCCESS", result, null); updateBatch(batchId, "SUCCESS", result, null);
// 仅在事务成功提交后异步全量刷新 ES避免索引读到未提交或最终回滚的岗位数据。
eventPublisher.publishEvent(new ExternalJobImportSucceededEvent(batchId));
return result; return result;
} }

View File

@@ -0,0 +1,18 @@
package com.ruoyi.cms.externalimport;
/**
* 外部岗位全量快照已成功落库的领域事件。
*
* 事件由事务监听器在提交后处理,因此不会为回滚、阻断或重复跳过的批次刷新 ES。
*/
public class ExternalJobImportSucceededEvent {
private final String batchId;
public ExternalJobImportSucceededEvent(String batchId) {
this.batchId = batchId;
}
public String getBatchId() {
return batchId;
}
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.cms.externalimport;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class ExternalJobImportEsRefreshListenerTest {
@Test
void delegatesCommittedBatchToAsyncRefreshService() {
ExternalJobImportEsRefreshService refreshService = mock(ExternalJobImportEsRefreshService.class);
ExternalJobImportEsRefreshListener listener = new ExternalJobImportEsRefreshListener(refreshService);
listener.onImportSucceeded(new ExternalJobImportSucceededEvent("batch-20260721"));
verify(refreshService).refreshAfterCommitAsync("batch-20260721");
}
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.cms.externalimport;
import com.ruoyi.cms.service.IESJobSearchService;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class ExternalJobImportEsRefreshServiceTest {
@Test
void rebuildsJobIndexForCommittedBatch() {
IESJobSearchService searchService = mock(IESJobSearchService.class);
ExternalJobImportEsRefreshService refreshService = new ExternalJobImportEsRefreshService(searchService);
refreshService.refreshAfterCommitAsync("batch-20260721");
verify(searchService).resetTextCache();
}
}