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: 8988

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/demo
    username: postgres
    password: 123456
    driver-class-name: org.postgresql.Driver

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

-- 创建任务调度表
create table public.t_task
(
    id         varchar(32)       not null
        constraint pc_task_id
            primary 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 {
 
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    /**
     * 创建JOB实例
     */
    @Override
    protected 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 {
 
    @Autowired
    private MyJobFactory myJobFactory;

    /**
     * 加载任务工厂
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(myJobFactory);
        return schedulerFactoryBean;
    }
 
    @Bean
    public 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 {

    @Autowired
    private TaskMapper taskMapper;

    @Test
    public 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 {
 
    @Autowired
    private QuartzManager quartzManager;
 
    @Autowired
    private 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 {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private List<Job> taskList;

    private Map<String, Job> taskMap;

    @PostConstruct
    public 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 {
        // 创建Job
        JobKey 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接口的类;

    @Autowired
    private 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 {

    @Override
    public 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 {

    @Override
    public 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);

        // 创建Job
        JobKey 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名判断当前容器中是否存在这个Job
        if (taskMap.containsKey(name)) {
            // 执行任务:获取Job类时,也用Job名
            Class<? extends Job> taskClazz = taskMap.get(name).getClass();
            // 创建任务式,用Job和唯一标识拼接作为任务名
            JobDetail jobDetail = JobBuilder.newJob(taskClazz).withIdentity(jobName, group).build();

            // 创建 JobDataMap 并放入唯一标识 id
            JobDataMap 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 并放入唯一标识 id
        JobDataMap 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 {

    @Override
    public 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完成定时任务执行配置热修改

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/944486.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)

1.安装video-player npm install video.js videojs-player/vue --save在main.js中配置全局引入 // 导入视频播放组件 import VueVideoPlayer from videojs-player/vue import video.js/dist/video-js.cssconst app createApp(App) // 视频播放组件 app.use(VueVideoPlayer)2…

从入门到精通:Vim 高效文本编辑全面指南

文章目录 前言&#x1f9ec;一、Vim 的编辑哲学&#xff1a;模式分离与高效键盘操作&#x1f9ec;二、基础命令与快捷键&#xff1a;从简单到熟悉&#x1f9ec;三、进阶功能&#xff1a;多文件、分屏与可视化模式&#x1f9ec;四、自定义配置与 .vimrc&#xff1a;打造你的专属…

正则表达式(三剑客之sed)

1.sed工具的使用 1.1 sed工具 1&#xff09;命令格式&#xff1a;sed -n ‘n’ p filename 1.2 打印某行 1&#xff09;打印第二行 [rootlocalhost ~]# sed -n 2p /etc/passwd 2&#xff09;第二行重复打印 [rootlocalhost ~]# sed 2p /etc/passwd 3&#xff09;所有行全部…

珞珈一号夜光遥感数据地理配准,栅格数据地理配准

目录 一、夜光数据下载&#xff1a; 二、夜光遥感数据地理配准 三、计算夜光数据值 四、辐射定标 五、以表格显示分区统计 五、结果验证 夜光数据位置和路网位置不匹配&#xff0c;虽然都是WGS84坐标系&#xff0c;不匹配&#xff01;&#xff01;&#xff01;不要看到就直接…

虚幻引擎是什么?

Unreal Engine&#xff0c;是一款由Epic Games开发的游戏引擎。该引擎主要是为了开发第一人称射击游戏而设计&#xff0c;但现在已经被成功地应用于开发模拟游戏、恐怖游戏、角色扮演游戏等多种不同类型的游戏。虚幻引擎除了被用于开发游戏&#xff0c;现在也用于电影的虚拟制片…

多个微服务 Mybatis 过程中出现了Invalid bound statement (not found)的特殊问题

针对多个微服务的场景&#xff0c;记录一下这个特殊问题&#xff1a; 如果启动类上用了这个MapperScan注解 在resource 目录下必须建相同的 com.demo.biz.mapper 目录结构&#xff0c;否则会加载不到XML资源文件 。 并且切记是com/demo/biz 这样的格式创建&#xff0c;不要使用…

使用Excel制作通达信自定义外部数据,安排!!!

Excel相信大家电脑上都有这个工具&#xff0c;相比敲编程代码&#xff0c;用这个去做自定义数据对大多数人&#xff0c;应该是比较友好的。自定义数据分为外部序列数据&#xff0c;看了一下内容理解起来比较多&#xff0c;分两期给大家介绍。为了照顾电脑基础薄弱的朋友&#x…

win10、win11-鼠标右键还原、暂停更新

系统优化 win 10jihuo win 11jihuo鼠标右键还原暂停更新 update 2024.12.28win 10 jihuo winx&#xff0c;打开powershell管理员&#xff0c;输入以下命令,选择1并等待 irm https://get.activated.win | iex参考&#xff1a;https://www.bilibili.com/video/BV1TN411M72J/?sp…

QT集成IntelRealSense双目摄像头2,集成OpenGL

上一篇文章写了如何把IntelRealSense摄像头的SDK集成到QT项目&#xff0c;并成功采集数据&#xff0c;在没有用OpenCV的情况下完成色彩数据&#xff0c;以及深度数据的显示。 具体地址&#xff1a;https://blog.csdn.net/qujia121qu/article/details/144734163 本次主要写如何…

数据分析的分类和EDIT思维框架

为了服务于企业不同层次的决策&#xff0c;商业数据分析过程需要提供相应的数据科学产出物。 一般而言&#xff0c;数据分析需要经历从需求层、数据层、分析层到输出层四个阶段。 第一个阶段是需求层——确定目标&#xff0c;具体目标需要依据具体的层次进行分析&#xff1a…

面试场景题系列:设计URL短链

1.场景需求界定 1.缩短URL&#xff1a;提供一个长URL&#xff0c;返回一个短很多的URL。 2.重定向URL&#xff1a;提供一个缩短了的URL&#xff0c;重定向到原URL。 3.高可用、可扩展性和容错性考量。 •写操作&#xff1a;每天生成1亿个URL。 •每秒的写操作数&#xff1a…

Linux 基本指令

目录 1.常见指令 1.1 ls指令 1.2 pwd指令 1.3 cd指令 1.4 touch指令 1.5 mkdir指令 1.6 rm和rmdir指令 1.7 man指令 1.8 cp指令 1.9 mv指令 ​编辑 1.10 cat指令 1.11 more指令 1.12 less指令 1.13 head指令 1.14.tail指令 1.15 时间相关的指令 1.16 cal…

WEB UI 创建视图

1 视图名称 (点第1创建视图) 2 模型节点 可以空 3 上下文节点 4 新增节点下的属性 &#xff0c;参考结构(先建好的结构) 5 选择视图类型&#xff1a;&#xff08;表单&#xff0c; 列表&#xff09; 表单 &#xff1a;单条数据 列表 &#xff1a;多条数据&#xff08;表格…

redis cluster实验详解

华子目录 实验环境准备部署redis cluster添加节点删除节点redis cluster集群维护 实验 环境准备 再开3台主机 先把之前3台源码编译的redis删除 [rootredis-node1 ~]# cd /usr/local/redis/ [rootredis-node1 redis]# make uninstall[rootredis-node2 ~]# cd /usr/local/redi…

【详细讲解】hive优化

1、开启本地模式 大多数的Hadoop Job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过&#xff0c;有时Hive的输入数据量是非常小的。在这种情况下&#xff0c;为查询触发执行任务消耗的时间可能会比实际job的执行时间要多的多。对于大多数这种情况&#xff0c;Hive可…

Unity3d UGUI如何优雅的实现Web框架(Vue/Rect)类似数据绑定功能(含源码)

前言 Unity3d的UGUI系统与Web前端开发中常见的数据绑定和属性绑定机制有所不同。UGUI是一个相对简单和基础的UI系统&#xff0c;并不内置像Web前端&#xff08;例如 Vue.js或React中&#xff09;那样的双向数据绑定或自动更新UI的机制。UGUI是一种比较传统的 UI 系统&#xff…

828华为云征文|使用sysbench对Flexus X实例对mysql进行性能测评

目录 一、Flexus X实例概述 1.1?Flexus X实例 1.2?在mysql方面的优势 二、在服务器上安装MySQL 2.1 在宝塔上安装docker 2.2 使用宝塔安装mysql 2.3 准备测试数据库和数据库表 三、安装sysbench并进行性能测试 3.1 使用yum命令sysbench 3.2?运行?sysbench 并进行…

影刀进阶指令 | Kimi (对标ChatGPT)

文章目录 影刀进阶指令 | Kimi &#xff08;对标ChatGPT&#xff09;一. 需求二. 流程三. 实现3.1 流程概览3.2 流程步骤讲解1\. 确定问题2\. 填写问题并发送3\. 检测答案是否出完 四. 运维 影刀进阶指令 | Kimi &#xff08;对标ChatGPT&#xff09; 简单讲讲RPA调用kimi实现…

【教程】通过Docker运行AnythingLLM

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 官方教程&#xff1a;Local Docker Installation ~ AnythingLLM 1、先创建一个目录用于保存anythingllm的持久化文件&#xff1a; sudo mkdir /app su…

游戏引擎学习第65天

回顾我们在模拟区域更改方面的进展 目前我们正在进行游戏的架构调整&#xff0c;目标是建立一个引擎架构。我们正在实施的一个关键变化是引入模拟区域的概念&#xff0c;这样我们可以创建非常大的游戏世界&#xff0c;而这些世界的跨度不必受限于单个浮点变量。 通过这种方式…