feat:增加个人博客系统文档
This commit is contained in:
120
docs/Web/SpringBoot/SpringBoot-Quartz.md
Normal file
120
docs/Web/SpringBoot/SpringBoot-Quartz.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: SpringBoot Quarzt简介和使用
|
||||
date: 2026-06-08
|
||||
---
|
||||
|
||||
# 一、基本概念
|
||||
  Quartz 是 Java 生态最主流的企业级作业调度框架,支持简单间隔触发、Cron 表达式、持久化到数据库、集群部署。
|
||||
- Job(任务) — 实现 execute()方法,写你要执行的业务逻辑
|
||||
- Trigger(触发器) — 定义"什么时候执行",常用 SimpleTrigger(固定间隔)和 CronTrigger(Cron 表达式)
|
||||
- Scheduler(调度器) — 把 Job + Trigger 绑定,负责启动和停止调度
|
||||
|
||||
# 二、原理
|
||||
  Job相当于员工,只知道干活;Trigger 相当于排班表,决定什么时候干活;Scheduler 相当于老板,统一管理 Job 和 Trigger。
|
||||
  Quartz内部有一个核心线程 QuartzSchedulerThread,会不停的检查哪个员工需要在啥时候干活,但并不是按照固定间隔轮询的方式,而是计算出最近一次 Trigger 的触发时间,通过 wait()精确等待,当等待结束后,调度线程会唤醒线程池中的工作线程,执行到点的 Job。如果当前没有任何 Trigger了,默认最多等待 30 秒,然后再重新扫描。如果此时有新增/修改/删除任务了,调度线程会被 notifyAll()立即唤醒,重新计算最近的触发时间。
|
||||
|
||||
# 三、安装
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-quartz</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
  持久化:
|
||||
```yml
|
||||
spring:
|
||||
quartz:
|
||||
job-store-type: jdbc
|
||||
```
|
||||
|
||||
  启用后会在数据库自动建表。
|
||||
|
||||
# 四、使用
|
||||
## 4.1 定义Job
|
||||
```java
|
||||
@Component
|
||||
@DisallowConcurrentExecution // 防止同一 Job 并发执行
|
||||
public class QuartzJob extends QuartzJobBean {
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) {
|
||||
// 获取任务内容
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
|
||||
// TODO 根据任务内容执行任务
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4.2 定义Trigger
|
||||
```java
|
||||
@Component
|
||||
public class QuartzUtils {
|
||||
public static JobDetail buildJobDetail(Task task, JobKey jobKey) {
|
||||
JobDataMap jobDataMap = new JobDataMap();
|
||||
jobDataMap.put("id", task.getId());
|
||||
jobDataMap.put("type", task.getType());
|
||||
jobDataMap.put("content", task.getContent());
|
||||
|
||||
return JobBuilder.newJob(QuartzJob.class)
|
||||
.withIdentity(jobKey) // 使用 id 作为唯一标识
|
||||
.usingJobData(jobDataMap)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Trigger buildTrigger(Task task, JobKey jobKey) {
|
||||
Trigger trigger;
|
||||
// 判断任务类型:Cron 任务或者一次性任务
|
||||
if (task.getCronExpression() != null && !task.getCronExpression().isEmpty()) {
|
||||
// 定时任务:使用 Cron 表达式
|
||||
trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(jobKey.getName() + "Trigger")
|
||||
.withSchedule(CronScheduleBuilder.cronSchedule(task.getCronExpression()))
|
||||
.forJob(jobKey)
|
||||
.build();
|
||||
} else if (task.getExecuteTime() != null) {
|
||||
// 一次性任务:使用 executeTime
|
||||
trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(jobKey.getName() + "Trigger")
|
||||
.startAt(task.getExecuteTime())
|
||||
.forJob(jobKey)
|
||||
.build();
|
||||
} else {
|
||||
// 如果没有 Cron 表达式和 executeTime,任务不能调度,抛出异常
|
||||
throw new CustomException("任务没有有效的 Cron 表达式或执行时间");
|
||||
}
|
||||
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
这里的Task可以根据实际情况定义:
|
||||
```java
|
||||
@Data
|
||||
public class Task {
|
||||
private Long id;
|
||||
private String type;
|
||||
private String content;
|
||||
private Date executeTime;
|
||||
private String cronExpression;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
## 4.3 执行Scheduler
|
||||
```java
|
||||
@Resource
|
||||
private Scheduler scheduler;
|
||||
|
||||
// 新增/更新
|
||||
JobKey jobKey = new JobKey(task.getId().toString());
|
||||
scheduler.scheduleJob(QuartzUtils.buildJobDetail(task, jobKey), QuartzUtils.buildTrigger(task, jobKey));
|
||||
|
||||
// 更新
|
||||
scheduler.rescheduleJob(QuartzUtils.buildJobDetail(task, jobKey), QuartzUtils.buildTrigger(task, jobKey));
|
||||
|
||||
// 删除
|
||||
scheduler.deleteJob(jobKey);
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: SpringBoot WebSocket使用
|
||||
title: SpringBoot WebSocket简介和使用
|
||||
date: 2026-06-08
|
||||
---
|
||||
|
||||
|
||||
@@ -9,7 +9,26 @@ npm install @vueuse/core
|
||||
```
|
||||
|
||||
# 二、使用
|
||||
## 2.1 useWebSocket
|
||||
## 2.1 实时获取当前时间
|
||||
```ts
|
||||
<template>
|
||||
<div class="card">
|
||||
<p>当前时间: {{ formattedTime }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useNow, useDateFormat } from '@vueuse/core'
|
||||
|
||||
// 获取当前时间响应式对象
|
||||
const now = useNow()
|
||||
|
||||
// 格式化时间
|
||||
const formattedTime = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss')
|
||||
</script>
|
||||
```
|
||||
|
||||
## 2.2 useWebSocket
|
||||
```ts
|
||||
<script setup lang="ts">
|
||||
iconst { status, send } = useWebSocket('wss://example.com/ws', {
|
||||
@@ -35,4 +54,105 @@ iconst { status, send } = useWebSocket('wss://example.com/ws', {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
```
|
||||
|
||||
## 2.3 防抖和节流
|
||||
| | 防抖 Debounce | 节流 Throttle |
|
||||
| :--- | :--- | :--- |
|
||||
| **执行时机** | 连续触发 **停下后** | **每隔一段时间** |
|
||||
| **执行次数** | 通常 1 次(最后) | 多次,但受限制 |
|
||||
| **典型场景** | 搜索联想、校验 | 滚动、拖拽 |
|
||||
|
||||
  防抖:
|
||||
```vue
|
||||
<template>
|
||||
<input @input="handleInput" placeholder="输入搜索关键字" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
|
||||
const keyword = ref('')
|
||||
|
||||
// 输入停止 500ms 后执行搜索
|
||||
const debouncedSearch = useDebounceFn((val) => {
|
||||
console.log('发起搜索请求:', val)
|
||||
// api.search(val)
|
||||
}, 500)
|
||||
|
||||
const handleInput = (e) => {
|
||||
keyword.value = e.target.value
|
||||
debouncedSearch(keyword.value)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
  节流:
|
||||
```vue
|
||||
<template>
|
||||
<div style="padding: 20px">
|
||||
<p>点击次数(被节流):{{ count }}</p>
|
||||
<button @click="throttledClick">
|
||||
狂点我(节流 500ms)
|
||||
</button>
|
||||
<p style="color:#999;font-size:12px">
|
||||
快速连点也不会一直 +1,每 500ms 最多加一次
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useThrottleFn } from '@vueuse/core'
|
||||
|
||||
const count = ref(0)
|
||||
|
||||
// 节流函数:500ms 内最多执行一次
|
||||
const throttledClick = useThrottleFn(() => {
|
||||
count.value++
|
||||
console.log('真正执行 +1')
|
||||
}, 500)
|
||||
</script>
|
||||
```
|
||||
|
||||
::: warning
|
||||
如果是表单提交,应该使用状态锁来限制,即当提交后,将按钮置为loading状态,提交成功后再重置。
|
||||
```vue
|
||||
<template>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ loading ? '提交中...' : '提交' }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 模拟接口请求
|
||||
await submitForm()
|
||||
ElMessage.success('提交成功')
|
||||
} catch (err) {
|
||||
ElMessage.error('提交失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const submitForm = () => {
|
||||
return new Promise(resolve => setTimeout(resolve, 1500))
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
Reference in New Issue
Block a user