公司项目改造为微服务之后,单个项目可能起多个实例,决定采用xxl-job进行管理定时任务。原生的xxl-job不能满足项目需要,将xxl-job作为微服务引入项目。
数据库地址写到了nacos里,从nacos里取
### actuator
management:
health:
mail:
enabled: false
server:
base-path: /actuator
### web
server:
port: 8088
servlet:
context-path: /xxl-job-admin
### xxl-job, datasource
spring:
datasource:
url: ${blade.datasource.xxl.url}
username: ${blade.datasource.xxl.username}
password: ${blade.datasource.xxl.password}
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 10
maximum-pool-size: 30
auto-commit: true
idle-timeout: 30000
pool-name: HikariCP
max-lifetime: 900000
connection-timeout: 10000
connection-test-query: SELECT 1
validation-timeout: 1000
mvc:
servlet:
load-on-startup: 0
static-path-pattern: /static/**
### freemarker
freemarker:
templateLoaderPath: classpath:/templates/
suffix: .ftl
charset: UTF-8
request-context-attribute: request
settings.number_format: 0.##########
mail:
host: smtp.qq.com
port: 25
username: xxxxxxxxxxxx
from: xxxxxxxxxxxxx
password: xxxxxxxxxx
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
web:
resources:
static-locations: classpath:/static/
# Mybatis-plus
mybatis-plus:
mapper-locations: classpath*:mybatis-mapper/*.xml
### xxl-job, access token
xxl:
job:
accessToken: default_token
i18n: zh_CN
triggerpool:
fast:
max: 200
slow:
max: 100
logretentiondays: 30
minio:
enabled: false
@EnableDiscoveryClient
@MapperScan({"com.xxl.job.admin.dao"})
@EnableAutoConfiguration(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@BladeCloudApplication
public class XxlJobAdminApplication {
public static void main(String[] args) {
BladeApplication.run(AppConstant.APPLICATION_XXL_NAME, XxlJobAdminApplication.class, args);
}
}
<dependency>
<groupId>com.eastsoftgroupId>
<artifactId>blade-commonartifactId>
<version>${eastsoft.common.version}-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springbladegroupId>
<artifactId>blade-core-cloudartifactId>
<version>${blade.tool.version}version>
dependency>
<dependency>
<groupId>org.springbladegroupId>
<artifactId>blade-core-secureartifactId>
<version>${blade.tool.version}version>
dependency>
<dependency>
<groupId>org.springbladegroupId>
<artifactId>blade-core-socialartifactId>
<version>${blade.tool.version}version>
dependency>
这里因为xxl-job-admin设置了context-path,需要在@FeignClient中增加path
package com.xxl.job.core.feign;
import com.eastsoft.common.constant.AppConstant;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.vo.XxlJobInfoVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
/**
* xxlJobInfo Feign实现类
*
* @author run
*/
@FeignClient(
value = AppConstant.APPLICATION_XXL_NAME,
fallback = IJobInfoClientFallback.class,
path = "xxl-job-admin"
)
public interface IJobInfoClient {
String API_PREFIX = "/client";
@PostMapping(API_PREFIX + "/addXxlJob")
ReturnT<String> addXxlJob(
@RequestBody XxlJobInfoVo xxlJobInfoVo
);
@PostMapping(API_PREFIX + "/updateXxlJob")
ReturnT<String> updateXxlJob(
@RequestBody XxlJobInfoVo xxlJobInfoVo
);
@GetMapping(API_PREFIX + "/removeXxlJob")
ReturnT<String> removeXxlJob(
@RequestParam("id") int id
);
@GetMapping(API_PREFIX + "/stopXxlJob")
ReturnT<String> stopXxlJob(
@RequestParam("id") int id
);
@GetMapping(API_PREFIX + "/startXxlJob")
ReturnT<String> startXxlJob(
@RequestParam("id") int id
);
@GetMapping(API_PREFIX + "/triggerXxlJob")
ReturnT<String> triggerXxlJob(
@RequestParam("id") int id,
@RequestParam("executorParam") String executorParam,
@RequestParam("addressList") String addressList
);
@GetMapping(API_PREFIX + "/nextTriggerTimeXxlJob")
ReturnT<List<String>> nextTriggerTimeXxlJob(
@RequestParam("scheduleType") String scheduleType,
@RequestParam("scheduleConf") String scheduleConf
);
@PostMapping(API_PREFIX + "/getXxlJobPageList")
Map<String, Object> getXxlJobPageList(
@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
@RequestParam("jobGroup") int jobGroup,
@RequestParam("triggerStatus") int triggerStatus,
@RequestParam("jobDesc") String jobDesc,
@RequestParam("executorHandler") String executorHandler,
@RequestParam("author") String author
);
}
package com.xxl.job.core.vo;
import lombok.Data;
import java.util.Date;
/**
* xxl-job info
*
* @author xuxueli 2016-1-12 18:25:49
*/
@Data
public class XxlJobInfoVo {
private int id; // 主键ID
private int jobGroup; // 执行器主键ID
private String jobDesc;
private Date addTime;
private Date updateTime;
private String author; // 负责人
private String alarmEmail; // 报警邮件
private String scheduleType; // 调度类型
private String scheduleConf; // 调度配置,值含义取决于调度类型
private String misfireStrategy; // 调度过期策略
private String executorRouteStrategy; // 执行器路由策略
private String executorHandler; // 执行器,任务Handler名称
private String executorParam; // 执行器,任务参数
private String executorBlockStrategy; // 阻塞处理策略
private int executorTimeout; // 任务执行超时时间,单位秒
private int executorFailRetryCount; // 失败重试次数
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueSource; // GLUE源代码
private String glueRemark; // GLUE备注
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
private int triggerStatus; // 调度状态:0-停止,1-运行
private long triggerLastTime; // 上次调度时间
private long triggerNextTime; // 下次调度时间
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-openfeign-coreartifactId>
dependency>
<dependency>
<groupId>com.eastsoftgroupId>
<artifactId>blade-commonartifactId>
<version>${eastsoft.common.version}-SNAPSHOTversion>
dependency>
xxl-job-admin将xxl-job-core作为jar包引用,其它需要使用xxl-job的服务只需要引用jar包
@EnableFeignClients({"com.xxl"})
package com.xxl.job.admin.feign;
/**
* @title: JobInfoClient
* @Description []
* @Author lhz
* @Date: 2022/8/31 0031 09:44
* @Version 1.0
*/
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.thread.JobScheduleHelper;
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.feign.IJobInfoClient;
import com.xxl.job.core.util.DateUtil;
import com.xxl.job.core.vo.XxlJobInfoVo;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* AlarmConfigService Feign实现类
*
* @author run
*/
@RestController
public class JobInfoClient implements IJobInfoClient {
@Resource
private XxlJobService xxlJobService;
@Override
public ReturnT<String> addXxlJob(@RequestBody XxlJobInfoVo xxlJobInfoVo) {
return xxlJobService.add(toEntity(xxlJobInfoVo));
}
@Override
public ReturnT<String> updateXxlJob(XxlJobInfoVo xxlJobInfoVo) {
return xxlJobService.update(toEntity(xxlJobInfoVo));
}
@Override
public ReturnT<String> removeXxlJob(int id) {
return xxlJobService.remove(id);
}
@Override
public ReturnT<String> stopXxlJob(int id) {
return xxlJobService.stop(id);
}
@Override
public ReturnT<String> startXxlJob(int id) {
return xxlJobService.start(id);
}
@Override
public ReturnT<String> triggerXxlJob(int id, String executorParam, String addressList) {
if (executorParam == null) {
executorParam = "";
}
JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
return ReturnT.SUCCESS;
}
@Override
public ReturnT<List<String>> nextTriggerTimeXxlJob(String scheduleType, String scheduleConf) {
XxlJobInfo paramXxlJobInfo = new XxlJobInfo();
paramXxlJobInfo.setScheduleType(scheduleType);
paramXxlJobInfo.setScheduleConf(scheduleConf);
List<String> result = new ArrayList<>();
try {
Date lastTime = new Date();
for (int i = 0; i < 5; i++) {
lastTime = JobScheduleHelper.generateNextValidTime(paramXxlJobInfo, lastTime);
if (lastTime != null) {
result.add(DateUtil.formatDateTime(lastTime));
}
else {
break;
}
}
}
catch (Exception e) {
return new ReturnT<List<String>>(ReturnT.FAIL_CODE,
(I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")) + e.getMessage());
}
return new ReturnT<List<String>>(result);
}
@Override
public Map<String, Object> getXxlJobPageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
}
private XxlJobInfo toEntity(XxlJobInfoVo xxlJobInfoVo){
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setId(xxlJobInfoVo.getId());
xxlJobInfo.setJobGroup(xxlJobInfoVo.getJobGroup());
xxlJobInfo.setJobDesc(xxlJobInfoVo.getJobDesc());
xxlJobInfo.setAddTime(xxlJobInfoVo.getAddTime());
xxlJobInfo.setUpdateTime(xxlJobInfoVo.getUpdateTime());
xxlJobInfo.setAuthor(xxlJobInfoVo.getAuthor());
xxlJobInfo.setAlarmEmail(xxlJobInfoVo.getAlarmEmail());
xxlJobInfo.setScheduleType(xxlJobInfoVo.getScheduleType());
xxlJobInfo.setScheduleConf(xxlJobInfoVo.getScheduleConf());
xxlJobInfo.setMisfireStrategy(xxlJobInfoVo.getMisfireStrategy());
xxlJobInfo.setExecutorRouteStrategy(xxlJobInfoVo.getExecutorRouteStrategy());
xxlJobInfo.setExecutorHandler(xxlJobInfoVo.getExecutorHandler());
xxlJobInfo.setExecutorParam(xxlJobInfoVo.getExecutorParam());
xxlJobInfo.setExecutorBlockStrategy(xxlJobInfoVo.getExecutorBlockStrategy());
xxlJobInfo.setExecutorTimeout(xxlJobInfoVo.getExecutorTimeout());
xxlJobInfo.setExecutorFailRetryCount(xxlJobInfoVo.getExecutorFailRetryCount());
xxlJobInfo.setGlueType(xxlJobInfoVo.getGlueType());
xxlJobInfo.setGlueSource(xxlJobInfoVo.getGlueSource());
xxlJobInfo.setGlueRemark(xxlJobInfoVo.getGlueRemark());
xxlJobInfo.setGlueUpdatetime(xxlJobInfoVo.getGlueUpdatetime());
xxlJobInfo.setChildJobId(xxlJobInfoVo.getChildJobId());
xxlJobInfo.setTriggerStatus(xxlJobInfoVo.getTriggerStatus());
xxlJobInfo.setTriggerLastTime(xxlJobInfoVo.getTriggerLastTime());
xxlJobInfo.setTriggerNextTime(xxlJobInfoVo.getTriggerNextTime());
return xxlJobInfo;
}
}
package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.service.LoginService;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 权限拦截
*
* @author xuxueli 2015-12-12 18:09:04
*/
@Component
public class PermissionInterceptor implements AsyncHandlerInterceptor {
private static final List<String> SKIP_URL = new ArrayList<>(Arrays.asList(
"/xxl-job-admin/client/addXxlJob",
"/xxl-job-admin/client/updateXxlJob",
"/xxl-job-admin/client/removeXxlJob",
"/xxl-job-admin/client/stopXxlJob",
"/xxl-job-admin/client/startXxlJob",
"/xxl-job-admin/client/triggerXxlJob",
"/xxl-job-admin/client/nextTriggerTimeXxlJob",
"/xxl-job-admin/client/getXxlJobPageList"
));
@Resource
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true; // proceed with the next interceptor
}
String url = request.getRequestURI();
// if need login
boolean needLogin = !SKIP_URL.contains(url);
boolean needAdminuser = false;
HandlerMethod method = (HandlerMethod)handler;
PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
if (permission!=null) {
needLogin = permission.limit();
needAdminuser = permission.adminuser();
}
if (needLogin) {
XxlJobUser loginUser = loginService.ifLogin(request, response);
if (loginUser == null) {
response.setStatus(302);
response.setHeader("location", request.getContextPath()+"/toLogin");
return false;
}
if (needAdminuser && loginUser.getRole()!=1) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit"));
}
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
}
return true; // proceed with the next interceptor
}
}
xxl-job自动化注册放到了项目公共使用的一个common包里,参考的项目:xxl-job自动化注册
使用的xxl-job-admin的address是从nacos获取服务列表,支持集群部署
package com.eastsoft.common.annotation;
import com.eastsoft.common.config.xxl.XxlJobAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
*
* 激活xxl-job配置
* @author aiden
* @date 2021/9/29 3:45 下午
* @return
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ XxlJobAutoConfiguration.class })
public @interface EnableXxlJob {
}
package com.eastsoft.common.config.xxl;
import com.eastsoft.common.constant.AppConstant;
import com.eastsoft.common.properties.XxlExecutorProperties;
import com.eastsoft.common.properties.XxlJobProperties;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import java.util.stream.Collectors;
/**
*
* xxl-job自动装配
* @author aiden
* @date 2021/9/29 4:38 下午
* @return
*/
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableConfigurationProperties(XxlJobProperties.class)
@Slf4j
public class XxlJobAutoConfiguration {
/**
* 配置xxl-job 执行器,提供自动发现 xxl-job-admin 能力
* @param xxlJobProperties xxl 配置
* @param environment 环境变量
* @param discoveryClient 注册发现客户端
* @return
*/
@Bean
public XxlJobSpringExecutor xxlJobSpringExecutor(XxlJobProperties xxlJobProperties, Environment environment,
DiscoveryClient discoveryClient) {
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
XxlExecutorProperties executor = xxlJobProperties.getExecutor();
// 应用名默认为服务名
String appName = executor.getAppname();
if (!StringUtils.hasText(appName)) {
appName = environment.getProperty("spring.application.name");
}
xxlJobSpringExecutor.setAppname(appName);
xxlJobSpringExecutor.setAddress(executor.getAddress());
xxlJobSpringExecutor.setIp(executor.getIp());
xxlJobSpringExecutor.setPort(executor.getPort());
xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
xxlJobSpringExecutor.setLogPath(executor.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays());
// 如果配置为空则获取注册中心的服务列表 "http://nacos:8848/xxl-job-admin"
if (!StringUtils.hasText(xxlJobProperties.getAdmin().getAddresses())) {
String serverList = discoveryClient.getServices().stream().filter(s -> s.contains(AppConstant.APPLICATION_XXL_NAME))
.flatMap(s -> discoveryClient.getInstances(s).stream()).map(instance -> String
.format("http://%s:%s/%s", instance.getHost(), instance.getPort(), AppConstant.APPLICATION_XXL_NAME_ADMIN))
.collect(Collectors.joining(","));
xxlJobSpringExecutor.setAdminAddresses(serverList);
log.info(">>>>>>>>>>> xxl-job-admin serverList:{}", serverList);
}
else {
xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdmin().getAddresses());
}
return xxlJobSpringExecutor;
}
}
package com.eastsoft.common.properties;
import lombok.Data;
/**
*
* xxl-job管理平台配置
* @author aiden
* @date 2021/9/29 3:45 下午
* @return
*/
@Data
public class XxlAdminProperties {
/**
* 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。 执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
*/
private String addresses;
}
package com.eastsoft.common.properties;
import lombok.Data;
/**
*
* xxl-job执行器配置
* @author aiden
* @date 2021/9/29 3:50 下午
* @return
*/
@Data
public class XxlExecutorProperties {
/**
* 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
*/
private String appname;
/**
* 服务注册地址,优先使用该配置作为注册地址 为空时使用内嵌服务 ”IP:PORT“ 作为注册地址 从而更灵活的支持容器类型执行器动态IP和动态映射端口问题
*/
private String address;
/**
* 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP ,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和
* "调度中心请求并触发任务"
*/
private String ip;
/**
* 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
*/
private Integer port = 0;
/**
* 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
*/
private String logPath = "logs/applogs/xxl-job/jobhandler";
/**
* 执行器日志保存天数 [选填] :值大于3时生效,启用执行器Log文件定期清理功能,否则不生效;
*/
private Integer logRetentionDays = 30;
}
package com.eastsoft.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import java.util.ArrayList;
import java.util.List;
/**
*
* xxl-job配置
* @author aiden
* @date 2021/9/29 3:45 下午
* @return
*/
@Data
@ConfigurationProperties("xxl.job")
public class XxlJobProperties {
@NestedConfigurationProperty
private XxlAdminProperties admin = new XxlAdminProperties();
@NestedConfigurationProperty
private XxlExecutorProperties executor = new XxlExecutorProperties();
private String accessToken = "";
}
package com.eastsoft.common.config;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @title: MinioCondition
* @Description []
* @Author lhz
* @Date: 2022/9/1 0001 14:08
* @Version 1.0
*/
public class MinioCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return Boolean.parseBoolean(context.getEnvironment().getProperty("minio.enabled"));
}
}
package com.eastsoft.common.config.minio;
import com.eastsoft.common.config.MinioCondition;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* description: 获取配置文件信息
*
* @author: weirx
* @time: 2021/8/25 9:50
*/
@Configuration
@EnableConfigurationProperties(MinioPropertiesConfig.class)
public class MinioConfig {
@Autowired
private MinioPropertiesConfig minioPropertiesConfig;
/**
* 初始化 MinIO 客户端
*/
@Conditional(MinioCondition.class)
@Bean
public MinioClient minioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(minioPropertiesConfig.getEndpoint())
.credentials(minioPropertiesConfig.getAccessKey(), minioPropertiesConfig.getSecretKey())
.build();
return minioClient;
}
}
启动器更改
@ComponentScan(excludeFilters= {@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE, value= {MinioUtil.class, XxlJobAutoConfiguration.class})})
xml配置更改
minio:
enabled: false
xxl-job详细使用指南
springboot启动的时候排除加载项
[读书笔记] 二、条件注解@Conditional,组合注解,元注解
SpringCloud微服务实战——搭建企业级开发框架(四十三):集成分布式任务调度平台XXL-JOB,实现定时任务功能