├── .gitignore ├── CHANGELOG.md ├── README.md ├── build.gradle ├── docs ├── jobs-mysql.sql └── jobs-postgres.sql ├── gradle.properties ├── gradlew ├── gradlew.bat ├── jobs-admin ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── baomidou │ │ └── jobs │ │ └── admin │ │ ├── JobsAdminApplication.java │ │ ├── JobsAdminConfig.java │ │ ├── WebConfigurer.java │ │ ├── controller │ │ ├── BaseController.java │ │ ├── JobsApiController.java │ │ ├── JobsInfoController.java │ │ ├── JobsLogController.java │ │ ├── JobsRegistryController.java │ │ └── JobsStatisticsController.java │ │ ├── mapper │ │ ├── JobsInfoMapper.java │ │ ├── JobsLockMapper.java │ │ ├── JobsLogMapper.java │ │ └── JobsRegistryMapper.java │ │ └── service │ │ ├── IJobsInfoService.java │ │ ├── IJobsLockService.java │ │ ├── IJobsLogService.java │ │ ├── IJobsRegistryService.java │ │ ├── IJobsStatisticsService.java │ │ ├── JobsPageHelper.java │ │ ├── impl │ │ ├── JobsInfoServiceImpl.java │ │ ├── JobsLockServiceImpl.java │ │ ├── JobsLogServiceImpl.java │ │ ├── JobsRegistryServiceImpl.java │ │ ├── JobsServiceImpl.java │ │ └── JobsStatisticsServiceImpl.java │ │ └── vo │ │ ├── JobsDateDistributionVO.java │ │ ├── JobsDateTempVO.java │ │ ├── JobsImportantNumVO.java │ │ └── JobsSuccessRatioVO.java │ └── resources │ ├── application-dev.yml │ ├── application-pro.yml │ ├── application-test.yml │ ├── application.yml │ ├── banner.txt │ ├── logback.xml │ └── spy.properties ├── jobs-spring-boot-sample ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── baomidou │ │ └── jobs │ │ └── sample │ │ ├── JobsSampleApplication.java │ │ └── handler │ │ └── DemoJobHandler.java │ └── resources │ ├── application.yml │ └── logback.xml ├── jobs-spring-boot-starter ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── baomidou │ │ └── jobs │ │ ├── JobsClock.java │ │ ├── JobsConstant.java │ │ ├── api │ │ ├── IJobsErrorCode.java │ │ ├── JobsErrorCode.java │ │ └── JobsResponse.java │ │ ├── disruptor │ │ ├── JobsDisruptorTemplate.java │ │ ├── JobsEventHandler.java │ │ └── JobsInfoEvent.java │ │ ├── exception │ │ ├── JobsException.java │ │ └── JobsRpcException.java │ │ ├── executor │ │ ├── IJobsExecutor.java │ │ ├── JobsAbstractExecutor.java │ │ ├── JobsExecutor.java │ │ └── JobsSpringExecutor.java │ │ ├── handler │ │ ├── IJobsHandler.java │ │ └── IJobsResultHandler.java │ │ ├── model │ │ ├── JobsInfo.java │ │ ├── JobsLock.java │ │ ├── JobsLog.java │ │ ├── JobsRegistry.java │ │ └── param │ │ │ ├── HandleCallbackParam.java │ │ │ ├── RegisterStatusEnum.java │ │ │ ├── RegistryParam.java │ │ │ └── TriggerParam.java │ │ ├── router │ │ ├── ExecutorConsistentHashRouter.java │ │ └── IJobsExecutorRouter.java │ │ ├── rpc │ │ ├── registry │ │ │ ├── IJobsServiceRegistry.java │ │ │ ├── client │ │ │ │ ├── JobsRegistryBaseClient.java │ │ │ │ ├── JobsRegistryClient.java │ │ │ │ ├── model │ │ │ │ │ ├── JobsRegistryDataParamVO.java │ │ │ │ │ └── JobsRegistryParamVO.java │ │ │ │ └── util │ │ │ │ │ ├── BasicHttpUtil.java │ │ │ │ │ └── json │ │ │ │ │ ├── BasicJson.java │ │ │ │ │ ├── BasicJsonReader.java │ │ │ │ │ └── BasicJsonWriter.java │ │ │ └── impl │ │ │ │ ├── JobsRegistryServiceRegistry.java │ │ │ │ └── LocalServiceRegistry.java │ │ ├── remoting │ │ │ ├── invoker │ │ │ │ ├── JobsRpcInvokerFactory.java │ │ │ │ ├── call │ │ │ │ │ ├── CallType.java │ │ │ │ │ ├── JobsRpcInvokeCallback.java │ │ │ │ │ └── JobsRpcInvokeFuture.java │ │ │ │ ├── generic │ │ │ │ │ └── JobsRpcGenericService.java │ │ │ │ ├── reference │ │ │ │ │ └── JobsRpcReferenceBean.java │ │ │ │ └── route │ │ │ │ │ ├── JobsRpcLoadBalance.java │ │ │ │ │ ├── LoadBalance.java │ │ │ │ │ └── impl │ │ │ │ │ ├── RpcLoadBalanceConsistentHashStrategy.java │ │ │ │ │ ├── RpcLoadBalanceRandomStrategy.java │ │ │ │ │ └── RpcLoadBalanceRoundStrategy.java │ │ │ ├── net │ │ │ │ ├── Client.java │ │ │ │ ├── NetEnum.java │ │ │ │ ├── Server.java │ │ │ │ ├── common │ │ │ │ │ └── ConnectClient.java │ │ │ │ ├── impl │ │ │ │ │ ├── netty │ │ │ │ │ │ ├── http │ │ │ │ │ │ │ ├── client │ │ │ │ │ │ │ │ ├── NettyHttpClient.java │ │ │ │ │ │ │ │ ├── NettyHttpClientHandler.java │ │ │ │ │ │ │ │ └── NettyHttpConnectClient.java │ │ │ │ │ │ │ └── server │ │ │ │ │ │ │ │ ├── NettyHttpServer.java │ │ │ │ │ │ │ │ └── NettyHttpServerHandler.java │ │ │ │ │ │ └── socket │ │ │ │ │ │ │ ├── client │ │ │ │ │ │ │ ├── NettyClient.java │ │ │ │ │ │ │ ├── NettyClientHandler.java │ │ │ │ │ │ │ └── NettyConnectClient.java │ │ │ │ │ │ │ ├── codec │ │ │ │ │ │ │ ├── NettyDecoder.java │ │ │ │ │ │ │ └── NettyEncoder.java │ │ │ │ │ │ │ └── server │ │ │ │ │ │ │ ├── NettyServer.java │ │ │ │ │ │ │ └── NettyServerHandler.java │ │ │ │ │ └── servlet │ │ │ │ │ │ └── server │ │ │ │ │ │ └── ServletServerHandler.java │ │ │ │ └── params │ │ │ │ │ ├── IJobsRpcCallback.java │ │ │ │ │ ├── JobsRpcFutureResponse.java │ │ │ │ │ ├── JobsRpcRequest.java │ │ │ │ │ └── JobsRpcResponse.java │ │ │ └── provider │ │ │ │ └── JobsRpcProviderFactory.java │ │ ├── serialize │ │ │ ├── IJobsRpcSerializer.java │ │ │ └── impl │ │ │ │ └── HessianSerializer.java │ │ └── util │ │ │ ├── ClassUtil.java │ │ │ ├── IpUtil.java │ │ │ ├── NetUtil.java │ │ │ └── ThreadPoolUtil.java │ │ ├── service │ │ ├── IJobsService.java │ │ ├── JobsHeartbeat.java │ │ └── JobsHelper.java │ │ ├── starter │ │ ├── EnableJobs.java │ │ ├── EnableJobsAdmin.java │ │ ├── JobsAdminAutoConfiguration.java │ │ ├── JobsAutoConfiguration.java │ │ ├── JobsProperties.java │ │ └── JobsScheduler.java │ │ ├── thread │ │ └── ExecutorRegistryThread.java │ │ ├── toolkit │ │ └── ConsistentHash.java │ │ └── trigger │ │ ├── JobsTrigger.java │ │ └── TriggerTypeEnum.java │ └── test │ └── java │ └── com │ └── baomidou │ └── jobs │ └── test │ └── CronUtilsTest.java ├── license.txt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | 3 | **/.gitignore 4 | # IntelliJ project files 5 | .idea 6 | *.iml 7 | **/out 8 | html 9 | *.ipr 10 | *.iws 11 | 12 | # Eclipse project files 13 | **/.classpath 14 | **/.project 15 | **/.settings/ 16 | **/bin/ 17 | 18 | # gradle 19 | .gradle 20 | **/build 21 | 22 | # MacOS 23 | .DS_Store 24 | /repo/ 25 | gradle/ 26 | gradlew 27 | gradlew.bat 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [v1.0.3] 2019.11.05 4 | - 升级 netty 版本 5 | - RPC 序列化支持自定义注入,默认修改为 kryo 6 | - 优化精简代码 7 | 8 | ## [v1.0.2] 2019.08.01 9 | - 完善 admin web ui 相关接口 10 | - RPC 处理逻辑代码设计优化 11 | - 新增 tenant_id 支持多租户任务 12 | 13 | 14 | ## [v1.0.1] 2019.07.25 15 | - 调整依赖引用 16 | - 调整失败报警接口为结果处理接口 17 | 18 | 19 | ## [v1.0] 2019.07.22 20 | - 参考 xxl-job 简化重构为 jobs 项目,目标是构建一个 Spring Boot 分布式任务 starter 插拔组件 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jobs 分布式任务调度组件 2 | 项目名:Jobs 【致敬: 史蒂夫·乔布斯(Steve Jobs)】 3 | 4 | 目标是构建一个 Spring Boot 分布式任务 starter 插拔组件 5 | 6 | 7 | Gitee   8 | Github 9 | 10 | # 特点 11 | - 不需独立部署类似 Swagger 模式的可插拔组件,引入 starter 注解启动 12 | - 只依赖数据库(默认 mybatis-plus 实现,支持主流数据库) 13 | - Rest API 接口适配任意系统 14 | - 实现接口支持切换为 JPA 等任意 ORM 框架 15 | - 任务 disruptor 异步处理 16 | 17 | # 使用 18 | - jobs-admin 测试后台,你可以理解为调度中心 19 | 实现 IJobsService 接口即完全调度中心的数据层实现,JobsApiController 提供客户端注册入口 20 | 21 | - jobs-spring-boot-sample 测试样例,你可以为理解为任务实现端 22 | 配置 application.yml 调度中心地址多个英文逗号分割,任务实现 IJobsHandler 接口即完成 23 | 24 | 1、初始化 docs 对应数据库,如果无你可以参考数据结构初始化(当然你可以PR) 25 | 26 | 2、启动 admin 启动 sample (观察控制台日志及 jobs-log 表数据) 27 | 28 | # 注意 29 | 30 | Client 端默认为内网 ip 外网调用,启动命令添加 `--jobs-app-ip=外网ip` 指定 IP 端口防火墙需要放行 31 | 32 | # 鸣谢 33 | Jobs 参考 xxl-job 但是更为轻量,目的是构建类似 Swagger 模式的插拔组件。 34 | 35 | VUE 前端:jobs-admin-web 36 | 37 | ### 界面效果 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/jobs-mysql.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : Mysql-1q4r 5 | Source Server Type : MySQL 6 | Source Server Version : 80012 7 | Source Host : localhost:3306 8 | Source Schema : jobs 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 80012 12 | File Encoding : 65001 13 | 14 | Date: 31/07/2019 16:23:45 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for jobs_info 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `jobs_info`; 24 | CREATE TABLE `jobs_info` ( 25 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', 26 | `tenant_id` varchar(100) DEFAULT NULL COMMENT '租户ID', 27 | `app` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '服务名', 28 | `cron` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务执行CRON', 29 | `handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', 30 | `param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数', 31 | `timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒', 32 | `fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数', 33 | `last_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '上次调度时间', 34 | `next_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '下次调度时间', 35 | `author` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '作者', 36 | `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注', 37 | `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '0、启用 1、已禁用', 38 | `update_time` bigint(20) DEFAULT NULL COMMENT '更新时间', 39 | `create_time` bigint(20) NOT NULL COMMENT '创建时间', 40 | PRIMARY KEY (`id`) 41 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='任务信息'; 42 | 43 | -- ---------------------------- 44 | -- Records of jobs_info 45 | -- ---------------------------- 46 | BEGIN; 47 | INSERT INTO `jobs_info` VALUES (3, NULL, 'jobs-executor-sample', '0/10 * * * * ? *', 'demoJobHandler', NULL, 30, 3, 1564539320000, 1564539330000, 'jobs', '测试', 0, 1563152000000, 1563152000000); 48 | COMMIT; 49 | 50 | -- ---------------------------- 51 | -- Table structure for jobs_lock 52 | -- ---------------------------- 53 | DROP TABLE IF EXISTS `jobs_lock`; 54 | CREATE TABLE `jobs_lock` ( 55 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', 56 | `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称', 57 | `owner` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '持有者', 58 | `create_time` bigint(20) NOT NULL COMMENT '创建时间', 59 | PRIMARY KEY (`id`), 60 | UNIQUE KEY `uidx_name` (`name`) USING BTREE 61 | ) ENGINE=InnoDB AUTO_INCREMENT=241201 DEFAULT CHARSET=utf8 COMMENT='任务锁'; 62 | 63 | -- ---------------------------- 64 | -- Table structure for jobs_log 65 | -- ---------------------------- 66 | DROP TABLE IF EXISTS `jobs_log`; 67 | CREATE TABLE `jobs_log` ( 68 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', 69 | `job_id` bigint(20) NOT NULL COMMENT '任务ID', 70 | `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '执行地址', 71 | `handler` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务 handler', 72 | `param` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任务参数', 73 | `fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数', 74 | `trigger_code` int(11) NOT NULL DEFAULT '0' COMMENT '触发器调度返回码', 75 | `trigger_type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '触发器调度类型', 76 | `trigger_msg` text COMMENT '触发器调度返回信息', 77 | `create_time` bigint(20) NOT NULL COMMENT '创建时间', 78 | PRIMARY KEY (`id`) 79 | ) ENGINE=InnoDB AUTO_INCREMENT=2091 DEFAULT CHARSET=utf8 COMMENT='任务调度日志'; 80 | 81 | -- ---------------------------- 82 | -- Table structure for jobs_registry 83 | -- ---------------------------- 84 | DROP TABLE IF EXISTS `jobs_registry`; 85 | CREATE TABLE `jobs_registry` ( 86 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键 ID', 87 | `app` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '服务名', 88 | `address` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'IP 地址', 89 | `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '0、启用 1、已禁用', 90 | `update_time` bigint(20) NOT NULL COMMENT '更新时间', 91 | PRIMARY KEY (`id`) 92 | ) ENGINE=InnoDB AUTO_INCREMENT=208 DEFAULT CHARSET=utf8 COMMENT='任务注册信息'; 93 | 94 | -- ---------------------------- 95 | -- Records of jobs_registry 96 | -- ---------------------------- 97 | BEGIN; 98 | INSERT INTO `jobs_registry` VALUES (1, 'jobs-executor-sample', '127.0.0.1:9999', 1, 1563626092397); 99 | INSERT INTO `jobs_registry` VALUES (205, 'jobs-executor-sample', '192.168.0.6:9999', 1, 1563794040004); 100 | COMMIT; 101 | 102 | SET FOREIGN_KEY_CHECKS = 1; 103 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | signing.keyId=1FD337F9 2 | signing.password=243194995 3 | signing.secretKeyRingFile=/Users/hubin/dev/signing.gpg 4 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /jobs-admin/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api "${lib.'mybatis-plus-boot-starter'}" 3 | api "${lib.'jobs-spring-boot-starter'}" 4 | api "${lib.'mysql-connector-java'}" 5 | api "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" 6 | api "org.springframework.boot:spring-boot-starter-mail:${springBootVersion}" 7 | api "org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}" 8 | implementation "${lib.'p6spy'}" 9 | 10 | } 11 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/JobsAdminApplication.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin; 2 | 3 | import org.springframework.boot.Banner; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.transaction.annotation.EnableTransactionManagement; 7 | 8 | /** 9 | * Job Admin 10 | * 11 | * @author jobob 12 | * @since 2019-05-31 13 | */ 14 | @EnableTransactionManagement 15 | @SpringBootApplication 16 | public class JobsAdminApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication application = new SpringApplication(JobsAdminApplication.class); 20 | application.setBannerMode(Banner.Mode.CONSOLE); 21 | application.run(args); 22 | } 23 | } -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/JobsAdminConfig.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin; 2 | 3 | import com.baomidou.jobs.handler.IJobsResultHandler; 4 | import com.baomidou.jobs.starter.EnableJobsAdmin; 5 | import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * Jobs Admin 启动配置 11 | * 12 | * @author jobob 13 | * @since 2019-06-08 14 | */ 15 | @EnableJobsAdmin 16 | @Configuration 17 | public class JobsAdminConfig { 18 | 19 | /** 20 | * mybatis-plus分页插件
21 | * 文档:http://mp.baomidou.com
22 | */ 23 | @Bean 24 | public PaginationInterceptor paginationInterceptor() { 25 | return new PaginationInterceptor(); 26 | } 27 | 28 | /** 29 | * 任务调度结果处理器,可用于失败报警成功通知 30 | * 31 | * @return 32 | */ 33 | @Bean 34 | public IJobsResultHandler jobsResultHandler() { 35 | return (jobInfo, address, jobsResponse) -> 36 | System.out.println("Jobs 报警处理器,调度地址:" 37 | + address); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/WebConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.bind.annotation.ControllerAdvice; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * MybatisPlus 配置 10 | * 11 | * @author xxl jobob 12 | * @since 2019-06-01 13 | */ 14 | @ControllerAdvice 15 | @Configuration 16 | public class WebConfigurer implements WebMvcConfigurer { 17 | 18 | @Override 19 | public void addCorsMappings(CorsRegistry registry) { 20 | registry.addMapping("/**").allowedOrigins("*") 21 | .allowedMethods("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE") 22 | .allowCredentials(true).maxAge(3600); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.controller; 2 | 3 | import com.baomidou.mybatisplus.extension.api.R; 4 | 5 | import javax.annotation.Resource; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | /** 10 | * Service 控制层父类 11 | * 12 | * @author jobob 13 | * @since 2019-05-31 14 | */ 15 | public class BaseController { 16 | @Resource 17 | protected HttpServletRequest request; 18 | @Resource 19 | protected HttpServletResponse response; 20 | 21 | 22 | /** 23 | * 请求成功 24 | * 25 | * @param data 数据内容 26 | * @param 对象泛型 27 | * @return ignore 28 | */ 29 | protected R success(T data) { 30 | return R.ok(data); 31 | } 32 | 33 | /** 34 | * 请求失败 35 | * 36 | * @param msg 提示内容 37 | * @return ignore 38 | */ 39 | protected R failed(String msg) { 40 | return R.failed(msg); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/controller/JobsApiController.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.controller; 2 | 3 | import com.baomidou.jobs.JobsConstant; 4 | import com.baomidou.jobs.starter.JobsScheduler; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | /** 15 | * Jobs Api 16 | * 17 | * @author jobob 18 | * @since 2019-07-13 19 | */ 20 | @Controller 21 | public class JobsApiController implements InitializingBean { 22 | 23 | 24 | @Override 25 | public void afterPropertiesSet() throws Exception { 26 | // to do nothing 27 | } 28 | 29 | @RequestMapping(JobsConstant.JOBS_API) 30 | public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 31 | JobsScheduler.invokeAdminService(request, response); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/controller/JobsInfoController.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.controller; 2 | 3 | import com.baomidou.jobs.admin.service.IJobsInfoService; 4 | import com.baomidou.jobs.model.JobsInfo; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | import com.baomidou.mybatisplus.extension.api.R; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | * 任务信息 13 | * 14 | * @author jobob 15 | * @since 2019-05-31 16 | */ 17 | @RestController 18 | @RequestMapping("/v1/jobs-info") 19 | public class JobsInfoController extends BaseController { 20 | @Resource 21 | private IJobsInfoService jobInfoService; 22 | 23 | /** 24 | * 分页 25 | */ 26 | @GetMapping("/page") 27 | public R> page(JobsInfo jobInfo) { 28 | return jobInfoService.page(request, jobInfo); 29 | } 30 | 31 | /** 32 | * 总任务数 33 | */ 34 | @GetMapping("/count") 35 | public R count() { 36 | return success(jobInfoService.count()); 37 | } 38 | 39 | /** 40 | * 执行 41 | */ 42 | @PostMapping("/execute-{id}") 43 | public R execute(@PathVariable("id") Long id, String param) { 44 | return success(jobInfoService.execute(id, param)); 45 | } 46 | 47 | /** 48 | * 启动 49 | */ 50 | @PostMapping("/start-{id}") 51 | public R start(@PathVariable("id") Long id) { 52 | return success(jobInfoService.start(id)); 53 | } 54 | 55 | /** 56 | * 停止 57 | */ 58 | @PostMapping("/stop-{id}") 59 | public R stop(@PathVariable("id") Long id) { 60 | return success(jobInfoService.stop(id)); 61 | } 62 | 63 | /** 64 | * 删除 65 | */ 66 | @PostMapping("/remove-{id}") 67 | public R remove(@PathVariable("id") Long id) { 68 | return success(jobInfoService.remove(id)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/controller/JobsLogController.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.controller; 2 | 3 | import com.baomidou.jobs.admin.service.IJobsLogService; 4 | import com.baomidou.jobs.api.JobsResponse; 5 | import com.baomidou.jobs.model.JobsLog; 6 | import com.baomidou.mybatisplus.core.metadata.IPage; 7 | import com.baomidou.mybatisplus.extension.api.R; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | /** 14 | * 日志信息 15 | * 16 | * @author jobob 17 | * @since 2019-05-31 18 | */ 19 | @RestController 20 | @RequestMapping("/v1/jobs-log") 21 | public class JobsLogController extends BaseController { 22 | @Autowired 23 | private IJobsLogService jobsInfoService; 24 | 25 | /** 26 | * 分页 27 | */ 28 | @GetMapping("/page") 29 | public R> page(JobsLog jobsInfo) { 30 | return jobsInfoService.page(request, jobsInfo); 31 | } 32 | 33 | /** 34 | * 总执行次数 35 | */ 36 | @GetMapping("/count") 37 | public R count() { 38 | return success(jobsInfoService.countAll()); 39 | } 40 | 41 | /** 42 | * 总执行成功次数 43 | */ 44 | @GetMapping("/count-success") 45 | public R countSuccess() { 46 | return success(jobsInfoService.countSuccess()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/controller/JobsRegistryController.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.controller; 2 | 3 | import com.baomidou.jobs.admin.service.IJobsRegistryService; 4 | import com.baomidou.jobs.model.JobsRegistry; 5 | import com.baomidou.mybatisplus.extension.api.R; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | * 用户信息 13 | * 14 | * @author jobob 15 | * @since 2019-05-31 16 | */ 17 | @RestController 18 | @RequestMapping("/v1/jobs-registry") 19 | public class JobsRegistryController extends BaseController { 20 | @Autowired 21 | private IJobsRegistryService jobRegistryService; 22 | 23 | /** 24 | * 分页 25 | */ 26 | @GetMapping("/page") 27 | public R page(JobsRegistry jobRegistry) { 28 | return success(null);//jobRegistryService.page(request, jobRegistry)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/controller/JobsStatisticsController.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.controller; 2 | 3 | import com.baomidou.jobs.admin.service.IJobsStatisticsService; 4 | import com.baomidou.jobs.admin.service.vo.JobsDateDistributionVO; 5 | import com.baomidou.jobs.admin.service.vo.JobsImportantNumVO; 6 | import com.baomidou.jobs.admin.service.vo.JobsSuccessRatioVO; 7 | import com.baomidou.mybatisplus.extension.api.R; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * 统计信息 17 | * 18 | * @author jobob 19 | * @since 2019-06-15 20 | */ 21 | @RestController 22 | @RequestMapping("/v1/jobs-statistics") 23 | public class JobsStatisticsController extends BaseController { 24 | @Autowired 25 | private IJobsStatisticsService statisticsService; 26 | 27 | /** 28 | * 重要参数数量 29 | */ 30 | @GetMapping("/important-num") 31 | public R importantNum() { 32 | return success(statisticsService.getImportantNum()); 33 | } 34 | 35 | /** 36 | * 成功比例 37 | */ 38 | @GetMapping("/success-ratio") 39 | public R successRatio() { 40 | return success(statisticsService.getSuccessRatio()); 41 | } 42 | 43 | /** 44 | * 日期分布图 45 | */ 46 | @GetMapping("/date-distribution") 47 | public R> dateDistribution() { 48 | return success(statisticsService.getDateDistribution()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/mapper/JobsInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.mapper; 2 | 3 | import com.baomidou.jobs.model.JobsInfo; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 任务信息 Mapper 9 | * 10 | * @author jobob 11 | * @since 2019-05-31 12 | */ 13 | @Mapper 14 | public interface JobsInfoMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/mapper/JobsLockMapper.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.mapper; 2 | 3 | import com.baomidou.jobs.model.JobsLock; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 锁信息 Mapper 9 | * 10 | * @author jobob 11 | * @since 2019-07-13 12 | */ 13 | @Mapper 14 | public interface JobsLockMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/mapper/JobsLogMapper.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.mapper; 2 | 3 | import com.baomidou.jobs.admin.service.vo.JobsDateTempVO; 4 | import com.baomidou.jobs.model.JobsLog; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Select; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 日志信息 Mapper 13 | * 14 | * @author jobob 15 | * @since 2019-05-31 16 | */ 17 | @Mapper 18 | public interface JobsLogMapper extends BaseMapper { 19 | 20 | /** 21 | * 查询任务日期分布 22 | * 23 | * @return 24 | */ 25 | @Select("SELECT t.num,t.code,t.ct AS at_date FROM (SELECT COUNT(1) num, trigger_code code,FROM_UNIXTIME(create_time / 1000, '%Y-%m-%d') ct FROM jobs_log GROUP BY trigger_code, ct) AS t,\n" + 26 | "(select FROM_UNIXTIME(create_time / 1000, '%Y-%m-%d') tm from jobs_log GROUP BY tm ORDER BY tm DESC LIMIT 7) AS s\n" + 27 | "WHERE t.ct=s.tm ORDER BY t.ct") 28 | List selectJobsDateTempVO(); 29 | } 30 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/mapper/JobsRegistryMapper.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.mapper; 2 | 3 | import com.baomidou.jobs.model.JobsRegistry; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 注册任务信息 Mapper 9 | * 10 | * @author jobob 11 | * @since 2019-05-31 12 | */ 13 | @Mapper 14 | public interface JobsRegistryMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/IJobsInfoService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service; 2 | 3 | import com.baomidou.jobs.model.JobsInfo; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.extension.api.R; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import java.util.List; 9 | 10 | /** 11 | * 任务信息接口 12 | * 13 | * @author jobob 14 | * @since 2019-07-18 15 | */ 16 | public interface IJobsInfoService { 17 | 18 | R> page(HttpServletRequest request, JobsInfo jobsInfo); 19 | 20 | /** 21 | * 执行任务总数 22 | * 23 | * @return 24 | */ 25 | int count(); 26 | 27 | List listNextTime(long nextTime); 28 | 29 | /** 30 | * 根据 ID 更新任务信息 31 | * 32 | * @param jobInfo 任务信息对象 33 | * @return 34 | */ 35 | boolean updateById(JobsInfo jobInfo); 36 | 37 | /** 38 | * 执行、指定 ID 任务 39 | * 40 | * @param id 主键 ID 41 | * @param param 执行参数 42 | * @return 43 | */ 44 | boolean execute(Long id, String param); 45 | 46 | /** 47 | * 启动、指定 ID 任务 48 | * 49 | * @param id 主键 ID 50 | * @return 51 | */ 52 | boolean start(Long id); 53 | 54 | /** 55 | * 停止、指定 ID 任务 56 | * 57 | * @param id 主键 ID 58 | * @return 59 | */ 60 | boolean stop(Long id); 61 | 62 | /** 63 | * 删除、指定 ID 任务 64 | * 65 | * @param id 主键 ID 66 | * @return 67 | */ 68 | boolean remove(Long id); 69 | 70 | /** 71 | * 根据 ID 获取任务信息对象 72 | * 73 | * @param id 任务 ID 74 | * @return 75 | */ 76 | JobsInfo getById(Long id); 77 | } 78 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/IJobsLockService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service; 2 | 3 | /** 4 | * Jobs 锁接口 , 实现该接口可以是数据库锁也可以是 redis 等 5 | * 6 | * @author jobob 7 | * @since 2019-07-13 8 | */ 9 | public interface IJobsLockService { 10 | 11 | /** 12 | * 插入一条记录,标志着占有锁 13 | * 14 | * @param name 锁的名称 15 | * @param owner 锁的持有者 16 | * @return 返回影响的记录行数 17 | */ 18 | int insert(String name, String owner); 19 | 20 | /** 21 | * 释放锁 22 | * 23 | * @param name 锁的名称 24 | * @param owner 锁的持有者,不存在则根据 name 删除 25 | * @return 返回影响的记录行数 26 | */ 27 | int delete(String name, String owner); 28 | } 29 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/IJobsLogService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service; 2 | 3 | import com.baomidou.jobs.admin.service.vo.JobsDateDistributionVO; 4 | import com.baomidou.jobs.model.JobsLog; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | import com.baomidou.mybatisplus.extension.api.R; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import java.util.List; 10 | 11 | /** 12 | * 任务日志接口 13 | * 14 | * @author jobob 15 | * @since 2019-07-18 16 | */ 17 | public interface IJobsLogService { 18 | 19 | /** 20 | * 任务执行总数 21 | * 22 | * @return 23 | */ 24 | int countAll(); 25 | 26 | /** 27 | * 执行成功日志记录总数 28 | */ 29 | int countSuccess(); 30 | 31 | JobsLog getById(Long id); 32 | 33 | boolean updateById(JobsLog jobsInfo); 34 | 35 | boolean save(JobsLog jobsInfo); 36 | 37 | /** 38 | * 39 | * @param id 40 | * @return 41 | */ 42 | boolean removeById(Long id); 43 | 44 | R> page(HttpServletRequest request, JobsLog jobsInfo); 45 | 46 | List getJobsDateDistributionVO(); 47 | } 48 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/IJobsRegistryService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 任务启动节点注册接口 7 | * 8 | * @author jobob 9 | * @since 2019-07-18 10 | */ 11 | public interface IJobsRegistryService { 12 | 13 | /** 14 | * 删除超时数据 15 | * 16 | * @param timeout 超时时长 17 | * @return 18 | */ 19 | int removeTimeOut(int timeout); 20 | 21 | /** 22 | * 查询注册地址列表 23 | * 24 | * @param app 客户端 APP 名称 25 | * @return 26 | */ 27 | List listAddress(String app); 28 | 29 | int update(String app, String address, int status); 30 | 31 | int save(String app, String address, int status); 32 | 33 | /** 34 | * 在线可用机器数 35 | */ 36 | int countOnline(); 37 | 38 | /** 39 | * 任务数量 40 | */ 41 | int countAll(); 42 | } 43 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/IJobsStatisticsService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service; 2 | 3 | import com.baomidou.jobs.admin.service.vo.JobsDateDistributionVO; 4 | import com.baomidou.jobs.admin.service.vo.JobsImportantNumVO; 5 | import com.baomidou.jobs.admin.service.vo.JobsSuccessRatioVO; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 统计接口 11 | */ 12 | public interface IJobsStatisticsService { 13 | 14 | /** 15 | * 重要数量统计 16 | */ 17 | JobsImportantNumVO getImportantNum(); 18 | 19 | /** 20 | * 成功比例统计 21 | */ 22 | JobsSuccessRatioVO getSuccessRatio(); 23 | 24 | /** 25 | * 日期分布统计 26 | */ 27 | List getDateDistribution(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/JobsPageHelper.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | public class JobsPageHelper { 9 | 10 | /** 11 | * 分页信息 12 | */ 13 | public static Page getPage(HttpServletRequest request) { 14 | long current = 1; 15 | long size = 10; 16 | String page = request.getParameter("page"); 17 | if (StringUtils.isNotEmpty(page)) { 18 | Long p = Long.valueOf(page); 19 | if (p > 0) { 20 | current = p; 21 | } 22 | } 23 | String rows = request.getParameter("rows"); 24 | if (StringUtils.isNotEmpty(page)) { 25 | Long l = Long.valueOf(rows); 26 | if (l > 0) { 27 | size = l; 28 | } 29 | } 30 | return new Page(current, size); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/impl/JobsInfoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.impl; 2 | 3 | import com.baomidou.jobs.admin.mapper.JobsInfoMapper; 4 | import com.baomidou.jobs.admin.service.IJobsInfoService; 5 | import com.baomidou.jobs.admin.service.IJobsLogService; 6 | import com.baomidou.jobs.admin.service.JobsPageHelper; 7 | import com.baomidou.jobs.model.JobsInfo; 8 | import com.baomidou.jobs.service.JobsHelper; 9 | import com.baomidou.jobs.trigger.JobsTrigger; 10 | import com.baomidou.jobs.trigger.TriggerTypeEnum; 11 | import com.baomidou.mybatisplus.core.metadata.IPage; 12 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 13 | import com.baomidou.mybatisplus.extension.api.Assert; 14 | import com.baomidou.mybatisplus.extension.api.R; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Service; 18 | 19 | import javax.annotation.Resource; 20 | import javax.servlet.http.HttpServletRequest; 21 | import java.util.List; 22 | 23 | @Slf4j 24 | @Service 25 | public class JobsInfoServiceImpl implements IJobsInfoService { 26 | @Resource 27 | private JobsInfoMapper jobInfoMapper; 28 | @Autowired 29 | private IJobsLogService jobsInfoService; 30 | 31 | @Override 32 | public R> page(HttpServletRequest request, JobsInfo jobsInfo) { 33 | return R.ok(jobInfoMapper.selectPage( 34 | JobsPageHelper.getPage(request), Wrappers.query(jobsInfo) 35 | )); 36 | } 37 | 38 | @Override 39 | public int count() { 40 | return jobInfoMapper.selectCount(null); 41 | } 42 | 43 | @Override 44 | public List listNextTime(long nextTime) { 45 | return jobInfoMapper.selectList(Wrappers.lambdaQuery() 46 | .eq(JobsInfo::getStatus, 0) 47 | .le(JobsInfo::getNextTime, nextTime)); 48 | } 49 | 50 | @Override 51 | public boolean updateById(JobsInfo jobInfo) { 52 | return jobInfoMapper.updateById(jobInfo) > 0; 53 | } 54 | 55 | @Override 56 | public boolean execute(Long id, String param) { 57 | return JobsTrigger.trigger(id, TriggerTypeEnum.MANUAL, -1, param); 58 | } 59 | 60 | @Override 61 | public boolean start(Long id) { 62 | JobsInfo dbJobInfo = getById(id); 63 | if (null == dbJobInfo) { 64 | return false; 65 | } 66 | JobsInfo jobsInfo = new JobsInfo(); 67 | jobsInfo.setId(dbJobInfo.getId()); 68 | jobsInfo.setStatus(1); 69 | jobsInfo.setLastTime(0L); 70 | Assert.fail(!JobsHelper.cronValidate(dbJobInfo.getCron()), "CRON 表达式不可用"); 71 | 72 | // next trigger time (10s后生效,避开预读周期) 73 | jobsInfo.setNextTime(JobsHelper.cronNextTime(dbJobInfo.getCron()) + 10000); 74 | return jobInfoMapper.updateById(jobsInfo) > 0; 75 | } 76 | 77 | @Override 78 | public boolean stop(Long id) { 79 | JobsInfo jobsInfo = new JobsInfo(); 80 | jobsInfo.setId(id); 81 | jobsInfo.setStatus(0); 82 | jobsInfo.setLastTime(0L); 83 | jobsInfo.setNextTime(0L); 84 | return jobInfoMapper.updateById(jobsInfo) > 0; 85 | } 86 | 87 | @Override 88 | public boolean remove(Long id) { 89 | jobsInfoService.removeById(id); 90 | return jobInfoMapper.deleteById(id) > 0; 91 | } 92 | 93 | @Override 94 | public JobsInfo getById(Long id) { 95 | return jobInfoMapper.selectById(id); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/impl/JobsLockServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.impl; 2 | 3 | import com.baomidou.jobs.admin.mapper.JobsLockMapper; 4 | import com.baomidou.jobs.JobsClock; 5 | import com.baomidou.jobs.model.JobsLock; 6 | import com.baomidou.jobs.admin.service.IJobsLockService; 7 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.annotation.Resource; 11 | 12 | @Service 13 | public class JobsLockServiceImpl implements IJobsLockService { 14 | @Resource 15 | private JobsLockMapper jobsLockMapper; 16 | 17 | @Override 18 | public int insert(String name, String owner) { 19 | JobsLock jobsLock = new JobsLock(); 20 | jobsLock.setName(name); 21 | jobsLock.setOwner(owner); 22 | jobsLock.setCreateTime(JobsClock.currentTimeMillis()); 23 | return jobsLockMapper.insert(jobsLock); 24 | } 25 | 26 | @Override 27 | public int delete(String name, String owner) { 28 | return jobsLockMapper.delete(Wrappers.lambdaQuery().eq(JobsLock::getName, name) 29 | .eq(null != owner, JobsLock::getOwner, owner)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/impl/JobsLogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.impl; 2 | 3 | import com.baomidou.jobs.admin.mapper.JobsLogMapper; 4 | import com.baomidou.jobs.admin.service.IJobsLogService; 5 | import com.baomidou.jobs.admin.service.JobsPageHelper; 6 | import com.baomidou.jobs.admin.service.vo.JobsDateDistributionVO; 7 | import com.baomidou.jobs.admin.service.vo.JobsDateTempVO; 8 | import com.baomidou.jobs.api.JobsResponse; 9 | import com.baomidou.jobs.model.JobsLog; 10 | import com.baomidou.mybatisplus.core.metadata.IPage; 11 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 12 | import com.baomidou.mybatisplus.extension.api.R; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.util.CollectionUtils; 15 | 16 | import javax.annotation.Resource; 17 | import javax.servlet.http.HttpServletRequest; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | @Service 22 | public class JobsLogServiceImpl implements IJobsLogService { 23 | @Resource 24 | private JobsLogMapper jobsLogMapper; 25 | 26 | @Override 27 | public int countAll() { 28 | return jobsLogMapper.selectCount(null); 29 | } 30 | 31 | @Override 32 | public int countSuccess() { 33 | return jobsLogMapper.selectCount(Wrappers.lambdaQuery() 34 | .eq(JobsLog::getTriggerCode, 0)); 35 | } 36 | 37 | @Override 38 | public JobsLog getById(Long id) { 39 | return jobsLogMapper.selectById(id); 40 | } 41 | 42 | @Override 43 | public boolean updateById(JobsLog jobsLog) { 44 | return jobsLogMapper.updateById(jobsLog) > 0; 45 | } 46 | 47 | @Override 48 | public boolean save(JobsLog jobsLog) { 49 | return jobsLogMapper.insert(jobsLog) > 0; 50 | } 51 | 52 | @Override 53 | public boolean removeById(Long id) { 54 | return jobsLogMapper.deleteById(id) > 0; 55 | } 56 | 57 | @Override 58 | public R> page(HttpServletRequest request, JobsLog jobsLog) { 59 | return R.ok(jobsLogMapper.selectPage( 60 | JobsPageHelper.getPage(request), Wrappers.query(jobsLog) 61 | )); 62 | } 63 | 64 | @Override 65 | public List getJobsDateDistributionVO() { 66 | List tempVOList = jobsLogMapper.selectJobsDateTempVO(); 67 | if (CollectionUtils.isEmpty(tempVOList)) { 68 | return null; 69 | } 70 | List voList = new ArrayList<>(); 71 | for (JobsDateTempVO tempVO : tempVOList) { 72 | JobsDateDistributionVO vo = new JobsDateDistributionVO(); 73 | if (0 == tempVO.getCode()) { 74 | vo.setSuccessful(tempVO.getNum()); 75 | vo.setFailed(0); 76 | } else { 77 | vo.setSuccessful(0); 78 | vo.setFailed(tempVO.getNum()); 79 | } 80 | vo.setAtDate(tempVO.getAtDate()); 81 | voList.add(vo); 82 | } 83 | return voList; 84 | } 85 | } -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/impl/JobsRegistryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.impl; 2 | 3 | import com.baomidou.jobs.JobsClock; 4 | import com.baomidou.jobs.admin.mapper.JobsRegistryMapper; 5 | import com.baomidou.jobs.admin.service.IJobsRegistryService; 6 | import com.baomidou.jobs.model.JobsRegistry; 7 | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; 8 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | public class JobsRegistryServiceImpl implements IJobsRegistryService { 17 | @Resource 18 | private JobsRegistryMapper jobRegistryMapper; 19 | 20 | @Override 21 | public int removeTimeOut(int timeout) { 22 | return jobRegistryMapper.update(new JobsRegistry().setStatus(1), Wrappers.lambdaQuery() 23 | .eq(JobsRegistry::getStatus, 0).le(JobsRegistry::getUpdateTime, JobsClock.currentTimeMillis() - timeout)); 24 | } 25 | 26 | @Override 27 | public List listAddress(String app) { 28 | List jobsRegistryList = jobRegistryMapper.selectList(Wrappers.lambdaQuery() 29 | .eq(JobsRegistry::getApp, app).eq(JobsRegistry::getStatus, 0)); 30 | return CollectionUtils.isEmpty(jobsRegistryList) ? null : jobsRegistryList.stream() 31 | .map(j -> j.getAddress()).collect(Collectors.toList()); 32 | } 33 | 34 | @Override 35 | public int update(String app, String address, int status) { 36 | return jobRegistryMapper.update(new JobsRegistry().setStatus(status).setUpdateTime(JobsClock.currentTimeMillis()), 37 | Wrappers.lambdaQuery().eq(JobsRegistry::getApp, app) 38 | .eq(JobsRegistry::getAddress, address)); 39 | } 40 | 41 | @Override 42 | public int save(String app, String address, int status) { 43 | return jobRegistryMapper.insert(new JobsRegistry().setApp(app).setStatus(status) 44 | .setAddress(address).setUpdateTime(JobsClock.currentTimeMillis())); 45 | } 46 | 47 | @Override 48 | public int countOnline() { 49 | return jobRegistryMapper.selectCount(Wrappers.lambdaQuery() 50 | .eq(JobsRegistry::getStatus, 0)); 51 | } 52 | 53 | @Override 54 | public int countAll() { 55 | return jobRegistryMapper.selectCount(null); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/impl/JobsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.impl; 2 | 3 | import com.baomidou.jobs.admin.service.IJobsInfoService; 4 | import com.baomidou.jobs.admin.service.IJobsLockService; 5 | import com.baomidou.jobs.admin.service.IJobsLogService; 6 | import com.baomidou.jobs.admin.service.IJobsRegistryService; 7 | import com.baomidou.jobs.model.JobsInfo; 8 | import com.baomidou.jobs.model.JobsLog; 9 | import com.baomidou.jobs.model.param.RegistryParam; 10 | import com.baomidou.jobs.service.IJobsService; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Jobs Admin Impl 19 | * 20 | * @author jobob 21 | * @since 2019-07-12 22 | */ 23 | @Slf4j 24 | @Service 25 | public class JobsServiceImpl implements IJobsService { 26 | @Autowired 27 | private IJobsRegistryService jobsRegistryService; 28 | @Autowired 29 | private IJobsLockService jobsLockService; 30 | @Autowired 31 | private IJobsInfoService jobsInfoService; 32 | @Autowired 33 | public IJobsLogService jobsLogService; 34 | 35 | @Override 36 | public boolean registry(RegistryParam registryParam) { 37 | int ret = jobsRegistryService.update(registryParam.getApp(), registryParam.getAddress(), 38 | registryParam.getRegisterStatusEnum().getValue()); 39 | if (ret < 1) { 40 | ret = jobsRegistryService.save(registryParam.getApp(), registryParam.getAddress(), 41 | registryParam.getRegisterStatusEnum().getValue()); 42 | } 43 | return ret > 0; 44 | } 45 | 46 | @Override 47 | public List getJobsInfoList(long nextTime) { 48 | return jobsInfoService.listNextTime(nextTime); 49 | } 50 | 51 | @Override 52 | public JobsInfo getJobsInfoById(Long id) { 53 | return jobsInfoService.getById(id); 54 | } 55 | 56 | @Override 57 | public boolean updateJobsInfoById(JobsInfo jobsInfo) { 58 | return jobsInfoService.updateById(jobsInfo); 59 | } 60 | 61 | @Override 62 | public boolean tryLock(String name, String owner) { 63 | return jobsLockService.insert(name, owner) > 0; 64 | } 65 | 66 | @Override 67 | public boolean unlock(String name, String owner) { 68 | return jobsLockService.delete(name, owner) > 0; 69 | } 70 | 71 | @Override 72 | public int removeTimeOutApp(int timeout) { 73 | return jobsRegistryService.removeTimeOut(timeout); 74 | } 75 | 76 | @Override 77 | public boolean removeApp(RegistryParam registryParam) { 78 | return jobsRegistryService.update(registryParam.getApp(), registryParam.getAddress(), 79 | registryParam.getRegisterStatusEnum().getValue()) > 0; 80 | } 81 | 82 | @Override 83 | public List getAppAddressList(String app) { 84 | return jobsRegistryService.listAddress(app); 85 | } 86 | 87 | @Override 88 | public boolean saveOrUpdateLogById(JobsLog jobsLog) { 89 | if (null == jobsLog) { 90 | return false; 91 | } 92 | if (null == jobsLog.getId()) { 93 | return jobsLogService.save(jobsLog); 94 | } 95 | return jobsLogService.updateById(jobsLog); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/impl/JobsStatisticsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.impl; 2 | 3 | import com.baomidou.jobs.admin.service.IJobsInfoService; 4 | import com.baomidou.jobs.admin.service.IJobsLogService; 5 | import com.baomidou.jobs.admin.service.IJobsRegistryService; 6 | import com.baomidou.jobs.admin.service.IJobsStatisticsService; 7 | import com.baomidou.jobs.admin.service.vo.JobsDateDistributionVO; 8 | import com.baomidou.jobs.admin.service.vo.JobsImportantNumVO; 9 | import com.baomidou.jobs.admin.service.vo.JobsSuccessRatioVO; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.List; 14 | 15 | @Service 16 | public class JobsStatisticsServiceImpl implements IJobsStatisticsService { 17 | @Autowired 18 | private IJobsInfoService jobsInfoService; 19 | @Autowired 20 | private IJobsLogService jobsLogService; 21 | @Autowired 22 | private IJobsRegistryService jobsRegistryService; 23 | 24 | @Override 25 | public JobsImportantNumVO getImportantNum() { 26 | JobsImportantNumVO vo = new JobsImportantNumVO(); 27 | vo.setRunTaskNum(jobsRegistryService.countAll()); 28 | vo.setTriggeredNum(jobsLogService.countAll()); 29 | vo.setOnlineExecutorNum(jobsRegistryService.countOnline()); 30 | return vo; 31 | } 32 | 33 | @Override 34 | public JobsSuccessRatioVO getSuccessRatio() { 35 | JobsSuccessRatioVO vo = new JobsSuccessRatioVO(); 36 | vo.setSuccessful(jobsLogService.countSuccess()); 37 | vo.setFailed(jobsLogService.countAll() - vo.getSuccessful()); 38 | return vo; 39 | } 40 | 41 | @Override 42 | public List getDateDistribution() { 43 | return jobsLogService.getJobsDateDistributionVO(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/vo/JobsDateDistributionVO.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.vo; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 日期分布统计 VO 9 | * 10 | * @author xxl jobob 11 | * @since 2019-06-15 12 | */ 13 | @Data 14 | @EqualsAndHashCode(callSuper=false) 15 | @Accessors(chain = true) 16 | public class JobsDateDistributionVO extends JobsSuccessRatioVO { 17 | /** 18 | * 日期 19 | */ 20 | private String atDate; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/vo/JobsDateTempVO.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.vo; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 日期分布统计 VO 9 | * 10 | * @author xxl jobob 11 | * @since 2019-06-15 12 | */ 13 | @Data 14 | @EqualsAndHashCode(callSuper=false) 15 | @Accessors(chain = true) 16 | public class JobsDateTempVO { 17 | /** 18 | * 数量 19 | */ 20 | private Integer num; 21 | /** 22 | * 编码 23 | */ 24 | private Integer code; 25 | /** 26 | * 日期 27 | */ 28 | private String atDate; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/vo/JobsImportantNumVO.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.vo; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | /** 7 | * 重要数量统计 VO 8 | * 9 | * @author xxl jobob 10 | * @since 2019-06-15 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class JobsImportantNumVO { 15 | /** 16 | * 调度中心运行的任务数量 17 | */ 18 | private Integer runTaskNum; 19 | /** 20 | * 调度中心触发的调度次数 21 | */ 22 | private Integer triggeredNum; 23 | /** 24 | * 调度中心在线的执行器机器数量 25 | */ 26 | private Integer onlineExecutorNum; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /jobs-admin/src/main/java/com/baomidou/jobs/admin/service/vo/JobsSuccessRatioVO.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.admin.service.vo; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | /** 7 | * 成功比例统计 VO 8 | * 9 | * @author xxl jobob 10 | * @since 2019-06-15 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class JobsSuccessRatioVO { 15 | /** 16 | * 成功 17 | */ 18 | private Integer successful; 19 | /** 20 | * 失败 21 | */ 22 | private Integer failed; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | # 开发环境 2 | spring: 3 | datasource: 4 | # 打印 SQL 日志 5 | # driver-class-name: com.p6spy.engine.spy.P6SpyDriver 6 | # url: jdbc:p6spy:mysql://127.0.0.1:3306/jobs?Unicode=true&characterEncoding=UTF-8&useSSL=false 7 | url: jdbc:mysql://127.0.0.1:3306/jobs?Unicode=true&characterEncoding=UTF-8&useSSL=false 8 | username: root 9 | password: 1q2w3e4r 10 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/application-pro.yml: -------------------------------------------------------------------------------- 1 | # 生产环境 2 | spring: 3 | datasource: 4 | url: jdbc:mysql://127.0.0.1:3306/jobs?Unicode=true&characterEncoding=UTF-8 5 | username: root 6 | password: 1q2w3e4r 7 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | # 测试环境 2 | spring: 3 | datasource: 4 | url: jdbc:mysql://127.0.0.1:3306/jobs?Unicode=true&characterEncoding=UTF-8 5 | username: root 6 | password: 1q2w3e4r 7 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8088 3 | spring: 4 | # 环境 dev|test|pro 5 | profiles: 6 | active: dev 7 | datasource: 8 | driver-class-name: com.mysql.jdbc.Driver 9 | mail: 10 | host: smtp.qq.com 11 | username: xxx@qq.com 12 | password: xxx 13 | port: 25 14 | properties: 15 | mail: 16 | smtp: 17 | auth: true 18 | starttls: 19 | enable: true 20 | required: true 21 | 22 | ### mybatis 23 | mybatis-plus: 24 | mapper-locations: classpath:/mapper/*Mapper.xml 25 | global-config: 26 | banner: true 27 | db-config: 28 | id-type: auto 29 | configuration: 30 | cache-enabled: false 31 | map-underscore-to-camel-case: true 32 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | (_) _ 2 | | | ___ | |__ ___ 3 | _/ | / _ \ | '_ \ (_-< 4 | |__/_ \___/ |_.__/ /__/_ 5 | _|"""""|_|"""""|_|"""""|_|"""""| 6 | "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'. 1.0 7 | 8 | :: Spring Boot :: (v${spring-boot.version}) 9 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logback 5 | 6 | 7 | 8 | 9 | %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | ${log.path} 15 | 16 | ${log.path}.%d{yyyy-MM-dd}.zip 17 | 18 | 19 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /jobs-admin/src/main/resources/spy.properties: -------------------------------------------------------------------------------- 1 | module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory 2 | # 自定义日志打印 3 | logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger 4 | #日志输出到控制台 5 | appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger 6 | # 使用日志系统记录 sql 7 | #appender=com.p6spy.engine.spy.appender.Slf4JLogger 8 | # 设置 p6spy driver 代理 9 | deregisterdrivers=true 10 | # 取消JDBC URL前缀 11 | useprefix=true 12 | # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. 13 | excludecategories=info,debug,result,batch,resultset 14 | # 日期格式 15 | dateformat=yyyy-MM-dd HH:mm:ss 16 | # 是否开启慢SQL记录 17 | outagedetection=true 18 | # 慢SQL记录标准 2 秒 19 | outagedetectioninterval=2 20 | -------------------------------------------------------------------------------- /jobs-spring-boot-sample/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api "${lib.'jobs-spring-boot-starter'}" 3 | api "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" 4 | 5 | } 6 | -------------------------------------------------------------------------------- /jobs-spring-boot-sample/src/main/java/com/baomidou/jobs/sample/JobsSampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.sample; 2 | 3 | import com.baomidou.jobs.starter.EnableJobs; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * Spring boot jobs sample 9 | * 10 | * @author jobob 11 | * @since 2019-06-25 12 | */ 13 | @EnableJobs 14 | @SpringBootApplication 15 | public class JobsSampleApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(JobsSampleApplication.class, args); 19 | } 20 | } -------------------------------------------------------------------------------- /jobs-spring-boot-sample/src/main/java/com/baomidou/jobs/sample/handler/DemoJobHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.sample.handler; 2 | 3 | import com.baomidou.jobs.api.JobsResponse; 4 | import com.baomidou.jobs.exception.JobsException; 5 | import com.baomidou.jobs.handler.IJobsHandler; 6 | import org.springframework.stereotype.Component; 7 | 8 | 9 | /** 10 | * 处理器 DEMO 11 | * 12 | * @author jobob 13 | * @since 2019-07-16 14 | */ 15 | @Component 16 | public class DemoJobHandler implements IJobsHandler { 17 | 18 | @Override 19 | public JobsResponse execute(String tenantId, String param) throws JobsException { 20 | System.out.println("执行 DemoJobHandler tenantId=" + tenantId 21 | + ",param=" + param); 22 | return JobsResponse.ok(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jobs-spring-boot-sample/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # web port 2 | server: 3 | port: 8081 4 | 5 | ### jobs 6 | jobs: 7 | admin-address: http://127.0.0.1:8088 8 | app-name: jobs-executor-sample 9 | app-port: 9999 10 | 11 | # log config 12 | logging: 13 | config: classpath:logback.xml 14 | -------------------------------------------------------------------------------- /jobs-spring-boot-sample/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logback 5 | 6 | 7 | 8 | 9 | %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | ${log.path} 15 | 16 | ${log.path}.%d{yyyy-MM-dd}.zip 17 | 18 | 19 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api "${lib.'netty-all'}" 3 | api "${lib.'disruptor'}" 4 | api "${lib.'cron-utils'}" 5 | api "${lib.'hessian'}" 6 | implementation "${lib.'slf4j-api'}" 7 | implementation "org.springframework:spring-context:${springVersion}" 8 | implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}" as String)) 9 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}" 10 | annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor:${springBootVersion}" 11 | api "org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}" 12 | implementation 'org.springframework.boot:spring-boot-configuration-processor' 13 | implementation 'org.springframework.boot:spring-boot-autoconfigure-processor' 14 | implementation 'org.springframework.boot:spring-boot-starter-web' 15 | compileOnly "org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}" 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | testCompile "${lib.'javax.servlet-api'}" 19 | } 20 | 21 | compileJava.dependsOn(processResources) 22 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/JobsClock.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs; 2 | 3 | import java.sql.Timestamp; 4 | import java.time.Instant; 5 | import java.util.Date; 6 | 7 | /** 8 | * Jobs 时钟辅助类 9 | * 10 | * @author jobob 11 | * @since 2019-07-13 12 | */ 13 | public class JobsClock { 14 | 15 | public static Timestamp now() { 16 | return Timestamp.from(Instant.now()); 17 | } 18 | 19 | public static long currentTimeMillis() { 20 | return System.currentTimeMillis(); 21 | } 22 | 23 | public static Date date(long currentTimeMillis) { 24 | return new Date(currentTimeMillis); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/JobsConstant.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs; 2 | 3 | /** 4 | * 常量 5 | * 6 | * @author jobob 7 | * @since 2019-07-13 8 | */ 9 | public interface JobsConstant { 10 | int CODE_SUCCESS = 0; 11 | int CODE_FAILED = -1; 12 | /** 13 | * 心跳时长 14 | */ 15 | int BEAT_TIMEOUT = 30; 16 | /** 17 | * 清理时长,需比心跳稍大 18 | */ 19 | int CLEAN_TIMEOUT = 50000; 20 | String COMMA = ","; 21 | /** 22 | * API URI 23 | */ 24 | String JOBS_API = "/jobs-api"; 25 | /** 26 | * owner标志常量,用于标志是否做过tryLock()操作 27 | */ 28 | String OPERATION_TRY_LOCK = "OPERATION_TRY_LOCK"; 29 | /** 30 | * 锁唯一标示 31 | */ 32 | String DEFAULT_LOCK_KEY = "JOBS_LOCK"; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/api/IJobsErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.api; 2 | 3 | /** 4 | * REST API 错误码接口 5 | * 6 | * @author jobob 7 | * @since 2019-06-08 8 | */ 9 | public interface IJobsErrorCode { 10 | 11 | /** 12 | * 错误编码 -1、失败 0、成功 13 | */ 14 | int getCode(); 15 | 16 | /** 17 | * 错误描述 18 | */ 19 | String getMsg(); 20 | } 21 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/api/JobsErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.api; 2 | 3 | import com.baomidou.jobs.JobsConstant; 4 | 5 | /** 6 | * REST API 错误码 7 | * 8 | * @author jobob 9 | * @since 2019-06-08 10 | */ 11 | public enum JobsErrorCode implements IJobsErrorCode { 12 | /** 13 | * 失败 14 | */ 15 | FAILED(JobsConstant.CODE_FAILED, "操作失败"), 16 | /** 17 | * 成功 18 | */ 19 | SUCCESS(JobsConstant.CODE_SUCCESS, "执行成功"); 20 | 21 | private final int code; 22 | private final String msg; 23 | 24 | JobsErrorCode(final int code, final String msg) { 25 | this.code = code; 26 | this.msg = msg; 27 | } 28 | 29 | public static JobsErrorCode fromCode(int code) { 30 | JobsErrorCode[] ecs = JobsErrorCode.values(); 31 | for (JobsErrorCode ec : ecs) { 32 | if (ec.getCode() == code) { 33 | return ec; 34 | } 35 | } 36 | return SUCCESS; 37 | } 38 | 39 | @Override 40 | public int getCode() { 41 | return code; 42 | } 43 | 44 | @Override 45 | public String getMsg() { 46 | return msg; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return String.format(" ErrorCode:{code=%s, msg=%s} ", code, msg); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/api/JobsResponse.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.api; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | import java.util.Optional; 9 | 10 | /** 11 | * REST API 返回结果 12 | * 13 | * @author jobob 14 | * @since 2019-06-08 15 | */ 16 | @Data 17 | @ToString 18 | @Accessors(chain = true) 19 | public class JobsResponse implements Serializable { 20 | /** 21 | * 业务错误码 22 | */ 23 | private int code; 24 | /** 25 | * 描述 26 | */ 27 | private String msg; 28 | 29 | public JobsResponse() { 30 | // to do nothing 31 | } 32 | 33 | public JobsResponse(IJobsErrorCode errorCode) { 34 | errorCode = Optional.ofNullable(errorCode).orElse(JobsErrorCode.FAILED); 35 | this.code = errorCode.getCode(); 36 | this.msg = errorCode.getMsg(); 37 | } 38 | 39 | public static JobsResponse ok() { 40 | return restResult(JobsErrorCode.SUCCESS); 41 | } 42 | 43 | public static JobsResponse failed(String msg) { 44 | return restResult(JobsErrorCode.FAILED.getCode(), msg); 45 | } 46 | 47 | public static JobsResponse restResult(IJobsErrorCode errorCode) { 48 | return restResult(errorCode.getCode(), errorCode.getMsg()); 49 | } 50 | 51 | private static JobsResponse restResult(int code, String msg) { 52 | JobsResponse apiResult = new JobsResponse(); 53 | apiResult.setCode(code); 54 | apiResult.setMsg(msg); 55 | return apiResult; 56 | } 57 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/disruptor/JobsDisruptorTemplate.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.disruptor; 2 | 3 | import com.baomidou.jobs.model.JobsInfo; 4 | import com.lmax.disruptor.dsl.Disruptor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | /** 8 | * 日志 Disruptor 辅助类 9 | * 10 | * @author jobob 11 | * @since 2019-06-27 12 | */ 13 | public class JobsDisruptorTemplate { 14 | @Autowired 15 | protected Disruptor disruptor; 16 | 17 | public void publish(JobsInfo jobsInfo, long waitSecond) { 18 | JobsInfoEvent jobsInfoEvent = new JobsInfoEvent(); 19 | jobsInfoEvent.setJobsInfo(jobsInfo); 20 | jobsInfoEvent.setWaitSecond(waitSecond); 21 | disruptor.publishEvent((event, sequence, bind) -> event.setJobsInfo( 22 | bind.getJobsInfo()), jobsInfoEvent); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/disruptor/JobsEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.disruptor; 2 | 3 | import com.baomidou.jobs.model.JobsInfo; 4 | import com.baomidou.jobs.trigger.JobsTrigger; 5 | import com.baomidou.jobs.trigger.TriggerTypeEnum; 6 | import com.lmax.disruptor.EventHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * disruptor EventHandler 13 | * 14 | * @author jobob 15 | * @since 2019-06-27 16 | */ 17 | @Slf4j 18 | public class JobsEventHandler implements EventHandler { 19 | 20 | @Override 21 | public void onEvent(JobsInfoEvent jobsInfoEvent, long sequence, boolean endOfBatch) throws Exception { 22 | JobsInfo jobsInfo = jobsInfoEvent.getJobsInfo(); 23 | if (null != jobsInfo) { 24 | if (jobsInfoEvent.getWaitSecond() > 0) { 25 | try { 26 | TimeUnit.MILLISECONDS.sleep(jobsInfoEvent.getWaitSecond()); 27 | } catch (InterruptedException e) { 28 | // to do nothing 29 | } 30 | } 31 | if (log.isDebugEnabled()) { 32 | log.debug("Jobs Event Handler: {}", jobsInfo.toString()); 33 | } 34 | JobsTrigger.trigger(jobsInfo.getId(), TriggerTypeEnum.CRON, -1, null); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/disruptor/JobsInfoEvent.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.disruptor; 2 | 3 | import com.baomidou.jobs.model.JobsInfo; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 日志事件 10 | * 11 | * @author jobob 12 | * @since 2019-06-27 13 | */ 14 | @Data 15 | public class JobsInfoEvent implements Serializable { 16 | /** 17 | * 任务信息 18 | */ 19 | private JobsInfo jobsInfo; 20 | /** 21 | * 等待执行时长 22 | */ 23 | private long waitSecond; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/exception/JobsException.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.exception; 2 | 3 | /** 4 | * Jobs 异常 5 | * 6 | * @author jobob 7 | * @since 2019-07-13 8 | */ 9 | public class JobsException extends RuntimeException { 10 | 11 | public JobsException() { 12 | // to do nothing 13 | } 14 | 15 | public JobsException(String message) { 16 | super(message); 17 | } 18 | 19 | public JobsException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public JobsException(String message, Throwable cause) { 24 | super(message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/exception/JobsRpcException.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.exception; 2 | 3 | /** 4 | * Jobs RPC 异常 5 | * 6 | * @author jobob 7 | * @since 2019-07-19 8 | */ 9 | public class JobsRpcException extends JobsException { 10 | 11 | public JobsRpcException(String msg) { 12 | super(msg); 13 | } 14 | 15 | public JobsRpcException(String msg, Throwable cause) { 16 | super(msg, cause); 17 | } 18 | 19 | public JobsRpcException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/executor/IJobsExecutor.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.executor; 2 | 3 | import com.baomidou.jobs.api.JobsResponse; 4 | import com.baomidou.jobs.exception.JobsException; 5 | import com.baomidou.jobs.model.param.TriggerParam; 6 | 7 | /** 8 | * Jobs 执行器 9 | * 10 | * @author jobob 11 | * @since 2019-07-13 12 | */ 13 | public interface IJobsExecutor { 14 | 15 | /** 16 | * 执行任务调度 17 | * 18 | * @param triggerParam 触发参数 19 | * @return JobsResponse 20 | * @throws JobsException 21 | */ 22 | JobsResponse run(TriggerParam triggerParam) throws JobsException; 23 | } 24 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/executor/JobsExecutor.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.executor; 2 | 3 | import com.baomidou.jobs.api.JobsResponse; 4 | import com.baomidou.jobs.exception.JobsException; 5 | import com.baomidou.jobs.handler.IJobsHandler; 6 | import com.baomidou.jobs.model.param.TriggerParam; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * Jobs 执行器 11 | * 12 | * @author jobob 13 | * @since 2019-07-13 14 | */ 15 | @Slf4j 16 | public class JobsExecutor implements IJobsExecutor { 17 | 18 | /** 19 | * 执行任务调度 20 | * 21 | * @param triggerParam 触发参数 22 | * @return 23 | */ 24 | @Override 25 | public JobsResponse run(TriggerParam triggerParam) throws JobsException { 26 | if (null == triggerParam) { 27 | throw new JobsException("JobsExecutor execute triggerParam is null"); 28 | } 29 | IJobsHandler jobsHandler = JobsAbstractExecutor.getJobsHandler(triggerParam.getHandler()); 30 | if (null == jobsHandler) { 31 | throw new JobsException("JobsExecutor not found execute handler:" + triggerParam.getHandler()); 32 | } 33 | return jobsHandler.execute(triggerParam.getTenantId(), triggerParam.getParam()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/executor/JobsSpringExecutor.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.executor; 2 | 3 | import com.baomidou.jobs.handler.IJobsHandler; 4 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.context.ApplicationContextAware; 9 | 10 | /** 11 | * jobs spring executor 12 | * 13 | * @author jobob 14 | * @since 2019-11-01 15 | */ 16 | public class JobsSpringExecutor extends JobsAbstractExecutor implements ApplicationContextAware { 17 | @Autowired 18 | private IJobsRpcSerializer jobsRpcSerializer; 19 | 20 | @Override 21 | public void start() throws Exception { 22 | // init JobsHandler Repository 23 | initJobHandlerRepository(applicationContext); 24 | 25 | // super start 26 | super.start(); 27 | } 28 | 29 | private void initJobHandlerRepository(ApplicationContext applicationContext) { 30 | if (applicationContext == null) { 31 | return; 32 | } 33 | 34 | // init job handler action 35 | String[] jobsHandlerArr = applicationContext.getBeanNamesForType(IJobsHandler.class); 36 | if (null != jobsHandlerArr && jobsHandlerArr.length > 0) { 37 | for (String jobsHandler : jobsHandlerArr) { 38 | putJobsHandler(jobsHandler, (IJobsHandler) applicationContext.getBean(jobsHandler)); 39 | } 40 | } 41 | } 42 | 43 | private static ApplicationContext applicationContext; 44 | 45 | @Override 46 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 47 | JobsSpringExecutor.applicationContext = applicationContext; 48 | } 49 | 50 | @Override 51 | public IJobsRpcSerializer getJobsRpcSerializer() { 52 | return jobsRpcSerializer; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/handler/IJobsHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.handler; 2 | 3 | import com.baomidou.jobs.api.JobsResponse; 4 | import com.baomidou.jobs.exception.JobsException; 5 | 6 | /** 7 | * job handler interface 8 | * 9 | * @author jobob 10 | * @since 2019-07-20 11 | */ 12 | public interface IJobsHandler { 13 | 14 | 15 | /** 16 | * 任务调度执行方法 17 | * 18 | * @param tenantId 租户ID 19 | * @param param 执行参数 20 | * @return 21 | * @throws JobsException 22 | */ 23 | JobsResponse execute(String tenantId, String param) throws JobsException; 24 | } 25 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/handler/IJobsResultHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.handler; 2 | 3 | import com.baomidou.jobs.api.JobsResponse; 4 | import com.baomidou.jobs.model.JobsInfo; 5 | 6 | 7 | /** 8 | * Job 结果处理器 9 | * 10 | * @author jobob 11 | * @since 2019-07-16 12 | */ 13 | public interface IJobsResultHandler { 14 | 15 | /** 16 | * 调度结果处理方法 17 | * 18 | * @param jobsInfo 任务信息 19 | * @param address 调度地址 20 | * @param jobsResponse 异常响应,根据 code 对应类 JobsErrorCode 值确定调度正确失败 21 | */ 22 | void handle(JobsInfo jobsInfo, String address, JobsResponse jobsResponse); 23 | } 24 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/JobsInfo.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 管理任务信息表 11 | * 12 | * @author jobob 13 | * @since 2019-07-12 14 | */ 15 | @Data 16 | @ToString 17 | @Accessors(chain = true) 18 | public class JobsInfo implements Serializable { 19 | /** 20 | * 主键ID 21 | */ 22 | private Long id; 23 | /** 24 | * 租户ID 25 | */ 26 | private String tenantId; 27 | /** 28 | * 对应 JobsRegistry app 属性 29 | */ 30 | private String app; 31 | /** 32 | * 任务执行CRON表达式 33 | */ 34 | private String cron; 35 | /** 36 | * 执行器,任务 Handler 名称 37 | */ 38 | private String handler; 39 | /** 40 | * 执行器,任务参数 41 | */ 42 | private String param; 43 | /** 44 | * 任务执行超时时间,单位秒 45 | */ 46 | private Integer timeout; 47 | /** 48 | * 失败重试次数 49 | */ 50 | private Integer failRetryCount; 51 | /** 52 | * 上次调度时间 53 | */ 54 | private Long lastTime; 55 | /** 56 | * 下次调度时间 57 | */ 58 | private Long nextTime; 59 | /** 60 | * 负责人 61 | */ 62 | private String author; 63 | /** 64 | * 备注 65 | */ 66 | private String remark; 67 | /** 68 | * 状态:0、启用 1、已禁用 69 | */ 70 | private Integer status; 71 | /** 72 | * 更新时间 73 | */ 74 | private Long updateTime; 75 | /** 76 | * 创建时间 77 | */ 78 | private Long createTime; 79 | } 80 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/JobsLock.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 管理任务日志表 11 | * 12 | * @author jobob 13 | * @since 2019-07-13 14 | */ 15 | @Data 16 | @ToString 17 | @Accessors(chain = true) 18 | public class JobsLock implements Serializable { 19 | /** 20 | * 主键ID 21 | */ 22 | private Long id; 23 | /** 24 | * 名称 25 | */ 26 | private String name; 27 | /** 28 | * 持有者 29 | */ 30 | private String owner; 31 | /** 32 | * 创建时间 33 | */ 34 | private Long createTime; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/JobsLog.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | /** 11 | * 管理任务日志表 12 | * 13 | * @author xxl jobob 14 | * @since 2019-05-30 15 | */ 16 | @Data 17 | @ToString 18 | @Accessors(chain = true) 19 | public class JobsLog implements Serializable { 20 | /** 21 | * 主键ID 22 | */ 23 | private Long id; 24 | /** 25 | * 任务ID 26 | */ 27 | private Long jobId; 28 | /** 29 | * 执行器地址 30 | */ 31 | private String address; 32 | /** 33 | * 执行器,任务 Handler 名称 34 | */ 35 | private String handler; 36 | /** 37 | * 执行器,任务参数 38 | */ 39 | private String param; 40 | /** 41 | * 失败重试次数 42 | */ 43 | private Integer failRetryCount; 44 | /** 45 | * 触发器调度返回码 46 | */ 47 | private Integer triggerCode; 48 | /** 49 | * 触发器调度类型 50 | */ 51 | private String triggerType; 52 | /** 53 | * 触发器调度返回信息 54 | */ 55 | private String triggerMsg; 56 | /** 57 | * 创建时间 58 | */ 59 | private Long createTime; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/JobsRegistry.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 任务注册表 11 | * 12 | * @author xxl jobob 13 | * @since 2019-05-30 14 | */ 15 | @Data 16 | @ToString 17 | @Accessors(chain = true) 18 | public class JobsRegistry implements Serializable { 19 | /** 20 | * 主键ID 21 | */ 22 | private Long id; 23 | /** 24 | * 服务名 25 | */ 26 | private String app; 27 | /** 28 | * 地址 = IP:PORT 例如:127.0.0.1:9999 29 | */ 30 | private String address; 31 | /** 32 | * 状态:0、启用 1、已禁用 33 | */ 34 | private Integer status; 35 | /** 36 | * 更新时间 37 | */ 38 | private Long updateTime; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/param/HandleCallbackParam.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model.param; 2 | 3 | import com.baomidou.jobs.api.JobsResponse; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 处理器回调参数 11 | * 12 | * @author jobob 13 | * @since 2019-07-15 14 | */ 15 | @Data 16 | @ToString 17 | public class HandleCallbackParam implements Serializable { 18 | private Long logId; 19 | private Long logDateTime; 20 | 21 | private JobsResponse executeResult; 22 | 23 | public HandleCallbackParam() { 24 | // to do nothing 25 | } 26 | 27 | public HandleCallbackParam(Long logId, Long logDateTime, JobsResponse executeResult) { 28 | this.logId = logId; 29 | this.logDateTime = logDateTime; 30 | this.executeResult = executeResult; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/param/RegisterStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model.param; 2 | 3 | /** 4 | * 任务注册节点状态枚举 5 | * 6 | * @author jobob 7 | * @since 2019-07-20 8 | */ 9 | public enum RegisterStatusEnum { 10 | /** 11 | * 启用 12 | */ 13 | ENABLED(0), 14 | /** 15 | * 已禁用 16 | */ 17 | DISABLED(1); 18 | 19 | private Integer value; 20 | 21 | RegisterStatusEnum(Integer value){ 22 | this.value = value; 23 | } 24 | 25 | public Integer getValue() { 26 | return value; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/param/RegistryParam.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model.param; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 注册参数 10 | * 11 | * @author jobob 12 | * @since 2019-07-13 13 | */ 14 | @Data 15 | @ToString 16 | public class RegistryParam implements Serializable { 17 | private RegisterStatusEnum registerStatusEnum = RegisterStatusEnum.ENABLED; 18 | private String app; 19 | private String address; 20 | 21 | public RegistryParam() { 22 | // to do nothing 23 | } 24 | 25 | public RegistryParam(String app, String address) { 26 | this.app = app; 27 | this.address = address; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/model/param/TriggerParam.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.model.param; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 触发器参数 10 | * 11 | * @author jobob 12 | * @since 2019-07-15 13 | */ 14 | @Data 15 | @ToString 16 | public class TriggerParam implements Serializable{ 17 | private Long jobId; 18 | /** 19 | * 租户ID 20 | */ 21 | private String tenantId; 22 | private String handler; 23 | private String param; 24 | private int timeout; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/router/ExecutorConsistentHashRouter.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.router; 2 | 3 | import com.baomidou.jobs.toolkit.ConsistentHash; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.List; 7 | import java.util.Random; 8 | 9 | /** 10 | * 执行器调度一次性 HASH 路由 11 | * 12 | * @author jobob 13 | * @since 2019-07-19 14 | */ 15 | @Slf4j 16 | public class ExecutorConsistentHashRouter implements IJobsExecutorRouter { 17 | 18 | @Override 19 | public String route(String app, List addressList) { 20 | if (null == app || null == addressList) { 21 | return null; 22 | } 23 | int nodeCount = addressList.size(); 24 | if (0 == nodeCount) { 25 | return null; 26 | } else if (nodeCount > 1) { 27 | nodeCount = nodeCount * 10; 28 | } else { 29 | // 一个节点不需要负载直接返回 30 | return addressList.get(0); 31 | } 32 | // 设置虚拟节点为真实节点数的 10 倍 33 | ConsistentHash consistentHash = new ConsistentHash(nodeCount); 34 | consistentHash.add(addressList); 35 | String address = consistentHash.getNode(app + new Random().nextInt(nodeCount)); 36 | log.debug("{} Consistent Hash Address [ {} ]", app, address); 37 | return address; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/router/IJobsExecutorRouter.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.router; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 执行器路由接口 7 | * 8 | * @author jobob 9 | * @since 2019-06-27 10 | */ 11 | public interface IJobsExecutorRouter { 12 | 13 | /** 14 | * 路由方法 15 | * 16 | * @param app 客户端名称 17 | * @param addressList 待调用地址列表 18 | * @return 路由选择执行地址 19 | */ 20 | String route(String app, List addressList); 21 | } 22 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/IJobsServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry; 2 | 3 | 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.TreeSet; 7 | 8 | /** 9 | * Jobs 服务注册接口 10 | * 11 | * @author jobob 12 | * @since 2019-08-01 13 | */ 14 | public interface IJobsServiceRegistry { 15 | 16 | /** 17 | * 启动 18 | * 19 | * @param param 参数 20 | */ 21 | void start(Map param); 22 | 23 | /** 24 | * 停止 25 | */ 26 | void stop(); 27 | 28 | 29 | /** 30 | * registry service, for mult 31 | * 32 | * @param keys service key 33 | * @param value service value/ip:port 34 | * @return 35 | */ 36 | boolean registry(Set keys, String value); 37 | 38 | 39 | /** 40 | * remove service, for mult 41 | * 42 | * @param keys 43 | * @param value 44 | * @return 45 | */ 46 | boolean remove(Set keys, String value); 47 | 48 | /** 49 | * discovery services, for mult 50 | * 51 | * @param keys 52 | * @return 53 | */ 54 | Map> discovery(Set keys); 55 | 56 | /** 57 | * discovery service, for one 58 | * 59 | * @param key service key 60 | * @return service value/ip:port 61 | */ 62 | TreeSet discovery(String key); 63 | 64 | } 65 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/client/model/JobsRegistryDataParamVO.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.client.model; 2 | 3 | import lombok.ToString; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * @author xuxueli 2018-12-03 9 | */ 10 | @ToString 11 | public class JobsRegistryDataParamVO { 12 | private String key; 13 | private String value; 14 | 15 | public JobsRegistryDataParamVO() { 16 | 17 | } 18 | 19 | public JobsRegistryDataParamVO(String key, String value) { 20 | this.key = key; 21 | this.value = value; 22 | } 23 | 24 | 25 | public String getKey() { 26 | return key; 27 | } 28 | 29 | public void setKey(String key) { 30 | this.key = key; 31 | } 32 | 33 | public String getValue() { 34 | return value; 35 | } 36 | 37 | public void setValue(String value) { 38 | this.value = value; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) { 44 | return true; 45 | } 46 | if (o == null || getClass() != o.getClass()) { 47 | return false; 48 | } 49 | JobsRegistryDataParamVO that = (JobsRegistryDataParamVO) o; 50 | return Objects.equals(key, that.key) && 51 | Objects.equals(value, that.value); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hash(key, value); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/client/model/JobsRegistryParamVO.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.client.model; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author xuxueli 2018-12-03 10 | */ 11 | @Data 12 | @ToString 13 | public class JobsRegistryParamVO { 14 | private String accessToken; 15 | private String biz; 16 | private String env; 17 | private List registryDataList; 18 | private List keys; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/client/util/BasicHttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.client.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.DataOutputStream; 7 | import java.io.InputStreamReader; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | 11 | /** 12 | * @author xuxueli 2018-11-25 00:55:31 13 | */ 14 | @Slf4j 15 | public class BasicHttpUtil { 16 | 17 | /** 18 | * post 19 | */ 20 | public static String postBody(String url, String requestBody, int timeout) { 21 | HttpURLConnection connection = null; 22 | BufferedReader bufferedReader = null; 23 | try { 24 | // connection 25 | URL realUrl = new URL(url); 26 | connection = (HttpURLConnection) realUrl.openConnection(); 27 | 28 | // connection setting 29 | connection.setRequestMethod("POST"); 30 | connection.setDoOutput(true); 31 | connection.setDoInput(true); 32 | connection.setUseCaches(false); 33 | connection.setReadTimeout(timeout * 1000); 34 | connection.setConnectTimeout(3 * 1000); 35 | connection.setRequestProperty("connection", "Keep-Alive"); 36 | connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); 37 | connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); 38 | 39 | // do connection 40 | connection.connect(); 41 | 42 | // write requestBody 43 | DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream()); 44 | dataOutputStream.writeBytes(requestBody); 45 | dataOutputStream.flush(); 46 | dataOutputStream.close(); 47 | 48 | /*byte[] requestBodyBytes = requestBody.getBytes("UTF-8"); 49 | connection.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length)); 50 | OutputStream outwritestream = connection.getOutputStream(); 51 | outwritestream.write(requestBodyBytes); 52 | outwritestream.flush(); 53 | outwritestream.close();*/ 54 | 55 | // valid StatusCode 56 | int statusCode = connection.getResponseCode(); 57 | if (statusCode != 200) { 58 | throw new RuntimeException("http request StatusCode("+ statusCode +") invalid. for url : " + url); 59 | } 60 | 61 | // result 62 | bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 63 | StringBuilder result = new StringBuilder(); 64 | String line; 65 | while ((line = bufferedReader.readLine()) != null) { 66 | result.append(line); 67 | } 68 | return result.toString(); 69 | } catch (Exception e) { 70 | log.error(e.getMessage(), e); 71 | } finally { 72 | try { 73 | if (bufferedReader != null) { 74 | bufferedReader.close(); 75 | } 76 | if (connection != null) { 77 | connection.disconnect(); 78 | } 79 | } catch (Exception e2) { 80 | log.error(e2.getMessage(), e2); 81 | } 82 | } 83 | return null; 84 | } 85 | 86 | /** 87 | * get 88 | */ 89 | public static String get(String url, int timeout) { 90 | HttpURLConnection connection = null; 91 | BufferedReader bufferedReader = null; 92 | try { 93 | // connection 94 | URL realUrl = new URL(url); 95 | connection = (HttpURLConnection) realUrl.openConnection(); 96 | 97 | // connection setting 98 | connection.setRequestMethod("GET"); 99 | connection.setDoOutput(true); 100 | connection.setDoInput(true); 101 | connection.setUseCaches(false); 102 | connection.setReadTimeout(timeout * 1000); 103 | connection.setConnectTimeout(3 * 1000); 104 | connection.setRequestProperty("connection", "Keep-Alive"); 105 | connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); 106 | connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); 107 | 108 | // do connection 109 | connection.connect(); 110 | 111 | //Map> map = connection.getHeaderFields(); 112 | 113 | // valid StatusCode 114 | int statusCode = connection.getResponseCode(); 115 | if (statusCode != 200) { 116 | throw new RuntimeException("Http Request StatusCode("+ statusCode +") Invalid."); 117 | } 118 | 119 | // result 120 | bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 121 | StringBuilder result = new StringBuilder(); 122 | String line; 123 | while ((line = bufferedReader.readLine()) != null) { 124 | result.append(line); 125 | } 126 | return result.toString(); 127 | } catch (Exception e) { 128 | log.error(e.getMessage(), e); 129 | } finally { 130 | try { 131 | if (bufferedReader != null) { 132 | bufferedReader.close(); 133 | } 134 | if (connection != null) { 135 | connection.disconnect(); 136 | } 137 | } catch (Exception e2) { 138 | log.error(e2.getMessage(), e2); 139 | } 140 | } 141 | return null; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/client/util/json/BasicJson.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.client.util.json; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author xuxueli 2018-11-30 10 | */ 11 | public class BasicJson { 12 | private static final BasicJsonReader basicJsonReader = new BasicJsonReader(); 13 | private static final BasicJsonWriter basicJsonwriter = new BasicJsonWriter(); 14 | 15 | /** 16 | * object to json 17 | * 18 | * @param object 19 | * @return 20 | */ 21 | public static String toJson(Object object) { 22 | return basicJsonwriter.toJson(object); 23 | } 24 | 25 | /** 26 | * parse json to map 27 | * 28 | * @param json 29 | * @return only for filed type "null、ArrayList、LinkedHashMap、String、Long、Double、..." 30 | */ 31 | public static Map parseMap(String json) { 32 | return basicJsonReader.parseMap(json); 33 | } 34 | 35 | /** 36 | * json to List 37 | * 38 | * @param json 39 | * @return 40 | */ 41 | public static List parseList(String json) { 42 | return basicJsonReader.parseList(json); 43 | } 44 | 45 | 46 | public static void main(String[] args) { 47 | Map result = new HashMap<>(); 48 | result.put("code", 200); 49 | result.put("msg", "success"); 50 | result.put("arr", Arrays.asList("111", "222")); 51 | result.put("float", 1.11f); 52 | result.put("temp", null); 53 | 54 | String json = toJson(result); 55 | System.out.println(json); 56 | 57 | Map mapObj = parseMap(json); 58 | System.out.println(mapObj); 59 | 60 | List listInt = parseList("[111,222,33]"); 61 | System.out.println(listInt); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/client/util/json/BasicJsonReader.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.client.util.json; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedHashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author xuxueli 2018-11-30 10 | */ 11 | public class BasicJsonReader { 12 | 13 | public Map parseMap(String json) { 14 | if (json != null) { 15 | json = json.trim(); 16 | if (json.startsWith("{")) { 17 | return parseMapInternal(json); 18 | } 19 | } 20 | throw new IllegalArgumentException("Cannot parse JSON"); 21 | } 22 | 23 | public List parseList(String json) { 24 | if (json != null) { 25 | json = json.trim(); 26 | if (json.startsWith("[")) { 27 | return parseListInternal(json); 28 | } 29 | } 30 | throw new IllegalArgumentException("Cannot parse JSON"); 31 | } 32 | 33 | 34 | private List parseListInternal(String json) { 35 | List list = new ArrayList(); 36 | json = trimLeadingCharacter(trimTrailingCharacter(json, ']'), '['); 37 | for (String value : tokenize(json)) { 38 | list.add(parseInternal(value)); 39 | } 40 | return list; 41 | } 42 | 43 | private Object parseInternal(String json) { 44 | if (json.equals("null")) { 45 | return null; 46 | } 47 | if (json.startsWith("[")) { 48 | return parseListInternal(json); 49 | } 50 | if (json.startsWith("{")) { 51 | return parseMapInternal(json); 52 | } 53 | if (json.startsWith("\"")) { 54 | return trimTrailingCharacter(trimLeadingCharacter(json, '"'), '"'); 55 | } 56 | try { 57 | return Long.valueOf(json); 58 | } catch (NumberFormatException ex) { 59 | // ignore 60 | } 61 | try { 62 | return Double.valueOf(json); 63 | } catch (NumberFormatException ex) { 64 | // ignore 65 | } 66 | return json; 67 | } 68 | 69 | private Map parseMapInternal(String json) { 70 | Map map = new LinkedHashMap(); 71 | json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{'); 72 | for (String pair : tokenize(json)) { 73 | String[] values = trimArrayElements(split(pair, ":")); 74 | String key = trimLeadingCharacter(trimTrailingCharacter(values[0], '"'), '"'); 75 | Object value = parseInternal(values[1]); 76 | map.put(key, value); 77 | } 78 | return map; 79 | } 80 | 81 | // append start 82 | private static String[] split(String toSplit, String delimiter) { 83 | if (toSplit != null && !toSplit.isEmpty() && delimiter != null && !delimiter.isEmpty()) { 84 | int offset = toSplit.indexOf(delimiter); 85 | if (offset < 0) { 86 | return null; 87 | } else { 88 | String beforeDelimiter = toSplit.substring(0, offset); 89 | String afterDelimiter = toSplit.substring(offset + delimiter.length()); 90 | return new String[]{beforeDelimiter, afterDelimiter}; 91 | } 92 | } else { 93 | return null; 94 | } 95 | } 96 | 97 | private static String[] trimArrayElements(String[] array) { 98 | if (array == null || array.length == 0) { 99 | return new String[0]; 100 | } else { 101 | String[] result = new String[array.length]; 102 | 103 | for (int i = 0; i < array.length; ++i) { 104 | String element = array[i]; 105 | result[i] = element != null ? element.trim() : null; 106 | } 107 | 108 | return result; 109 | } 110 | } 111 | // append end 112 | 113 | 114 | private List tokenize(String json) { 115 | List list = new ArrayList(); 116 | int index = 0; 117 | int inObject = 0; 118 | int inList = 0; 119 | boolean inValue = false; 120 | boolean inEscape = false; 121 | StringBuilder build = new StringBuilder(); 122 | while (index < json.length()) { 123 | char current = json.charAt(index); 124 | if (inEscape) { 125 | build.append(current); 126 | index++; 127 | inEscape = false; 128 | continue; 129 | } 130 | if (current == '{') { 131 | inObject++; 132 | } 133 | if (current == '}') { 134 | inObject--; 135 | } 136 | if (current == '[') { 137 | inList++; 138 | } 139 | if (current == ']') { 140 | inList--; 141 | } 142 | if (current == '"') { 143 | inValue = !inValue; 144 | } 145 | if (current == ',' && inObject == 0 && inList == 0 && !inValue) { 146 | list.add(build.toString()); 147 | build.setLength(0); 148 | } else if (current == '\\') { 149 | inEscape = true; 150 | } else { 151 | build.append(current); 152 | } 153 | index++; 154 | } 155 | if (build.length() > 0) { 156 | list.add(build.toString()); 157 | } 158 | return list; 159 | } 160 | 161 | // plugin util 162 | private static String trimTrailingCharacter(String string, char c) { 163 | if (string.length() > 0 && string.charAt(string.length() - 1) == c) { 164 | return string.substring(0, string.length() - 1); 165 | } 166 | return string; 167 | } 168 | 169 | private static String trimLeadingCharacter(String string, char c) { 170 | if (string.length() > 0 && string.charAt(0) == c) { 171 | return string.substring(1); 172 | } 173 | return string; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/impl/JobsRegistryServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.impl; 2 | 3 | import com.baomidou.jobs.rpc.registry.IJobsServiceRegistry; 4 | import com.baomidou.jobs.rpc.registry.client.JobsRegistryClient; 5 | import com.baomidou.jobs.rpc.registry.client.model.JobsRegistryDataParamVO; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * service registry for "jobs-registry v1.0.1" 11 | * 12 | * @author xuxueli 2018-11-30 13 | */ 14 | public class JobsRegistryServiceRegistry implements IJobsServiceRegistry { 15 | 16 | public static final String REGISTRY_ADDRESS = "REGISTRY_ADDRESS"; 17 | public static final String ACCESS_TOKEN = "ACCESS_TOKEN"; 18 | public static final String BIZ = "BIZ"; 19 | public static final String ENV = "ENV"; 20 | 21 | private JobsRegistryClient jobsRegistryClient; 22 | 23 | public JobsRegistryClient getJobsRegistryClient() { 24 | return jobsRegistryClient; 25 | } 26 | 27 | @Override 28 | public void start(Map param) { 29 | String xxlRegistryAddress = param.get(REGISTRY_ADDRESS); 30 | String accessToken = param.get(ACCESS_TOKEN); 31 | String biz = param.get(BIZ); 32 | String env = param.get(ENV); 33 | 34 | // fill 35 | biz = (biz != null && biz.trim().length() > 0) ? biz : "default"; 36 | env = (env != null && env.trim().length() > 0) ? env : "default"; 37 | 38 | jobsRegistryClient = new JobsRegistryClient(xxlRegistryAddress, accessToken, biz, env); 39 | } 40 | 41 | @Override 42 | public void stop() { 43 | if (jobsRegistryClient != null) { 44 | jobsRegistryClient.stop(); 45 | } 46 | } 47 | 48 | @Override 49 | public boolean registry(Set keys, String value) { 50 | if (keys == null || keys.size() == 0 || value == null) { 51 | return false; 52 | } 53 | 54 | // init 55 | List registryDataList = new ArrayList<>(); 56 | for (String key : keys) { 57 | registryDataList.add(new JobsRegistryDataParamVO(key, value)); 58 | } 59 | 60 | return jobsRegistryClient.registry(registryDataList); 61 | } 62 | 63 | @Override 64 | public boolean remove(Set keys, String value) { 65 | if (keys == null || keys.size() == 0 || value == null) { 66 | return false; 67 | } 68 | 69 | // init 70 | List registryDataList = new ArrayList<>(); 71 | for (String key : keys) { 72 | registryDataList.add(new JobsRegistryDataParamVO(key, value)); 73 | } 74 | 75 | return jobsRegistryClient.remove(registryDataList); 76 | } 77 | 78 | @Override 79 | public Map> discovery(Set keys) { 80 | return jobsRegistryClient.discovery(keys); 81 | } 82 | 83 | @Override 84 | public TreeSet discovery(String key) { 85 | return jobsRegistryClient.discovery(key); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/registry/impl/LocalServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.registry.impl; 2 | 3 | import com.baomidou.jobs.rpc.registry.IJobsServiceRegistry; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.TreeSet; 9 | 10 | /** 11 | * service registry for "local" 12 | * 13 | * @author xuxueli 2018-10-17 14 | */ 15 | public class LocalServiceRegistry implements IJobsServiceRegistry { 16 | 17 | /** 18 | * registry data 19 | */ 20 | private Map> registryData; 21 | 22 | 23 | /** 24 | * @param param ignore, not use 25 | */ 26 | @Override 27 | public void start(Map param) { 28 | registryData = new HashMap>(); 29 | } 30 | 31 | @Override 32 | public void stop() { 33 | registryData.clear(); 34 | } 35 | 36 | 37 | @Override 38 | public boolean registry(Set keys, String value) { 39 | if (keys == null || keys.size() == 0 || value == null || value.trim().length() == 0) { 40 | return false; 41 | } 42 | for (String key : keys) { 43 | TreeSet values = registryData.get(key); 44 | if (values == null) { 45 | values = new TreeSet<>(); 46 | registryData.put(key, values); 47 | } 48 | values.add(value); 49 | } 50 | return true; 51 | } 52 | 53 | @Override 54 | public boolean remove(Set keys, String value) { 55 | if (keys == null || keys.size() == 0 || value == null || value.trim().length() == 0) { 56 | return false; 57 | } 58 | for (String key : keys) { 59 | TreeSet values = registryData.get(key); 60 | if (values != null) { 61 | values.remove(value); 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | @Override 68 | public Map> discovery(Set keys) { 69 | if (keys == null || keys.size() == 0) { 70 | return null; 71 | } 72 | Map> registryDataTmp = new HashMap>(); 73 | for (String key : keys) { 74 | TreeSet valueSetTmp = discovery(key); 75 | if (valueSetTmp != null) { 76 | registryDataTmp.put(key, valueSetTmp); 77 | } 78 | } 79 | return registryDataTmp; 80 | } 81 | 82 | @Override 83 | public TreeSet discovery(String key) { 84 | return registryData.get(key); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/call/CallType.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.call; 2 | 3 | /** 4 | * rpc call type 5 | * 6 | * @author xuxueli 2018-10-19 7 | */ 8 | public enum CallType { 9 | SYNC, 10 | FUTURE, 11 | CALLBACK, 12 | ONEWAY; 13 | 14 | public static CallType match(String name, CallType defaultCallType) { 15 | for (CallType item : CallType.values()) { 16 | if (item.name().equals(name)) { 17 | return item; 18 | } 19 | } 20 | return defaultCallType; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/call/JobsRpcInvokeCallback.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.call; 2 | 3 | 4 | /** 5 | * @author xuxueli 2018-10-23 6 | */ 7 | public abstract class JobsRpcInvokeCallback { 8 | 9 | public abstract void onSuccess(T result); 10 | 11 | public abstract void onFailure(Throwable exception); 12 | 13 | 14 | // ---------------------- thread invoke callback ---------------------- 15 | 16 | private static ThreadLocal threadInvokerFuture = new ThreadLocal(); 17 | 18 | /** 19 | * get callback 20 | * 21 | * @return JobsRpcInvokeCallback 22 | */ 23 | public static JobsRpcInvokeCallback getCallback() { 24 | JobsRpcInvokeCallback invokeCallback = threadInvokerFuture.get(); 25 | threadInvokerFuture.remove(); 26 | return invokeCallback; 27 | } 28 | 29 | /** 30 | * set future 31 | */ 32 | public static void setCallback(JobsRpcInvokeCallback invokeCallback) { 33 | threadInvokerFuture.set(invokeCallback); 34 | } 35 | 36 | /** 37 | * remove future 38 | */ 39 | public static void removeCallback() { 40 | threadInvokerFuture.remove(); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/call/JobsRpcInvokeFuture.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.call; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcFutureResponse; 4 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 5 | import com.baomidou.jobs.exception.JobsRpcException; 6 | 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.Future; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.concurrent.TimeoutException; 11 | 12 | /** 13 | * @author xuxueli 2018-10-22 18:31:42 14 | */ 15 | public class JobsRpcInvokeFuture implements Future { 16 | 17 | 18 | private JobsRpcFutureResponse futureResponse; 19 | 20 | public JobsRpcInvokeFuture(JobsRpcFutureResponse futureResponse) { 21 | this.futureResponse = futureResponse; 22 | } 23 | 24 | public void stop() { 25 | // remove-InvokerFuture 26 | futureResponse.removeInvokerFuture(); 27 | } 28 | 29 | 30 | @Override 31 | public boolean cancel(boolean mayInterruptIfRunning) { 32 | return futureResponse.cancel(mayInterruptIfRunning); 33 | } 34 | 35 | @Override 36 | public boolean isCancelled() { 37 | return futureResponse.isCancelled(); 38 | } 39 | 40 | @Override 41 | public boolean isDone() { 42 | return futureResponse.isDone(); 43 | } 44 | 45 | @Override 46 | public Object get() throws ExecutionException, InterruptedException { 47 | try { 48 | return get(-1, TimeUnit.MILLISECONDS); 49 | } catch (TimeoutException e) { 50 | throw new JobsRpcException(e); 51 | } 52 | } 53 | 54 | @Override 55 | public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 56 | try { 57 | // future get 58 | JobsRpcResponse xxlRpcResponse = futureResponse.get(timeout, unit); 59 | if (xxlRpcResponse.getErrorMsg() != null) { 60 | throw new JobsRpcException(xxlRpcResponse.getErrorMsg()); 61 | } 62 | return xxlRpcResponse.getResult(); 63 | } finally { 64 | stop(); 65 | } 66 | } 67 | 68 | 69 | // ---------------------- thread invoke future ---------------------- 70 | 71 | private static ThreadLocal threadInvokerFuture = new ThreadLocal(); 72 | 73 | /** 74 | * get future 75 | */ 76 | public static Future getFuture(Class type) { 77 | Future future = (Future) threadInvokerFuture.get(); 78 | threadInvokerFuture.remove(); 79 | return future; 80 | } 81 | 82 | /** 83 | * set future 84 | */ 85 | public static void setFuture(JobsRpcInvokeFuture future) { 86 | threadInvokerFuture.set(future); 87 | } 88 | 89 | /** 90 | * remove future 91 | */ 92 | public static void removeFuture() { 93 | threadInvokerFuture.remove(); 94 | } 95 | 96 | } 97 | 98 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/generic/JobsRpcGenericService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.generic; 2 | 3 | /** 4 | * @author xuxueli 2018-12-04 5 | */ 6 | public interface JobsRpcGenericService { 7 | 8 | /** 9 | * generic invoke 10 | * 11 | * @param iface iface name 12 | * @param version iface version 13 | * @param method method name 14 | * @param parameterTypes parameter types, limit base type like "int、java.lang.Integer、java.util.List、java.util.Map ..." 15 | * @param args 16 | * @return 17 | */ 18 | Object invoke(String iface, String version, String method, String[] parameterTypes, Object[] args); 19 | 20 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/route/JobsRpcLoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.route; 2 | 3 | import java.util.TreeSet; 4 | 5 | /** 6 | * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器; 7 | * a、virtual node:解决不均衡问题 8 | * b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围 9 | * 10 | * @author xuxueli 2018-12-04 11 | */ 12 | public abstract class JobsRpcLoadBalance { 13 | 14 | public abstract String route(String serviceKey, TreeSet addressSet); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/route/LoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.route; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.route.impl.*; 4 | 5 | /** 6 | * 负载均衡策略 7 | * 8 | * @author jobob 9 | * @since 2019-11-01 10 | */ 11 | public enum LoadBalance { 12 | RANDOM(new RpcLoadBalanceRandomStrategy()), 13 | ROUND(new RpcLoadBalanceRoundStrategy()), 14 | CONSISTENT_HASH(new RpcLoadBalanceConsistentHashStrategy()); 15 | 16 | public final JobsRpcLoadBalance rpcLoadBalance; 17 | 18 | LoadBalance(JobsRpcLoadBalance rpcLoadBalance) { 19 | this.rpcLoadBalance = rpcLoadBalance; 20 | } 21 | 22 | public static LoadBalance match(String name, LoadBalance defaultRouter) { 23 | for (LoadBalance item : LoadBalance.values()) { 24 | if (item.equals(name)) { 25 | return item; 26 | } 27 | } 28 | return defaultRouter; 29 | } 30 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/route/impl/RpcLoadBalanceConsistentHashStrategy.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.route.impl; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.route.JobsRpcLoadBalance; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.*; 9 | 10 | /** 11 | * consustent hash 12 | * 13 | * 单个JOB对应的每个执行器,使用频率最低的优先被选举 14 | * a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数 15 | * b、LRU(Least Recently Used):最近最久未使用,时间 16 | * 17 | * @author xuxueli 2018-12-04 18 | */ 19 | public class RpcLoadBalanceConsistentHashStrategy extends JobsRpcLoadBalance { 20 | 21 | private int VIRTUAL_NODE_NUM = 5; 22 | 23 | /** 24 | * get hash code on 2^32 ring (md5散列的方式计算hash值) 25 | * @param key 26 | * @return 27 | */ 28 | private long hash(String key) { 29 | 30 | // md5 byte 31 | MessageDigest md5; 32 | try { 33 | md5 = MessageDigest.getInstance("MD5"); 34 | } catch (NoSuchAlgorithmException e) { 35 | throw new RuntimeException("MD5 not supported", e); 36 | } 37 | md5.reset(); 38 | byte[] keyBytes = null; 39 | try { 40 | keyBytes = key.getBytes("UTF-8"); 41 | } catch (UnsupportedEncodingException e) { 42 | throw new RuntimeException("Unknown string :" + key, e); 43 | } 44 | 45 | md5.update(keyBytes); 46 | byte[] digest = md5.digest(); 47 | 48 | // hash code, Truncate to 32-bits 49 | long hashCode = ((long) (digest[3] & 0xFF) << 24) 50 | | ((long) (digest[2] & 0xFF) << 16) 51 | | ((long) (digest[1] & 0xFF) << 8) 52 | | (digest[0] & 0xFF); 53 | 54 | long truncateHashCode = hashCode & 0xffffffffL; 55 | return truncateHashCode; 56 | } 57 | 58 | public String doRoute(String serviceKey, TreeSet addressSet) { 59 | 60 | // ------A1------A2-------A3------ 61 | // -----------J1------------------ 62 | TreeMap addressRing = new TreeMap(); 63 | for (String address: addressSet) { 64 | for (int i = 0; i < VIRTUAL_NODE_NUM; i++) { 65 | long addressHash = hash("SHARD-" + address + "-NODE-" + i); 66 | addressRing.put(addressHash, address); 67 | } 68 | } 69 | 70 | long jobHash = hash(serviceKey); 71 | SortedMap lastRing = addressRing.tailMap(jobHash); 72 | if (!lastRing.isEmpty()) { 73 | return lastRing.get(lastRing.firstKey()); 74 | } 75 | return addressRing.firstEntry().getValue(); 76 | } 77 | 78 | @Override 79 | public String route(String serviceKey, TreeSet addressSet) { 80 | String finalAddress = doRoute(serviceKey, addressSet); 81 | return finalAddress; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/route/impl/RpcLoadBalanceRandomStrategy.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.route.impl; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.route.JobsRpcLoadBalance; 4 | 5 | import java.util.Random; 6 | import java.util.TreeSet; 7 | 8 | /** 9 | * random 10 | * 11 | * @author xuxueli 2018-12-04 12 | */ 13 | public class RpcLoadBalanceRandomStrategy extends JobsRpcLoadBalance { 14 | 15 | private Random random = new Random(); 16 | 17 | @Override 18 | public String route(String serviceKey, TreeSet addressSet) { 19 | // arr 20 | String[] addressArr = addressSet.toArray(new String[addressSet.size()]); 21 | 22 | // random 23 | String finalAddress = addressArr[random.nextInt(addressSet.size())]; 24 | return finalAddress; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/invoker/route/impl/RpcLoadBalanceRoundStrategy.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.invoker.route.impl; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.route.JobsRpcLoadBalance; 4 | 5 | import java.util.Random; 6 | import java.util.TreeSet; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * round 11 | * 12 | * @author xuxueli 2018-12-04 13 | */ 14 | public class RpcLoadBalanceRoundStrategy extends JobsRpcLoadBalance { 15 | private ConcurrentHashMap routeCountEachJob = new ConcurrentHashMap(); 16 | private long CACHE_VALID_TIME = 0; 17 | private int count(String serviceKey) { 18 | // cache clear 19 | if (System.currentTimeMillis() > CACHE_VALID_TIME) { 20 | routeCountEachJob.clear(); 21 | CACHE_VALID_TIME = System.currentTimeMillis() + 24*60*60*1000; 22 | } 23 | 24 | // count++ 25 | Integer count = routeCountEachJob.get(serviceKey); 26 | // 初始化时主动Random一次,缓解首次压力 27 | count = (count==null || count>1000000)?(new Random().nextInt(100)):++count; 28 | routeCountEachJob.put(serviceKey, count); 29 | return count; 30 | } 31 | 32 | @Override 33 | public String route(String serviceKey, TreeSet addressSet) { 34 | // arr 35 | String[] addressArr = addressSet.toArray(new String[addressSet.size()]); 36 | 37 | // round 38 | String finalAddress = addressArr[count(serviceKey)%addressArr.length]; 39 | return finalAddress; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/Client.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.reference.JobsRpcReferenceBean; 4 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 5 | 6 | /** 7 | * i client 8 | * 9 | * @author xuxueli 2015-11-24 22:18:10 10 | */ 11 | public abstract class Client { 12 | 13 | // ---------------------- init ---------------------- 14 | 15 | protected volatile JobsRpcReferenceBean rpcReferenceBean; 16 | 17 | public void init(JobsRpcReferenceBean rpcReferenceBean) { 18 | this.rpcReferenceBean = rpcReferenceBean; 19 | } 20 | 21 | 22 | // ---------------------- send ---------------------- 23 | 24 | /** 25 | * async send, bind requestId and future-response 26 | * 27 | * @param address 28 | * @param xxlRpcRequest 29 | * @return 30 | * @throws Exception 31 | */ 32 | public abstract void asyncSend(String address, JobsRpcRequest xxlRpcRequest) throws Exception; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/NetEnum.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.http.client.NettyHttpClient; 4 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.http.server.NettyHttpServer; 5 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.client.NettyClient; 6 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.server.NettyServer; 7 | 8 | /** 9 | * remoting net 10 | * 11 | * @author xuxueli 2015-11-24 22:09:57 12 | */ 13 | public enum NetEnum { 14 | /** 15 | * netty tcp server 16 | */ 17 | NETTY(NettyServer.class, NettyClient.class), 18 | /** 19 | * netty http server (servlet no server, ServletServerHandler) 20 | */ 21 | NETTY_HTTP(NettyHttpServer.class, NettyHttpClient.class); 22 | 23 | public final Class serverClass; 24 | public final Class clientClass; 25 | 26 | NetEnum(Class serverClass, Class clientClass) { 27 | this.serverClass = serverClass; 28 | this.clientClass = clientClass; 29 | } 30 | 31 | public static NetEnum autoMatch(String name, NetEnum defaultEnum) { 32 | for (NetEnum item : NetEnum.values()) { 33 | if (item.name().equals(name)) { 34 | return item; 35 | } 36 | } 37 | return defaultEnum; 38 | } 39 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/Server.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net; 2 | 3 | import com.baomidou.jobs.exception.JobsRpcException; 4 | import com.baomidou.jobs.rpc.remoting.net.params.IJobsRpcCallback; 5 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 6 | import lombok.Setter; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * server 11 | * 12 | * @author xuxueli 2015-11-24 20:59:49 13 | */ 14 | @Slf4j 15 | @Setter 16 | public abstract class Server { 17 | private IJobsRpcCallback startedCallback; 18 | private IJobsRpcCallback stoppedCallback; 19 | 20 | /** 21 | * start server 22 | */ 23 | public abstract void start(final JobsRpcProviderFactory jobsRpcProviderFactory) throws Exception; 24 | 25 | /** 26 | * callback when started 27 | */ 28 | public void onStarted() { 29 | if (startedCallback != null) { 30 | try { 31 | startedCallback.execute(); 32 | } catch (JobsRpcException e) { 33 | log.error("Jobs rpc, server startedCallback error.", e); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * stop server 40 | * 41 | * @throws Exception 42 | */ 43 | public abstract void stop() throws Exception; 44 | 45 | /** 46 | * callback when stopped 47 | */ 48 | public void onStopped() { 49 | if (null != stoppedCallback) { 50 | try { 51 | stoppedCallback.execute(); 52 | } catch (JobsRpcException e) { 53 | log.error("Jobs rpc, server stoppedCallback error.", e); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/common/ConnectClient.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.common; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 4 | import com.baomidou.jobs.rpc.remoting.invoker.reference.JobsRpcReferenceBean; 5 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 6 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * @author xuxueli 2018-10-19 13 | */ 14 | @Slf4j 15 | public abstract class ConnectClient { 16 | 17 | public abstract void init(String address, final IJobsRpcSerializer serializer, final JobsRpcInvokerFactory xxlRpcInvokerFactory) throws Exception; 18 | 19 | public abstract void close(); 20 | 21 | public abstract boolean isValidate(); 22 | 23 | public abstract void send(JobsRpcRequest jobsRpcRequest) throws Exception; 24 | 25 | /** 26 | * async send 27 | */ 28 | public static void asyncSend(JobsRpcRequest xxlRpcRequest, String address, 29 | Class connectClientImpl, 30 | final JobsRpcReferenceBean xxlRpcReferenceBean) throws Exception { 31 | 32 | // client pool [tips03 : may save 35ms/100invoke if move it to constructor, but it is necessary. cause by ConcurrentHashMap.get] 33 | ConnectClient clientPool = ConnectClient.getPool(address, connectClientImpl, xxlRpcReferenceBean); 34 | 35 | try { 36 | // do invoke 37 | clientPool.send(xxlRpcRequest); 38 | } catch (Exception e) { 39 | throw e; 40 | } 41 | } 42 | 43 | private static volatile ConcurrentHashMap connectClientMap; 44 | private static volatile ConcurrentHashMap connectClientLockMap = new ConcurrentHashMap<>(); 45 | 46 | private static ConnectClient getPool(String address, Class connectClientImpl, 47 | final JobsRpcReferenceBean jobsRpcReferenceBean) throws Exception { 48 | 49 | // init base compont, avoid repeat init 50 | if (connectClientMap == null) { 51 | synchronized (ConnectClient.class) { 52 | if (connectClientMap == null) { 53 | // init 54 | connectClientMap = new ConcurrentHashMap<>(16); 55 | // stop callback 56 | jobsRpcReferenceBean.getInvokerFactory().addStopCallBack(() -> { 57 | if (connectClientMap.size() > 0) { 58 | for (String key : connectClientMap.keySet()) { 59 | ConnectClient clientPool = connectClientMap.get(key); 60 | clientPool.close(); 61 | } 62 | connectClientMap.clear(); 63 | } 64 | }); 65 | } 66 | } 67 | } 68 | 69 | // get-valid client 70 | ConnectClient connectClient = connectClientMap.get(address); 71 | if (connectClient != null && connectClient.isValidate()) { 72 | return connectClient; 73 | } 74 | 75 | // lock 76 | Object clientLock = connectClientLockMap.get(address); 77 | if (clientLock == null) { 78 | connectClientLockMap.putIfAbsent(address, new Object()); 79 | clientLock = connectClientLockMap.get(address); 80 | } 81 | 82 | // remove-create new client 83 | synchronized (clientLock) { 84 | 85 | // get-valid client, avlid repeat 86 | connectClient = connectClientMap.get(address); 87 | if (connectClient != null && connectClient.isValidate()) { 88 | return connectClient; 89 | } 90 | 91 | // remove old 92 | if (connectClient != null) { 93 | connectClient.close(); 94 | connectClientMap.remove(address); 95 | } 96 | 97 | // set pool 98 | ConnectClient connectClient_new = connectClientImpl.newInstance(); 99 | try { 100 | connectClient_new.init(address, jobsRpcReferenceBean.getSerializer(), jobsRpcReferenceBean.getInvokerFactory()); 101 | connectClientMap.put(address, connectClient_new); 102 | } catch (Exception e) { 103 | connectClient_new.close(); 104 | throw e; 105 | } 106 | 107 | return connectClient_new; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/http/client/NettyHttpClient.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.http.client; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.Client; 4 | import com.baomidou.jobs.rpc.remoting.net.common.ConnectClient; 5 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 6 | 7 | /** 8 | * netty_http client 9 | * 10 | * @author xuxueli 2015-11-24 22:25:15 11 | */ 12 | public class NettyHttpClient extends Client { 13 | 14 | private Class connectClientImpl = NettyHttpConnectClient.class; 15 | 16 | @Override 17 | public void asyncSend(String address, JobsRpcRequest rpcRequest) throws Exception { 18 | ConnectClient.asyncSend(rpcRequest, address, connectClientImpl, rpcReferenceBean); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/http/client/NettyHttpClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.http.client; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 4 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 5 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 6 | import com.baomidou.jobs.exception.JobsRpcException; 7 | import io.netty.buffer.ByteBufUtil; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import io.netty.handler.codec.http.FullHttpResponse; 11 | import io.netty.handler.timeout.IdleStateEvent; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * netty_http 17 | * 18 | * @author xuxueli 2015-11-24 22:25:15 19 | */ 20 | public class NettyHttpClientHandler extends SimpleChannelInboundHandler { 21 | private static final Logger logger = LoggerFactory.getLogger(NettyHttpClientHandler.class); 22 | 23 | 24 | private JobsRpcInvokerFactory xxlRpcInvokerFactory; 25 | private IJobsRpcSerializer serializer; 26 | 27 | public NettyHttpClientHandler(final JobsRpcInvokerFactory xxlRpcInvokerFactory, IJobsRpcSerializer serializer) { 28 | this.xxlRpcInvokerFactory = xxlRpcInvokerFactory; 29 | this.serializer = serializer; 30 | } 31 | 32 | 33 | @Override 34 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception { 35 | 36 | // response parse 37 | byte[] responseBytes = ByteBufUtil.getBytes(msg.content()); 38 | 39 | // valid 40 | if (responseBytes.length == 0) { 41 | throw new JobsRpcException("Jobs rpc request data empty."); 42 | } 43 | 44 | // response deserialize 45 | JobsRpcResponse xxlRpcResponse = (JobsRpcResponse) serializer.deserialize(responseBytes, JobsRpcResponse.class); 46 | 47 | // notify response 48 | xxlRpcInvokerFactory.notifyInvokerFuture(xxlRpcResponse.getRequestId(), xxlRpcResponse); 49 | 50 | } 51 | 52 | @Override 53 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 54 | //super.exceptionCaught(ctx, cause); 55 | logger.error("Jobs rpc netty_http client caught exception", cause); 56 | ctx.close(); 57 | } 58 | 59 | /*@Override 60 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 61 | // retry 62 | super.channelInactive(ctx); 63 | }*/ 64 | 65 | @Override 66 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 67 | if (evt instanceof IdleStateEvent) { 68 | ctx.channel().close(); // close idle channel 69 | logger.debug("Jobs rpc netty_http client close an idle channel."); 70 | } else { 71 | super.userEventTriggered(ctx, evt); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/http/client/NettyHttpConnectClient.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.http.client; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 4 | import com.baomidou.jobs.rpc.remoting.net.common.ConnectClient; 5 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 6 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 7 | import io.netty.bootstrap.Bootstrap; 8 | import io.netty.buffer.Unpooled; 9 | import io.netty.channel.Channel; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.SocketChannel; 15 | import io.netty.channel.socket.nio.NioSocketChannel; 16 | import io.netty.handler.codec.http.*; 17 | import io.netty.handler.timeout.IdleStateHandler; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | import java.net.URI; 21 | import java.net.URL; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | * netty_http 26 | * 27 | * @author xuxueli 2015-11-24 22:25:15 28 | */ 29 | @Slf4j 30 | public class NettyHttpConnectClient extends ConnectClient { 31 | 32 | private EventLoopGroup group; 33 | private Channel channel; 34 | 35 | private IJobsRpcSerializer serializer; 36 | private String address; 37 | private String host; 38 | 39 | @Override 40 | public void init(String address, final IJobsRpcSerializer serializer, final JobsRpcInvokerFactory xxlRpcInvokerFactory) throws Exception { 41 | 42 | if (!address.toLowerCase().startsWith("http")) { 43 | address = "http://" + address; // IP:PORT, need parse to url 44 | } 45 | 46 | this.address = address; 47 | URL url = new URL(address); 48 | this.host = url.getHost(); 49 | int port = url.getPort() > -1 ? url.getPort() : 80; 50 | 51 | 52 | this.group = new NioEventLoopGroup(); 53 | Bootstrap bootstrap = new Bootstrap(); 54 | bootstrap.group(group) 55 | .channel(NioSocketChannel.class) 56 | .handler(new ChannelInitializer() { 57 | @Override 58 | public void initChannel(SocketChannel channel) throws Exception { 59 | channel.pipeline() 60 | .addLast(new IdleStateHandler(0, 0, 10, TimeUnit.MINUTES)) 61 | .addLast(new HttpClientCodec()) 62 | .addLast(new HttpObjectAggregator(5 * 1024 * 1024)) 63 | .addLast(new NettyHttpClientHandler(xxlRpcInvokerFactory, serializer)); 64 | } 65 | }) 66 | .option(ChannelOption.SO_KEEPALIVE, true); 67 | this.channel = bootstrap.connect(host, port).sync().channel(); 68 | 69 | this.serializer = serializer; 70 | 71 | // valid 72 | if (!isValidate()) { 73 | close(); 74 | return; 75 | } 76 | 77 | log.debug("Jobs rpc netty client proxy, connect to server success at host:{}, port:{}", host, port); 78 | } 79 | 80 | 81 | @Override 82 | public boolean isValidate() { 83 | if (this.channel != null) { 84 | return this.channel.isActive(); 85 | } 86 | return false; 87 | } 88 | 89 | 90 | @Override 91 | public void close() { 92 | if (this.channel != null && this.channel.isActive()) { 93 | this.channel.close(); // if this.channel.isOpen() 94 | } 95 | if (this.group != null && !this.group.isShutdown()) { 96 | this.group.shutdownGracefully(); 97 | } 98 | log.debug("Jobs rpc netty client close."); 99 | } 100 | 101 | 102 | @Override 103 | public void send(JobsRpcRequest xxlRpcRequest) throws Exception { 104 | byte[] requestBytes = serializer.serialize(xxlRpcRequest); 105 | 106 | DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, new URI(address).getRawPath(), Unpooled.wrappedBuffer(requestBytes)); 107 | request.headers().set(HttpHeaderNames.HOST, host); 108 | request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 109 | request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes()); 110 | 111 | this.channel.writeAndFlush(request).sync(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/http/server/NettyHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.http.server; 2 | 3 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 4 | import com.baomidou.jobs.rpc.util.ThreadPoolUtil; 5 | import com.baomidou.jobs.rpc.remoting.net.Server; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.EventLoopGroup; 11 | import io.netty.channel.nio.NioEventLoopGroup; 12 | import io.netty.channel.socket.SocketChannel; 13 | import io.netty.channel.socket.nio.NioServerSocketChannel; 14 | import io.netty.handler.codec.http.HttpObjectAggregator; 15 | import io.netty.handler.codec.http.HttpServerCodec; 16 | import io.netty.handler.timeout.IdleStateHandler; 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | import java.util.concurrent.ThreadPoolExecutor; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | /** 23 | * netty_http 24 | * 25 | * @author xuxueli 2015-11-24 22:25:15 26 | */ 27 | @Slf4j 28 | public class NettyHttpServer extends Server { 29 | 30 | private Thread thread; 31 | 32 | @Override 33 | public void start(final JobsRpcProviderFactory xxlRpcProviderFactory) throws Exception { 34 | 35 | thread = new Thread(() -> { 36 | 37 | // param 38 | final ThreadPoolExecutor serverHandlerPool = ThreadPoolUtil.makeServerThreadPool(NettyHttpServer.class.getSimpleName()); 39 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 40 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 41 | 42 | try { 43 | // start server 44 | ServerBootstrap bootstrap = new ServerBootstrap(); 45 | bootstrap.group(bossGroup, workerGroup) 46 | .channel(NioServerSocketChannel.class) 47 | .childHandler(new ChannelInitializer() { 48 | @Override 49 | public void initChannel(SocketChannel channel) throws Exception { 50 | channel.pipeline() 51 | .addLast(new IdleStateHandler(0, 0, 10, TimeUnit.MINUTES)) 52 | .addLast(new HttpServerCodec()) 53 | .addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL 54 | .addLast(new NettyHttpServerHandler(xxlRpcProviderFactory, serverHandlerPool)); 55 | } 56 | }) 57 | .childOption(ChannelOption.SO_KEEPALIVE, true); 58 | 59 | // bind 60 | ChannelFuture future = bootstrap.bind(xxlRpcProviderFactory.getPort()).sync(); 61 | 62 | log.info("Jobs rpc remoting server start success, nettype = {}, port = {}", NettyHttpServer.class.getName(), xxlRpcProviderFactory.getPort()); 63 | onStarted(); 64 | 65 | // wait util stop 66 | future.channel().closeFuture().sync(); 67 | 68 | } catch (InterruptedException e) { 69 | if (e instanceof InterruptedException) { 70 | log.info("Jobs rpc remoting server stop."); 71 | } else { 72 | log.error("Jobs rpc remoting server error.", e); 73 | } 74 | } finally { 75 | // stop 76 | try { 77 | // shutdownNow 78 | serverHandlerPool.shutdown(); 79 | } catch (Exception e) { 80 | log.error(e.getMessage(), e); 81 | } 82 | try { 83 | workerGroup.shutdownGracefully(); 84 | bossGroup.shutdownGracefully(); 85 | } catch (Exception e) { 86 | log.error(e.getMessage(), e); 87 | } 88 | } 89 | 90 | }); 91 | thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave 92 | thread.start(); 93 | } 94 | 95 | @Override 96 | public void stop() throws Exception { 97 | // destroy server thread 98 | if (thread != null && thread.isAlive()) { 99 | thread.interrupt(); 100 | } 101 | 102 | // on stop 103 | onStopped(); 104 | log.info("Jobs rpc remoting server destroy success."); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/http/server/NettyHttpServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.http.server; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 4 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 5 | import com.baomidou.jobs.exception.JobsRpcException; 6 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 7 | import com.baomidou.jobs.service.JobsHelper; 8 | import io.netty.buffer.ByteBufUtil; 9 | import io.netty.buffer.Unpooled; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.channel.SimpleChannelInboundHandler; 12 | import io.netty.handler.codec.http.*; 13 | import io.netty.handler.timeout.IdleStateEvent; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.util.concurrent.ThreadPoolExecutor; 18 | 19 | 20 | /** 21 | * netty_http 22 | * 23 | * @author xuxueli 2015-11-24 22:25:15 24 | */ 25 | public class NettyHttpServerHandler extends SimpleChannelInboundHandler { 26 | private static final Logger logger = LoggerFactory.getLogger(NettyHttpServerHandler.class); 27 | 28 | 29 | private JobsRpcProviderFactory xxlRpcProviderFactory; 30 | private ThreadPoolExecutor serverHandlerPool; 31 | 32 | public NettyHttpServerHandler(final JobsRpcProviderFactory xxlRpcProviderFactory, final ThreadPoolExecutor serverHandlerPool) { 33 | this.xxlRpcProviderFactory = xxlRpcProviderFactory; 34 | this.serverHandlerPool = serverHandlerPool; 35 | } 36 | 37 | @Override 38 | protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { 39 | 40 | // request parse 41 | final byte[] requestBytes = ByteBufUtil.getBytes(msg.content()); // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8); 42 | final String uri = msg.uri(); 43 | final boolean keepAlive = HttpUtil.isKeepAlive(msg); 44 | 45 | // do invoke 46 | serverHandlerPool.execute(new Runnable() { 47 | @Override 48 | public void run() { 49 | process(ctx, uri, requestBytes, keepAlive); 50 | } 51 | }); 52 | } 53 | 54 | private void process(ChannelHandlerContext ctx, String uri, byte[] requestBytes, boolean keepAlive) { 55 | String requestId = null; 56 | try { 57 | if ("/services".equals(uri)) { // services mapping 58 | 59 | // request 60 | StringBuffer stringBuffer = new StringBuffer(""); 61 | for (String serviceKey : xxlRpcProviderFactory.getServiceData().keySet()) { 62 | stringBuffer.append("
  • ").append(serviceKey).append(": ").append(xxlRpcProviderFactory.getServiceData().get(serviceKey)).append("
  • "); 63 | } 64 | stringBuffer.append("
    "); 65 | 66 | // response serialize 67 | byte[] responseBytes = stringBuffer.toString().getBytes("UTF-8"); 68 | 69 | // response-write 70 | writeResponse(ctx, keepAlive, responseBytes); 71 | 72 | } else { 73 | 74 | // valid 75 | if (requestBytes.length == 0) { 76 | throw new JobsRpcException("Jobs rpc request data empty."); 77 | } 78 | 79 | // request deserialize 80 | JobsRpcRequest xxlRpcRequest = (JobsRpcRequest) xxlRpcProviderFactory.getSerializer().deserialize(requestBytes, JobsRpcRequest.class); 81 | requestId = xxlRpcRequest.getRequestId(); 82 | 83 | // invoke + response 84 | JobsRpcResponse xxlRpcResponse = xxlRpcProviderFactory.invokeService(xxlRpcRequest); 85 | 86 | // response serialize 87 | byte[] responseBytes = xxlRpcProviderFactory.getSerializer().serialize(xxlRpcResponse); 88 | 89 | // response-write 90 | writeResponse(ctx, keepAlive, responseBytes); 91 | } 92 | } catch (Exception e) { 93 | logger.error(e.getMessage(), e); 94 | 95 | // response error 96 | JobsRpcResponse xxlRpcResponse = new JobsRpcResponse(); 97 | xxlRpcResponse.setRequestId(requestId); 98 | xxlRpcResponse.setErrorMsg(JobsHelper.getErrorInfo(e)); 99 | 100 | // response serialize 101 | byte[] responseBytes = xxlRpcProviderFactory.getSerializer().serialize(xxlRpcResponse); 102 | 103 | // response-write 104 | writeResponse(ctx, keepAlive, responseBytes); 105 | } 106 | 107 | } 108 | 109 | /** 110 | * write response 111 | */ 112 | private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, byte[] responseBytes) { 113 | FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(responseBytes)); 114 | response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8"); // HttpHeaderValues.TEXT_PLAIN.toString() 115 | response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); 116 | if (keepAlive) { 117 | response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 118 | } 119 | ctx.writeAndFlush(response); 120 | } 121 | 122 | @Override 123 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 124 | ctx.flush(); 125 | } 126 | 127 | @Override 128 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 129 | logger.error("Jobs rpc provider netty_http server caught exception", cause); 130 | ctx.close(); 131 | } 132 | 133 | @Override 134 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 135 | if (evt instanceof IdleStateEvent) { 136 | ctx.channel().close(); // close idle channel 137 | logger.debug("Jobs rpc provider netty_http server close an idle channel."); 138 | } else { 139 | super.userEventTriggered(ctx, evt); 140 | } 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/client/NettyClient.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.client; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.Client; 4 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 5 | import com.baomidou.jobs.rpc.remoting.net.common.ConnectClient; 6 | 7 | /** 8 | * netty client 9 | * 10 | * @author xuxueli 2015-11-24 22:25:15 11 | */ 12 | public class NettyClient extends Client { 13 | 14 | private Class connectClientImpl = NettyConnectClient.class; 15 | 16 | @Override 17 | public void asyncSend(String address, JobsRpcRequest rpcRequest) throws Exception { 18 | ConnectClient.asyncSend(rpcRequest, address, connectClientImpl, rpcReferenceBean); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/client/NettyClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.client; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 4 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import io.netty.handler.timeout.IdleStateEvent; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * rpc netty client handler 13 | * 14 | * @author xuxueli 2015-10-31 18:00:27 15 | */ 16 | public class NettyClientHandler extends SimpleChannelInboundHandler { 17 | private static final Logger logger = LoggerFactory.getLogger(NettyClientHandler.class); 18 | private JobsRpcInvokerFactory jobsRpcInvokerFactory; 19 | 20 | public NettyClientHandler(final JobsRpcInvokerFactory jobsRpcInvokerFactory) { 21 | this.jobsRpcInvokerFactory = jobsRpcInvokerFactory; 22 | } 23 | 24 | @Override 25 | protected void channelRead0(ChannelHandlerContext ctx, JobsRpcResponse xxlRpcResponse) throws Exception { 26 | // notify response 27 | jobsRpcInvokerFactory.notifyInvokerFuture(xxlRpcResponse.getRequestId(), xxlRpcResponse); 28 | } 29 | 30 | @Override 31 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 32 | logger.error("Jobs rpc netty client caught exception", cause); 33 | ctx.close(); 34 | } 35 | 36 | @Override 37 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 38 | if (evt instanceof IdleStateEvent) { 39 | // close idle channel 40 | ctx.channel().close(); 41 | logger.debug("Jobs rpc netty client close an idle channel."); 42 | } else { 43 | super.userEventTriggered(ctx, evt); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/client/NettyConnectClient.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.client; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 4 | import com.baomidou.jobs.rpc.remoting.net.common.ConnectClient; 5 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.codec.NettyDecoder; 6 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.codec.NettyEncoder; 7 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 8 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 9 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 10 | import com.baomidou.jobs.rpc.util.IpUtil; 11 | import io.netty.bootstrap.Bootstrap; 12 | import io.netty.channel.Channel; 13 | import io.netty.channel.ChannelInitializer; 14 | import io.netty.channel.ChannelOption; 15 | import io.netty.channel.EventLoopGroup; 16 | import io.netty.channel.nio.NioEventLoopGroup; 17 | import io.netty.channel.socket.SocketChannel; 18 | import io.netty.channel.socket.nio.NioSocketChannel; 19 | import io.netty.handler.timeout.IdleStateHandler; 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | * netty pooled client 26 | * 27 | * @author xuxueli 28 | */ 29 | @Slf4j 30 | public class NettyConnectClient extends ConnectClient { 31 | private EventLoopGroup group; 32 | private Channel channel; 33 | 34 | @Override 35 | public void init(String address, final IJobsRpcSerializer serializer, final JobsRpcInvokerFactory xxlRpcInvokerFactory) throws Exception { 36 | 37 | Object[] array = IpUtil.parseIpPort(address); 38 | String host = (String) array[0]; 39 | int port = (int) array[1]; 40 | 41 | 42 | this.group = new NioEventLoopGroup(); 43 | Bootstrap bootstrap = new Bootstrap(); 44 | bootstrap.group(group) 45 | .channel(NioSocketChannel.class) 46 | .handler(new ChannelInitializer() { 47 | @Override 48 | public void initChannel(SocketChannel channel) throws Exception { 49 | channel.pipeline() 50 | .addLast(new IdleStateHandler(0, 0, 10, TimeUnit.MINUTES)) 51 | .addLast(new NettyEncoder(JobsRpcRequest.class, serializer)) 52 | .addLast(new NettyDecoder(JobsRpcResponse.class, serializer)) 53 | .addLast(new NettyClientHandler(xxlRpcInvokerFactory)); 54 | } 55 | }) 56 | .option(ChannelOption.TCP_NODELAY, true) 57 | .option(ChannelOption.SO_KEEPALIVE, true) 58 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); 59 | this.channel = bootstrap.connect(host, port).sync().channel(); 60 | 61 | // valid 62 | if (!isValidate()) { 63 | close(); 64 | return; 65 | } 66 | 67 | log.debug("Jobs rpc netty client proxy, connect to server success at host:{}, port:{}", host, port); 68 | } 69 | 70 | 71 | @Override 72 | public boolean isValidate() { 73 | if (this.channel != null) { 74 | return this.channel.isActive(); 75 | } 76 | return false; 77 | } 78 | 79 | @Override 80 | public void close() { 81 | if (this.channel != null && this.channel.isActive()) { 82 | this.channel.close(); // if this.channel.isOpen() 83 | } 84 | if (this.group != null && !this.group.isShutdown()) { 85 | this.group.shutdownGracefully(); 86 | } 87 | log.debug("Jobs rpc netty client close."); 88 | } 89 | 90 | 91 | @Override 92 | public void send(JobsRpcRequest xxlRpcRequest) throws Exception { 93 | this.channel.writeAndFlush(xxlRpcRequest).sync(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/codec/NettyDecoder.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.codec; 2 | 3 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageDecoder; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * decoder 12 | * 13 | * @author xuxueli 2015-10-29 19:02:36 14 | */ 15 | public class NettyDecoder extends ByteToMessageDecoder { 16 | private Class genericClass; 17 | private IJobsRpcSerializer serializer; 18 | 19 | public NettyDecoder(Class genericClass, final IJobsRpcSerializer serializer) { 20 | this.genericClass = genericClass; 21 | this.serializer = serializer; 22 | } 23 | 24 | @Override 25 | public final void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 26 | if (in.readableBytes() < 4) { 27 | return; 28 | } 29 | in.markReaderIndex(); 30 | int dataLength = in.readInt(); 31 | if (dataLength < 0) { 32 | ctx.close(); 33 | } 34 | if (in.readableBytes() < dataLength) { 35 | in.resetReaderIndex(); 36 | return; // fix 1024k buffer splice limix 37 | } 38 | byte[] data = new byte[dataLength]; 39 | in.readBytes(data); 40 | 41 | Object obj = serializer.deserialize(data, genericClass); 42 | out.add(obj); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/codec/NettyEncoder.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.codec; 2 | 3 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | 8 | /** 9 | * encoder 10 | * 11 | * @author xuxueli 2015-10-29 19:43:00 12 | */ 13 | public class NettyEncoder extends MessageToByteEncoder { 14 | private Class genericClass; 15 | private IJobsRpcSerializer serializer; 16 | 17 | public NettyEncoder(Class genericClass, final IJobsRpcSerializer serializer) { 18 | this.genericClass = genericClass; 19 | this.serializer = serializer; 20 | } 21 | 22 | @Override 23 | public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { 24 | if (genericClass.isInstance(in)) { 25 | byte[] data = serializer.serialize(in); 26 | out.writeInt(data.length); 27 | out.writeBytes(data); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/server/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.server; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.Server; 4 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.codec.NettyDecoder; 5 | import com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.codec.NettyEncoder; 6 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 7 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 8 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 9 | import com.baomidou.jobs.rpc.util.ThreadPoolUtil; 10 | import io.netty.bootstrap.ServerBootstrap; 11 | import io.netty.channel.ChannelFuture; 12 | import io.netty.channel.ChannelInitializer; 13 | import io.netty.channel.ChannelOption; 14 | import io.netty.channel.EventLoopGroup; 15 | import io.netty.channel.nio.NioEventLoopGroup; 16 | import io.netty.channel.socket.SocketChannel; 17 | import io.netty.channel.socket.nio.NioServerSocketChannel; 18 | import io.netty.handler.timeout.IdleStateHandler; 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | import java.util.concurrent.ThreadPoolExecutor; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | * netty rpc server 26 | * 27 | * @author xuxueli 2015-10-29 18:17:14 28 | */ 29 | @Slf4j 30 | public class NettyServer extends Server { 31 | private Thread thread; 32 | 33 | @Override 34 | public void start(final JobsRpcProviderFactory jobsRpcProviderFactory) throws Exception { 35 | thread = new Thread(() -> { 36 | 37 | // param 38 | final ThreadPoolExecutor serverHandlerPool = ThreadPoolUtil.makeServerThreadPool(NettyServer.class.getSimpleName()); 39 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 40 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 41 | 42 | try { 43 | // start server 44 | ServerBootstrap bootstrap = new ServerBootstrap(); 45 | bootstrap.group(bossGroup, workerGroup) 46 | .channel(NioServerSocketChannel.class) 47 | .childHandler(new ChannelInitializer() { 48 | @Override 49 | public void initChannel(SocketChannel channel) throws Exception { 50 | channel.pipeline() 51 | .addLast(new IdleStateHandler(0, 0, 10, TimeUnit.MINUTES)) 52 | .addLast(new NettyDecoder(JobsRpcRequest.class, jobsRpcProviderFactory.getSerializer())) 53 | .addLast(new NettyEncoder(JobsRpcResponse.class, jobsRpcProviderFactory.getSerializer())) 54 | .addLast(new NettyServerHandler(jobsRpcProviderFactory, serverHandlerPool)); 55 | } 56 | }) 57 | .childOption(ChannelOption.TCP_NODELAY, true) 58 | .childOption(ChannelOption.SO_KEEPALIVE, true); 59 | 60 | // bind 61 | ChannelFuture future = bootstrap.bind(jobsRpcProviderFactory.getPort()).sync(); 62 | 63 | log.info("Jobs rpc remoting server start success, nettype = {}, port = {}", NettyServer.class.getName(), jobsRpcProviderFactory.getPort()); 64 | onStarted(); 65 | 66 | // wait util stop 67 | future.channel().closeFuture().sync(); 68 | 69 | } catch (Exception e) { 70 | if (e instanceof InterruptedException) { 71 | log.info("Jobs rpc remoting server stop."); 72 | } else { 73 | log.error("Jobs rpc remoting server error.", e); 74 | } 75 | } finally { 76 | // stop 77 | try { 78 | serverHandlerPool.shutdown(); 79 | } catch (Exception e) { 80 | log.error(e.getMessage(), e); 81 | } 82 | try { 83 | workerGroup.shutdownGracefully(); 84 | bossGroup.shutdownGracefully(); 85 | } catch (Exception e) { 86 | log.error(e.getMessage(), e); 87 | } 88 | } 89 | }); 90 | thread.setDaemon(true); 91 | thread.start(); 92 | 93 | } 94 | 95 | @Override 96 | public void stop() throws Exception { 97 | 98 | // destroy server thread 99 | if (thread != null && thread.isAlive()) { 100 | thread.interrupt(); 101 | } 102 | 103 | // on stop 104 | onStopped(); 105 | log.info("Jobs rpc remoting server destroy success."); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/netty/socket/server/NettyServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.netty.socket.server; 2 | 3 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 4 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 5 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 6 | import com.baomidou.jobs.service.JobsHelper; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | 14 | /** 15 | * netty server handler 16 | * 17 | * @author xuxueli 2015-10-29 20:07:37 18 | */ 19 | @Slf4j 20 | public class NettyServerHandler extends SimpleChannelInboundHandler { 21 | private JobsRpcProviderFactory xxlRpcProviderFactory; 22 | private ThreadPoolExecutor serverHandlerPool; 23 | 24 | public NettyServerHandler(final JobsRpcProviderFactory xxlRpcProviderFactory, final ThreadPoolExecutor serverHandlerPool) { 25 | this.xxlRpcProviderFactory = xxlRpcProviderFactory; 26 | this.serverHandlerPool = serverHandlerPool; 27 | } 28 | 29 | @Override 30 | public void channelRead0(final ChannelHandlerContext ctx, final JobsRpcRequest xxlRpcRequest) throws Exception { 31 | try { 32 | // do invoke 33 | serverHandlerPool.execute(() -> { 34 | // invoke + response 35 | JobsRpcResponse xxlRpcResponse = xxlRpcProviderFactory.invokeService(xxlRpcRequest); 36 | 37 | ctx.writeAndFlush(xxlRpcResponse); 38 | }); 39 | } catch (Exception e) { 40 | // catch error 41 | JobsRpcResponse jobsRpcResponse = new JobsRpcResponse(); 42 | jobsRpcResponse.setRequestId(xxlRpcRequest.getRequestId()); 43 | jobsRpcResponse.setErrorMsg(JobsHelper.getErrorInfo(e)); 44 | ctx.writeAndFlush(jobsRpcResponse); 45 | } 46 | } 47 | 48 | @Override 49 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 50 | log.error("Jobs rpc provider netty server caught exception", cause); 51 | ctx.close(); 52 | } 53 | 54 | @Override 55 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 56 | if (evt instanceof IdleStateEvent) { 57 | ctx.channel().close(); 58 | log.debug("Jobs rpc provider netty server close an idle channel."); 59 | } else { 60 | super.userEventTriggered(ctx, evt); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/impl/servlet/server/ServletServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.impl.servlet.server; 2 | 3 | import com.baomidou.jobs.exception.JobsRpcException; 4 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcRequest; 5 | import com.baomidou.jobs.rpc.remoting.net.params.JobsRpcResponse; 6 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 7 | import com.baomidou.jobs.service.JobsHelper; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.OutputStream; 16 | 17 | /** 18 | * servlet 19 | * 20 | * @author xuxueli 2015-11-24 22:25:15 21 | */ 22 | @Slf4j 23 | public class ServletServerHandler { 24 | private JobsRpcProviderFactory jobsRpcProviderFactory; 25 | 26 | public ServletServerHandler(JobsRpcProviderFactory jobsRpcProviderFactory) { 27 | this.jobsRpcProviderFactory = jobsRpcProviderFactory; 28 | } 29 | 30 | /** 31 | * handle servlet request 32 | */ 33 | public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 34 | 35 | if ("/services".equals(target)) { 36 | // services mapping 37 | 38 | StringBuffer stringBuffer = new StringBuffer(""); 39 | for (String serviceKey : jobsRpcProviderFactory.getServiceData().keySet()) { 40 | stringBuffer.append("
  • ").append(serviceKey).append(": ").append(jobsRpcProviderFactory.getServiceData().get(serviceKey)).append("
  • "); 41 | } 42 | stringBuffer.append("
    "); 43 | 44 | writeResponse(response, stringBuffer.toString().getBytes()); 45 | return; 46 | } else { // default remoting mapping 47 | 48 | // request parse 49 | JobsRpcRequest jobsRpcRequest; 50 | try { 51 | 52 | jobsRpcRequest = parseRequest(request); 53 | } catch (Exception e) { 54 | writeResponse(response, JobsHelper.getErrorInfo(e).getBytes()); 55 | return; 56 | } 57 | 58 | // invoke 59 | JobsRpcResponse xxlRpcResponse = jobsRpcProviderFactory.invokeService(jobsRpcRequest); 60 | 61 | // response-serialize + response-write 62 | byte[] responseBytes = jobsRpcProviderFactory.getSerializer().serialize(xxlRpcResponse); 63 | writeResponse(response, responseBytes); 64 | } 65 | 66 | } 67 | 68 | /** 69 | * write response 70 | */ 71 | private void writeResponse(HttpServletResponse response, byte[] responseBytes) throws IOException { 72 | response.setContentType("text/html;charset=UTF-8"); 73 | response.setStatus(HttpServletResponse.SC_OK); 74 | OutputStream out = response.getOutputStream(); 75 | out.write(responseBytes); 76 | out.flush(); 77 | } 78 | 79 | /** 80 | * parse request 81 | */ 82 | private JobsRpcRequest parseRequest(HttpServletRequest request) throws Exception { 83 | // deserialize request 84 | byte[] requestBytes = readBytes(request); 85 | if (requestBytes == null || requestBytes.length == 0) { 86 | throw new JobsRpcException("Jobs rpc request data is empty."); 87 | } 88 | return (JobsRpcRequest) jobsRpcProviderFactory.getSerializer() 89 | .deserialize(requestBytes, JobsRpcRequest.class); 90 | } 91 | 92 | /** 93 | * read bytes from http request 94 | */ 95 | public static final byte[] readBytes(HttpServletRequest request) throws IOException { 96 | request.setCharacterEncoding("UTF-8"); 97 | int contentLen = request.getContentLength(); 98 | InputStream is = request.getInputStream(); 99 | if (contentLen > 0) { 100 | int readLen = 0; 101 | int readLengthThisTime; 102 | byte[] message = new byte[contentLen]; 103 | try { 104 | while (readLen != contentLen) { 105 | readLengthThisTime = is.read(message, readLen, contentLen - readLen); 106 | if (readLengthThisTime == -1) { 107 | break; 108 | } 109 | readLen += readLengthThisTime; 110 | } 111 | return message; 112 | } catch (IOException e) { 113 | log.error(e.getMessage(), e); 114 | } 115 | } 116 | return new byte[]{}; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/params/IJobsRpcCallback.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.params; 2 | 3 | import com.baomidou.jobs.exception.JobsRpcException; 4 | 5 | /** 6 | * Job RPC 回调接口 7 | * 8 | * @author jobob 9 | * @since 2019-06-08 10 | */ 11 | public interface IJobsRpcCallback { 12 | 13 | /** 14 | * 执行回调逻辑 15 | */ 16 | void execute() throws JobsRpcException; 17 | } 18 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/params/JobsRpcFutureResponse.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.params; 2 | 3 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 4 | import com.baomidou.jobs.rpc.remoting.invoker.call.JobsRpcInvokeCallback; 5 | import com.baomidou.jobs.exception.JobsRpcException; 6 | 7 | import java.util.concurrent.*; 8 | 9 | /** 10 | * call back future 11 | * 12 | * @author xuxueli 2015-11-5 14:26:37 13 | */ 14 | public class JobsRpcFutureResponse implements Future { 15 | 16 | private JobsRpcInvokerFactory invokerFactory; 17 | 18 | /** 19 | * net data 20 | */ 21 | private JobsRpcRequest request; 22 | private JobsRpcResponse response; 23 | 24 | /** 25 | * future lock 26 | */ 27 | private boolean done = false; 28 | private Object lock = new Object(); 29 | 30 | /** 31 | * callback, can be null 32 | */ 33 | private JobsRpcInvokeCallback invokeCallback; 34 | 35 | 36 | public JobsRpcFutureResponse(final JobsRpcInvokerFactory invokerFactory, JobsRpcRequest request, JobsRpcInvokeCallback invokeCallback) { 37 | this.invokerFactory = invokerFactory; 38 | this.request = request; 39 | this.invokeCallback = invokeCallback; 40 | 41 | // set-InvokerFuture 42 | setInvokerFuture(); 43 | } 44 | 45 | 46 | // ---------------------- response pool ---------------------- 47 | 48 | public void setInvokerFuture() { 49 | this.invokerFactory.setInvokerFuture(request.getRequestId(), this); 50 | } 51 | 52 | public void removeInvokerFuture() { 53 | this.invokerFactory.removeInvokerFuture(request.getRequestId()); 54 | } 55 | 56 | 57 | // ---------------------- get ---------------------- 58 | 59 | public JobsRpcRequest getRequest() { 60 | return request; 61 | } 62 | 63 | public JobsRpcInvokeCallback getInvokeCallback() { 64 | return invokeCallback; 65 | } 66 | 67 | 68 | // ---------------------- for invoke back ---------------------- 69 | 70 | public void setResponse(JobsRpcResponse response) { 71 | this.response = response; 72 | synchronized (lock) { 73 | done = true; 74 | lock.notifyAll(); 75 | } 76 | } 77 | 78 | 79 | // ---------------------- for invoke ---------------------- 80 | 81 | @Override 82 | public boolean cancel(boolean mayInterruptIfRunning) { 83 | // TODO 84 | return false; 85 | } 86 | 87 | @Override 88 | public boolean isCancelled() { 89 | // TODO 90 | return false; 91 | } 92 | 93 | @Override 94 | public boolean isDone() { 95 | return done; 96 | } 97 | 98 | @Override 99 | public JobsRpcResponse get() throws InterruptedException, ExecutionException { 100 | try { 101 | return get(-1, TimeUnit.MILLISECONDS); 102 | } catch (TimeoutException e) { 103 | throw new JobsRpcException(e); 104 | } 105 | } 106 | 107 | @Override 108 | public JobsRpcResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 109 | if (!done) { 110 | synchronized (lock) { 111 | try { 112 | if (timeout < 0) { 113 | lock.wait(); 114 | } else { 115 | long timeoutMillis = (TimeUnit.MILLISECONDS == unit) ? timeout : TimeUnit.MILLISECONDS.convert(timeout, unit); 116 | lock.wait(timeoutMillis); 117 | } 118 | } catch (InterruptedException e) { 119 | throw e; 120 | } 121 | } 122 | } 123 | 124 | if (!done) { 125 | throw new JobsRpcException("Jobs rpc, request timeout at:" + System.currentTimeMillis() + ", request:" + request.toString()); 126 | } 127 | return response; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/params/JobsRpcRequest.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.params; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * request 10 | * 11 | * @author xuxueli 2015-10-29 19:39:12 12 | */ 13 | @Data 14 | @ToString 15 | public class JobsRpcRequest implements Serializable{ 16 | private String requestId; 17 | private long createMillisTime; 18 | private String accessToken; 19 | private String className; 20 | private String methodName; 21 | private Class[] parameterTypes; 22 | private Object[] parameters; 23 | private String version; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/remoting/net/params/JobsRpcResponse.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.remoting.net.params; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * response 10 | * 11 | * @author xuxueli 2015-10-29 19:39:54 12 | */ 13 | @Data 14 | @ToString 15 | public class JobsRpcResponse implements Serializable{ 16 | private String requestId; 17 | private String errorMsg; 18 | private Object result; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/serialize/IJobsRpcSerializer.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.serialize; 2 | 3 | /** 4 | * Rpc 序列化接口 5 | * 6 | * @author jobob 7 | * @since 2019-11-01 8 | */ 9 | public interface IJobsRpcSerializer { 10 | 11 | /** 12 | * 序列化对象 13 | * 14 | * @param obj 对象 15 | * @param 返回序列化字节数组 16 | */ 17 | byte[] serialize(T obj); 18 | 19 | /** 20 | * 反序列化字节数组为类对象 21 | * 22 | * @param bytes 字节数组 23 | * @param clazz 待反序列化类 24 | * @param 反序列化对象 25 | */ 26 | Object deserialize(byte[] bytes, Class clazz); 27 | } 28 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/serialize/impl/HessianSerializer.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.serialize.impl; 2 | 3 | import com.baomidou.jobs.exception.JobsRpcException; 4 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 5 | import com.caucho.hessian.io.Hessian2Input; 6 | import com.caucho.hessian.io.Hessian2Output; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | 12 | /** 13 | * hessian serializer 14 | * 15 | * @author jobob 16 | * @since 2019-11-01 17 | */ 18 | public class HessianSerializer implements IJobsRpcSerializer { 19 | 20 | @Override 21 | public byte[] serialize(T obj) { 22 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 23 | Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream); 24 | try { 25 | hessian2Output.writeObject(obj); 26 | // 必须先关闭,才能转成二进制数组 27 | hessian2Output.close(); 28 | return byteArrayOutputStream.toByteArray(); 29 | } catch (IOException e) { 30 | throw new JobsRpcException(e); 31 | } finally { 32 | try { 33 | byteArrayOutputStream.close(); 34 | } catch (IOException e) { 35 | throw new JobsRpcException(e); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | public T deserialize(byte[] data, Class clazz) { 42 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data); 43 | Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream); 44 | try { 45 | return (T) hessian2Input.readObject(clazz); 46 | } catch (IOException e) { 47 | throw new JobsRpcException(e); 48 | } finally { 49 | try { 50 | hessian2Input.close(); 51 | byteArrayInputStream.close(); 52 | } catch (IOException e) { 53 | throw new JobsRpcException(e); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/util/ClassUtil.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.util; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * @author xuxueli 2019-02-19 7 | */ 8 | public class ClassUtil { 9 | 10 | private static final HashMap> primClasses = new HashMap<>(); 11 | 12 | static { 13 | primClasses.put("boolean", boolean.class); 14 | primClasses.put("byte", byte.class); 15 | primClasses.put("char", char.class); 16 | primClasses.put("short", short.class); 17 | primClasses.put("int", int.class); 18 | primClasses.put("long", long.class); 19 | primClasses.put("float", float.class); 20 | primClasses.put("double", double.class); 21 | primClasses.put("void", void.class); 22 | } 23 | 24 | public static Class resolveClass(String className) throws ClassNotFoundException { 25 | try { 26 | return Class.forName(className); 27 | } catch (ClassNotFoundException ex) { 28 | Class cl = primClasses.get(className); 29 | if (cl != null) { 30 | return cl; 31 | } else { 32 | throw ex; 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/util/NetUtil.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.util; 2 | 3 | import com.baomidou.jobs.exception.JobsRpcException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.net.ServerSocket; 9 | 10 | /** 11 | * net util 12 | * 13 | * @author xuxueli 2017-11-29 17:00:25 14 | */ 15 | public class NetUtil { 16 | private static Logger logger = LoggerFactory.getLogger(NetUtil.class); 17 | 18 | /** 19 | * find avaliable port 20 | * 21 | * @param defaultPort 22 | * @return 23 | */ 24 | public static int findAvailablePort(int defaultPort) { 25 | int portTmp = defaultPort; 26 | while (portTmp < 65535) { 27 | if (!isPortUsed(portTmp)) { 28 | return portTmp; 29 | } else { 30 | portTmp++; 31 | } 32 | } 33 | portTmp = defaultPort--; 34 | while (portTmp > 0) { 35 | if (!isPortUsed(portTmp)) { 36 | return portTmp; 37 | } else { 38 | portTmp--; 39 | } 40 | } 41 | throw new JobsRpcException("no available port."); 42 | } 43 | 44 | /** 45 | * check port used 46 | * 47 | * @param port 48 | * @return 49 | */ 50 | public static boolean isPortUsed(int port) { 51 | boolean used = false; 52 | ServerSocket serverSocket = null; 53 | try { 54 | serverSocket = new ServerSocket(port); 55 | used = false; 56 | } catch (IOException e) { 57 | logger.info("Jobs rpc, port[{}] is in use.", port); 58 | used = true; 59 | } finally { 60 | if (serverSocket != null) { 61 | try { 62 | serverSocket.close(); 63 | } catch (IOException e) { 64 | logger.info(""); 65 | } 66 | } 67 | } 68 | return used; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/rpc/util/ThreadPoolUtil.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.rpc.util; 2 | 3 | import com.baomidou.jobs.exception.JobsRpcException; 4 | 5 | import java.util.concurrent.*; 6 | 7 | /** 8 | * @author xuxueli 2019-02-18 9 | */ 10 | public class ThreadPoolUtil { 11 | 12 | /** 13 | * make server thread pool 14 | * 15 | * @param serverType 16 | * @return 17 | */ 18 | public static ThreadPoolExecutor makeServerThreadPool(final String serverType) { 19 | ThreadPoolExecutor serverHandlerPool = new ThreadPoolExecutor( 20 | 60, 21 | 300, 22 | 60L, 23 | TimeUnit.SECONDS, 24 | new LinkedBlockingQueue<>(1000), 25 | r -> new Thread(r, "jobs-rpc, " + serverType + "-serverHandlerPool-" + r.hashCode()), 26 | (r, executor) -> { 27 | throw new JobsRpcException("jobs-rpc " + serverType + " Thread pool is EXHAUSTED!"); 28 | }); // default maxThreads 300, minThreads 60 29 | 30 | return serverHandlerPool; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/service/IJobsService.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.service; 2 | 3 | import com.baomidou.jobs.JobsConstant; 4 | import com.baomidou.jobs.model.JobsInfo; 5 | import com.baomidou.jobs.model.JobsLog; 6 | import com.baomidou.jobs.model.param.RegistryParam; 7 | import com.baomidou.jobs.trigger.JobsTrigger; 8 | import com.baomidou.jobs.trigger.TriggerTypeEnum; 9 | 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | /** 14 | * Jobs Admin 接口 15 | * 16 | * @author jobob 17 | * @since 2019-07-18 18 | */ 19 | public interface IJobsService { 20 | 21 | /** 22 | * 节点注册 23 | * 24 | * @param registryParam 注册参数 25 | * @return 26 | */ 27 | boolean registry(RegistryParam registryParam); 28 | 29 | /** 30 | * 手动触发任务ID执行 31 | * 32 | * @param jobId 任务ID 33 | * @param param 执行参数 34 | * @return 35 | */ 36 | default boolean execute(Long jobId, String param) { 37 | return JobsTrigger.trigger(jobId, TriggerTypeEnum.MANUAL, -1, param); 38 | } 39 | 40 | /** 41 | * 待调度任务列表 42 | * 43 | * @param nextTime 下次执行时间 44 | * @return 45 | */ 46 | List getJobsInfoList(long nextTime); 47 | 48 | /** 49 | * 根据 任务ID 获取任务信息对象 50 | * 51 | * @param id 任务 ID 52 | * @return 53 | */ 54 | JobsInfo getJobsInfoById(Long id); 55 | 56 | /** 57 | * 根据 任务ID 更新任务信息 58 | * 59 | * @param jobsInfo 任务信息对象 60 | * @return 61 | */ 62 | boolean updateJobsInfoById(JobsInfo jobsInfo); 63 | 64 | /** 65 | * 使用线程本地变量记录锁的持有者 66 | */ 67 | ThreadLocal ownerThreadLocal = new ThreadLocal<>(); 68 | 69 | /** 70 | * 尝试获取锁 71 | * 72 | * @param lockKey 锁 KEY 73 | * @return 返回true代表已经获得锁,false代表获取锁失败(锁已经被别的进程占有) 74 | */ 75 | default boolean tryLock(String lockKey) { 76 | String owner = ownerThreadLocal.get(); 77 | if (null != owner && !owner.equals(JobsConstant.OPERATION_TRY_LOCK)) { 78 | // already hold a lock 79 | return true; 80 | } 81 | ownerThreadLocal.set(JobsConstant.OPERATION_TRY_LOCK); 82 | owner = UUID.randomUUID().toString(); 83 | if (tryLock(lockKey, owner)) { 84 | ownerThreadLocal.set(owner); 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | 91 | /** 92 | * 释放锁 93 | * 94 | * @param lockKey 锁 KEY 95 | * @param force 强制解锁 96 | */ 97 | default void unlock(String lockKey, boolean force) { 98 | if (force) { 99 | unlock(lockKey, null); 100 | } else { 101 | String owner = ownerThreadLocal.get(); 102 | if (null == owner) { 103 | throw new IllegalMonitorStateException("should not call unlock() without tryLock(()"); 104 | } 105 | ownerThreadLocal.remove(); 106 | if (!JobsConstant.OPERATION_TRY_LOCK.equals(owner)) { 107 | unlock(lockKey, owner); 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * 插入一条记录,标志着占有锁 114 | * 115 | * @param name 锁的名称 116 | * @param owner 锁的持有者 117 | * @return 返回影响的记录行数 118 | */ 119 | boolean tryLock(String name, String owner); 120 | 121 | /** 122 | * 释放锁 123 | * 124 | * @param name 锁的名称 125 | * @param owner 锁的持有者,不存在则根据 name 删除 126 | * @return 返回影响的记录行数 127 | */ 128 | boolean unlock(String name, String owner); 129 | 130 | 131 | /** 132 | * 清理超时节点 133 | * 134 | * @return 影响行数 135 | */ 136 | default int cleanTimeoutApp() { 137 | return removeTimeOutApp(JobsConstant.CLEAN_TIMEOUT); 138 | } 139 | 140 | /** 141 | * 移除超时节点 142 | * 143 | * @param timeout 超时时长 144 | * @return 145 | */ 146 | int removeTimeOutApp(int timeout); 147 | 148 | /** 149 | * 移除节点 150 | * 151 | * @param registryParam 注册参数 152 | * @return 153 | */ 154 | boolean removeApp(RegistryParam registryParam); 155 | 156 | 157 | /** 158 | * 查询注册地址列表 159 | * 160 | * @param app 客户端 APP 名称 161 | * @return 162 | */ 163 | List getAppAddressList(String app); 164 | 165 | 166 | /** 167 | * 保存或者根据 ID 更新日志 168 | * 169 | * @param jobsLog 日志对象 170 | * @return 171 | */ 172 | boolean saveOrUpdateLogById(JobsLog jobsLog); 173 | } 174 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/service/JobsHeartbeat.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.service; 2 | 3 | import com.baomidou.jobs.JobsConstant; 4 | import com.baomidou.jobs.model.JobsInfo; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.dao.DuplicateKeyException; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * jobs 心跳 12 | * 13 | * @author jobob 14 | * @since 2019-07-15 15 | */ 16 | @Slf4j 17 | public class JobsHeartbeat implements Runnable { 18 | /** 19 | * 上锁时长 20 | */ 21 | private long wait = 0; 22 | /** 23 | * 心跳时长 24 | */ 25 | private long beat = 0; 26 | 27 | @Override 28 | public void run() { 29 | log.debug("Jobs, JobsHeartbeat begin"); 30 | IJobsService jobsService = JobsHelper.getJobsService(); 31 | try { 32 | // 尝试获取锁 33 | if (jobsService.tryLock(JobsConstant.DEFAULT_LOCK_KEY)) { 34 | wait = 0; 35 | long nowTime = System.currentTimeMillis(); 36 | // 1、预读10s内调度任务 37 | List scheduleList = jobsService.getJobsInfoList(nowTime + 10000); 38 | if (scheduleList != null && scheduleList.size() > 0) { 39 | // 2、推送时间轮 40 | for (JobsInfo jobsInfo : scheduleList) { 41 | long waitSecond; 42 | if (jobsInfo.getNextTime() < nowTime - 10000) { 43 | // 过期超10s:本地忽略,当前时间开始计算下次触发时间 44 | waitSecond = -1; 45 | } else if (jobsInfo.getNextTime() < nowTime) { 46 | // 过期10s内:立即触发,计算延迟触发时长 47 | waitSecond = nowTime - jobsInfo.getLastTime(); 48 | } else { 49 | // 未过期:等待下次循环 50 | continue; 51 | } 52 | JobsInfo tempJobsInfo = new JobsInfo(); 53 | tempJobsInfo.setId(jobsInfo.getId()); 54 | tempJobsInfo.setLastTime(jobsInfo.getNextTime()); 55 | tempJobsInfo.setNextTime(JobsHelper.cronNextTime(jobsInfo.getCron())); 56 | if (waitSecond >= 0) { 57 | // 推送任务消息 58 | JobsHelper.getJobsDisruptorTemplate().publish(jobsInfo, waitSecond); 59 | } 60 | // 更新任务状态 61 | jobsService.updateJobsInfoById(tempJobsInfo); 62 | } 63 | 64 | } 65 | } 66 | } catch (Exception e) { 67 | if (e instanceof DuplicateKeyException) { 68 | // 上锁时长累计 69 | ++wait; 70 | if (log.isDebugEnabled()) { 71 | log.debug("Jobs, JobsHeartbeat locking"); 72 | } 73 | } else { 74 | log.error("Jobs, JobsHeartbeat error:{}", e); 75 | } 76 | } finally { 77 | // 释放锁,上锁时长超过 90 秒强制解锁 78 | jobsService.unlock(JobsConstant.DEFAULT_LOCK_KEY, wait > 90); 79 | // 清理异常注册节点 80 | ++beat; 81 | if (beat > JobsConstant.BEAT_TIMEOUT) { 82 | jobsService.cleanTimeoutApp(); 83 | beat = 0; 84 | } 85 | } 86 | log.debug("Jobs, JobsHeartbeat end"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/service/JobsHelper.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.service; 2 | 3 | import com.baomidou.jobs.disruptor.JobsDisruptorTemplate; 4 | import com.baomidou.jobs.handler.IJobsResultHandler; 5 | import com.baomidou.jobs.router.IJobsExecutorRouter; 6 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 7 | import com.baomidou.jobs.starter.JobsProperties; 8 | import com.cronutils.model.time.ExecutionTime; 9 | import com.cronutils.parser.CronParser; 10 | import org.springframework.beans.factory.InitializingBean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | import javax.annotation.Resource; 14 | import java.io.PrintWriter; 15 | import java.io.StringWriter; 16 | import java.time.ZonedDateTime; 17 | 18 | /** 19 | * Spring Boot 相关辅助类 20 | * 21 | * @author jobob 22 | * @since 2019-07-18 23 | */ 24 | @Configuration 25 | public class JobsHelper implements InitializingBean { 26 | 27 | private static JobsHelper JOB_HELPER = null; 28 | 29 | @Override 30 | public void afterPropertiesSet() throws Exception { 31 | JOB_HELPER = this; 32 | } 33 | 34 | @Resource 35 | private CronParser _cronParser; 36 | @Resource 37 | private IJobsService _jobsService; 38 | @Resource 39 | private IJobsExecutorRouter _jobsExecutorRouter; 40 | @Resource 41 | private IJobsResultHandler _jobsResultHandler; 42 | @Resource 43 | private IJobsRpcSerializer _jobsRpcSerializer; 44 | @Resource 45 | private JobsProperties _jobsProperties; 46 | @Resource 47 | private JobsDisruptorTemplate _jobsDisruptorTemplate; 48 | 49 | public static CronParser getCronParser() { 50 | return JOB_HELPER._cronParser; 51 | } 52 | 53 | /** 54 | * CRON 表达式校验 55 | * 56 | * @param expression CRON 表达式 57 | * @return 58 | */ 59 | public static boolean cronValidate(final String expression) { 60 | try { 61 | getCronParser().parse(expression).validate(); 62 | } catch (Throwable t) { 63 | // 非法 64 | return false; 65 | } 66 | // 合法 67 | return true; 68 | } 69 | 70 | /** 71 | * CRON 表达式下次执行时间 72 | * 73 | * @param expression CRON 表达式 74 | * @return 75 | */ 76 | public static long cronNextTime(final String expression) { 77 | ExecutionTime executionTime = ExecutionTime.forCron(getCronParser().parse(expression)); 78 | return executionTime.nextExecution(ZonedDateTime.now()).get().toInstant().toEpochMilli(); 79 | } 80 | 81 | public static IJobsService getJobsService() { 82 | return JOB_HELPER._jobsService; 83 | } 84 | 85 | public static IJobsExecutorRouter getJobsExecutorRouter() { 86 | return JOB_HELPER._jobsExecutorRouter; 87 | } 88 | 89 | public static IJobsResultHandler getJobsResultHandler() { 90 | return JOB_HELPER._jobsResultHandler; 91 | } 92 | 93 | public static IJobsRpcSerializer getJobsRpcSerializer() { 94 | return JOB_HELPER._jobsRpcSerializer; 95 | } 96 | 97 | public static JobsProperties getJobsProperties() { 98 | return JOB_HELPER._jobsProperties; 99 | } 100 | 101 | public static JobsDisruptorTemplate getJobsDisruptorTemplate() { 102 | return JOB_HELPER._jobsDisruptorTemplate; 103 | } 104 | 105 | public static String getErrorInfo(Throwable t) { 106 | try { 107 | StringWriter sw = new StringWriter(); 108 | PrintWriter pw = new PrintWriter(sw); 109 | t.printStackTrace(pw); 110 | String str = sw.toString(); 111 | sw.close(); 112 | pw.close(); 113 | return str; 114 | } catch (Exception ex) { 115 | return "获得Exception信息的异常"; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/starter/EnableJobs.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.starter; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * 启动 Jobs 14 | * 15 | * @author jobob 16 | * @since 2019-07-23 17 | */ 18 | @Configuration 19 | @Target(ElementType.TYPE) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Import({JobsAutoConfiguration.class}) 22 | @ConditionalOnProperty(prefix = JobsProperties.PREFIX, name="enabled", havingValue="true", matchIfMissing = true) 23 | public @interface EnableJobs { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/starter/EnableJobsAdmin.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.starter; 2 | 3 | import com.baomidou.jobs.service.JobsHelper; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * 启动 Jobs Admin 15 | * 16 | * @author jobob 17 | * @since 2019-06-08 18 | */ 19 | @Configuration 20 | @Target(ElementType.TYPE) 21 | @Retention(RetentionPolicy.RUNTIME) 22 | @Import({JobsAdminAutoConfiguration.class, JobsHelper.class, JobsScheduler.class}) 23 | @ConditionalOnProperty(prefix = JobsProperties.PREFIX, name="enabled", havingValue="true", matchIfMissing = true) 24 | public @interface EnableJobsAdmin { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/starter/JobsAdminAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.starter; 2 | 3 | import com.baomidou.jobs.disruptor.JobsDisruptorTemplate; 4 | import com.baomidou.jobs.disruptor.JobsEventHandler; 5 | import com.baomidou.jobs.disruptor.JobsInfoEvent; 6 | import com.baomidou.jobs.router.ExecutorConsistentHashRouter; 7 | import com.baomidou.jobs.router.IJobsExecutorRouter; 8 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 9 | import com.baomidou.jobs.rpc.serialize.impl.HessianSerializer; 10 | import com.cronutils.model.CronType; 11 | import com.cronutils.model.definition.CronDefinition; 12 | import com.cronutils.model.definition.CronDefinitionBuilder; 13 | import com.cronutils.parser.CronParser; 14 | import com.lmax.disruptor.RingBuffer; 15 | import com.lmax.disruptor.SleepingWaitStrategy; 16 | import com.lmax.disruptor.WaitStrategy; 17 | import com.lmax.disruptor.dsl.Disruptor; 18 | import com.lmax.disruptor.dsl.ProducerType; 19 | import com.lmax.disruptor.util.DaemonThreadFactory; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | 26 | import java.util.concurrent.ThreadFactory; 27 | 28 | /** 29 | * Jobs Admin 启动配置 30 | * 31 | * @author jobob 32 | * @since 2019-06-27 33 | */ 34 | @Configuration 35 | @EnableConfigurationProperties(JobsProperties.class) 36 | public class JobsAdminAutoConfiguration { 37 | 38 | @Bean 39 | @ConditionalOnMissingBean 40 | public IJobsRpcSerializer jobsRpcSerializer() { 41 | return new HessianSerializer(); 42 | } 43 | 44 | @Bean 45 | @ConditionalOnMissingBean 46 | public CronParser cronParser(JobsProperties jobsProperties) { 47 | CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor( 48 | CronType.valueOf(jobsProperties.getCronType())); 49 | return new CronParser(cronDefinition); 50 | } 51 | 52 | @Bean 53 | @ConditionalOnMissingBean 54 | public WaitStrategy waitStrategy() { 55 | return new SleepingWaitStrategy(); 56 | } 57 | 58 | @Bean 59 | @ConditionalOnMissingBean 60 | public ThreadFactory threadFactory() { 61 | return DaemonThreadFactory.INSTANCE; 62 | } 63 | 64 | @Bean 65 | @ConditionalOnMissingBean 66 | public JobsEventHandler jobsEventHandler() { 67 | return new JobsEventHandler(); 68 | } 69 | 70 | @Bean 71 | @ConditionalOnMissingBean 72 | public IJobsExecutorRouter jobsExecutorRouter() { 73 | return new ExecutorConsistentHashRouter(); 74 | } 75 | 76 | @Bean 77 | @ConditionalOnClass({Disruptor.class}) 78 | public Disruptor disruptor(WaitStrategy waitStrategy, ThreadFactory threadFactory, 79 | JobsEventHandler jobsEventHandler) { 80 | Disruptor disruptor = new Disruptor<>(() -> new JobsInfoEvent(), 256 * 1024, 81 | threadFactory, ProducerType.MULTI, waitStrategy); 82 | disruptor.handleEventsWith(jobsEventHandler); 83 | 84 | // 启动 85 | disruptor.start(); 86 | 87 | // WEB 容器关闭执行 88 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 89 | try { 90 | // OK 91 | disruptor.shutdown(); 92 | 93 | // wait up to 10 seconds for the ringbuffer to drain 94 | RingBuffer ringBuffer = disruptor.getRingBuffer(); 95 | for (int i = 0; i < 20; i++) { 96 | if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) { 97 | break; 98 | } 99 | try { 100 | // give ringbuffer some time to drain... 101 | Thread.sleep(500); 102 | } catch (InterruptedException e) { 103 | // ignored 104 | } 105 | } 106 | disruptor.shutdown(); 107 | } catch (Exception e) { 108 | // to do nothing 109 | } 110 | })); 111 | return disruptor; 112 | } 113 | 114 | @Bean 115 | public JobsDisruptorTemplate jobsDisruptorTemplate() { 116 | return new JobsDisruptorTemplate(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/starter/JobsAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.starter; 2 | 3 | import com.baomidou.jobs.executor.JobsSpringExecutor; 4 | import com.baomidou.jobs.rpc.serialize.IJobsRpcSerializer; 5 | import com.baomidou.jobs.rpc.serialize.impl.HessianSerializer; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * Job 启动配置 13 | * 14 | * @author jobob 15 | * @since 2019-06-08 16 | */ 17 | @Configuration 18 | @EnableConfigurationProperties(JobsProperties.class) 19 | public class JobsAutoConfiguration { 20 | 21 | @Bean 22 | @ConditionalOnMissingBean 23 | public IJobsRpcSerializer jobsRpcSerializer() { 24 | return new HessianSerializer(); 25 | } 26 | 27 | @Bean(initMethod = "start", destroyMethod = "destroy") 28 | public JobsSpringExecutor jobsSpringExecutor(JobsProperties jobsProperties) { 29 | JobsSpringExecutor jobsSpringExecutor = new JobsSpringExecutor(); 30 | jobsSpringExecutor.setAccessToken(jobsProperties.getAdminAccessToken()); 31 | jobsSpringExecutor.setAdminAddress(jobsProperties.getAdminAddress()); 32 | jobsSpringExecutor.setApp(jobsProperties.getAppName()); 33 | jobsSpringExecutor.setIp(jobsProperties.getAppIp()); 34 | jobsSpringExecutor.setPort(jobsProperties.getAppPort()); 35 | return jobsSpringExecutor; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/starter/JobsProperties.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.starter; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | /** 7 | * Job 启动参数 8 | * 9 | * @author xxl jobob 10 | * @since 2019-06-08 11 | */ 12 | @Data 13 | @ConfigurationProperties(JobsProperties.PREFIX) 14 | public class JobsProperties { 15 | public static final String PREFIX = "jobs"; 16 | /** 17 | * cron 类型,默认 QUARTZ 【 支持 CRON4J, QUARTZ, UNIX, SPRING 】 18 | * com.cronutils.model.CronType 19 | */ 20 | private String cronType = "QUARTZ"; 21 | /** 22 | * admin 访问票据 23 | */ 24 | private String adminAccessToken; 25 | /** 26 | * Jobs admin address, such as "http://address" or "http://address01,http://address02" 27 | */ 28 | private String adminAddress; 29 | /** 30 | * APP 服务名 31 | */ 32 | private String appName; 33 | /** 34 | * APP IP 地址 35 | */ 36 | private String appIp; 37 | /** 38 | * APP 端口 39 | */ 40 | private int appPort; 41 | /** 42 | * APP 访问票据 43 | */ 44 | private String appAccessToken; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/starter/JobsScheduler.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.starter; 2 | 3 | import com.baomidou.jobs.executor.IJobsExecutor; 4 | import com.baomidou.jobs.rpc.remoting.invoker.JobsRpcInvokerFactory; 5 | import com.baomidou.jobs.rpc.remoting.invoker.call.CallType; 6 | import com.baomidou.jobs.rpc.remoting.invoker.reference.JobsRpcReferenceBean; 7 | import com.baomidou.jobs.rpc.remoting.invoker.route.LoadBalance; 8 | import com.baomidou.jobs.rpc.remoting.net.NetEnum; 9 | import com.baomidou.jobs.rpc.remoting.net.impl.servlet.server.ServletServerHandler; 10 | import com.baomidou.jobs.rpc.remoting.provider.JobsRpcProviderFactory; 11 | import com.baomidou.jobs.service.IJobsService; 12 | import com.baomidou.jobs.service.JobsHeartbeat; 13 | import com.baomidou.jobs.service.JobsHelper; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.factory.DisposableBean; 16 | import org.springframework.beans.factory.InitializingBean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | import javax.servlet.ServletException; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import java.io.IOException; 23 | import java.util.Map; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | import java.util.concurrent.ScheduledExecutorService; 26 | import java.util.concurrent.ScheduledThreadPoolExecutor; 27 | import java.util.concurrent.TimeUnit; 28 | 29 | /** 30 | * Jobs Scheduler 31 | * 32 | * @author xxl jobob 33 | * @since 2019-06-22 34 | */ 35 | @Slf4j 36 | @Configuration 37 | public class JobsScheduler implements InitializingBean, DisposableBean { 38 | 39 | private ScheduledExecutorService executor; 40 | 41 | @Override 42 | public void afterPropertiesSet() throws Exception { 43 | // init rpc 44 | initRpcProvider(); 45 | 46 | /** 47 | * jobs 执行心跳 48 | * 1 秒后第一次执行,之后每隔 1 秒执行一次 49 | */ 50 | executor = new ScheduledThreadPoolExecutor(5); 51 | executor.scheduleAtFixedRate(new JobsHeartbeat(), 1, 1, TimeUnit.SECONDS); 52 | 53 | // 启动清理异常注册 54 | JobsHelper.getJobsService().cleanTimeoutApp(); 55 | log.debug("init jobs admin success."); 56 | } 57 | 58 | @Override 59 | public void destroy() throws Exception { 60 | // stop-schedule 61 | if (null != executor) { 62 | executor.shutdown(); 63 | } 64 | 65 | // stop rpc 66 | stopRpcProvider(); 67 | } 68 | 69 | /** 70 | * ---------------------- admin rpc provider (no server version) ---------------------- 71 | */ 72 | private static ServletServerHandler servletServerHandler; 73 | 74 | private void initRpcProvider() { 75 | // init 76 | JobsRpcProviderFactory jobsRpcProviderFactory = new JobsRpcProviderFactory(); 77 | jobsRpcProviderFactory.initConfig( 78 | NetEnum.NETTY_HTTP, 79 | JobsHelper.getJobsRpcSerializer(), 80 | null, 81 | 0, 82 | JobsHelper.getJobsProperties().getAdminAccessToken(), 83 | null, 84 | null); 85 | 86 | // add services 87 | jobsRpcProviderFactory.addService(IJobsService.class.getName(), null, JobsHelper.getJobsService()); 88 | 89 | // servlet handler 90 | servletServerHandler = new ServletServerHandler(jobsRpcProviderFactory); 91 | } 92 | 93 | private void stopRpcProvider() throws Exception { 94 | JobsRpcInvokerFactory.getInstance().stop(); 95 | } 96 | 97 | public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 98 | servletServerHandler.handle(null, request, response); 99 | } 100 | 101 | 102 | /** 103 | * ---------------------- executor-client ---------------------- 104 | */ 105 | private static Map JOBS_EXECUTOR = new ConcurrentHashMap<>(); 106 | 107 | public static IJobsExecutor getJobsExecutor(String address) { 108 | // valid 109 | if (address == null || address.trim().length() == 0) { 110 | return null; 111 | } 112 | 113 | // load-cache 114 | address = address.trim(); 115 | IJobsExecutor jobsExecutor = JOBS_EXECUTOR.get(address); 116 | if (jobsExecutor != null) { 117 | return jobsExecutor; 118 | } 119 | 120 | // set-cache 121 | jobsExecutor = (IJobsExecutor) new JobsRpcReferenceBean( 122 | NetEnum.NETTY_HTTP, 123 | JobsHelper.getJobsRpcSerializer(), 124 | CallType.SYNC, 125 | LoadBalance.ROUND, 126 | IJobsExecutor.class, 127 | null, 128 | 5000, 129 | address, 130 | JobsHelper.getJobsProperties().getAppAccessToken(), 131 | null, 132 | null).getObject(); 133 | 134 | JOBS_EXECUTOR.put(address, jobsExecutor); 135 | return jobsExecutor; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/thread/ExecutorRegistryThread.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.thread; 2 | 3 | import com.baomidou.jobs.JobsConstant; 4 | import com.baomidou.jobs.executor.JobsAbstractExecutor; 5 | import com.baomidou.jobs.model.param.RegisterStatusEnum; 6 | import com.baomidou.jobs.model.param.RegistryParam; 7 | import com.baomidou.jobs.service.IJobsService; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * 执行器注册线程 14 | * 15 | * @author jobob 16 | * @since 2019-07-18 17 | */ 18 | @Slf4j 19 | public class ExecutorRegistryThread { 20 | private static ExecutorRegistryThread INSTANCE = new ExecutorRegistryThread(); 21 | 22 | public static ExecutorRegistryThread getInstance() { 23 | return INSTANCE; 24 | } 25 | 26 | private Thread registryThread; 27 | private volatile boolean toStop = false; 28 | 29 | public void start(final String appName, final String address) { 30 | 31 | // valid 32 | if (appName == null || appName.trim().length() == 0) { 33 | log.warn("Jobs executor registry config fail, appName is null."); 34 | return; 35 | } 36 | if (null == JobsAbstractExecutor.getJobsServiceList()) { 37 | log.warn("Jobs executor registry config fail, adminAddresses is null."); 38 | return; 39 | } 40 | 41 | registryThread = new Thread(() -> { 42 | 43 | // registry 44 | while (!toStop) { 45 | try { 46 | RegistryParam registryParam = new RegistryParam(appName, address); 47 | for (IJobsService jobsService : JobsAbstractExecutor.getJobsServiceList()) { 48 | try { 49 | if (jobsService.registry(registryParam)) { 50 | log.debug("Jobs registry success, registryParam:{}", registryParam); 51 | break; 52 | } else { 53 | log.info("Jobs registry fail, registryParam:{}", registryParam); 54 | } 55 | } catch (Exception e) { 56 | log.info("Jobs registry error, registryParam:{}", registryParam, e); 57 | } 58 | 59 | } 60 | } catch (Exception e) { 61 | if (!toStop) { 62 | log.error(e.getMessage(), e); 63 | } 64 | 65 | } 66 | 67 | try { 68 | if (!toStop) { 69 | TimeUnit.SECONDS.sleep(JobsConstant.BEAT_TIMEOUT); 70 | } 71 | } catch (InterruptedException e) { 72 | if (!toStop) { 73 | log.warn("Jobs executor registry thread interrupted, error msg:{}", e.getMessage()); 74 | } 75 | } 76 | } 77 | 78 | // registry remove 79 | try { 80 | RegistryParam registryParam = new RegistryParam(appName, address); 81 | for (IJobsService jobsService : JobsAbstractExecutor.getJobsServiceList()) { 82 | try { 83 | registryParam.setRegisterStatusEnum(RegisterStatusEnum.DISABLED); 84 | if (jobsService.removeApp(registryParam)) { 85 | log.info("Jobs registry-remove success, registryParam:{}", registryParam); 86 | break; 87 | } else { 88 | log.info("Jobs registry-remove fail, registryParam:{}", registryParam); 89 | } 90 | } catch (Exception e) { 91 | if (!toStop) { 92 | log.info("Jobs registry-remove error, registryParam:{}", registryParam, e); 93 | } 94 | 95 | } 96 | 97 | } 98 | } catch (Exception e) { 99 | if (!toStop) { 100 | log.error(e.getMessage(), e); 101 | } 102 | } 103 | log.info("Jobs executor registry thread destory."); 104 | 105 | }); 106 | registryThread.setDaemon(true); 107 | registryThread.setName("Jobs executor ExecutorRegistryThread"); 108 | registryThread.start(); 109 | } 110 | 111 | public void toStop() { 112 | toStop = true; 113 | // interrupt and wait 114 | registryThread.interrupt(); 115 | try { 116 | registryThread.join(); 117 | } catch (InterruptedException e) { 118 | log.error(e.getMessage(), e); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/toolkit/ConsistentHash.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.toolkit; 2 | 3 | import java.util.Collection; 4 | import java.util.SortedMap; 5 | import java.util.TreeMap; 6 | 7 | /** 8 | * 一次性 HASH 9 | * 10 | * @author jobob 11 | * @since 2019-07-20 12 | */ 13 | public class ConsistentHash { 14 | private int virtualNodeCount = 1; 15 | private SortedMap hashRing = new TreeMap<>(); 16 | 17 | /** 18 | * Default constructor. 19 | */ 20 | public ConsistentHash() { 21 | // to do nothing 22 | } 23 | 24 | /** 25 | * Constructor that can be set virtualNodeCount. 26 | * If you add new node, it will be created randomly any index as the number of virtualNodeCount. 27 | * 28 | * @param virtualNodeCount the number of virtual count per each node. 29 | */ 30 | public ConsistentHash(int virtualNodeCount) { 31 | this.virtualNodeCount = virtualNodeCount; 32 | } 33 | 34 | /** 35 | * Add new node. 36 | * 37 | * @param node T node object. 38 | */ 39 | public synchronized void add(T node) { 40 | for (int i = 0; i < virtualNodeCount; i++) { 41 | addNode(node, i); 42 | } 43 | } 44 | 45 | public synchronized void add(Collection nodes) { 46 | int i = 0; 47 | for (T node : nodes) { 48 | addNode(node, ++i); 49 | } 50 | } 51 | 52 | private void addNode(T node, int i) { 53 | hashRing.put(getHashKey(node, i), node); 54 | } 55 | 56 | /** 57 | * Remove node from hash. 58 | * 59 | * @param node T node object. 60 | */ 61 | public synchronized void remove(T node) { 62 | for (int i = 0; i < virtualNodeCount; i++) { 63 | hashRing.remove(getHashKey(node, i)); 64 | } 65 | } 66 | 67 | public int getHashKey(T node, int i) { 68 | return hash(new StringBuffer("HASH-").append(node.toString()) 69 | .append("-NODE-").append(i).toString()); 70 | } 71 | 72 | /** 73 | * Get node from the given key. 74 | * 75 | * @param key key data. 76 | * @return Node node. 77 | */ 78 | public T getNode(T key) { 79 | // 大于当前 hash 的所有 map 80 | SortedMap subMap = hashRing.tailMap(hash(key.toString())); 81 | if (subMap.isEmpty()) { 82 | return hashRing.get(hashRing.firstKey()); 83 | } 84 | return subMap.get(subMap.firstKey()); 85 | } 86 | 87 | /** 88 | * FNV1_32_HASH 算法计算 Hash 值 89 | * 90 | * @param key 待计算 KEY 91 | * @return 92 | */ 93 | private int hash(String key) { 94 | final int p = 16777619; 95 | int hash = (int) 2166136261L; 96 | for (int i = 0; i < key.length(); i++) { 97 | hash = (hash ^ key.charAt(i)) * p; 98 | } 99 | hash += hash << 13; 100 | hash ^= hash >> 7; 101 | hash += hash << 3; 102 | hash ^= hash >> 17; 103 | hash += hash << 5; 104 | 105 | // 如果算出来的值为负数则取其绝对值 106 | if (hash < 0) { 107 | hash = Math.abs(hash); 108 | } 109 | return hash; 110 | } 111 | } -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/main/java/com/baomidou/jobs/trigger/TriggerTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.trigger; 2 | 3 | /** 4 | * trigger type enum 5 | * 6 | * @author xuxueli 2018-09-16 04:56:41 7 | */ 8 | public enum TriggerTypeEnum { 9 | MANUAL("手动触发"), 10 | CRON("Cron触发"), 11 | RETRY("失败重试触发"), 12 | PARENT("父任务触发"), 13 | API("API触发"); 14 | 15 | TriggerTypeEnum(String title){ 16 | this.title = title; 17 | } 18 | 19 | private String title; 20 | 21 | public String getTitle() { 22 | return title; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jobs-spring-boot-starter/src/test/java/com/baomidou/jobs/test/CronUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.baomidou.jobs.test; 2 | 3 | import com.cronutils.model.Cron; 4 | import com.cronutils.model.CronType; 5 | import com.cronutils.model.definition.CronDefinition; 6 | import com.cronutils.model.definition.CronDefinitionBuilder; 7 | import com.cronutils.model.time.ExecutionTime; 8 | import com.cronutils.parser.CronParser; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | import java.time.ZonedDateTime; 13 | import java.util.Optional; 14 | 15 | /** 16 | * cron 工具类测试 17 | *

    18 | * Java 组件:https://github.com/jmrozanec/cron-utils 19 | * vue 插件:https://github.com/1615450788/vue-cron 20 | * cron 在线生成:https://1615450788.github.io/vue-cron/dist/index 21 | * 22 | * @author jobob 23 | */ 24 | public class CronUtilsTest { 25 | 26 | public CronParser getCronParser() { 27 | //get a predefined instance 28 | CronDefinition cronDefinition = 29 | CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ); 30 | //create a parser based on provided definition 31 | return new CronParser(cronDefinition); 32 | } 33 | 34 | @Test(expected = IllegalArgumentException.class) 35 | public void validate() { 36 | Cron cron = getCronParser().parse("1* * * * * ? *"); 37 | cron.validate(); 38 | } 39 | 40 | @Test 41 | public void test() { 42 | // Get date for last execution 43 | ZonedDateTime now = ZonedDateTime.now(); 44 | Cron cron = getCronParser().parse("* * * * * ? *"); 45 | 46 | ExecutionTime executionTime = ExecutionTime.forCron(cron); 47 | Optional lastExecution = executionTime.lastExecution(now); 48 | System.out.println(lastExecution.get().toInstant().toEpochMilli()); 49 | 50 | // Get date for next execution 51 | Optional nextExecution = executionTime.nextExecution(now); 52 | System.out.println(nextExecution.get().toInstant().toEpochMilli()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2020, baomidou (jobob@qq.com). 2 |

    3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | use this file except in compliance with the License. You may obtain a copy of 5 | the License at 6 |

    7 | https://www.apache.org/licenses/LICENSE-2.0 8 |

    9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | License for the specific language governing permissions and limitations under 13 | the License. 14 | 15 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jobs' 2 | include 'jobs-spring-boot-starter' 3 | include 'jobs-admin' 4 | include 'jobs-spring-boot-sample' 5 | 6 | --------------------------------------------------------------------------------