From e4be119fdb39e251b9cc6930e766e0eda50016e4 Mon Sep 17 00:00:00 2001 From: zhuibobo Date: Thu, 16 Jul 2026 16:21:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E6=95=B0=E6=8D=AE=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BizSyncManageController.java | 67 +++++- .../executor/SyncJobStatusHolder.java | 113 +++++++++ .../src/api/efcode/biz/index.ts | 11 + .../src/views/efcode/biz/index.vue | 227 +++++++++++++++--- .../src/views/efcode/interface/index.vue | 33 --- 5 files changed, 379 insertions(+), 72 deletions(-) create mode 100644 zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/SyncJobStatusHolder.java diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/controller/BizSyncManageController.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/controller/BizSyncManageController.java index cde6c3a9..51ffc461 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/controller/BizSyncManageController.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/controller/BizSyncManageController.java @@ -9,6 +9,7 @@ import com.zdxt.enforcementcode.businessdatasyn.domain.mapper.BizSyncLogMapper; import com.zdxt.enforcementcode.businessdatasyn.domain.mapper.BizSyncRecordMapper; import com.zdxt.enforcementcode.businessdatasyn.domain.mapper.BizSyncTaskMapper; import com.zdxt.enforcementcode.businessdatasyn.executor.BusinessDataSyncScheduler; +import com.zdxt.enforcementcode.businessdatasyn.executor.SyncJobStatusHolder; import com.zdxt.enforcementcode.businessdatasyn.service.SyncRecordService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,6 +17,10 @@ import org.dromara.common.core.domain.R; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + /** * 业务数据同步 - 管理接口 *

权限标识:efcode:bizsync:manage

@@ -34,35 +39,79 @@ public class BizSyncManageController { private final BizSyncTaskMapper taskMapper; private final BizSyncRecordMapper recordMapper; private final BizSyncLogMapper logMapper; + private final SyncJobStatusHolder jobStatusHolder; + + /** 专用线程池:串行执行同步任务,避免多个 force 并发压垮数据库 */ + private static final ExecutorService SYNC_EXECUTOR = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "biz-sync-async"); + t.setDaemon(true); + return t; + }); /** - * 手工触发指定表同步 + * 手工触发指定表同步(异步,立即返回 jobId) */ @PostMapping("/trigger/{configKey}") public R trigger(@PathVariable String configKey) { log.info("[BizSync] 手动触发同步: {}", configKey); - scheduler.runOne(configKey); - return R.ok("触发成功: " + configKey); + String jobId = jobStatusHolder.createJob("TRIGGER_ONE", configKey); + CompletableFuture.runAsync(() -> { + try { + scheduler.runOne(configKey); + jobStatusHolder.markSuccess(jobId, "同步完成: " + configKey); + } catch (Exception e) { + jobStatusHolder.markFail(jobId, e.getMessage()); + } + }, SYNC_EXECUTOR); + return R.ok(jobId); } /** - * 手工触发全部同步 + * 手工触发全部同步(异步) */ @PostMapping("/trigger-all") public R triggerAll() { log.info("[BizSync] 手动触发全部同步"); - scheduler.runAll(); - return R.ok("全部触发成功"); + String jobId = jobStatusHolder.createJob("TRIGGER_ALL", null); + CompletableFuture.runAsync(() -> { + try { + scheduler.runAll(); + jobStatusHolder.markSuccess(jobId, "全部同步完成"); + } catch (Exception e) { + jobStatusHolder.markFail(jobId, e.getMessage()); + } + }, SYNC_EXECUTOR); + return R.ok(jobId); } /** - * 强制全量同步 + * 强制全量同步(异步) */ @PostMapping("/trigger-force") public R triggerForce() { log.info("[BizSync] 手动强制全量同步"); - scheduler.runAllForce(); - return R.ok("强制全量触发成功"); + String jobId = jobStatusHolder.createJob("TRIGGER_FORCE", null); + CompletableFuture.runAsync(() -> { + try { + scheduler.runAllForce(); + jobStatusHolder.markSuccess(jobId, "强制全量同步完成"); + } catch (Exception e) { + jobStatusHolder.markFail(jobId, e.getMessage()); + } + }, SYNC_EXECUTOR); + return R.ok(jobId); + } + + /** + * 查询异步任务状态(前端轮询用) + */ + @GetMapping("/job-status/{jobId}") + public R jobStatus(@PathVariable String jobId) { + SyncJobStatusHolder.JobStatus status = jobStatusHolder.getJob(jobId); + if (status == null) { + return R.fail("任务不存在或已过期"); + } + return R.ok(status); } /** diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/SyncJobStatusHolder.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/SyncJobStatusHolder.java new file mode 100644 index 00000000..0931b073 --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/SyncJobStatusHolder.java @@ -0,0 +1,113 @@ +package com.zdxt.enforcementcode.businessdatasyn.executor; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 同步任务运行状态持有器 + *

基于内存 ConcurrentHashMap,用于前端轮询判断异步任务是否完成

+ *

注意:仅供 UI 触发的同步任务使用;Quartz Job 和单元测试直接调 scheduler,不经过本类

+ *

单节点部署有效,多节点部署需改为 Redis

+ * + * @author zrb + */ +@Slf4j +@Component +public class SyncJobStatusHolder { + + /** 任务过期时间(毫秒),完成后 30 分钟自动清理 */ + private static final long EXPIRE_MS = 30 * 60 * 1000L; + + private final Map jobs = new ConcurrentHashMap<>(); + + /** + * 创建一个新任务,返回 jobId + */ + public String createJob(String jobType, String configKey) { + String jobId = UUID.randomUUID().toString().replace("-", ""); + JobStatus status = new JobStatus(); + status.setJobId(jobId); + status.setJobType(jobType); + status.setConfigKey(configKey); + status.setStatus("RUNNING"); + status.setStartTime(LocalDateTime.now()); + jobs.put(jobId, status); + log.info("[BizSync-Job] 创建任务: jobId={}, type={}, configKey={}", jobId, jobType, configKey); + cleanExpired(); + return jobId; + } + + /** + * 标记任务成功 + */ + public void markSuccess(String jobId, String message) { + JobStatus status = jobs.get(jobId); + if (status != null) { + status.setStatus("SUCCESS"); + status.setEndTime(LocalDateTime.now()); + status.setMessage(message); + log.info("[BizSync-Job] 任务成功: jobId={}, msg={}", jobId, message); + } + } + + /** + * 标记任务失败 + */ + public void markFail(String jobId, String errorMsg) { + JobStatus status = jobs.get(jobId); + if (status != null) { + status.setStatus("FAIL"); + status.setEndTime(LocalDateTime.now()); + status.setMessage(errorMsg); + log.error("[BizSync-Job] 任务失败: jobId={}, err={}", jobId, errorMsg); + } + } + + /** + * 获取任务状态 + */ + public JobStatus getJob(String jobId) { + return jobs.get(jobId); + } + + /** + * 清理过期任务(完成时间超过 EXPIRE_MS 的) + */ + private void cleanExpired() { + long now = System.currentTimeMillis(); + jobs.entrySet().removeIf(entry -> { + JobStatus s = entry.getValue(); + if (s.getEndTime() == null) return false; + long endMillis = s.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + return (now - endMillis) > EXPIRE_MS; + }); + } + + /** + * 任务状态 DTO + */ + @Data + public static class JobStatus { + /** 任务 UUID */ + private String jobId; + /** 任务类型: TRIGGER_ONE / TRIGGER_ALL / TRIGGER_FORCE */ + private String jobType; + /** 关联配置 key(TRIGGER_ONE 时有值) */ + private String configKey; + /** 状态: RUNNING / SUCCESS / FAIL */ + private String status; + /** 提示信息 */ + private String message; + /** 开始时间 */ + private LocalDateTime startTime; + /** 结束时间 */ + private LocalDateTime endTime; + } +} diff --git a/zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/biz/index.ts b/zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/biz/index.ts index 178a19f0..23f400a3 100644 --- a/zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/biz/index.ts +++ b/zdxt-web-client/zdxt-admin-plus-ui/src/api/efcode/biz/index.ts @@ -41,6 +41,17 @@ export const getTaskList = (params: { configKey?: string; pageNum?: number; page }); }; +/** + * 查询异步任务状态(前端轮询用,silent 不弹错误提示) + */ +export const getJobStatus = (jobId: string) => { + return request({ + url: '/web/biz-sync/job-status/' + jobId, + method: 'get', + headers: { silent: true } + }); +}; + /** * 分页查询同步日志 */ diff --git a/zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/biz/index.vue b/zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/biz/index.vue index d32c2413..d6e74aa9 100644 --- a/zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/biz/index.vue +++ b/zdxt-web-client/zdxt-admin-plus-ui/src/views/efcode/biz/index.vue @@ -1,12 +1,15 @@