Spring Boot 集成 XXL-Job 完整实现方案(支持动态CRUD)

Spring Boot 集成 XXL-Job 完整实现方案

一、概述

在微服务架构中,分布式任务调度是一个常见的需求。本文将介绍基于 Spring Boot 集成 XXL-Job 的完整实现方案,包括执行器配置、任务管理、Feign 调用等核心功能。

主要包含以下模块:

模块 说明
config XXL-Job 配置类、Feign 配置、Cookie 管理
feignApi 远程调用 XXL-Job Admin 的 Feign 接口
handler 任务处理器,定义具体的任务逻辑
properties 配置属性类,支持 Nacos 动态配置
model/vo/from 数据模型和请求/响应对象

二、核心配置类

2.1 XxlJobConfig - 执行器配置

这是整个 XXL-Job 集成的核心配置类,负责初始化和管理执行器实例。

java 复制代码
@Data
@Configuration
@RefreshScope
public class XxlJobConfig {
    @Value("${xxl.job.admin.accessToken:default_token}")
    private String accessToken;
    
    @Value("${xxl.job.executor.appname:unstructured-task-executor}")
    private String appName;
    
    @Value("${xxl.job.admin.addresses:http://127.0.0.1:8080/xxl-job-admin}")
    private String adminAddresses;
    
    @Value("${XXL_JOB_REGISTER_SERVER_IP:127.0.0.1}")
    private String serverIp;
    
    @Value("${XXL_JOB_REGISTER_SERVER_PORT:${random.int(9999,65535)}}")
    private Integer serverPort;
    
    private volatile XxlJobSpringExecutor currentExecutor;
    private final AtomicBoolean isRegistered = new AtomicBoolean(false);
    
    @Bean
    @ConditionalOnMissingBean(name = "xxlJobExecutor")
    @Profile("!dev")  // 开发环境不注册
    public XxlJobSpringExecutor xxlJobExecutor() {
        // 执行器初始化逻辑
        XxlJobSpringExecutor executor = createExecutor();
        if (executor != null) {
            currentExecutor = executor;
            isRegistered.set(true);
        }
        return executor;
    }
}

核心特性:

  1. @RefreshScope 支持:配置可动态刷新,配合 Nacos 使用
  2. 开发环境隔离 :通过 @Profile("!dev") 注解,开发环境不注册到调度中心
  3. 自动端口分配 :使用 ${random.int(9999,65535)} 自动生成端口,避免端口冲突
  4. 线程安全设计 :使用 AtomicBoolean 保证注册状态的线程安全

2.2 执行器重新注册机制

配置变更时支持热更新,无需重启服务:

java 复制代码
public synchronized void reRegister() {
    // 1. 停止旧执行器
    if (currentExecutor != null) {
        currentExecutor.destroy();
        currentExecutor = null;
    }
    
    // 2. 等待端口释放
    Thread.sleep(3000);
    
    // 3. 创建新执行器
    XxlJobSpringExecutor newExecutor = createExecutor();
    if (newExecutor != null) {
        newExecutor.start();
        currentExecutor = newExecutor;
        isRegistered.set(true);
    }
}

2.3 XxlJobCnf - 配置属性类

支持从 Nacos 读取配置,结构清晰:

java 复制代码
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "xxl.job")
public class XxlJobCnf {
    private Admin admin;      // 调度中心配置
    private Executor executor; // 执行器配置
    private String userName;  // 登录用户名
    private String password;  // 登录密码
    
    @Data
    public static class Admin {
        private String addresses;  // 调度中心地址
        private String accessToken; // 访问令牌
    }
    
    @Data
    public static class Executor {
        private String appname;      // 执行器名称
        private String ip;           // 执行器IP
        private Integer port;        // 执行器端口
        private String logpath;      // 日志路径
        private Integer logretentiondays; // 日志保留天数
    }
}

2.4 yml配置

yaml 复制代码
xxl:
  job:
    executor:
      appName: unstructured-task-executor
    userName: admin
    password: 123456
    admin:
      addresses: http://10.19.68.7:9016/xxl-job-admin
      accessToken: default_token
    executor:
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
      corePoolSize: 0
      maximumPoolSize: 200
      keepAliveTime: 60 
XXL_JOB_REGISTER_SERVER_IP: 10.19.68.7
XXL_JOB_REGISTER_SERVER_PORT: 19046      

三、Feign 远程调用

3.1 RemoteXxlJobApiFeign - 任务管理接口

封装了 XXL-Job Admin 的 REST API:

java 复制代码
@FeignClient(contextId = "xxlJobService", 
             url = "#{@xxlJobConfig.adminAddresses}", 
             name = "xxlJobAdminClient",
             path = "/jobinfo", 
             fallbackFactory = RemoteXxlJobFallbackFactory.class)
public interface RemoteXxlJobApiFeign {
    
    // 分页查询任务列表
    @RequestMapping(value = "/pageList", consumes = "application/x-www-form-urlencoded")
    XxlJobInfoListVoReturnT pageList(@RequestParam("start") Integer start,
                                     @RequestParam("length") Integer length,
                                     @RequestParam(value = "jobGroup", required = false) Integer jobGroup,
                                     @RequestParam(value = "triggerStatus", defaultValue = "-1") Integer triggerStatus);
    
    // 新增任务
    @RequestMapping(value = "/add", consumes = "application/x-www-form-urlencoded")
    ReturnT<String> add(@ModelAttribute XxlJobInfoFrom form);
    
    // 更新任务
    @RequestMapping(value = "/update", consumes = "application/x-www-form-urlencoded")
    ReturnT<String> update(@ModelAttribute XxlJobInfoFrom form);
    
    // 删除任务
    @RequestMapping(value = "/remove", consumes = "application/x-www-form-urlencoded")
    ReturnT<String> remove(@RequestParam("id") Long id);
    
    // 启动/停止任务
    @RequestMapping(value = "/start", consumes = "application/x-www-form-urlencoded")
    ReturnT<String> start(@RequestParam("id") Long id);
    
    @RequestMapping(value = "/stop", consumes = "application/x-www-form-urlencoded")
    ReturnT<String> stop(@RequestParam("id") Long id);
    
    // 手动触发任务
    @RequestMapping(value = "/trigger", consumes = "application/x-www-form-urlencoded")
    ReturnT<String> trigger(@RequestParam("id") Integer id,
                            @RequestParam("executorParam") String executorParam,
                            @RequestParam("addressList") String addressList);
}

3.2 RemoteXxlJobGroupFeign - 执行器组管理

java 复制代码
@FeignClient(contextId = "xxlJobGroupService", 
             url = "#{@xxlJobConfig.adminAddresses}", 
             name = "xxlJobAdminJobGroupClient",
             path = "/", 
             fallbackFactory = RemoteXxlJobFallbackFactory.class)
public interface RemoteXxlJobGroupFeign {
    
    // 登录获取Cookie
    @PostMapping("/login")
    ResponseEntity<ReturnT<String>> login(@RequestParam("userName") String userName,
                                         @RequestParam("password") String password);
    
    // 查询执行器列表
    @GetMapping("/jobgroup/pageList")
    XXlJobGroupReturnT pageList(@RequestParam("start") Integer start,
                                @RequestParam("length") Integer length,
                                @RequestParam(value = "appname", required = false) String appname);
    
    // 根据ID查询执行器
    @PostMapping("/jobgroup/loadById")
    ReturnT<XxlJobGroup> loadById(@RequestParam("id") Integer id);
}

XXL-Job Admin 需要登录验证,通过定时刷新 Cookie 保证请求合法性:

java 复制代码
@Component
public class XxlJobCookieComponent {
    public static String COOKIE = null;
    
    @Resource
    RemoteXxlJobGroupFeign remoteXxlJobGroupFeign;
    
    @Scheduled(fixedRate = 20 * 60 * 1000)  // 每20分钟刷新一次
    public void refreshCookie() {
        ResponseEntity<ReturnT<String>> response = 
            remoteXxlJobGroupFeign.login(xxlJobConfig.getUserName(), 
                                         xxlJobConfig.getPassword(), null);
        List<String> setCookies = response.getHeaders().get("Set-Cookie");
        if (setCookies != null && !setCookies.isEmpty()) {
            COOKIE = String.join(";", setCookies)
                .replace("Path=/; HttpOnly", "")
                .replace("Secure", "")
                .trim();
        }
    }
}

3.4 Feign 请求拦截器

自动为请求添加 Cookie:

java 复制代码
@Component
public class XxlJobFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        if (XxlJobCookieComponent.COOKIE != null) {
            template.header("Cookie", XxlJobCookieComponent.COOKIE);
        }
    }
}

3.5 服务降级处理

java 复制代码
@Component
public class RemoteXxlJobFallbackFactory implements FallbackFactory<RemoteXxlJobApiFeign> {
    @Override
    public RemoteXxlJobApiFeign create(Throwable throwable) {
        log.error("XXL-Job服务调用失败:{}", throwable.getMessage());
        return new RemoteXxlJobApiFeign() {
            @Override
            public ReturnT<String> add(XxlJobInfoFrom form) {
                return new ReturnT(ReturnT.FAIL_CODE, "XxlJob新增任务信息失败");
            }
            // ... 其他方法降级处理
        };
    }
}

四、任务处理器

4.1 JobHandlerContext - 数据处理任务

java 复制代码
@Component
@AllArgsConstructor
@Slf4j
public class JobHandlerContext {
    
    private final UnstructuredDataProcessingService unstructuredDataProcessingService;
    private final UnstructuredDataProcessingTaskMapper taskMapper;
    
    @XxlJob(value = "dataProcessTaskHandler")
    public void dataProcessTaskHandler() {
        String jobParam = XxlJobHelper.getJobParam();
        log.info("执行参数:{}", jobParam);
        
        JSONObject jsonObject = JSONUtil.parseObj(jobParam);
        Long instanceId = jsonObject.getLong("instanceId", 0L);
        
        // 异步执行,不阻塞调度线程
        CompletableFuture.runAsync(() -> {
            try {
                execTask(instanceId);
            } catch (Exception e) {
                log.error("异步处理失败", e);
            }
        });
    }
    
    private void execTask(Long instanceId) {
        // 1. 查询任务配置
        UnstructuredDataProcessingTaskPo taskPo = taskMapper.selectById(instanceId);
        if (taskPo == null) {
            log.warn("任务配置不存在");
            return;
        }
        
        // 2. 校验发布状态
        if (!Objects.equals(taskPo.getReleaseState(), UnstructuredDatasetReleaseEnum.ONLINE.getType())) {
            log.info("任务未上线,跳过执行");
            return;
        }
        
        // 3. 校验调度时间范围
        if (!isWithinScheduleTime(startTime, endTime, instanceId)) {
            log.info("当前时间不在调度范围内");
            return;
        }
        
        // 4. 执行任务
        unstructuredDataProcessingService.run(instanceId, "xxlJob");
    }
}

任务执行流程:

  1. 参数解析 :从 XxlJobHelper.getJobParam() 获取任务参数
  2. 异步提交 :使用 CompletableFuture.runAsync() 异步执行,避免阻塞调度线程
  3. 状态校验:检查任务是否已发布、调度开关是否开启
  4. 时间校验:判断当前时间是否在调度时间范围内
  5. 任务执行:调用业务服务执行具体逻辑

4.2 时间范围校验

java 复制代码
private boolean isWithinScheduleTime(String startTimeStr, String endTimeStr, Long instanceId) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime now = LocalDateTime.now();
    
    // 校验开始时间
    if (StringUtils.isNotBlank(startTimeStr)) {
        LocalDateTime startTime = LocalDateTime.parse(startTimeStr, formatter);
        if (startTime.isAfter(now)) {
            return false;
        }
    }
    
    // 校验结束时间
    if (StringUtils.isNotBlank(endTimeStr)) {
        LocalDateTime endTime = LocalDateTime.parse(endTimeStr, formatter);
        if (endTime.isBefore(now)) {
            return false;
        }
    }
    
    return true;
}

五、Controller 示例

XXLTestController 提供了完整的任务管理 REST API:

java 复制代码
@RestController
@RequestMapping("/xxl/test")
public class XXLTestController {
    
    @Resource
    private RemoteXxlJobApiFeign remoteXxlJobApiFeign;
    @Resource
    private RemoteXxlJobGroupFeign remoteXxlJobGroupFeign;
    
    /**
     * 创建定时任务
     */
    @PostMapping("/create")
    public R create(@RequestParam Long dataId, @RequestParam String corn) {
        XxlJobGroup xxlJobGroup = getGroupId();
        
        XxlJobInfoFrom xxlJobInfoFrom = new XxlJobInfoFrom();
        xxlJobInfoFrom.setScheduleConf(corn);
        xxlJobInfoFrom.setJobGroup(xxlJobGroup.getId());
        xxlJobInfoFrom.setExecutorHandler("dataProcessTaskHandler");
        xxlJobInfoFrom.setGlueType(GlueTypeEnum.BEAN.getDesc());
        xxlJobInfoFrom.setTriggerStatus(0); // 初始状态为停止
        
        ReturnT<String> addResult = remoteXxlJobApiFeign.add(xxlJobInfoFrom);
        if (addResult.getCode() != ReturnT.SUCCESS_CODE) {
            return R.fail("创建任务失败");
        }
        return R.ok(addResult);
    }
    
    /**
     * 启动任务
     */
    @PostMapping("/start")
    public R start(@RequestParam Long jobId) {
        ReturnT<String> result = remoteXxlJobApiFeign.start(jobId);
        if (result.getCode() != ReturnT.SUCCESS_CODE) {
            return R.fail("启动任务失败");
        }
        return R.ok(result);
    }
    
    // 停止、更新、删除方法类似...
}

六、配置说明

6.1 application.yml 配置示例

java 复制代码
xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
      accessToken: default_token
    executor:
      appname: unstructured-task-executor
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
    userName: admin
    password: 123456

# 执行器注册IP(容器环境使用环境变量)
XXL_JOB_REGISTER_SERVER_IP: ${POD_IP:127.0.0.1}
XXL_JOB_REGISTER_SERVER_PORT: ${random.int(9999,65535)}

6.2 关键配置说明

配置项 说明 默认值
xxl.job.admin.addresses 调度中心地址 localhost
xxl.job.executor.appname 执行器名称 unstructured-task-executor
xxl.job.executor.logpath 日志路径 /data/applogs/xxl-job/jobhandler
xxl.job.userName 登录用户名 admin
xxl.job.password 登录密码 123456

七、设计亮点

7.1 高可用设计

  1. 自动端口分配:使用随机端口避免冲突
  2. 配置热更新:支持 Nacos 配置动态刷新
  3. 优雅降级:Feign 调用失败时有降级处理

7.2 安全机制

  1. Cookie 自动刷新:定时刷新登录状态
  2. 请求拦截器:自动添加认证信息
  3. AccessToken 校验:支持接口访问令牌

7.3 性能优化

  1. 异步执行:任务处理异步化,不阻塞调度线程
  2. 线程安全:使用原子变量保证并发安全
  3. 日志分级:不同环境日志级别可控

八、总结

该 XXL-Job 集成方案具有以下特点:

  1. 完整的配置管理:支持动态配置和热更新
  2. 灵活的任务管理:提供完整的 CRUD 操作
  3. 安全的远程调用:Cookie 自动管理和请求拦截
  4. 健壮的异常处理:完善的降级和错误处理机制
  5. 生产级可用:经过实际项目验证,稳定可靠

通过以上设计,实现了一个完整、健壮的分布式任务调度解决方案,适用于企业级微服务架构。

相关推荐
顶级自由人1 小时前
【前端菜鸟的补课01】Zod 与 PostgreSQL 全栈数据工程教学
前端·后端·程序员
swipe2 小时前
11|(前端转全栈)购物车不能只存在前端:用户维度数据如何在后端落库
前端·后端·全栈
薛定谔的猫-菜鸟程序员2 小时前
基于 Electron 的本地短视频解析与下载工具:架构设计与工程实践
java·electron·音视频
音符犹如代码3 小时前
Arthas Profiler 火焰图实战:CPU 热点在哪一目了然
java·jvm·spring boot
小园子的小菜3 小时前
Java 并发编程:线程安全队列全解 —— 阻塞与非阻塞实现原理及源码深度剖析
java·开发语言
好好沉淀3 小时前
Windows 下升级 Maven 3.6.1 到 3.9.9 踩坑全记录
java·windows·maven
CodexDave3 小时前
Python 自动化接单实战(一):把 CSV 需求做成配置驱动解析器
java·python·自动化·json·数据清洗·python自动化·csv处理
XS0301063 小时前
Spring框架
java·后端·spring
Rabitebla3 小时前
C++ 内存管理全面复习:从内存分布到 operator new/delete
java·c语言·开发语言·c++·算法·leetcode