最近做项目需求中,需要生成6-12数的用户邀请码,并且邀请码可根据用户灵活配置的cron(比如一小时更新一次,一天更新一次)等条件进行更新。因为我是参照这篇文章
https://blog.csdn.net/w1047667241/article/details/109527671 注:仅限于 单机下的调度,不是分布式调度的管理(这个要看清楚哦)
进行复制性开发...,而且功能也是达到了。所以记录下,以后肯定也会用到。
1.要用到定时器任务肯定要在spring-application.xml配置文件先配置好,也可以单独建 spring-task.xml配置,我就是单独建spring-task.xml方式(这个不多说了,网上一大堆)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.1.xsd" default-lazy-init="true">
<!--<context:component-scan base-package="cn.pw.manager.*.*.task" />-->
<!-- 定时器开关-->
<task:executor id="executor" pool-size="5" />
<task:scheduler id="scheduler" pool-size="10" />
<task:annotation-driven executor="executor" scheduler="scheduler" />
</beans>2. 建一个 “ITriggerTask” 接口类代码如下:(这个是完全我是完全复制过来的)
package cn.pw.manager.admin.task;
import org.springframework.scheduling.support.CronTrigger;
/**
* TriggerTask 必须实现的方法,为了支持动态配置 cron表达式
* @author liuYY
* @date 2021-03-11 11:21
*/
public interface ITriggerTask {
/**
* 获取 类别,区分 不同的Bean 对象
* @return
*/
String type();
/**
* 获取 run 方法
* @return
*/
Runnable getTask();
/**
* 获取触发器,一般是 CronTrigger
* @return
*/
CronTrigger getTrigger();
/**
* 接口 动态修改 定时任务的表达式
*/
CronTrigger setTrigger(String cron);
}
3. TriggerTaskSupport抽象类实现 ITriggerTask 接口
package cn.pw.manager.admin.task;
import lombok.extern.slf4j.Slf4j;
/**
* @author liuYY
* @date 2021-3-11 10:29
*/
@Slf4j
public abstract class TriggerTaskSupport implements ITriggerTask {
@Override
public String type() {
return this.getClass().getSimpleName().toLowerCase();
}
@Override
public String toString() {
return "TriggerTask{" +
"type=" + type() +
", task=" + getTask() +
"cronTrigger=" + getTrigger().getExpression() +
'}';
}
}
4.JobsConfigUtil 支持 停止、重启、更新定时任务,反正我只要用到更新定时任务 (如果你们想省事不用管复制过去拿来用就行了,我是直接复制原博客的..)
package cn.pw.manager.admin.task.utils;
import cn.pw.manager.admin.task.ITriggerTask;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
* <p>定时任务控制动态 cron表达式 工具类</p>
* @author liuYY
* @date 2021-3-11 10:29
*/
@EnableScheduling
@Configuration
@Slf4j
public class JobsConfigUtil implements SchedulingConfigurer, DisposableBean {
// 自定义,参考 TriggerTask,为了统一在实现类中,调用 getTrigger() 和 getTask()
public Collection<ITriggerTask> scheduledServices;
// 句柄,方便后期获取 future
TaskScheduler taskScheduler;
// spring特性: 初始化该类时,自动获取和装配 项目中 所有的子类 ITriggerTask
public JobsConfigUtil(Collection<ITriggerTask> scheduledServices) {
this.scheduledServices = scheduledServices;
}
/**
* Future handles, to cancel the running jobs
*/
private static final Map<String, ScheduledFuture> FUTURE_MAP = new ConcurrentHashMap<>();
/**
* 获取 定时任务的具体的类,用于后期 重启,更新等操作
*/
private static final Map<String, ITriggerTask> SERVICE_MAP = new ConcurrentHashMap<>();
/**
* 线程池任务调度器
* <p>
* 支持注解方式,@Scheduled(cron = "0/5 * * * * ?")
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1);
scheduler.setThreadNamePrefix("TaskScheduler-");
scheduler.setRemoveOnCancelPolicy(true); // 保证能立刻丢弃运行中的任务
taskScheduler = scheduler; // 获取 句柄,方便后期获取 future
return scheduler;
}
/**
* @see <a href='https://www.codota.com/code/java/methods/org.springframework.scheduling.config.ScheduledTaskRegistrar/addTriggerTask'>codota 代码提示工具</a>
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
if (null != scheduledServices && scheduledServices.size() > 0) {
for (final ITriggerTask service : scheduledServices) {
// old 方式,不推荐,因为无法获取 调度任务的 future 对象
// taskRegistrar.addTriggerTask(scheduledService.getTask(),scheduledService.getTrigger());
ScheduledFuture<?> schedule = taskScheduler.schedule(service.getTask(), service.getTrigger());
FUTURE_MAP.put(service.type(), schedule);
SERVICE_MAP.put(service.type(), service);
}
}
}
//=============================动态配置 cron 表达式,立刻生效,支持 停止、重启、更新cron==============================================
public Object get() {
final Set<String> names = FUTURE_MAP.keySet();
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("futures", names);
map.put("services", new HashMap<Object, Object>() {{
for (Map.Entry<String, ITriggerTask> entry : SERVICE_MAP.entrySet()) {
put(entry.getKey(), entry.getValue().getTrigger().getExpression());
}
}});
return map.toString();
}
/**
* 新增
*/
public Object add(@NonNull ITriggerTask task) {
String type = task.type(), cron = task.getTrigger().getExpression();
if (FUTURE_MAP.containsKey(type)) {
return "请重新指定 任务的 type 属性";
}
ScheduledFuture<?> future = taskScheduler.schedule(task.getTask(), task.getTrigger());
FUTURE_MAP.put(type, future);
SERVICE_MAP.put(type, task);
String format = String.format("添加新任务成功: :[%s],[%s]", type, cron);
log.info(format);
return format;
}
/**
* 更新
*/
public void update(@NonNull final String type, @NonNull final String cron) {
if (!FUTURE_MAP.containsKey(type)) {
return;
}
ScheduledFuture future = FUTURE_MAP.get(type);
if (future != null) {
future.cancel(true);
}
ITriggerTask service = SERVICE_MAP.get(type);
CronTrigger old = service.getTrigger(), newTri = service.setTrigger(cron);
future= taskScheduler.schedule(service.getTask(), newTri);
// 必须更新一下对象,否则下次cencel 会失败
FUTURE_MAP.put(type, future);
}
/**
* 取消
*/
public Object cancel(@NonNull String type) {
if (!FUTURE_MAP.containsKey(type)) {
return "取消失败,不存在该任务,请检查 type: " + type;
}
ScheduledFuture future = FUTURE_MAP.get(type);
if (future != null) {
future.cancel(true);
}
FUTURE_MAP.remove(type);
return "成功取消执行中的任务 : " + type;
}
/**
* 重启已经存在的任务
*/
public Object restart(@NonNull String type) {
ITriggerTask service = SERVICE_MAP.get(type);
if (service == null) {
return "无法启动任务,请检查 type: " + type;
}
if (FUTURE_MAP.containsKey(type)) {
ScheduledFuture future = FUTURE_MAP.get(type);
if (future != null) {
future.cancel(true);
}
}
ScheduledFuture<?> future = taskScheduler.schedule(service.getTask(), service.getTrigger());
FUTURE_MAP.put(type, future); // 必须更新一下对象,否则下次cencel 会失败
return "成功重启任务 type: " + type + ",cron: " + service.getTrigger().getExpression();
}
@Override
public void destroy() throws Exception {
for (ScheduledFuture future : FUTURE_MAP.values()) {
if (future != null) {
future.cancel(true);
}
}
FUTURE_MAP.clear();
SERVICE_MAP.clear();
((ThreadPoolTaskScheduler) taskScheduler).destroy();
}
}
5.自己定时器的具体任务类(我的叫InvitationCodeTaskService,因为这个是我自定义的定时器任务类,你们复制过去会报错,因为很多类是我的比如什么UserService RedisDao,InvitationCodeMapper 啊什么的,你们删掉就行)
package cn.pw.manager.admin.task.customTask;
import cn.pw.common.redis.RedisDao;
import cn.pw.common.redisKey.UserConstants;
import cn.pw.manager.admin.setting.entity.InvitationCode;
import cn.pw.manager.admin.setting.mapper.InvitationCodeMapper;
import cn.pw.manager.admin.setting.service.InvitationCodeService;
import cn.pw.manager.admin.setting.service.impl.InvitationCodeServiceImpl;
import cn.pw.manager.admin.task.TriggerTaskSupport;
import cn.pw.manager.admin.task.utils.JobsConfigUtil;
import cn.pw.manager.admin.userManage.entity.User;
import cn.pw.manager.admin.userManage.service.UserService;
import cn.pw.manager.admin.userManage.utils.InviteCodeUtil;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.*;
@Service
@Slf4j(topic = "推销员邀请码更新定时任务")
public class InvitationCodeTaskService extends TriggerTaskSupport {
@Autowired
private InvitationCodeMapper invitationCodeMapper;
@Autowired
private RedisDao redisDao;
@Autowired
private UserService userService;
@Getter
@Builder.Default
private CronTrigger trigger;
// 默认为每天更新一次
private String cron="0 0 0 * * ?";
// 邀请码生成长度默认为6位
private Integer codeLength=6;
@PostConstruct
public void init(){
// 启用的规则
InvitationCode invitationCode= invitationCodeMapper.findInvitationCode();
if (Objects.nonNull(invitationCode)){
cron=invitationCode.getCronName();
codeLength=invitationCode.getCodeLength();
}
// 系统启动时应读取 数据库启用的cron规则
trigger= new CronTrigger(cron);
}
@Override
public CronTrigger setTrigger(String expression) {
String old = trigger.getExpression();
this.trigger = new CronTrigger(expression);
log.info("邀请码更新cron表达式成功, 旧值: {} , 新值: {}", old, trigger.getExpression());
return this.trigger;
}
@Getter
@Builder.Default
private Runnable task = new Runnable() {
@Override
public void run() {
//自己的具体业务写在run()方法这里 updateUserInviteCode就是我的定时器任务具体要执行的代码,你们自己可以写自己的业务代码
//updateUserInviteCode();
}
};
/**
* 更新邀请码
*/
private void updateUserInviteCode(){
try {
// 启用的规则
InvitationCode invitationCode=invitationCodeMapper.findInvitationCode();
if(Objects.nonNull(invitationCode)){
codeLength=invitationCode.getCodeLength();
cron=invitationCode.getCronName();
}
log.info("推广邀请码更新cron规则:{}",cron);
// 清除存在redis里面的邀请码
if (redisDao.exists(UserConstants.USER_INVITATION_CODE_LIST)&&redisDao.exists(UserConstants.USER_INVITATION_CODE)){
redisDao.del(UserConstants.USER_INVITATION_CODE_LIST);
redisDao.del(UserConstants.USER_INVITATION_CODE);
}
// 查询分销员用户重新生成邀请码
List<User> userList=userService.listByPromoterFlag();
if (Objects.isNull(userList)) return;
Set set = InviteCodeUtil.generateInviteCodeToSet(codeLength,userList.size());
Iterator iterator = set.iterator();
for (Integer i=0;i<userList.size();i++){
String code=iterator.next().toString();
redisDao.hset(UserConstants.USER_INVITATION_CODE,String.valueOf(userList.get(i).getId()),code);
redisDao.hset(UserConstants.USER_INVITATION_CODE_LIST,code,String.valueOf(userList.get(i).getId()));
}
}catch (Exception e){
log.error("更新邀请码时出错了:{}",e.getMessage());
}
}
}
6 接下来就是写一个Controller来接收一个修改cron表达式的请求,动态接收最新的Cron表达式。我写的Controller 如下:
@Autowired
JobsConfigUtil jobsConfigUtil;
/**
* 更新更新邀请码 cron表达式
* @param cron
* @return
*/
@RequestMapping("/updateCron")
@ResponseBody
public TableResult updateCron(String cron){
InvitationCodeTaskService taskService=new InvitationCodeTaskService();//自己定义的定时器任务
// taskService.getClass().getSimpleName().toLowerCase() 获取类的名称
// 也可以直接 jobsConfigUtil.update("invitationCodeTaskService".toLowerCase(),,cron);
jobsConfigUtil.update(taskService.getClass().getSimpleName().toLowerCase(),cron);
return TableResult.Result();
}这样就完成动态接收cron并且不用重启服务就能重新更新定时任务的cron表达式功能。注:仅限于 单机下的调度,不是分布式调度的管理(这个要看清楚哦)
最后说明下 我没有找任何的存在感啊(而且我也是复制),我只是自己记录下这个功能 说不定以后还会用到。可以看原文章
https://blog.csdn.net/w1047667241/article/details/109527671。如果你们更好的解决方案可以分享给我么?。。。我想这个肯定不是最好的!!,但是能完成自己的需求就行了。
版权声明:本文为qq_41999204原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。