Quartz框架介绍

news2024/11/13 12:27:16

一、何为Quartz

        Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。

二、使用Quartz

2.1 依赖导入

        Spring是整合了Quartz的,所以我们在Spring工程中导入以下依赖即可:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.2 核心组件 

        1)任务Job(接口):即想要调用的任务类,需要实现org.quartz.job接口,并重写execute()方法,任务调度时会执行execute()方法。(最新版本继承QuartzJobBean类,重写executeInternal方法)
        2)触发器Trigger(接口):即执行任务的触发器,当满足什么条件时会去执行你的任务Job,主要分为根据时长间隔执行的SimpleTrigger和根据日历执行的CronTrigger
        3)调度器Scheduler(接口):即将Trigger和Job绑定之后,根据Trigger中的设定,负责进行Job调度的组件。

        我们首先完成一个任务类:

package all.demo.test;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyJob extends QuartzJobBean // 或者 implements Job
{ // 实现Job接口

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 重写父类的execute方法
//    @Override
//    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//        // 这里就是任务!放任务的业务逻辑!
//        // 实时获取天气、订单定时取消等
//        String now = simpleDateFormat.format(new Date());
//        System.out.println("MyJob is running at " + now);
//    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        String now = simpleDateFormat.format(new Date());
        System.out.println("MyJob is running at " + now);
    }
}

        接着我们在主程序中设置触发器和调度器:

package all.demo.test;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class QuartzTest {
    public static void main(String[] args) {

        try {
            // 1. 创建任务调度器Scheduler,需要从工厂类中获取,需要注意的是Scheduler是一个接口,不能直接new
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 2. 创建触发器,定义了任务调度的时机(延迟、循环、定时...)
            // SimpleTrigger
//            CronTrigger 依据时间表达式构造的触发器
            SimpleTrigger // 创建简单触发器,也可以写成Trigger
                    trigger = TriggerBuilder.newTrigger()
                            .withIdentity("trigger1", "group1") // 给定触发器唯一标识,设置触发器名称和组名
                            .startAt(new Date(System.currentTimeMillis() + 3000)) // 设置触发器开始时间
                            .withSchedule(
                                    SimpleScheduleBuilder // 简单调度方式
                                            .simpleSchedule() // 创建调度规则对象
                                            .withIntervalInSeconds(3) // 设置间隔时间,单位为秒
                                            .withRepeatCount(10) // 设置循环次数
                            ) // 设置触发器调度方式,比如循环、延迟、定时等,即调度规则
                            //.endAt() // 设置触发器结束时间
                            .build();


//            // 3. JobBuilder创建任务对象
//            JobBuilder jobBuilder = JobBuilder.newJob(MyJob.class) // 创建任务对象
//                            .withIdentity("job1", "group1"); // 给定任务唯一标识,设置任务名称和组名
//            // 4. JobDetail才是真正的任务对象,它里面包含了任务名称、组名、任务执行类等
//            JobDetail jobDetail = jobBuilder.build();
            JobDetail job = JobBuilder.newJob(MyJob.class)
                    .withIdentity("job1", "group1")
                    .build();

            // 5. 将任务和触发器关联起来
            scheduler.scheduleJob(job, trigger);

            // 启动任务
            scheduler.start();

            // 在start()之后,在shutdown()之前,需要对任务进行装载



            // 暂停所有任务(还有其他的也可以暂停)
//            scheduler.pauseAll();
            // 恢复所有任务
//            scheduler.resumeAll();





            // 停止调度器
//            scheduler.shutdown();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}

        我们运行一下程序:

MyJob is running at 2024-09-18 16:42:04
MyJob is running at 2024-09-18 16:42:07
MyJob is running at 2024-09-18 16:42:10
MyJob is running at 2024-09-18 16:42:13
MyJob is running at 2024-09-18 16:42:16
MyJob is running at 2024-09-18 16:42:19
MyJob is running at 2024-09-18 16:42:22
MyJob is running at 2024-09-18 16:42:25
MyJob is running at 2024-09-18 16:42:28
MyJob is running at 2024-09-18 16:42:31
MyJob is running at 2024-09-18 16:42:34

        以上就是一个定时任务的简单应用。

2.3 实现原理

         

         首先,我们是要有一个Scheduler对任务的调度进行管理,那么只要我们有了合适的Job和Trigger的一个组合,我们将它们放到其中,即可对这个任务进行管理。

        其次,我们看下Job,对于一个任务类来说,里面放的就是我们要想实现功能的具体业务逻辑代码,例如实时获取天气预报、超时订单清理等。具体来说我们只需要实现Job接口(或者去继承QuartzJobBean),重写当中的execute(executeInternal)方法,完成业务逻辑代码即可。这样一来,我们可以写出很多的任务。

        接着,与这些任务(这些任务就好比死的)相关联的是Trigger,一个触发器。触发器那就规定了对于一个任务它的执行是符合于什么样的规律。触发器分为两类,SimpleTrigger和CronTrigger。第一个是根据时长间隔来触发的,好比对于接受的每一个订单,在我们业务处理的过程中就应该为其设置上一个好比间隔10分钟的触发器,在10分钟后对超时的订单进行业务逻辑处理。而第二个是可以设置定点触发的,好比每周三晚上10:00。举个例子,我们在一些网站上登录时会设置我们的生日,我们就可以设计一个每天晚上00:00的一个触发器,完成一个查询所有用户今天生日从而给他们发送一条祝福短信的业务。

        那么,我们有了一些设计好的任务以及设定好的规则。其次,就是将我们的任务Job用JobDetail包装起来,用包装起来的JobDetail和一个Trigger得到一个组合。将这个组合注册到Scheduler中,那么就会依照这种规则去执行这个任务。

        其中,对于相同的任务我们可以和不同的Trigger组合,从而对一个任务可能有多种规则与之匹配。但是一个Trigger只能与一个任务JobDetail匹配。想实现相同规则,就在写一个Trigger。

三、细说Quartz

3.1 Scheduler

        Scheduler对任务进行调度管理

        在Quartz中Scheduler由StdSchedulerFactory所创建(其他方法暂不介绍)。使用的是StdScheduler。

3.2 Trigger

        Trigger是用于定义调度时间的元素,也就是按照什么样的规则去执行任务。

        Quartz中提供了四种类型的Trigger:

        SimpleTrigger、CronTrigger、DateIntervalTrigger、NthIncludeDayTtigger。后两个不常用。

        以上四种可以满足绝大部分的需求。

3.2.1 SimpleTrigger - 简单

        可以满足的是:在具体的时间点执行一次;或在具体时间点执行,然后以指定的时间间隔重复执行若干次。简单说就是能做到1.延时任务(超时订单处理)、2.循环任务(每隔指定周期循环一次,可以无限循环、也可以循环指定的次数、还可以循坏到指定的时间点)、3.定时任务(每晚12:00发送生日祝福)。

        常见例子:

package all.demo.test;

import org.quartz.DateBuilder;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;

import java.util.Date;

public class QuartzSimpleTest {
    public static void main(String[] args) {
        // 使用强制类型转化,获得一个SimpleTrigger对象
        // 指定开始时间,但是不重复
        SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // 设置触发器名称和组名
                .startAt(new Date(System.currentTimeMillis())) // 设置触发器开始时间
                .forJob("job1", "group1") // 设置触发器关联的任务名称和组名
                .build();

        // 使用SimpleScheduleBuilder创建一个SimpleTrigger对象,设置重复执行5次,每1秒执行一次
        SimpleTrigger trigger2 = (SimpleTrigger) TriggerBuilder.newTrigger()
                .withIdentity("trigger2", "group2")
                .startAt(new Date(System.currentTimeMillis()))
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule() // 设置SimpleTrigger的调度器
                                .withRepeatCount(5) // 重复5次
                                .withIntervalInSeconds(1) // 每1秒执行一次 // .var
                )
                .forJob("job2", "group2")
                .build();

        // 5分钟后开始触发,只触发一次
        SimpleTrigger trigger3 = (SimpleTrigger) TriggerBuilder.newTrigger()
                .withIdentity("trigger3", "group3")
                // futureDate方法创建一个Date对象,表示5分钟后
                .startAt(DateBuilder.futureDate(5, DateBuilder.IntervalUnit.MINUTE))
                .forJob("job3", "group3")
                .build();

        // 立即触发,每隔5分钟触发一次,直到22;00
        SimpleTrigger trigger4 = (SimpleTrigger) TriggerBuilder.newTrigger()
                .withIdentity("trigger4", "group4")
                .startNow()
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                                .withIntervalInMinutes(5) // 每5分钟执行一次
                                .repeatForever() // 不停止,一直重复
                )
                .endAt(DateBuilder.dateOf(22, 0, 0)) // 结束时间
                .forJob("job4", "group4")
               .build();

        // 建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次
        SimpleTrigger trigger5 = (SimpleTrigger) TriggerBuilder.newTrigger()
                .withIdentity("trigger5", "group5")
                .startAt(DateBuilder.evenHourDate(null)) // 下一个小时整点触发
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                                .withIntervalInHours(2) // 每2小时重复一次
                                .repeatForever() // 不停止,一直重复
                )
                .forJob("job5", "group5")
                .build();
    }
}

3.2.2 CronTrigger - 强大

        使用Cron表达式来定义触发时间的规则。Cron表达式即一个基于日历的字符串表达。

3.2.2.1 Cron表达式

        详见Cron常用表达式详解_cron表达式-CSDN博客、cron表达式详解-CSDN博客。

        在线生成器Cron - 在线Cron表达式生成器 (ciding.cc)。

 

        而CronTrigger差别不大,只需修改个别地方。

package all.demo.test;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.TriggerBuilder;

import java.util.Date;

public class QuartzCronTest {
    public static void main(String[] args) {

        // 创建一个触发器,在开始10秒后,每5秒执行一次,直到15点整
        CronTrigger cronTrigger = (CronTrigger) TriggerBuilder.newTrigger()
                .withIdentity("cronTrigger", "group1")
                .startAt(DateBuilder.futureDate(10, DateBuilder.IntervalUnit.SECOND))
                .withSchedule(
                        // 每5秒执行一次
                        CronScheduleBuilder.cronSchedule("0/5 * * * * ?")

                )
                .endAt(DateBuilder.dateOf(15, 0, 0))
                .build();
    }
}

3.3 Misfire策略

        我们已经将触发器及任务绑定到了一个调度器上,并且在执行的过程中,调度器突发故障停了,或者说线程池不足(任务的调度会占用线程)。就会导致我们的触发器时间到了却没有执行任务的情况——Misfire。

        而处理这种情况的解决方法我们称为Misfire策略。对所有类型的Trigger,Quartz有指定默认的Misfire策略。(MISFIRE_INSTRUCTION_SMART_POLICY,即当恢复调度器时立即执行Misfire的任务,然后继续执行任务)

        而对于可以持久化的Trigger(存到数据库),我们可以通过指定Misfire策略,来定义其恢复之后的执行策略。

 3.4 Job与JobDetail

        任务类实现Job接口,接着包装在JobDetail中从而与Trigger组合绑定到Scheduler中。

        其中我们可以使用usingDateMap方法设置Trigger、JobDetail信息,从而在任务类中通过JobExecutionContext获取信息。

        任务类:

package all.demo.test;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyJob extends QuartzJobBean // 或者 implements Job
{ // 实现Job接口

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 重写父类的execute方法
//    @Override
//    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//        // 这里就是任务!放任务的业务逻辑!
//        // 实时获取天气、订单定时取消等
//        String now = simpleDateFormat.format(new Date());
//        System.out.println("MyJob is running at " + now);
//    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        // 我们发现这个方法有个参数,这个参数就是JobExecutionContext,这个对象中封装了任务的信息,包括任务名称、组名称、参数等。
        // 获取JobDetail或者Trigger的信息
        System.out.println(context.getTrigger().getJobDataMap().get("name"));
        System.out.println(context.getJobDetail().getJobDataMap().get("age"));
        String now = simpleDateFormat.format(new Date());
        System.out.println("MyJob is running at " + now);
    }
}

        测试:

package all.demo.test;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class QuartzMapTest {
    public static void main(String[] args) {
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            SimpleTrigger // 创建简单触发器,也可以写成Trigger
                    trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1") // 给定触发器唯一标识,设置触发器名称和组名
                    .startAt(new Date(System.currentTimeMillis() + 3000)) // 设置触发器开始时间
                    .withSchedule(
                            SimpleScheduleBuilder // 简单调度方式
                                    .simpleSchedule() // 创建调度规则对象
                                    .withIntervalInSeconds(3) // 设置间隔时间,单位为秒
                                    .withRepeatCount(10) // 设置循环次数
                    )
                    .usingJobData("name", "zhangsan")
                    .build();

//            JobDetail jobDetail = jobBuilder.build();
            JobDetail job = JobBuilder.newJob(MyJob.class)
                    .withIdentity("job1", "group1")
                    .usingJobData("age", "18")
                    .build();

            scheduler.scheduleJob(job, trigger);
            // 启动任务
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }

    }
}

3.5 监听器

        简单使用:

        1. 创建监听器

package all.demo.test.lisner;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MyJobListener implements JobListener {
    // 设置监听器的名称,要唯一标识
    @Override
    public String getName() {
        return "MyJobListener";
    }

    // 任务之前触发执行
    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        System.out.println("任务开始执行");
    }

    // 任务被否决触发执行时触发
    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        System.out.println("任务被否决");
    }

    // 任务执行完毕触发执行
    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        System.out.println("任务执行完毕");
    }
}

        2. 注册监听器

        TriggerListener类似。 

3.6 存储及持久化 

3.6.1 JobStore

        事实上,我们将JobDetail和Trigger组合加入到Scheduler中的时候,相关的信息(包括JobDataMap)就会存储在JobStore当中。即负责存储调度器中的工作数据:Job、Trigger、JobDataMap。存什么就执行什么。

        而持久化的概念就是在数据库中存储下我们的工作数据。假如我们现在有个需要执行100次的一个任务,其中每执行一次,JobStore中的数据会随时更新。当我执行了40次的时候,系统崩溃了。然后整个项目重启,任务需要重头开始(默认又从0开始,基于内存RAM)。所以这会不崩溃的话,我们实际上会执行140次。

        那么我们只要在崩溃的时候存储下JobStore中的内容,恢复的时候我们在数据库中拿工作数据就可以继续上一次的任务执行。

        以上即RAMJobStore,而我们现在使用JDBCJobStore。

        实现也不难,只要在配置中修改Quartz的配置属性JobStore修改为JDBCJobStore,并配上我们的数据源即可。支持集群。

spring:
  quartz:
    job-store-type: jdbc

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

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

相关文章

【每日刷题】Day125

【每日刷题】Day125 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 2. LCR 080. 组合 - 力扣&#…

数字IC设计\FPGA 职位经典笔试面试整理--语法篇 Verilog System Verilog(部分)

注&#xff1a; 资料都是基于网上一些博客分享和自己学习整理而成的 Verilog 1. 数据类型 Verilog一共有19种数据类型 基础四种数据类型&#xff1a;reg型&#xff0c;wire型&#xff0c;integer型&#xff0c;parameter型 reg型   reg类型是寄存器数据类型的关键字。寄存…

VBS学习1 - 语法、内置函数、内置对象

文章目录 概述执行脚本语法转义字符文本弹框msgbx定义变量dim&#xff08;普通类型&#xff09;定义接收对象set字符拼接&用户自定义输入框inputbox以及输入判断ifelse数组&#xff08;参数表最大索引&#xff0c;非数组容量&#xff09;有容量无元素基于元素确定容量 循环…

解密.bixi、.baxia勒索病毒:如何安全恢复被加密数据

导言 在数字化时代&#xff0c;数据安全已成为个人和企业面临的重大挑战之一。随着网络攻击手段的不断演进&#xff0c;勒索病毒的出现尤为引人关注。其中&#xff0c;.bixi、.baxia勒索病毒是一种新型的恶意软件&#xff0c;它通过加密用户的重要文件&#xff0c;迫使受害者支…

Java-数据结构-优先级队列(堆)-(一) (;´д`)ゞ

文本目录&#xff1a; ❄️一、优先级队列&#xff1a; ➷ 1、概念&#xff1a; ❄️二、优先级队列的模拟实现&#xff1a; ➷ 1、堆的概念&#xff1a; ➷ 2、堆的性质&#xff1a; ➷ 3、堆的创建&#xff1a; ▶ 向下调整&#xff1a; ➷ 4、堆的插入和删除&#xff1a; …

Java 值传递与引用传递

以下是包含引用的完整博客文章&#xff0c;以markdown格式输出&#xff0c;附带“Java 只有值传递”的相关参考来源。 Java 是一种广泛使用的面向对象编程语言&#xff0c;但对于值传递&#xff08;pass by value&#xff09;和引用传递&#xff08;pass by reference&#xff…

只有公网IP地址可以申请SSL证书吗?

是的&#xff0c;只有公网IP地址可以申请SSL证书。这是因为SSL证书主要用于加密互联网上的数据传输&#xff0c;确保通信的安全性和数据的完整性。而公网IP地址是互联网通信的核心&#xff0c;具有全球唯一性&#xff0c;允许互联网上的用户通过它们访问互联网上的资源。 具体…

北森笔试测评之言语理解到底难不难

前篇笔记我提到过&#xff0c;言语理解是最难的&#xff0c;有同学质疑了。言语我都会做呀&#xff0c;甚至有的可以搜到&#xff0c;而图标和图形我有的不会。这里需要指出&#xff0c;会做不等于作对&#xff0c;可以回顾到高中甚至初中的时候&#xff0c;感觉做的好的一般都…

dcmtk的自动输入数据纠错模式对DICOMDIR读取的影响

软件版本 dcmtk 3.6.7 自动纠错的全局变量 输入数据的自动纠错是一个全局变量&#xff0c;定义在dcmtk/dcmdata/dcobject.h中&#xff0c;如下所示&#xff1a; /** This flags defines whether automatic correction should be applied to input* data (e.g.\ stripping …

【多视图学习】基于多视图信息瓶颈的鲁棒表示学习

论文链接 代码链接 0.论文摘要和信息 摘要 信息瓶颈原理为表示学习提供了一种信息论方法&#xff0c;通过训练编码器来保留与预测标签相关的所有信息&#xff0c;同时最小化表示中的其他过量信息的量。然而&#xff0c;原始配方需要标记数据来识别多余的信息。在这项工作中&…

【C++ Primer Plus习题】16.8

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include <set> #includ…

Android 15 正式发布至 AOSP

Google官方宣布&#xff0c;将于近期发布了 Android 15&#xff0c;而在早些时候&#xff0c;Google已经将其源代码推送至 Android 开源项目 (AOSP)。未来几周内&#xff0c;Android 15 将在受支持的 Pixel 设备上正式推出&#xff0c;并将于今年晚些时候在三星、Honor、iQOO、…

easy_cloudantivirus

0x00前言 必须安装在virtualbox 攻击机&#xff1a;kali 靶机 easy_cloudantivirus 链接&#xff1a; https://www.vulnhub.com/entry/boredhackerblog-cloud-av,453/ 0x01信息搜集 经过测试发现靶场IP为192.168.56.106 进一部对IP搜集信息 发现8080端口youhttp服务。…

MySQL系列—12.Undo log

1、概念 DML 操作导致数据变化 , 将变化前的记录写入 Undo 日志。 作用 用于记录更改前的一份 copy &#xff0c;在操作出错时&#xff0c;可以用于回滚、撤销还原&#xff0c;只将数据库 逻辑地恢复到原来的样子 你 插入一条记录时&#xff0c;至少要把这条记录的主键值记下来…

上汽集团社招入职SHL测评:语言理解及数字推理高分攻略、真题题库

上汽集团社招待遇 上汽集团作为国内领先的汽车制造企业&#xff0c;其社招待遇和面试问题一直是求职者关注的焦点。以下是根据最新信息整理的上汽集团社招待遇及面试问题概览&#xff1a; 工资待遇&#xff1a;上汽集团的工资待遇在国内汽车行业中属于较高水平。根据不同职位和…

【C++二叉树】236.二叉树的最近公共祖先

236. 二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; 思路分析&#xff1a; 公共祖先满足的特征&#xff1a; p是q的孩子&#xff0c;则q是祖先&#xff0c;反之亦然。p和q是“我”的左子树和右子树中的节点&#xff0c;则“我”是祖先。&#xff08;如题目中给…

物联网——USART协议

接口 串口通信 硬件电路 电平标准 串口参数、时序 USART USART主要框图 TXE: 判断发送寄存器是否为空 RXNE: 判断接收寄存器是否非空 RTS为输出信号&#xff0c;用于表示MCU串口是否准备好接收数据&#xff0c;若输出信号为低电平&#xff0c;则说明MCU串口可以接收数据&#…

springboot实训学习笔记(5)(用户登录接口的主逻辑)

接着上篇博客学习。上篇博客是已经基本完成用户模块的注册接口的开发以及注册时的参数合法性校验。具体往回看了解的链接如下。 springboot实训学习笔记&#xff08;4&#xff09;(Spring Validation参数校验框架、全局异常处理器)-CSDN博客文章浏览阅读576次&#xff0c;点赞7…

NANO 9K玩转RISCV之ARDUINO开发环境

一、前言 在从0-1探索RISCV指令集的路上&#xff0c;我们用百元不到的NANO 9K开发板一步步的实现&#xff1a;  &#xff11;&#xff09;最小的内核架构 &#xff12;&#xff09;取值&#xff0c;译码和执行的过程&#xff08;电路实现ISA指令集&#xff09; &#xff1…

使用神经网络拟合6项参数

使用神经网络拟合6项参数 1. 数据预处理1.1 添加参数解析1.2 数据预处理逻辑1.3 数据归一化及划分1.4 数据标签处理逻辑1.5 数据转torch 2. 定义model2.1 CNN_LSTM2.2 Transformer 3. 定义train脚本3.1 loss和optimizer3.2 train3.3 predict 1. 数据预处理 1.1 添加参数解析 …