实现动态增QuartzJob,通过自定义注解调用相应方法

news2025/5/23 22:16:18

:::tip
动态增加Quartz定时任务,通过自定义注解来实现具体的定时任务方法调用。
:::
相关依赖如下

<!-- 用来动态创建 Quartz 定时任务 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

1. 注解及相关实体类

1. TaskDesc注解

用于描述定时任务的方法名和描述信息, 方便

import java.lang.annotation.*;

/**
 * @author eleven
 * @date 2025/2/25 9:45
 * @apiNote
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TaskDesc {
    String methodName();

    String desc();
}

2. 任务实体类

@Data
@TableName("sys_task_config")
@ApiModel(value="定时任务配置")
public class TaskConfig extends BaseEntity {

    @ApiModelProperty("定时任务表达式")
    private String cron;

    @ApiModelProperty("执行类的全限定名")
    private String execClass;

    @ApiModelProperty("方法名")
    private String execMethod;

    @ApiModelProperty("是否运行")
    private Boolean startFlag;

    @ApiModelProperty("任务名称")
    private String cronName;

    public String getExecMethod() {
        return StrUtil.isNotBlank(execMethod) ? execMethod.replace("()", "").trim() : execMethod;
    }

    public String getExecClass() {
        return StrUtil.isNotBlank(execClass) ? execClass.trim(): execClass;
    }

    public String getCron() {
        return StrUtil.isNotBlank(cron) ? cron.trim() : cron;
    }
} 

3. 可选任务配置 vo

用于前端展示,前端配置定时任务的时候只能从 @TaskDesc 注解中获取到的方法名中选择。
也是为了限制前端用户乱填方法名,避免定时任务执行失败

@Data
public class TaskDescVo {

    private Integer index;

    private String beanName;

    private String className;

    private String methodName;

    private String desc;
} 

4. 任务执行记录

@Data
@TableName("credit_task_run_log")
@ApiModel("定时任务日志")
public class TaskRunLog extends BaseEntity<TaskRunLog> {

    @NotBlank(message = "任务id不能为空")
    private String taskId;
     
    @ApiModelProperty("任务开始时间")
    private LocalDateTime runTime;
     
    @ApiModelProperty("任务完成时间")
    private LocalDateTime completedTime;
     
    @ApiModelProperty("任务间隔时间")
    private Long intervalSeconds;
     
    @ApiModelProperty("任务运行状态")
    private Boolean runFlag;
     
    @ApiModelProperty("任务运行消息")
    private String message;

    public LocalDateTime getRunTime() {
        return getTime(runTime);
    }

    public LocalDateTime getCompletedTime() {
        return getTime(completedTime);
    }

    public LocalDateTime getTime(LocalDateTime time) {
        return Optional.ofNullable(time).orElse(LocalDateTime.now());
    }

    public Long getIntervalSeconds() {
        return Math.abs(Duration.between(getRunTime(), getCompletedTime()).getSeconds());
    }
}

5. CronDto

前端传入选择的执行时间,通过CronUtil生成cron表达式

import lombok.Data;

import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * @author eleven
 * @date 2023/12/6 8:19
 * @apiNote
 */
@Data
public class CronDto {
    /**
     * 选择的小时
     */
    @NotNull(message = "执行小时参数不允许为空")
    private List<String> chooseHours;

    /**
     * 选择的天数
     */
    private List<String> chooseDays;

    /**
     * 选择周几执行
     */
    private List<String> chooseDayOfWeeks;
}

2. 定时任务配置

1. PostRunner

用于在项目启动的时候,从数据库中获取到所有的定时任务配置,然后根据配

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import net.lesscoding.task.domain.TaskConfig;
import net.lesscoding.task.service.TaskConfigService;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * @author eleven
 * @date 2024/11/11 15:01
 * @apiNote
 */
@Component
@Slf4j
public class PostRunner {
    @Autowired
    private TaskConfigService taskConfigService;

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private Gson gson;

    @PostConstruct
    public void run() throws Exception {
        List<TaskConfig> planTaskList = taskConfigService.selectAll();
        log.info("==============定时任务配置中心日志开始====================");
        log.info("计划任务列表:{}", gson.toJson(planTaskList));
        log.info("==============定时任务配置中心日志结束====================");
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        if (CollUtil.isNotEmpty(planTaskList)) {
            for (TaskConfig planTask : planTaskList) {
                JobDetail jobDetail = JobBuilder.newJob(RunnerJob.class)
                        .withIdentity(planTask.getId(), StrUtil.format("{}#{}", planTask.getExecClass(), planTask.getExecMethod()))
                        .build();
                Trigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(planTask.getId(), StrUtil.format("{}#{}", planTask.getExecClass(), planTask.getExecMethod()))
                        .startNow()
                        .withSchedule(CronScheduleBuilder.cronSchedule(planTask.getCron()))
                        .build();
                scheduler.scheduleJob(jobDetail, trigger);
                scheduler.start();
            }
        }
    }
}

2. Job类

具体 Quartz 任务执行的 Job, Quartz 最终会调用 RunnerJobexecute 方法来执行定时任务

import net.lesscoding.task.service.TaskConfigService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author eleven
 * @date 2024/11/11 14:22
 * @apiNote
 */
@Slf4j
@Component
public class RunnerJob implements Job {
    @Autowired
    private TaskConfigService taskConfigService;
    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobKey key = jobDetail.getKey();
        String planId = key.getName();
        log.info("{} trigger {}", planId, ((CronTriggerImpl) jobExecutionContext.getTrigger()).getCronExpression());
        log.info("{} jobKey {} time {}", planId, key, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        log.info("定时任务开始执行");
        try {
            taskConfigService.runPlan(jobDetail);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3. 定时任务控制器

用于前端展示定时任务配置,以及新增、修改、删除定时任务配置

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import net.lesscoding.task.core.annotations.Log;
import net.lesscoding.task.core.common.AjaxResult;
import net.lesscoding.task.core.enums.BusinessType;
import net.lesscoding.task.domain.CronDto;
import net.lesscoding.task.domain.TaskConfig;
import net.lesscoding.task.service.TaskConfigService;
import net.lesscoding.task.utils.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * @author eleven
 * @date 2024/11/11 15:56
 * @apiNote
 */

@Api(tags = "定时任务配置")
@RestController
@RequestMapping("/task/config")
public class TaskConfigController {

    @Autowired
    private TaskConfigService taskConfigService;

    @ApiOperation("查询配置列表")
    @PostMapping("/page")
    public AjaxResult page(@RequestBody TaskConfig taskConfig) {
        Page<TaskConfig> list = taskConfigService.getConfigList(taskConfig);
        return ResultUtil.success(list);
    }

    @ApiOperation("编辑配置")
    @PostMapping("/edit")
    @Log(title = "编辑定时任务配置", businessType = BusinessType.UPDATE)
    public AjaxResult edit(@RequestBody TaskConfig taskConfig) throws SchedulerException {
        return ResultUtil.toAjax(taskConfigService.editTaskConfig(taskConfig));
    }

    @PostMapping("/getCron")
    @ApiOperation("获取表达式")
    public AjaxResult getCron(@Valid @RequestBody CronDto dto) {
        return ResultUtil.success(taskConfigService.getCron(dto));
    }

    @ApiOperation("删除配置")
    @DeleteMapping("/del/{id}")
    @Log(title = "删除定时任务配置", businessType = BusinessType.DELETE)
    public AjaxResult del(@PathVariable String id) throws SchedulerException {
        return ResultUtil.toAjax(taskConfigService.delTaskConfig(id));
    }

    @ApiOperation("获取所有任务列表")
    @GetMapping("/taskList")
    public AjaxResult taskList() {
        return ResultUtil.success(taskConfigService.getAllTaskDescList());
    }
}

4. ServiceImpl实现类

用于实现定时任务的具体逻辑,包括获取所有任务列表、获取表达式、编辑配置、删除配置、获取配置列表、运行计划等方法

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.lesscoding.task.core.annotations.TaskDesc;
import net.lesscoding.task.core.common.GlobalException;
import net.lesscoding.task.dao.TaskConfigMapper;
import net.lesscoding.task.dao.TaskRunLogMapper;
import net.lesscoding.task.domain.CronDto;
import net.lesscoding.task.domain.TaskConfig;
import net.lesscoding.task.domain.TaskRunLog;
import net.lesscoding.task.model.vo.TaskDescVo;
import net.lesscoding.task.runner.RunnerJob;
import net.lesscoding.task.service.TaskConfigService;
import net.lesscoding.task.utils.CronUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author eleven
 * @date 2024/11/11 14:21
 * @apiNote
 */
@Service
@Slf4j
public class TaskConfigServiceImpl extends ServiceImpl<TaskConfigMapper, TaskConfig> implements TaskConfigService {

    @Autowired
    private TaskConfigMapper configMapper;
    @Autowired
    private TaskRunLogMapper runLogMapper;
    @Autowired
    private ConfigurableListableBeanFactory beanFactory;
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public List<TaskConfig> selectAll() {
        return configMapper.selectList(new QueryWrapper<>());
    }
    
    /**
     * 具体执行任务的方法
     * @param jobDetail Quartz的JobDetail对象,包含任务的详细信息
     * @return
     */
    @Override
    @Async
    public void runPlan(JobDetail jobDetail) {
        JobKey key = jobDetail.getKey();
        String taskId = key.getName();
        TaskRunLog runLog = new TaskRunLog();
        runLog.setId(IdUtil.simpleUUID());
        runLog.setTaskId(taskId);
        runLog.setRunTime(LocalDateTime.now());
        TaskConfig taskConfig = configMapper.selectById(taskId);
        if (taskConfig == null || !taskConfig.getStartFlag()) {
            String logStr = StrUtil.format("任务ID {} 不存在或配置为关闭 {}", taskId, taskConfig);
            log.info(logStr);
            runLog.setRunFlag(false);
            runLog.setCompletedTime(LocalDateTime.now());
            runLog.setMessage(logStr);
            runLogMapper.insert(runLog);
            return;
        }

        String className = taskConfig.getExecClass();
        String methodName = taskConfig.getExecMethod();
        try {
            // 这里可以直接通过 applicationContext 获取到类的实例
            // Object bean = applicationContext.getBean(className);
            // 加载类并获取实例
            Class<?> execClass = getClass().getClassLoader().loadClass(className);
            // 从Spring容器中获取实例
            Object bean = beanFactory.getBean(execClass);
            // 获取方法
            Method execMethod = execClass.getDeclaredMethod(methodName);
            // 执行方法
            Object invoke = execMethod.invoke(bean);
            runLog.setRunFlag(true);
            runLog.setMessage(String.valueOf(invoke));
        } catch (Exception e) {
            runLog.setRunFlag(false);
            runLog.setMessage(e.getCause().getMessage());
            log.error("执行任务失败", e);
        }
        runLog.setCompletedTime(LocalDateTime.now());
        runLogMapper.insert(runLog);
    }

    @Override
    public Page<TaskConfig> getConfigList(TaskConfig taskConfig) {
        PageDTO page = taskConfig.getPage();
        List<TaskConfig> list = configMapper.getPageByLike(page, taskConfig);
        page.setRecords(list);
        return page;
    }

    @Override
    public int editTaskConfig(TaskConfig taskConfig) throws SchedulerException {
        checkEditTaskConfig(taskConfig);
        if (StrUtil.isBlank(taskConfig.getId())) {
            return saveTaskConfig(taskConfig);
        }
        return updateTaskConfig(taskConfig);
    }

    @Override
    public int delTaskConfig(String id) throws SchedulerException {
        TaskConfig taskConfig = configMapper.selectById(id);
        deleteJob(taskConfig);
        return configMapper.deleteById(id);
    }

    private void checkEditTaskConfig(TaskConfig taskConfig) {
        boolean valid = CronUtil.isValid(taskConfig.getCron());
        if (!valid) {
            throw new GlobalException("cron表达式不合法");
        }
        try {
            Class<?> execClass = getClass().getClassLoader().loadClass(taskConfig.getExecClass());
            Object bean = beanFactory.getBean(execClass);
            if (bean == null) {
                throw new GlobalException("请检查当前类名是否存在");
            }
            Method declaredMethod = execClass.getDeclaredMethod(taskConfig.getExecMethod());
            if (declaredMethod == null) {
                throw new GlobalException(StrUtil.format("请检查当前方法{}#{}()是否存在", taskConfig.getExecClass(), taskConfig.getExecMethod()));
            }
        } catch (ClassNotFoundException e) {
            throw new GlobalException("请检查当前类名是否存在");
        } catch (NoSuchMethodException e) {
            throw new GlobalException(StrUtil.format("请检查当前方法{}#{}()是否存在", taskConfig.getExecClass(), taskConfig.getExecMethod()));
        }
        List<TaskConfig> allTasks = selectAll();
        List<TaskConfig> sameTaskList = allTasks.stream()
                .filter(item -> StrUtil.equals(item.getExecClass(), taskConfig.getExecClass())
                        && StrUtil.equals(item.getExecMethod(), taskConfig.getExecMethod()))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(sameTaskList)) {
            // 新增任务的时候存在相同的类名和方法名
            if (StrUtil.isBlank(taskConfig.getId())) {
                throw new GlobalException(StrUtil.format("任务{}.{}()已存在", taskConfig.getExecClass(), taskConfig.getExecMethod()));
            }
            // 修改任务的时候存在相同的类名和方法名
            if (sameTaskList.size() == 1 && !StrUtil.equals(sameTaskList.get(0).getId(), taskConfig.getId())) {
                throw new GlobalException(StrUtil.format("任务{}.{}()已存在", taskConfig.getExecClass(), taskConfig.getExecMethod()));
            }
        }
    }

    public int saveTaskConfig(TaskConfig taskConfig) throws SchedulerException {
        taskConfig.setId(IdUtil.simpleUUID());
        int effect = configMapper.insert(taskConfig);
        createNewScheduler(taskConfig);
        return effect;
    }

    public int updateTaskConfig(TaskConfig taskConfig) throws SchedulerException {
        deleteJob(configMapper.selectById(taskConfig.getId()));
        int effect = configMapper.updateById(taskConfig);
        createNewScheduler(taskConfig);
        return effect;
    }

    private void createNewScheduler(TaskConfig task) throws SchedulerException {
        log.info("开始执行创建新任务");
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = jobKey(task);
        JobDetail jobDetail = JobBuilder.newJob(RunnerJob.class)
                .withIdentity(jobKey)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(task.getId(), StrUtil.format("{}#{}", task.getExecClass(), task.getExecMethod()))
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule(task.getCron()))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
        log.info("任务创建完成");
    }

    /**
     * 阐述job
     *
     * @param task
     * @throws SchedulerException
     */
    public boolean deleteJob(TaskConfig task) throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = jobKey(task);
        boolean deleteJob = scheduler.deleteJob(jobKey);
        log.info("当前 jobKey {} 删除结果{}", jobKey, deleteJob);
        return deleteJob;
    }

    private JobKey jobKey(TaskConfig task) {
        JobKey jobKey = new JobKey(task.getId(), StrUtil.format("{}#{}", task.getExecClass(), task.getExecMethod()));
        log.info("当前任务 {}, jobKey{}", task, jobKey);
        return jobKey;
    }

    @Override
    public String getCron(CronDto dto) {
        boolean daysEmptyFlag = CollUtil.isEmpty(dto.getChooseDays());
        boolean dayOfWeeksEmptyFlag = CollUtil.isEmpty(dto.getChooseDayOfWeeks());
        if (daysEmptyFlag && dayOfWeeksEmptyFlag) {
            throw new RuntimeException("执行天数和星期必须选择一个");
        }
        if (!daysEmptyFlag && !dayOfWeeksEmptyFlag) {
            throw new RuntimeException("执行天数和星期只能选择一个");
        }

        String hours = String.join(",", dto.getChooseHours());
        String days = CollUtil.isEmpty(dto.getChooseDays()) ? "?" : String.join(",", dto.getChooseDays());
        String dayOfWeek = CollUtil.isEmpty(dto.getChooseDayOfWeeks()) ? "?" : String.join(",", dto.getChooseDayOfWeeks());
        String cronStr = String.format("0 0 %s %s * %s", hours, days, dayOfWeek);
        if (!CronUtil.isValid(cronStr)) {
            throw new RuntimeException("定时任务表达式不合法");
        }
        log.info("当前任务表达式 {}", cronStr);
        return cronStr;
    }

    @Override
    public List<TaskDescVo> getAllTaskDescList() {
        List<TaskDescVo> result = new ArrayList<>();
        List<String> beanNames = new ArrayList<>(Arrays.asList(applicationContext.getBeanDefinitionNames()));
        beanNames.sort(String::compareTo);
        TaskDescVo vo = null;
        for (String beanName : beanNames) {
            Object bean = applicationContext.getBean(beanName);
            // 使用 AopUtils 来获取代理对象的原始类, 否则获得的是代理类,无法获取@Service等类上的注解
            Class<?> beanClass = AopUtils.getTargetClass(bean);
            if (beanClass.isAnnotationPresent(TaskDesc.class)) {
                TaskDesc annotation = beanClass.getAnnotation(TaskDesc.class);
                vo = new TaskDescVo();
                vo.setMethodName(annotation.methodName());
                vo.setDesc(annotation.desc());
                vo.setBeanName(beanName);
                vo.setClassName(beanClass.getName());
                vo.setIndex(beanNames.indexOf(beanName));
                result.add(vo);
            }
        }
        return result;
    }

    private CronDto parseCron(String cron) {
        String[] split = cron.split(" ");
        // 计算几个小时
        String cronHours = split[2];
        // 计算几天
        String cronDays = split[3];
        // 计算的周期
        String cronDayOfWeeks = split[5];
        CronDto cronDto = new CronDto();
        cronDto.setChooseHours(Arrays.asList(cronHours.split(",")));
        cronDto.setChooseDays(Arrays.asList(cronDays.split(",")));
        cronDto.setChooseDayOfWeeks(Arrays.asList(cronDayOfWeeks.split(",")));
        return cronDto;
    }
}

5. CronUtil

import org.quartz.CronExpression;

import java.text.ParseException;
import java.util.Date;

/**
 * cron表达式工具类
 *
 * @author ruoyi
 */
public class CronUtil {
    /**
     * 返回一个布尔值代表一个给定的Cron表达式的有效性
     *
     * @param cronExpression Cron表达式
     * @return boolean 表达式是否有效
     */
    public static boolean isValid(String cronExpression) {
        return CronExpression.isValidExpression(cronExpression);
    }

    public static void main(String[] args) {
        System.out.println(isValid("0/1 * * * * ?"));
    }

    /**
     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
     *
     * @param cronExpression Cron表达式
     * @return String 无效时返回表达式错误描述,如果有效返回null
     */
    public static String getInvalidMessage(String cronExpression) {
        try {
            new CronExpression(cronExpression);
            return null;
        } catch (ParseException pe) {
            return pe.getMessage();
        }
    }

    /**
     * 返回下一个执行时间根据给定的Cron表达式
     *
     * @param cronExpression Cron表达式
     * @return Date 下次Cron表达式执行时间
     */
    public static Date getNextExecution(String cronExpression) {
        try {
            CronExpression cron = new CronExpression(cronExpression);
            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
    }
}

6. @TaskDesc注解使用

@TaskDesc注解的类需要使用@Component注解标注,被SpringBoot容器管理到定时任务才能正常执行

import net.lesscoding.task.core.annotations.TaskDesc;
import net.lesscoding.task.dao.EvaluateDsCustomerMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.jexl3.JexlEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author eleven
 * @date 2025/3/12 15:11
 * @apiNote 客户评分计数器
 */
@Component
@Slf4j
@TaskDesc(methodName = "scoreCounter", desc = "客户评分计数器")
public class CustomerScoreCounter extends AbstractScoreCounter {

    @Autowired
    private EvaluateDsCustomerMapper dsCustomerMapper;
    @Autowired
    private JexlEngine jexlEngine;
    
    // 定时任务实际执行的方法
    @Override
    public void scoreCounter() {
        calcScoreAndSave(2, null, "customer_id",dsCustomerMapper.selectList(null));
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyTorch可视化工具——使用Visdom进行深度学习可视化

文章目录 前置环境Visdom安装并启动VisdomVisdom图形APIVisdom静态更新API详解通用参数说明使用示例Visdom动态更新API详解1. 使用updateappend参数2. ~~使用vis.updateTrace方法~~3. 完整训练监控示例 Visdom可视化操作散点图plot.scatter()散点图案例线性图vis.line()vis.lin…

企业级爬虫进阶开发指南

企业级爬虫进阶开发指南 一、分布式任务调度系统的深度设计 1.1 架构设计原理 图表 1.2 核心代码实现与注释 分布式锁服务 # distributed_lock.py import redis import timeclass DistributedLock:def __init__(self, redis_conn):self.redis = redis_connself.lock_key = …

网络安全-等级保护(等保) 2-7 GB/T 25058—2019 《信息安全技术 网络安全等级保护实施指南》-2019-08-30发布【现行】

################################################################################ GB/T 22239-2019 《信息安全技术 网络安全等级保护基础要求》包含安全物理环境、安全通信网络、安全区域边界、安全计算环境、安全管理中心、安全管理制度、安全管理机构、安全管理人员、安…

数据结构实验10.1:内部排序的基本运算

文章目录 一&#xff0c;实验目的二&#xff0c;实验内容1. 数据生成与初始化2. 排序算法实现&#xff08;1&#xff09;直接插入排序&#xff08;2&#xff09;二分插入排序&#xff08;3&#xff09;希尔排序&#xff08;4&#xff09;冒泡排序&#xff08;5&#xff09;快速…

wps编辑技巧

1、编辑模式 2、图片提取方法&#xff1a;右键保存图片 可以直接右键保存下来看看是否是原始图&#xff0c;如果歪着的图&#xff0c;可能保存下来是正的&#xff0c;直接保存试下 3、加批注

开放世界RPG:无缝地图与动态任务的拓扑学架构

目录 开放世界RPG:无缝地图与动态任务的拓扑学架构引言第一章 地图分块系统1.1 动态加载算法1.2 内存管理模型第二章 任务拓扑网络2.1 任务依赖图2.2 动态可达性分析第三章 NPC行为系统3.1 行为森林架构3.2 日程规划算法第四章 动态事件系统4.1 事件传播模型4.2 玩家影响指标第…

【图像处理入门】1. 数字图像的本质:从像素到色彩模型

作为图像处理的开篇&#xff0c;本文将带你拆解数字图像的底层逻辑&#xff1a;从模拟图像到数字信号的神奇转换&#xff0c;到像素世界的微观构成&#xff0c;再到彩色图像的编码奥秘。通过 Python 代码实战&#xff0c;你将亲手触摸图像的 “基因”—— 像素值&#xff0c;并…

(已解决:基于WSL2技术)Windows11家庭中文版(win11家庭版)如何配置和使用Docker Desktop

目录 问题现象&#xff1a; 问题分析&#xff1a; 拓展&#xff1a; 解决方法&#xff1a; 1、使用WSL2技术&#xff08;亲测有效&#xff09; 注意&#xff1a; 2、开启Hyper-V功能&#xff08;未经亲测&#xff0c;待研究&#xff09; 问题现象&#xff1a; 今天想在本…

Ubuntu20.04部署KVM

文章目录 一. 环境准备关闭防火墙&#xff08;UFW&#xff09;禁用 SELinux更换镜像源检查 CPU 虚拟化支持 二. 安装KVM安装 KVM 及相关组件启动 libvirtd 服务验证安装创建虚拟机 一. 环境准备 4C8G&#xff0c;50G硬盘——VMware Workstation需要给虚拟机开启虚拟化引擎 roo…

OpenCV CUDA 模块图像过滤------创建一个高斯滤波器函数createGaussianFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::createGaussianFilter 是 OpenCV CUDA 模块中的一个工厂函数&#xff0c;用于创建一个高斯滤波器。这个滤波器可以用来平滑图像&#…

可视化图解算法43:数组中的逆序对

1. 题目 ​牛客网 面试笔试TOP101 描述 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007 数据范围&…

【Python】使用Python实现调用API获取图片存储到本地

使用Python实现调用API获取图片存储到本地 目录 使用Python实现调用API获取图片存储到本地1、项目概述2、核心功能3、环境准备4、代码实现5、结果查看 1、项目概述 开发一个自动化工具&#xff0c;用于从JSON数据源中提取图像ID&#xff0c;通过调用指定API获取未经压缩的原始…

腾讯2025年校招笔试真题手撕(一)

一、题目 有n 把钥匙&#xff0c;m 个锁&#xff0c;每把锁只能由一把特定的钥匙打开&#xff0c;其他钥匙都无法打开。一把钥匙可能可以打开多把锁&#xff0c;钥匙也可以重复使用。 对于任意一把锁来说&#xff0c;打开它的钥匙是哪一把是等概率的。但你无法事先知道是哪一把…

Vue3 与 Vue2 区别

一、Vue3 与 Vue2 区别 对于生命周期来说&#xff0c;整体上变化不大&#xff0c;只是大部分生命周期钩子名称上 “on”&#xff0c;功能上是类似的。不过有一点需要注意&#xff0c;组合式API的Vue3 中使用生命周期钩子时需要先引入&#xff0c;而 Vue2 在选项API中可以直接…

嵌入式学习笔记 - STM32 U(S)ART 模块HAL 库函数总结

一 串口发送方式&#xff1a; ①轮训方式发送&#xff0c;也就是主动发送&#xff0c;这个容易理解&#xff0c;使用如下函数&#xff1a; HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout); ②中断方式发送&#xff…

【VLNs篇】04:SayNav-为新环境中的动态规划到导航进行大型语言模型的基础构建

栏目内容论文标题SayNav: 为新环境中的动态规划到导航进行大型语言模型的基础构建 (SayNav: Grounding Large Language Models for Dynamic Planning to Navigation in New Environments)研究问题自主代理在未知环境中执行复杂导航任务&#xff08;如MultiON&#xff09;时&…

oracle使用SPM控制执行计划

一 SPM介绍 Oracle在11G中推出了SPM&#xff08;SQL Plan management&#xff09;,SPM是一种主动的稳定执行计划的手段&#xff0c;能够保证只有被验证过的执行计划才会被启用&#xff0c;当由于种种原因&#xff08;比如统计信息的变更&#xff09;而导致目标SQL产生了新的执…

Openwrt下使用ffmpeg配合自建RTSP服务器实现推流

目前在Openwrt下时mjpg_streamer实现UVC摄像头转网络摄像头的方案很多&#xff0c;这种方案视频服在路由模组中&#xff0c;在局域网中使用很方便。但是对于需要远程监控管理的情况&#xff0c;mjpg_streamer不适应&#xff0c;因为不在局域网中的播放器无法访问到路由模组中的…

wifi 如果检查失败,UI 就会出现延迟或缺失打勾的现象。

问题&#xff1a;connectedSsid 的初始化依赖 onCreate 中的状态检查&#xff0c;如果检查失败&#xff0c;UI 就会出现延迟或缺失打勾的现象。 WIFI界面上上的一个标识代表成功连接。重启后出现偶尔不打勾的情况。 原始代码&#xff1a; // if (connectedSsid !…

点云(point cloud):自动驾驶的“三维扫描图“

点云&#xff08;Point Cloud&#xff09;&#xff1a;就是用很多“点”来表示一个物体或场景的三维形状和结构。&#xff08;用点描绘的3D画&#xff0c;好比素描&#xff0c;但不是用线条勾勒&#xff0c;而是“点点点点”拼出物体形状&#xff09; 观察这幅图像&#xff0c;…