当前位置: 首页 > news >正文

Quartz任务调度框架实现任务动态执行

说明:之前使用Quartz,都是写好Job,指定一个时间点,到点执行。最近有个需求,需要根据前端用户设置的时间点去执行,也就是说任务执行的时间点是动态变化的。本文介绍如何用Quartz任务调度框架实现任务动态执行。

基本实现

(0)思路

基本思路是,根据用户传入的时间点,转为cron表达式,定时去刷新容器内的JOB,更新任务的cron表达式。

(1)搭建项目

创建一个项目,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent><groupId>com.hezy</groupId><artifactId>auto_quartz</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><version>42.7.3</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.6</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
</project>

我项目的是postgresql,application.yml如下

server:port: 8988spring:datasource:url: jdbc:postgresql://localhost:5432/demousername: postgrespassword: 123456driver-class-name: org.postgresql.Driver

创建一张任务调度表,包含了任务名(对应Java类中JOB的Bean名称),任务的cron表达式;

-- 创建任务调度表
create table public.t_task
(id         varchar(32)       not nullconstraint pc_task_idprimary key,task_name  varchar(50)       not null,cron       varchar(50)       not null,is_deleted integer default 0 not null
);comment on table public.t_task is '任务调度表';
comment on column public.t_task.task_name is '任务名';
comment on column public.t_task.cron is 'cron表达式';
comment on column public.t_task.is_deleted is '是否删除,1是,0否,默认0';

(2)引入Quartz

创建两个关于Quartz的配置类

(MyJobFactory)

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;/*** 任务工厂*/
@Component
public class MyJobFactory extends AdaptableJobFactory {@Autowiredprivate AutowireCapableBeanFactory capableBeanFactory;/*** 创建JOB实例*/@Overrideprotected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {Object jobInstance = super.createJobInstance(bundle);capableBeanFactory.autowireBean(jobInstance);return jobInstance;}
}

(TaskConfig)

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;/*** 任务配置类*/
@Configuration
@DependsOn("myJobFactory")
public class TaskConfig {@Autowiredprivate MyJobFactory myJobFactory;/*** 加载任务工厂*/@Beanpublic SchedulerFactoryBean schedulerFactoryBean() {SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();schedulerFactoryBean.setJobFactory(myJobFactory);return schedulerFactoryBean;}@Beanpublic Scheduler scheduler() {return schedulerFactoryBean().getScheduler();}
}

(3) 数据库操作实现

创建一个TaskDTO

import lombok.Data;@Data
public class TaskDTO {private String id;/*** 任务名*/private String taskName;/*** CRON表达式*/private String cron;
}

写一个对应的Mapper,里面创建一个查询方法

import com.hezy.pojo.TaskDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface TaskMapper {@Select("select id, task_name taskName, cron from t_task where is_deleted = 0")List<TaskDTO> getAllTask();
}

插入两条数据,如下:

insert into public.t_task (id, task_name, cron, is_deleted)
values ('1', 'SimpleTask1', '0/5 * * * * ?', 0);insert into public.t_task (id, task_name, cron, is_deleted)
values ('2', 'SimpleTask2', '0/30 * * * * ?', 0);

测试,没得问题

import com.hezy.mapper.TaskMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class TaskMapperTest {@Autowiredprivate TaskMapper taskMapper;@Testpublic void testInsert() {System.out.println(taskMapper.getAllTask());}
}

在这里插入图片描述

(4)创建任务容器

创建一个任务容器,每10秒去刷新容器内的所有Job的cron表达式

import com.hezy.constant.TaskConst;
import com.hezy.mapper.TaskMapper;
import com.hezy.pojo.TaskDTO;
import lombok.extern.log4j.Log4j2;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.List;/*** 任务容器*/
@Component
@DependsOn(value = {"quartzManager"})
@Log4j2
public class TaskContext {@Autowiredprivate QuartzManager quartzManager;@Autowiredprivate TaskMapper taskMapper;/*** 更新任务* 刷新频率:10秒*/@Scheduled(fixedRate = 10000)public void update() {try {// 查出所有任务List<TaskDTO> taskDTOS = taskMapper.getAllTask();// 遍历操作for (TaskDTO taskDto : taskDTOS) {this.quartzManager.startJobTask(taskDto.getTaskName(), TaskConst.GroupEnum.SYSTEM.name(), taskDto.getCron());}} catch (SchedulerException e) {log.error("初始化定时任务异常", e);}}
}

需要注意,@Scheduled注解,需要在启动类上加@EnableScheduling注解才能生效

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@EnableScheduling
public class Start {public static void main(String[] args) {SpringApplication.run(Start.class, args);}
}

创建一个常量,表示任务的组名

(TaskConst)

/*** 任务常量*/
public class TaskConst {/*** 任务分组*/public enum GroupEnum {SYSTEM;}
}

任务管理器,对比传入的任务名、任务cron,更新或创建对应的任务;

import lombok.extern.log4j.Log4j2;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** 定时任务管理器* @author hezhongying* @create 2024/12/11*/
@Configuration
@Log4j2
public class QuartzManager {@Autowiredprivate Scheduler scheduler;@Autowiredprivate List<Job> taskList;private Map<String, Job> taskMap;@PostConstructpublic void init() {this.taskMap = taskList.stream().collect(Collectors.toMap(t -> t.getClass().getSimpleName(), t -> t));}/*** 启动任务* @param jobName 任务名* @param group 组名* @param cron cron表达式* @throws SchedulerException*/public void startJobTask(String jobName, String group, String cron) throws SchedulerException {// 创建JobJobKey jobKey = new JobKey(jobName, group);// 判断任务是否已存在,不存在新增,存在更新if (scheduler.checkExists(jobKey)) {if (modifyJob(jobName, group, cron)) {log.info("任务:{} 修改成功", jobName);}} else {if (createJob(jobName, group, cron)) {log.info("任务:{} 新增成功", jobName);}}}/*** 新增任务* @param jobName 任务名称* @param group 分组* @param cron CRON表达式* @throws SchedulerException*/private boolean createJob(String jobName, String group, String cron) throws SchedulerException {// 任务新增if (taskMap.containsKey(jobName)) {// 执行任务Class<? extends Job> taskClazz = taskMap.get(jobName).getClass();JobDetail jobDetail = JobBuilder.newJob(taskClazz).withIdentity(jobName, group).build();// 执行时间正则CronScheduleBuilder cronBuilder = CronScheduleBuilder.cronSchedule(cron);CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, group).withSchedule(cronBuilder).build();scheduler.scheduleJob(jobDetail, cronTrigger);return true;} else {log.debug("任务没有配置执行配置{}", jobName);}return false;}/*** 修改** @param jobName 任务名* @param group 组名* @param cron cron表达式* @return true,修改成功;false,修改失败* @throws SchedulerException*/public boolean modifyJob(String jobName, String group, String cron) throws SchedulerException {TriggerKey triggerKey = new TriggerKey(jobName, group);Trigger trigger = scheduler.getTrigger(triggerKey);if (trigger == null) {log.info("未存在的触发器[{}-{}]", jobName, group);return false;}String oldCron = ((CronTrigger) trigger).getCronExpression();// 判断cron是否相等,相等就不用改if (!cron.equals(oldCron)) {CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);CronTrigger newTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, group).withSchedule(cronScheduleBuilder).build();Date date = scheduler.rescheduleJob(triggerKey, newTrigger);return date != null;} else {log.info("任务:{} CRON表达式没有变化", jobName);}return false;}/*** 暂停所有任务** @throws SchedulerException*/public void pauseAll() throws SchedulerException {this.scheduler.pauseAll();}/*** 指定任务暂停** @param jobName 任务名* @param group 组名* @throws SchedulerException*/public void pause(String jobName, String group) throws SchedulerException {JobKey jobKey = new JobKey(jobName, group);JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);if (jobDetail == null) {return;}this.scheduler.pauseJob(jobKey);}/*** 恢复所有任务** @throws SchedulerException*/public void resumeAllJob() throws SchedulerException {this.scheduler.resumeAll();}/*** 删除指定任务** @param jobName 任务名* @param group 组名* @throws SchedulerException*/public void delete(String jobName, String group) throws SchedulerException {JobKey jobKey = new JobKey(jobName, group);JobDetail jobDetail = this.scheduler.getJobDetail(jobKey);if (jobDetail == null) {return;}this.scheduler.deleteJob(jobKey);}
}

注意这行代码,可自动注入容器内所有的Job,也就是所有实现了Quartz框架Job接口的类;

    @Autowiredprivate List<Job> taskList;

(5)创建两个Job

创建两个简单的Job

(SimpleTask1)

import lombok.extern.log4j.Log4j2;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** 简单任务1*/
@Component
@Log4j2
public class SimpleTask1 implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) {log.info("{} 定时任务执行时间:{}", "SimpleTask1", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));}
}

(SimpleTask2)

import lombok.extern.log4j.Log4j2;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** 简单任务2*/
@Component
@Log4j2
public class SimpleTask2 implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) {log.info("{} 定时任务执行时间:{}", "SimpleTask2", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));}
}

(6)启动测试

启动项目,查看控制台

在这里插入图片描述

此时,项目不停,修改数据对应任务的cron表达式,

在这里插入图片描述

查看控制台

在这里插入图片描述

到这,动态调度任务就实现了。

更进一步

回到我最开始提到的需求,再来考虑一些细节。

  • 前端传递的时间如何转为cron表达式;

  • 如何实现不同任务对同一个Job的动态执行;


第一个问题,前端传递的任务数据,可能是这样的,任务“每天、每周、每月”,然后设置一个时间点,要求任务按照这个周期来执行,如果将这些信息转为对应的cron表达式,需要有一段代码来实现这些映射关系。

如下:

    /*** 生成CRON表达式* @param type 类型:每天、每周、每月* @param hour 时* @param minute 分* @param dayOfWeek 星期* @param dayOfMonth 日期* @return CRON表达式或null*/public static String generateCronExpression(int type, int hour, int minute, int dayOfWeek, int dayOfMonth) {switch (type) {case 1:return String.format("0 %d %d * * ?", minute, hour);case 2:return String.format("0 %d %d ? * %s", minute, hour, TaskConst.DayOfWeekEnum.getByCode(dayOfWeek).getName());case 3:return String.format("0 %d %d %d * ?", minute, hour, dayOfMonth);default:return null;}}

(星期映射)

cron表达式末尾不能填对应星期的数字,而需要填星期几的英文简称

    /*** 星期枚举*/public enum DayOfWeekEnum {SUNDAY(1, "SUN"),MONDAY(2, "MON"),TUESDAY(3, "TUE"),WEDNESDAY(4, "WED"),THURSDAY(5, "THU"),FRIDAY(6, "FRI"),SATURDAY(7, "SAT");private final Integer code;private final String name;DayOfWeekEnum(Integer code, String name) {this.code = code;this.name = name;}public Integer getCode() {return code;}public String getName() {return name;}/*** 根据code查询枚举*/public static DayOfWeekEnum getByCode(Integer code) {for (DayOfWeekEnum value : DayOfWeekEnum.values()) {if (value.getCode().equals(code)) {return value;}}return null;}/*** 根据name查询枚举*/public static DayOfWeekEnum getByName(String name) {for (DayOfWeekEnum value : DayOfWeekEnum.values()) {if (value.getName().equals(name)) {return value;}}return null;}}

但这种方式局限于我上面说的,只能“每天、每周、每月”的某时、某分执行,而不能更精准到秒、或者每月的最后一周,这样更灵活的设置。(思考)或许可以考虑直接将cron表达式的设置,开放给前端,由用户直接来设置,后端只需校验cron的合法性即可。


第二个问题,有这样的情况:我的任务调度表中的多条记录,执行的是同一个Job,我的Job中会根据记录的唯一ID去查找这个记录对应的数据来处理。这种情况要怎样,还能实现动态调度。

如下,两条记录,对应同一个Job,但是cron不同,执行周期不同;

在这里插入图片描述

首先,在定时刷新这里,就不能只传入taskName,再传入一个唯一标识,我这里就传入ID;

    /*** 更新任务* 刷新频率:10秒*/@Scheduled(fixedRate = 10000)public void update() {try {// 查出所有任务List<TaskDTO> taskDTOS = taskMapper.taskInfo();// 遍历操作for (TaskDTO taskDto : taskDTOS) {// 带上唯一标识this.quartzManager.startJobTask(taskDto.getTaskName(), taskDto.getId(), TaskConst.GroupEnum.SYSTEM.name(), taskDto.getCron());}} catch (SchedulerException e) {log.error("初始化定时任务异常", e);}}

任务管理器这里,判断是否有当前任务,就用任务名_唯一标识判断,绑定Job的时候,就用Job名,即数据库记录的taskName,如下:

    /*** 启动任务* @param name Job名,即Job类名* @param id 唯一标识* @param group 组名* @param cron cron表达式* @throws SchedulerException*/public void startJobTask(String name, String id, String group, String cron) throws SchedulerException {// 任务名,用Job名和唯一标识拼接String jobName = String.format("%s_%s", name, id);// 创建JobJobKey jobKey = new JobKey(jobName, group);// 判断任务是否已存在,不存在新增,存在更新if (scheduler.checkExists(jobKey)) {if (modifyJob(jobName, id, group, cron)) {log.info("任务:{} 修改成功", jobName);}} else {if (createJob(name, jobName, id, group, cron)) {log.info("任务:{} 新增成功", jobName);}}}

新增Job

    /*** 新增任务* @param name JOB名* @param jobName 任务名称* @param id 唯一标识* @param group 分组* @param cron CRON表达式* @throws SchedulerException*/private boolean createJob(String name, String jobName, String id, String group, String cron) throws SchedulerException {// 任务新增,用Job名判断当前容器中是否存在这个Jobif (taskMap.containsKey(name)) {// 执行任务:获取Job类时,也用Job名Class<? extends Job> taskClazz = taskMap.get(name).getClass();// 创建任务式,用Job和唯一标识拼接作为任务名JobDetail jobDetail = JobBuilder.newJob(taskClazz).withIdentity(jobName, group).build();// 创建 JobDataMap 并放入唯一标识 idJobDataMap jobDataMap = new JobDataMap();jobDataMap.put("id", id);// 执行时间正则CronScheduleBuilder cronBuilder = CronScheduleBuilder.cronSchedule(cron);CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, group).usingJobData(jobDataMap).withSchedule(cronBuilder).build();scheduler.scheduleJob(jobDetail, cronTrigger);return true;} else {log.debug("任务没有配置执行配置{}", jobName);}return false;}

修改Job

    /*** 修改** @param jobName 任务名* @param group 组名* @param cron cron表达式* @return true,修改成功;false,修改失败* @throws SchedulerException*/public boolean modifyJob(String jobName, String id, String group, String cron) throws SchedulerException {TriggerKey triggerKey = new TriggerKey(jobName, group);Trigger trigger = scheduler.getTrigger(triggerKey);if (trigger == null) {log.info("未存在的触发器[{}-{}]", jobName, group);return false;}// 创建 JobDataMap 并放入唯一标识 idJobDataMap jobDataMap = new JobDataMap();jobDataMap.put("id", id);String oldCron = ((CronTrigger) trigger).getCronExpression();// 判断cron是否相等,相等就不用改if (!cron.equals(oldCron)) {CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);CronTrigger newTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, group).usingJobData(jobDataMap).withSchedule(cronScheduleBuilder).build();Date date = scheduler.rescheduleJob(triggerKey, newTrigger);return date != null;} else {log.info("任务:{} CRON表达式没有变化", jobName);}return false;}

Job实现里,打印唯一标识,用于区分。如果你有业务需求,完全可以用这个唯一标识,去查询数据库,找出这个唯一标识所拥有的数据,然后做一系列操作。

/*** 简单任务1*/
@Component
@Log4j2
public class SimpleTask1 implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) {log.info("{} 定时任务执行时间:{}", jobExecutionContext.getMergedJobDataMap().get("id"), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));}
}

启动程序,可见两条记录各自的执行周期。

在这里插入图片描述

一个Job,各自运行,让博主想起黑塞 《悉达多》中的一句话:可是也有另一些人,一些为数不多的人,像沿着一条固定轨道运行的星星,没有风吹得到它们;它们有自身的规律和轨道。

总结

本文介绍了如何使用Quartz任务调度框架实现任务动态执行,参考下面这篇文章:

  • SpringBoot整合quartz完成定时任务执行配置热修改

相关文章:

Quartz任务调度框架实现任务动态执行

说明&#xff1a;之前使用Quartz&#xff0c;都是写好Job&#xff0c;指定一个时间点&#xff0c;到点执行。最近有个需求&#xff0c;需要根据前端用户设置的时间点去执行&#xff0c;也就是说任务执行的时间点是动态变化的。本文介绍如何用Quartz任务调度框架实现任务动态执行…...

ESP-IDF学习记录(1)ESPIDF环境安装,框架了解,资料整理

以后只要有空就会进行学习记录&#xff0c;主要是自用&#xff0c;学到哪记录到哪&#xff0c;有时候东西记录下来能得到不通的理解。 最终的目的是为了用esp32驱动屏幕&#xff0c;学习设计LVGL界面&#xff0c;做一些小产品&#xff0c;有益于公司及个人。之前接触多的UI还是…...

Windows系统提示synsoacc.dll文件报错要怎么解决?

一、文件丢失问题&#xff1a;深度剖析与应对策略 文件丢失是电脑运行时常见的问题之一。它可能由多种原因引起&#xff0c;如硬盘故障、病毒攻击、不当的文件操作等。当Windows系统提示synsoacc.dll丢失时&#xff0c;通常意味着该文件对于当前正在运行的程序或系统服务至关重…...

React(一)—— router/useRef/useState

文章目录 项目地址一、构建项目1.1 使用vite构建项目1.2 所需插件二、Router2.1 安装router2.2 创建路由规则2.3 创建导航栏2.3.1 添加样式文件2.3.2 添加导航栏组件2.3.3 给每个页面添加Menu导航栏2.4 通过路由给页面传值三、Hooks3.1 useRef3.2 useRef操作DOM元素3.3 useRef进…...

ipad如何直连主机(Moonlight Sunshine)

Windows 被连接主机&#xff08;Windows&#xff09; 要使用的话需要固定ip&#xff0c;不然ip会换来换去&#xff0c;固定ip方法本人博客有记载Github下载Sunshine Sunshine下载地址除了安装路径需要改一下&#xff0c;其他一路点安装完成后会打开Sunshine的Web UI&#xff…...

音视频入门知识(二)、图像篇

⭐二、图像篇 视频基本要素&#xff1a;宽、高、帧率、编码方式、码率、分辨率 ​ 其中码率的计算&#xff1a;码率(kbps)&#xff1d;文件大小(KB)&#xff0a;8&#xff0f;时间(秒)&#xff0c;即码率和视频文件大小成正比 YUV和RGB可相互转换 ★YUV&#xff08;原始数据&am…...

v-if 和 v-show 的区别

一、原理区别 1. v-if 这是一个指令&#xff0c;用于条件性地渲染一个元素块。当v-if表达式的值为true时&#xff0c;元素及其包含的子元素才会被渲染到 DOM 中&#xff1b;当表达式的值为false时&#xff0c;元素及其子元素会被完全移除。这意味着在切换v-if的条件时&#x…...

解密MQTT协议:从QOS到消息传递的全方位解析

1、QoS介绍 1.1、QoS简介 使用MQTT协议的设备大部分都是运行在网络受限的环境下&#xff0c;而只依靠底层的TCP传输协议&#xff0c;并不 能完全保证消息的可靠到达。 MQTT提供了QoS机制&#xff0c;其核心是设计了多种消息交互机制来提供不同的服务质量&#xff0c;来满足…...

Java-02 深入浅出 MyBatis - MyBatis 快速入门(无 Spring) POM Mapper 核心文件 增删改查

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…...

Unity功能模块一对话系统(4)实现个性文本标签

本期我们将了解如何在TMPro中自定义我们的标签样式&#xff0c;并实现两种有趣的效果。 一.需求描述 1.定义<float>格式的标签&#xff0c;实现标签处延迟打印功能 2.定义<r" "></r>格式的标签&#xff0c;实现标签区间内文本片段的注释显示功能…...

git在idea中操作频繁出现让输入token或用户密码,可以使用凭证助手(使用git命令时输入的用户密码即可) use credential helper

1、打开 idea 设置&#xff0c;找到 git 路径 File | Settings | Version Control | Git 2、勾选 Use credential helper 即可...

毫米波雷达技术:(九)快时间窗和慢时间窗的概念

&#xff08;一&#xff09;快时间窗&#xff1a; 快时间窗通常指的是在雷达脉冲周期内&#xff0c;对每个脉冲回波进行采样的时间段。这个时间段非常短&#xff0c;通常在 0 − 100 n s 0-100ns 0−100ns 。在快时间窗内&#xff0c;雷达系统会对接收到的回波信号进行高分辨…...

宠物行业的出路:在爱与陪伴中寻找增长新机遇

在当下的消费市场中&#xff0c;如果说有什么领域能够逆势而上&#xff0c;宠物行业无疑是一个亮点。当人们越来越注重生活品质和精神寄托时&#xff0c;宠物成为了许多人的重要伴侣。它们不仅仅是家庭的一员&#xff0c;更是情感的寄托和生活的调剂。然而&#xff0c;随着行业…...

Android MQTT关于断开连接disconnect报错原因

最近项目遇到一个需求&#xff0c;就是在登录状态的时候。才能接收到消息。所有我在上线&#xff0c;下线状态的时候。做了MQTT断开和连接的动作。然后就是发生了。我们标题的这关键点了。直接报错了。报错的内容如下&#xff1a; MqttAndroidClient unregisterRecevicer afte…...

Unity3D中Huatuo可行性的思维实验详解

引言 Unity3D作为一款功能强大的跨平台游戏引擎&#xff0c;在游戏开发领域具有举足轻重的地位。它不仅支持2D和3D游戏开发&#xff0c;还广泛应用于虚拟现实、建筑可视化等领域。其中&#xff0c;Huatuo作为一个强大的热更新解决方案&#xff0c;通过扩展Unity的IL2CPP运行时…...

ES-聚合分析

ES的聚合分析 什么是ES的聚合分析 ElasticSearch除搜索意外&#xff0c;提供的针对ES数据进行统计分析的功能通过聚合&#xff0c;我们会得到一个数据的概览&#xff0c;是分析和总结全套的数据&#xff0c;而不是寻找单独的文档高性能&#xff0c;只要一条语句就可以得到分析…...

【CSS in Depth 2 精译_093】16.2:CSS 变换在动效中的应用(上)—— 图标的放大和过渡效果的设置

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第五部分 添加动效 ✔️【第 16 章 变换】 ✔️ 16.1 旋转、平移、缩放与倾斜 16.1.1 变换原点的更改16.1.2 多重变换的设置16.1.3 单个变换属性的设置 16.2 变换在动效中的应用 ✔️ 16.2.1 放大图…...

Linux Debian安装ClamAV和命令行扫描病毒方法,以及用Linux Shell编写了一个批量扫描病毒的脚本

ClamAV是一个开源的跨平台病毒扫描引擎&#xff0c;用于检测恶意软件、病毒、木马等安全威胁。 一、Linux Debian安装ClamAV 在Linux Debian系统上安装ClamAV&#xff0c;你可以按照以下步骤进行&#xff1a; 更新软件包列表&#xff1a; 打开终端并更新你的软件包列表&#…...

Spring创建异步线程,使用@Async注解时不指定value可以吗?

在Spring中使用Async注解时&#xff0c;不指定value是可以的。如果没有指定value&#xff08;即线程池的名称&#xff09;&#xff0c;Spring会默认使用名称为taskExecutor的线程池。如果没有定义taskExecutor线程池&#xff0c;则Spring会自动创建一个默认的线程池。 默认行为…...

二分和离散化

为什么把二分和离散化放一起&#xff1a;因为离散化其实是一种二分整数的过程。 二分 相信大家都接触过二分查找&#xff08;折半查找&#xff09;&#xff0c;这就是二分的思想。 二分通过每次舍弃一半并不存在答案的区间&#xff0c;进而快速锁定要求的答案&#xff08;二…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

起重机起升机构的安全装置有哪些?

起重机起升机构的安全装置是保障吊装作业安全的关键部件&#xff0c;主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理&#xff1a; 一、超载保护装置&#xff08;核心安全装置&#xff09; 1. 起重量限制器 功能&#xff1a;实时监测起升载荷&a…...

LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考

目录 lua脚本 记录流水 记录流水的作用 流水什么时候删除 我们在做库存扣减的时候&#xff0c;显示基于Lua脚本和Redis实现的预扣减 这样可以在秒杀扣减的时候保证操作的原子性和高效性 lua脚本 // ... 已有代码 ...Overridepublic InventoryResponse decrease(Inventor…...

CMS内容管理系统的设计与实现:多站点模式的实现

在一套内容管理系统中&#xff0c;其实有很多站点&#xff0c;比如企业门户网站&#xff0c;产品手册&#xff0c;知识帮助手册等&#xff0c;因此会需要多个站点&#xff0c;甚至PC、mobile、ipad各有一个站点。 每个站点关联的有站点所在目录及所属的域名。 一、站点表设计…...

云原生时代的系统设计:架构转型的战略支点

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、云原生的崛起&#xff1a;技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深&#xff0c;传统的 I…...