Skip to content

定时任务 XXL-Job ​

这篇解决什么 ​

按点扫描超时单、补打卡、清日志,不要堵在 HTTP 请求里。读完能配执行器、写 @XxlJob Handler,并在管理端「定时任务运维」里对上卡片、手动跑一次。

调度用 XXL-Job 2.4.2。默认端口 48080,管理端前缀 /admin-api。本地 profile 默认关掉执行器,菜单里仍能发现并触发已扫描到的 Handler。

示意图:Cron 从调度中心进执行器;运维页直接反射调用同一批 Handler。

组件位置 ​

名称说明仓库路径
Job Starter拉起 XxlJobSpringExecutor,同时打开 @EnableAsyncruoyi-office/yudao-framework/yudao-spring-boot-starter-job/
自动配置xxl.job.enabled=true 时注册执行器.../quartz/config/YudaoXxlJobAutoConfiguration.java
配置项enabled、accessToken、admin、executor.../quartz/config/XxlJobProperties.java
注册入口与异步任务同一份 imports.../META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
核心库xxl-job-core,BOM 2.4.2ruoyi-office/yudao-dependencies/pom.xml
运维接口启动扫描 @XxlJob,列表 + 手动触发ruoyi-office/yudao-server/.../controller/admin/JobOpsController.java
运维页卡片按模块筛选,点「立即执行」ruoyi-office-vben/apps/web-antd/src/views/infra/job-ops/index.vue
运维 APIGET /infra/job-ops/list、POST /infra/job-ops/trigger.../src/api/infra/job-ops/index.ts
中文说明@JobDesc,运维页读 remarkyudao-framework/yudao-common/.../annotation/JobDesc.java
多租户切面@TenantJob 按租户并行跑yudao-spring-boot-starter-biz-tenant/.../job/TenantJobAspect.java

system、infra 以及多数业务 *-server 已引入 yudao-spring-boot-starter-job。新模块先加这个依赖,再在 job 包写 Handler。

菜单在基础设施 → 系统运维 → 定时任务运维。官方菜单 id:5259 目录,5260 页面。路由 /infra/operation/job-ops。按钮权限:infra:job-ops:query、infra:job-ops:trigger。列表接口未再标权限,登录即可;触发必须有 infra:job-ops:trigger。

配置与执行器 ​

YudaoXxlJobAutoConfiguration 在 xxl.job.enabled=true(缺省也是 true)时创建 XxlJobSpringExecutor。属性来自 XxlJobProperties:调度中心地址、通讯 Token、执行器 AppName / IP / 端口 / 日志目录。

单体入口 application.yaml 只配执行器侧:

yaml
xxl:
  job:
    executor:
      appname: ${spring.application.name}
      logpath: ${user.home}/logs/xxl-job/${spring.application.name}
    accessToken: default_token

spring.application.name 是 yudao-server,调度中心里的 AppName 必须写成这个。accessToken 要和调度中心一致,默认 default_token。

本地 application-local.yaml 关掉执行器,只留调度中心地址:

yaml
xxl:
  job:
    enabled: false
    admin:
      addresses: http://127.0.0.1:9090/xxl-job-admin

本地默认不注册执行器

enabled: false 时整份 YudaoXxlJobAutoConfiguration 不装,进程不会向 9090 报到。Cron 不会触发。要联调调度中心,改成 true,先起 XXL-Job Admin,再重启 yudao-server。

执行器端口默认 -1(随机)。日志目录默认 ${user.home}/logs/xxl-job/yudao-server,保留 30 天,-1 表示不删。启动成功时日志会出现 初始化 XXL-Job 执行器的配置,以及 register jobhandler success, name:demoJob。

调度中心「执行器管理」用自动注册。任务管理里新增任务时,JobHandler 填 @XxlJob("…") 的字符串,必须和代码里完全一致,例如 punchRecordGenerateJob。

示意图:两条入口打到同一个方法;运维页不经过调度中心。

写一个 Job ​

放在对应模块的 job 包,类加 @Component。方法上三个注解各管一件事:调度名、租户、运维说明。

java
@Component
public class DemoJob {

    @XxlJob("demoJob")
    @TenantJob
    @JobDesc("示例任务,仅用于演示 XXL-Job 集成效果,无实际业务逻辑")
    public void execute() {
        System.out.println("美滋滋");
    }
}
名称说明仓库路径
@XxlJobHandler 名,调度中心和运维页都靠它找方法com.xxl.job.core.handler.annotation.XxlJob
@TenantJob只标在方法上,按租户并行执行.../tenant/core/job/TenantJob.java
@JobDesc运维卡片上的中文用途DemoJob、PunchRecordGenerateJob 等
无参方法JobOps 直接 invokeJobOpsController.triggerJob
带参方法运维页弹出参数框,传入 String同上,hasParam=true

业务任务把逻辑放进 Service,Job 只当入口。打卡生成是这种:

java
@XxlJob("punchRecordGenerateJob")
@TenantJob
@JobDesc("每日为所有在职员工生成当天的空白打卡记录")
public String execute() {
    return punchRecordService.generateDailyPunchRecords();
}
名称说明仓库路径
示例无业务副作用yudao-module-system-server/.../job/demo/DemoJob.java
打卡每天给在职员工补空白记录yudao-module-hrm-server/.../attendance/PunchRecordGenerateJob.java
支付通知扫描待通知并回调;Service 里再加锁yudao-module-pay-server/.../notify/PayNotifyJob.java
访问日志清理物理删 14 天前,每批 100 条yudao-module-infra-server/.../logger/AccessLogCleanJob.java
令牌清理过期超过 14 天的 access / refreshyudao-module-system-server/.../token/TokenCleanJob.java

JobOpsController 启动时扫全部 Bean 的 @XxlJob。模块名取包路径 cn.iocoder.yudao.module.{模块} 的那一段。卡片标题先用类名去掉 Job;remark 读 @JobDesc,没有就提示补注解。

说明写在方法上

运维页不会去读类注释。新任务请同时写 @XxlJob 和 @JobDesc,否则卡片上只剩「暂无说明」。

需要回写调度日志时用 XxlJobHelper.handleSuccess / handleFail。TokenCleanJob 把清理条数拼进成功结果。运维页走反射,返回值会显示在提示里。

同一时刻只允许一条在跑的任务,不要只靠 Cron 错开,在 Service 里加分布式锁。支付通知的 key 是 pay_notify:lock:%d,见 分布式锁。

多租户 ​

调度线程没有登录用户,也没有请求头 tenant-id。查带 tenant_id 的表时,必须自己进租户上下文。

@TenantJob 由 TenantJobAspect 切开:取出全部租户 id,parallelStream 里 TenantUtils.execute(tenantId, …) 再调原方法。某个租户抛错会记进该租户的结果,并 XxlJobHelper.handleFail。

查租户表必须加 @TenantJob

漏标时,Holder 是空的。MyBatis 租户插件会拦 SQL,或业务只打到「当前租户」(调度里通常没有)。TicketSlaCheckJob 只标了 @XxlJob,查工单前要自己保证租户上下文,不要照抄成默认写法。

跨租户清理用 @TenantIgnore,不要再用 @TenantJob 扫一遍。访问日志、错误日志、令牌清理都是这种:一次删全库过期行。

java
@XxlJob("accessLogCleanJob")
@TenantIgnore
@JobDesc("物理清理超过 14 天的接口访问日志,释放数据库空间")
public void execute() {
    Integer count = apiAccessLogService.cleanAccessLog(14, 100);
}

Handler 要幂等

@TenantJob 并行跑各租户。某个租户失败后,调度中心重试会整次再跑,已经成功的租户也会再进一遍。按天补记录、按 id 扫描,写成可重复执行。

多租户开关和 TenantBaseDO 见 SaaS 多租户。日志清理和访问日志表见 操作日志、访问日志、异常日志。

运维页 ​

菜单打开后,页面把 /infra/job-ops/list 的结果铺成卡片。当前环境扫到 41 个 Handler,可按模块筛、按 Handler / 类名 / 说明搜。

定时任务运维

本系统截图:基础设施 → 系统运维 → 定时任务运维。每张卡是模块、Handler、类名和「立即执行」。

「立即执行」走 POST /infra/job-ops/trigger?jobHandler=…。无参任务先确认;带参任务弹出文本框。Controller 按全类名取 Bean,再反射调用带该 @XxlJob 的方法。本地 xxl.job.enabled=false 时这条通路仍然可用。

名称说明仓库路径
元信息jobHandler / module / className / remark / hasParamJobOpsController.JobMeta
模块中文hrm→人力资源,infra→基础设施views/infra/job-ops/index.vue 的 moduleNameMap
触发权限infra:job-ops:triggerJobOpsController.triggerJob

Cron 表达式在调度中心维护,运维页不存调度时间。常用写法:

名称说明仓库路径
0 0 12 * * ?每天中午 12 点调度中心 Cron
0 15 10 ? * MON-FRI工作日 10:15同上
0 0/30 9-17 * * ?9 点到 17 点每半小时同上
0 15 10 15 * ?每月 15 日 10:15同上
0 15 10 L * ?每月最后一天 10:15同上

Job 里不要写死自己的 Cron。改周期只动调度中心,避免发版。

@Async 和 Job 共用同一个 Starter。接口返回后再落库,走线程池,不要塞进 @XxlJob。见 异步任务。

配置与操作 ​

运维在 基础设施 → 系统运维 → 定时任务运维(/infra/operation/job-ops,菜单 5260)看卡片、点立即执行。Cron 改 XXL-Job 调度中心,不要写死在 Handler 里。

本地 xxl.job.enabled 默认关,页面仍能扫到并手动跑。不要打开已删的 /infra/job。开启见 基建功能设计。截图见上文运维列表。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版