# 定时任务模块:使用 XXL-JOB 改造方案(仅方案) 在现有「Quartz + @Scheduled + 按租户顺序执行」的 SaaS 定时任务基础上,若调度层改为 **XXL-JOB**,可参考本方案。只给出改造思路与要点,不涉及具体代码实现。 --- ## 一、XXL-JOB 与当前架构的对应关系 | 当前 | 使用 XXL-JOB 后 | |------|------------------| | Quartz 调度器(内嵌在 fs-admin) | XXL-JOB Admin 中心:负责 cron 配置、触发、路由到执行器 | | 分发器 Job(TenantJobDispatcherJob)每分钟执行 | XXL-JOB 中注册一个 Job,cron 为每分钟,执行器执行「租户分发 + 执行各租户 sys_job」 | | fs-qw-task 的 @Scheduled 方法 | 每个原定时任务在 XXL-JOB 中对应一个 Job(或一个 Job + 不同 jobParam 区分),执行器内仍用 TenantTaskRunner.runForEachTenant(业务逻辑) | **原则**:调度由 XXL-JOB 负责;**按租户切库、按租户执行业务**的逻辑保留在执行器应用内,不交给 XXL-JOB 做租户维度的调度。 --- ## 二、fs-quartz 部分(管理端可配置任务) ### 2.1 角色划分 - **XXL-JOB Admin**:只配置**一条**任务,例如 - JobHandler:`tenantJobDispatcher` - Cron:`0 * * * * ?`(每分钟) - 路由策略、阻塞策略、失败重试等按需配置。 - **执行器**:部署在 **fs-admin** 所在应用(或与 fs-admin 同进程)。 - 引入 `xxl-job-core` / `xxl-job-spring-boot-starter`,配置 `xxl.job.admin.addresses`、`xxl.job.executor.appname`、`xxl.job.executor.port` 等。 - 注册一个 Bean 实现 `XxlJob` 的 `tenantJobDispatcher`,其内部逻辑与当前 **TenantJobDispatcherJob** 一致: 1. 主库查启用且未过期租户; 2. for 每个租户:切库 → 加载 projectConfig → 查该租户库 sys_job 中本分钟到点任务 → 执行 `JobInvokeUtil.invokeMethod(job)`; 3. finally 清理上下文。 ### 2.2 与 Quartz 的衔接 - **去掉**:fs-admin 中 Quartz 的 `SchedulerFactoryBean`、`SysJobServiceImpl.init()` 里对 Quartz 的注册(或通过配置关闭 Quartz 调度,仅保留执行器逻辑)。 - **保留**: - sys_job / sys_job_log 仍在各租户库; - `JobInvokeUtil`、CronUtils、ScheduleUtils 等仍由执行器在「租户分发」逻辑中调用; - 管理端「定时任务」界面仍对当前登录租户库的 sys_job 做 CRUD,不依赖 Quartz 内存态,仅需说明「实际触发由 XXL-JOB 统一每分钟执行分发器」即可。 ### 2.3 小结 - **谁触发**:XXL-JOB Admin 按 cron 触发「租户分发器」这一条任务。 - **谁执行**:执行器(fs-admin 进程)执行「查主库租户列表 → 按租户切库 → 执行各租户到点 sys_job」。 - **顺序/并行**:与现有一致,建议仍为**按租户顺序执行**,避免 ThreadLocal 与连接池在多线程下复杂化;若后续需要并行,再在执行器内用线程池对「每个租户」提交独立任务并在子线程内设租户上下文。 --- ## 三、fs-qw-task 部分(企微等固定节奏任务) ### 3.1 角色划分 - **XXL-JOB Admin**:为每个原 @Scheduled 任务在 Admin 中配置一条 Job。 - 例如:`qwCheckSopRuleTime`(cron:每天 1:10)、`addTag`(每 20 分钟)、`sendQwGroupMsgTask`(每 10 分钟)等,与现有 cron 保持一致。 - JobHandler 命名可与方法名一致,便于维护。 - **执行器**:部署在 **fs-qw-task** 应用。 - 引入 XXL-JOB 执行器依赖,配置 appname、admin 地址、端口等。 - 每个 XXL-JOB Job 对应一个 XxlJob 实现类(或一个统一 Dispatcher 根据 jobParam 路由到不同业务方法),在方法内部调用 **TenantTaskRunner.runForEachTenant(原有业务逻辑)**,保证仍是「按租户顺序执行、每租户切库+加载 projectConfig」。 ### 3.2 与 @Scheduled 的衔接 - **去掉**:fs-qw-task 中所有业务类上的 `@Scheduled`,以及 `@EnableScheduling`(若不再需要)。 - **保留**: - TenantTaskRunner、TenantDataSourceManager、按租户切库与 ProjectConfig 加载逻辑; - 原有业务方法(如 `qwSopService.checkSopRuleTime()`)不变,仅由「XXL-JOB 的 JobHandler」调用 `tenantTaskRunner.runForEachTenant(() -> qwSopService.checkSopRuleTime())` 这类形式。 - **SaaS 开关**:可保留 `saas.task.enabled`。当为 true 时,XXL-JOB 触发的 Job 内部用 `runForEachTenant`;为 false 时可直接执行业务方法(单租户),便于兼容。 ### 3.3 小结 - **谁触发**:XXL-JOB Admin 按各任务配置的 cron 触发对应 Job。 - **谁执行**:fs-qw-task 作为执行器,收到调度后执行「TenantTaskRunner.runForEachTenant(具体业务)」。 - **顺序/并行**:与现有一致,默认**按租户顺序**;若需并行,再在 TenantTaskRunner 或执行器层增加「按租户并行」的可选实现。 --- ## 四、统一说明(顺序 / 并行) - **当前方案(Quartz + @Scheduled)**:按租户**顺序**执行,无多线程并行。 - **改为 XXL-JOB 后**:仅把「谁在何时触发」从 Quartz/Spring 换成 XXL-JOB;**执行器内部**仍建议先保持「按租户顺序」执行,与现有一致。 - 若日后需要「多租户并行」: - 可在执行器内用线程池,每个租户一个任务,在子线程中先设置该租户的 DynamicDataSourceContextHolder + TenantConfigContext,再执行业务,finally 清理。 - 需注意连接池大小、超时与失败重试,以及 XXL-JOB 的「阻塞策略」「路由策略」是否与多线程兼容(例如选「单机串行」避免同一 Job 并发)。 --- ## 五、实施顺序建议 1. **执行器接入**:在 fs-admin、fs-qw-task 中分别引入 XXL-JOB 执行器依赖并配置,先注册一个简单测试 Job,确认 Admin 能正常调度。 2. **fs-quartz 替换**:关闭 Quartz 的调度注册,保留「租户分发器」逻辑,改为由 XXL-JOB 的 `tenantJobDispatcher` 触发;管理端界面与 sys_job 表不变。 3. **fs-qw-task 替换**:逐个去掉 @Scheduled,在 XXL-JOB Admin 中配置对应 Job 与 cron,执行器内用 TenantTaskRunner.runForEachTenant 包装原业务。 4. **回归与监控**:按租户验证任务执行、日志与数据;观察 XXL-JOB 调度日志与执行器负载。 --- ## 六、与现有文档的关系 - 租户数据源、TenantTaskRunner、按租户切库与 projectConfig 加载等,与 [定时任务模块SaaS化方案.md](./定时任务模块SaaS化方案.md) 一致,无需因改用 XXL-JOB 而改变。 - 本方案仅描述「**调度层由 Quartz/@Scheduled 换为 XXL-JOB**」的改造要点,不改变「按租户执行」的语义与实现方式。