定时器任务——若依源码分析
分析util包下面的工具类schedule utils:
ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。
createScheduleJob
createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobDetail 和 CronTrigger,设置调度策略和参数,然后将任务提交给调度器,并根据任务状态决定是否立即暂停
/*** 创建定时任务*/public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException{Class<? extends Job> jobClass = getQuartzJobClass(job);// 构建job信息Long jobId = job.getJobId();String jobGroup = job.getJobGroup();JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();// 表达式调度构建器CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);// 按新的cronExpression表达式构建一个新的triggerCronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)).withSchedule(cronScheduleBuilder).build();// 放入参数,运行时的方法可以获取jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);// 判断是否存在if (scheduler.checkExists(getJobKey(jobId, jobGroup))){// 防止创建时存在数据问题 先移除,然后在执行创建操作scheduler.deleteJob(getJobKey(jobId, jobGroup));}// 判断任务是否过期if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))){// 执行调度任务scheduler.scheduleJob(jobDetail, trigger);}// 暂停任务if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())){scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));}}
1. 获取要执行的 Job 实现类
Class<? extends Job> jobClass = getQuartzJobClass(job);
根据任务的并发属性,返回:
-
QuartzJob.class
或 -
QuartzDisallowConcurrentExecution.class不允许并发执行同一种任务
用于控制是否允许并发执行同一任务
QuartzJob和QuartzDisallowConcurrentExecution的父类:
/*** 抽象quartz调用** @author ruoyi*/
public abstract class AbstractQuartzJob implements Job
{private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);/*** 线程本地变量*/private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException{SysJob sysJob = new SysJob();BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));try{before(context, sysJob);if (sysJob != null){doExecute(context, sysJob);}after(context, sysJob, null);}catch (Exception e){log.error("任务执行异常 - :", e);after(context, sysJob, e);}}/*** 执行前** @param context 工作执行上下文对象* @param sysJob 系统计划任务*/protected void before(JobExecutionContext context, SysJob sysJob){threadLocal.set(new Date());}/*** 执行后** @param context 工作执行上下文对象* @param sysJob 系统计划任务*/protected void after(JobExecutionContext context, SysJob sysJob, Exception e){Date startTime = threadLocal.get();threadLocal.remove();final SysJobLog sysJobLog = new SysJobLog();sysJobLog.setJobName(sysJob.getJobName());sysJobLog.setJobGroup(sysJob.getJobGroup());sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());sysJobLog.setStartTime(startTime);sysJobLog.setStopTime(new Date());long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");if (e != null){sysJobLog.setStatus(Constants.FAIL);String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);sysJobLog.setExceptionInfo(errorMsg);}else{sysJobLog.setStatus(Constants.SUCCESS);}// 写入数据库当中SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);}/*** 执行方法,由子类重载** @param context 工作执行上下文对象* @param sysJob 系统计划任务* @throws Exception 执行过程中的异常*/protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}
这是一种 模板方法模式(Template Method):
定义任务执行的整体结构:
-
执行前(记录开始时间)
-
执行中(交由子类完成)
-
执行后(记录日志)
子类只需要实现 doExecute()
方法即可。
允许并发和不允许并发的场景理解
假设你配置了一个任务,每 10 秒执行一次,但这个任务某次执行花了 15 秒才结束。那么 Quartz 到第 10 秒的时候,上一次任务还没执行完,此时 Quartz 会:
-
如果没有加
@DisallowConcurrentExecution
→ Quartz 会再启动一个线程,执行下一次任务(出现并发执行)。 -
如果加了
@DisallowConcurrentExecution
→ Quartz 会等待上一次任务执行完再执行下一次,不并发。
2. 构建 JobDetail 对象
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
-
JobDetail 是 Quartz 的任务描述对象
-
withIdentity
给任务设定唯一 ID(JobKey) -
绑定任务类,Quartz 到点时会调用它的
execute
方法
3. 构建 Cron 调度规则
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
根据 cron 表达式
构造调度规则
-
handleCronScheduleMisfirePolicy()
设置“错过触发”的补救策略(MISFIRE)
什么是 Misfire(错过触发)?
当 Quartz 到点想执行一个任务时:
-
如果线程池没空,或者机器睡眠了,或者调度器重启中……
-
Quartz 就会错过这个触发点(misfire)
此时 Quartz 会根据我们设置的 misfire 策略 来决定是否补救、如何补救。
-
IgnoreMisfires()
→ 一恢复,就快速执行补回这 3 次(立刻补偿) -
FireAndProceed()
→ 一恢复,只执行 1 次补偿,然后按正常节奏 -
DoNothing()
→ 直接等下一分钟,不补
场景模拟:
你设置了一个定时任务:
-
每分钟执行一次
-
比如:
09:00
、09:01
、09:02
、09:03
、09:04
……
假设:
-
程序挂了 3 分钟(从
09:01
~09:03
) -
到
09:04
程序恢复了!
此时 Quartz 发现:哎,我错过了 09:01、09:02、09:03 的任务,应该怎么办?
策略 | 中文含义 | 发生了什么? |
---|---|---|
IgnoreMisfires() | 忽略错过,全部补跑 | 恢复时立刻把 09:01、09:02、09:03 的任务全部都补回来一次,快速连续执行三次。然后继续执行 09:04 |
FireAndProceed() | 补跑一次,继续执行 | 恢复时只补一次(比如执行 09:03),然后继续从 09:04 正常调度 |
DoNothing() | 错过就错过,不补 | Quartz 什么都不干,直接等下一次任务,也就是从 09:04 开始,前面三次全当没发生过 |
4. 构建 Trigger(触发器)
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)).withSchedule(cronScheduleBuilder).build();
-
Trigger 定义任务 何时触发
-
与 JobDetail 一起注册到 Scheduler
5. 设置运行时参数
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
-
JobDataMap 是任务执行时的上下文参数
-
把
SysJob
对象放进去,任务执行时可以获取
6. 清除已有同名任务(避免重复)
if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
-
防止任务已经存在,创建失败
-
先删除再重新注册
7. 判断任务是否过期(没有下一次执行时间就不注册)
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) {scheduler.scheduleJob(jobDetail, trigger);
}
-
有些 cron 表达式可能已经过时(比如定了过去的时间)
-
只有有下一次执行时间才注册
8. 如果任务状态是“暂停”,注册后立即暂停
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {scheduler.pauseJob(getJobKey(jobId, jobGroup));
}
-
数据库中任务状态为
PAUSE
-
即使注册了,也不立刻触发执行
Quartz 执行定时任务的完整过程
-
任务初始化
在 Spring 容器初始化完成后,
SysJobServiceImpl
中的@PostConstruct init()
方法会被自动调用。该方法首先清空调度器中已有的任务,然后从数据库中加载所有配置的定时任务(sys_job
表),并通过循环调用ScheduleUtils.createScheduleJob(...)
方法将它们逐一注册到 Quartz 的调度器中。 -
创建与注册任务
无论是系统启动时加载任务,还是前端新增任务,都会构建一个
SysJob
实体对象,包含任务名称、cron 表达式、调用方法(invokeTarget
)等信息。任务通过ScheduleUtils.createScheduleJob(...)
方法被包装为 Quartz 的JobDetail
和CronTrigger
,并使用scheduler.scheduleJob(...)
注册到调度器中。 -
等待触发
任务注册成功后,Quartz 会将其放入内部的 Trigger 队列。调度线程(
QuartzSchedulerThread
)会持续轮询所有 Trigger,根据任务的nextFireTime
判断是否该执行任务。 -
任务触发与执行
当某个任务的
nextFireTime <= 当前系统时间
时,Quartz 会从线程池中分配一个线程,实例化注册时绑定的 Job 类(如QuartzJob
或QuartzDisallowConcurrentExecution
),并调用其execute(JobExecutionContext context)
方法。 -
调用目标方法
QuartzJob
会在执行中调用 JobInvokeUtil.invokeMethod(SysJob)
,通过解析 invokeTarget
字符串提取出 Bean 名(或类的全限定名)、方法名与参数信息。如果是 Spring Bean,则通过 SpringUtils.getBean()
获取对象;如果是类名,则使用 Class.forName()
动态加载并实例化。随后调用重载的 invokeMethod()
方法,根据参数类型和值构建 Method
实例并执行。若无参数,调用 method.invoke(bean)
;若有参数,则调用 method.invoke(bean, params...)
,最终动态执行配置的方法逻辑。
obInvokeUtil.invokeMethod(SysJob):
public static void invokeMethod(SysJob sysJob) throws Exception{String invokeTarget = sysJob.getInvokeTarget();String beanName = getBeanName(invokeTarget);String methodName = getMethodName(invokeTarget);List<Object[]> methodParams = getMethodParams(invokeTarget);if (!isValidClassName(beanName)){Object bean = SpringUtils.getBean(beanName);invokeMethod(bean, methodName, methodParams);}else{Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();invokeMethod(bean, methodName, methodParams);}}
重载的 invokeMethod()
方法 ——根据参数调用有参方法还是无参方法:
/*** 调用任务方法** @param bean 目标对象* @param methodName 方法名称* @param methodParams 方法参数*/private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,InvocationTargetException{if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0){Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));method.invoke(bean, getMethodParamsValue(methodParams));}else{Method method = bean.getClass().getMethod(methodName);method.invoke(bean);}}
7. 记录执行日志
每次任务执行结束后,无论成功与否,系统都会将执行结果记录到 sys_job_log
表中,包括执行时间、状态、异常信息等,供前端“任务日志”模块查询查看。
暂停恢复定时任务:
/*** 任务调度状态修改* * @param job 调度信息*/@Override@Transactional(rollbackFor = Exception.class)public int changeStatus(SysJob job) throws SchedulerException{int rows = 0;String status = job.getStatus();if (ScheduleConstants.Status.NORMAL.getValue().equals(status)){rows = resumeJob(job);}else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)){rows = pauseJob(job);}return rows;}/*** 暂停任务* 功能:* 暂停一个正在运行的任务,包括:* 更新数据库中的任务状态为 "1"(暂停)* 命令 Quartz 调度器暂停该任务,不再触发执行* @param job 调度信息*/@Override@Transactional(rollbackFor = Exception.class)public int pauseJob(SysJob job) throws SchedulerException{Long jobId = job.getJobId();String jobGroup = job.getJobGroup();job.setStatus(ScheduleConstants.Status.PAUSE.getValue());int rows = jobMapper.updateJob(job);if (rows > 0){scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));}return rows;}/*** 恢复任务* 功能:* 恢复一个之前被暂停的任务:* 更新数据库中 status = 0(启用)* Quartz 恢复调度触发 * @param job 调度信息*/@Override@Transactional(rollbackFor = Exception.class)public int resumeJob(SysJob job) throws SchedulerException{Long jobId = job.getJobId();String jobGroup = job.getJobGroup();job.setStatus(ScheduleConstants.Status.NORMAL.getValue());int rows = jobMapper.updateJob(job);if (rows > 0){scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));}return rows;}
changeStatus()
是定时任务状态切换的总入口,内部通过调用 pauseJob()
或 resumeJob()
来完成数据库状态更新 + 调度器实际控制的双重操作,确保任务状态从“页面 → 数据库 → 调度器”三方始终一致。
总结
在企业级项目中,Quartz 常与数据库 + SpringBoot + 可视化后台(如若依)结合:
-
任务信息存在数据库中(如
sys_job
表) -
系统启动时从表中加载任务注册到调度器
-
管理页面可:
-
动态新增任务(配置 Bean 方法、参数)
-
启用/暂停任务
-
查看任务执行日志
-
🔧 关键代码点:
-
使用
Scheduler.scheduleJob()
注册任务 -
使用
pauseJob()
/resumeJob()
控制运行状态 -
使用
JobInvokeUtil
+反射
调用目标方法
相关文章:

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...