XXL-JOB这东西在我维护过的好几个项目里都出现过功能确实完整任务管理、调度日志、执行器控制台、失败告警一套下来相当成熟。但时间一长尤其是团队规模不大、微服务拆得不深的时候你会明显感觉到它“重”要单独维护调度中心的数据库、要部署admin控制台、要配执行器、还要定期处理版本升级带来的表结构变化。前阵子我处理一个跑了两年的老项目任务从几个涨到几十个XXL-JOB的调度中心在测试环境里被我拆了装、装了拆最后实在忍不了直接把调度这块抽了出来换成了基于Nacos的轻量方案。这套方案说白了就是用Nacos当注册中心管节点用Nacos配置中心管任务定义和Cron表达式再借助Nacos配置写入的版本机制实现简单的节点抢占让同一时刻只有一个实例执行任务。跑了几个月稳定性超出预期运维负担几乎为零。如果你想给中小团队找一套不那么笨重的分布式定时任务方案这篇文章要把我踩过的坑、选型的思路、具体的代码和排查过程都帮你理清楚。1. 为什么换掉XXL-JOB一点真实的心路历程1.1 XXL-JOB的“重”到底重在哪我说XXL-JOB重不是黑它是它的架构决定了它天生需要一个独立的调度中心。这个调度中心要运行在单独的进程里后面还得挂一个MySQL库里面初始化了十几张表从任务信息到调度日志、锁信息全都有。你要是单位里数据库资源紧张或者对PostgreSQL情有独钟那更难受因为有同事在社区里问“xxl-job适配postgresql怎么搞”底下回复都是让你自己改SQL脚本要么手工迁移表结构要么改方言配置折腾的功夫比写业务代码还多。再说升级。XXL-JOB版本更新也算勤可每次升级调度中心的包要替换数据库脚本可能要增量执行执行器依赖的版本也得跟着动。你要是同时维护十几个微服务每个服务里都嵌了执行器依赖一升级就是全局联动测试回归的范围一下就大了。还有一点XXL-JOB自带的那套调度、分片、告警能力对很多业务来说是用不上的。我们的任务无非就是凌晨同步数据、定是清理缓存、每天统计报表最核心的需求只有一个——别同一时刻两个实例一起跑。所以当我发现要把调度中心从Windows开发机搬到Linux测试机还要为它在防火墙里开端口的时候我就决定要换掉它了。1.2 换到Nacos方案的成本收益换方案之前我做过一次粗略的成本评估。旧方案里调度中心是一个独立部署单元差不多要占1C2G的资源还要单独备份数据库而新方案是零成本额外服务直接用现有的Nacos集群。我们微服务本身已经通过Spring Cloud Alibaba接入Nacos做注册和配置管理也就是说调度模块的底层能力已经存在了只是没人往这个方向用。当时我算了一下收益大概是这样减掉一个独立调度中心进程减掉一套调度表结构减掉执行器依赖换来的是任务配置可以走Nacos配置中心热更新节点注册直接用服务发现的结果任务互斥逻辑写在公共模块里所有微服务共享。更重要的是新方案的表结构我们完全自己掌控业务需要什么字段就加什么字段不用被框架的表结构绑死。有人可能担心稳定性说Nacos配置中心能当锁用吗我的回答是能但要注意边界。严格来说它不是Redisson那种强一致分布式锁但利用Nacos配置发布时的版本号特性可以在绝大多数定时任务场景下实现互斥。后文我会把原理和代码原原本本讲清楚包括它不适用什么场景。2. Nacos与周边组件的关系梳理选型必须知道的几件事2.1 Nacos和Consul到底选谁网上关于consul和nacos的区别讨论特别多我自己的选型经验是如果你主要用的是Spring Cloud体系那Nacos的优势非常明显它是“注册中心配置中心”二合一的Consul虽然也有KV存储能做配置但用起来没有Nacos那么顺手。Consul在服务发现和健康检查上做得很好特别是多数据中心原生支持容器网络环境下表现也稳但它的配置管理对中文团队来说上手要慢一些动态刷新的体感也没有Nacos那么直接。Nacos这边命名空间可以把开发、测试、生产隔得清清楚楚分组又把业务域分得干干净净。我在项目里就是用命名空间区分环境用group区分业务线任务配置放在各自的group下面互不干扰。这一点在写调度方案时特别重要因为你不想让测试环境的任务配置污染生产环境。另外热搜词里经常有人问“nacos namespaces未授权访问【原理扫描】复现与修复”这其实侧面说明Nacos用的人多被扫描得也多。我的态度很简单要裸奔就别怪扫描器。后面专门写一节怎么加固先记住一条——Nacos上线必须开鉴权不能图方便关掉。2.2 Ribbon和Nacos在企业级服务中的分工很多人会把Ribbon和Nacos混在一起其实它俩不在一个层面。Nacos是注册中心负责记录“有哪些服务实例、它们在哪个IP哪个端口、健康状态如何”Ribbon是客户端负载均衡器负责在调用方这一侧从注册中心拿到服务列表后按照轮询、随机或权重策略选一个实例去调用。放到我们的调度场景里Nacos的服务发现能力负责告诉我“当前有几个业务实例活着”我从中挑一个来执行任务。这里用的是注册中心能力跟Ribbon的负载均衡没有直接关系。实际上Spring Cloud LoadBalancer已经逐渐替换掉Ribbon了但微服务调用链路上它们解决的问题是一样的有多个实例怎么选一个合适的。搞清这个区别你就明白为什么Nacos方案能替代调度中心了——它本身就维护了每个服务实例的实时状态我只需要在上层写一点“选主”逻辑即可。2.3 为什么任务调度也需要注册中心传统的单机定时任务很简单Spring的Scheduled注解一加到点就执行。但微服务部署了多个实例同一个定时任务就会在每个实例里都跑一遍导致重复执行。解决的思路无非两种一种是统一调度中心来下发任务像XXL-JOB另一种就是让所有实例自己去抢任务谁能抢到谁执行。第二种方案必需的前提就是“每个实例知道自己叫什么、别人叫什么、谁还在线”这就是注册中心的活。Nacos把这块包了所以我只需要在任务执行前做一次“在线成员确认”和“抢占登记”。3. 环境准备从下载到跑起来一步都不含糊3.1 版本选择JDK、数据库、操作系统三者必须匹配很多人在Nacos安装上出问题根源是版本不匹配。我用的组合是Nacos 2.5.0JDK 1.8其实Nacos 2.x要求JDK 8及以上建议直接用JDK 8或者JDK 11MySQL 8.4.11。数据库这块要注意Nacos从2.2版本开始对MySQL 8.x支持得比较好但你用的mysql-connector-j驱动版本不能太低否则会报认证协议错误。MySQL 8.x的默认认证插件是caching_sha2_password旧驱动不认识连接就挂。我在配置里直接用8.4.11的驱动问题就消失了。如果你在ARM架构的服务器上部署记得下载nacos-server-2.5.0的arm版本官方在2.5.0之后对ARM处理器支持已经很成熟了。还有人在国产数据库环境里用Nacos比如连接达梦数据库社区里已经有人做了适配方案原理就是把Nacos的JDBC数据源替换成达梦的驱动再用对应的方言。这个不是官方主推的路径建议评估后再上生产。3.2 Windows和Linux下启动Nacos的完整步骤本地开发的时候我在Windows上跑Nacos下载的是nacos-server压缩包解压后进到bin目录直接执行startup.cmd -m standalone。这里有个高频坑Nacos 2.x默认是集群模式不加-m standalone起不来会一直报连接不上其他节点。还有端口问题8848是主端口另外还有9848这个gRPC端口会被使用防火墙只放8848是不行的。Linux服务器上也类似执行bin/startup.sh -m standalone日志在logs/start.out里。我建议第一次启动后先看日志确认“Nacos started successfully”再继续。很多人一看到控制台没输出就以为挂了其实进程在后台日志里才看得到真实情况。启动成功后访问http://localhost:8848/nacos默认账号密码是nacos/nacos这个必须马上改否则就是给扫描器送人头。3.3 建库建表与账户配置避免一上来就踩权限坑Nacos用MySQL存储数据前要先建一个库然后执行官方提供的nacos-mysql.sql脚本。这个脚本在conf目录下创建一堆以config_info、config_relation等开头的表。别自己手写建表脚本少字段或多字段后面都会出问题。建完表还要注意字符集我用的是utf8mb4避免任务配置里存中文或者特殊符号时乱码。账户方面给Nacos单独建一个数据库账号不要用root权限只给这个库的增删改查。这样就算出问题影响面也控制在单个库。更进一步如果你要用达梦或者其他数据库建库建表脚本也要对应更换。社区里有适配过的脚本但建议先在小环境试通再上生产。4. 基于Nacos的轻量调度方案设计核心思路拆解4.1 整体架构把“调度中心”拆成三个能力旧架构里XXL-JOB的调度中心是集中式的新架构里我把调度能力拆成三块全部依托Nacos实现。第一块是任务元数据包括任务名称、Cron表达式、执行的Bean名称、超时时间、开关状态这些全部作为JSON配置放在Nacos配置中心里。第二块是任务执行也就是每个微服务里跑一个调度器它定时从Nacos配置中心拉取任务清单解析Cron到点触发本地业务逻辑。第三块是互斥与分片互斥靠Nacos配置的写入抢占来实现分片靠注册中心里的实例列表来做。这个架构的好处是调度逻辑没有中心节点任何一个实例挂了其他实例照样能从配置中心拿到任务清单剩下健康的实例会重新抢占任务。不像传统的调度中心挂掉所有任务都停摆。4.2 任务配置的动态化配置中心管Cron表达式任务配置动态化是新方案的核心体验。在Nacos配置中心里新建一个任务配置dataId写成task-config.jsongroup写成我们自己定的业务组名内容大致长这样{ tasks: [ { name: dataSyncTask, cron: 0 0 2 * * ?, beanName: dataSyncTaskHandler, enabled: true }, { name: cacheCleanTask, cron: 0 */30 * * * ?, beanName: cacheCleanTaskHandler, enabled: false } ] }所有服务的实例都监听这个配置。配置更新后监听器拿到最新JSON对比旧配置把新增的任务注册到调度器里把删除的任务取消掉把enabled从true改成false的就暂停。整个过程不需要重启服务不需要发版这就是Nacos配置中心动态刷新的价值。4.3 分布式互斥利用Nacos配置的版本特性实现节点抢占这是整篇文章技术含量最高的一块。我先把原理讲清楚Nacos配置中心里同一个dataIdgroup的配置只有一个版本号任何客户端都可以向这个配置发起发布操作。如果两个实例同时发布Nacos会基于版本号做判断后面的发布要么基于最新版本要么因为版本冲突被拒绝。我利用的就是这个版本冲突。具体做法是给每个任务建一个“锁配置”dataId叫task-lock-{taskName}内容是一个JSON里面记录owner实例ID和抢占时间。任务触发前实例先获取当前配置的版本号再尝试publish一个把owner设为自身实例ID的新配置发布时带上cas版本号。如果发布成功说明这一轮锁被我抢到了如果发布失败说明别的实例已经抢到当前实例直接跳过。这套机制虽然不是严格的强一致锁但在无网络分区、正常运行的集群条件下足够避免任务双跑。这句话我一定要说清楚因为它在极端情况下会有边界问题比如Nacos集群脑裂或者配置中心短暂不可用。生产环境如果对一致性和可用性要求极高那就别省这个懒直接用Redis或者引入专业调度产品。但对大多数中小团队的业务定时任务来说Nacos抢占方案是性能足够、成本极低的选择。4.4 执行日志与失败补救轻量方案也要有兜底XXL-JOB有个很爽的特性是调度日志任务跑没跑、跑多久、结果如何都有记录。我用Nacos方案替代之后不能把日志功能丢掉否则出了问题连排查线索都没有。我的做法是建一张本地的task_execution_log表字段包括任务名、实例ID、开始时间、结束时间、执行状态、错误信息。每个任务执行时写一行失败时在错误信息里记录异常堆栈。这张表是自己建的所以想加扩展字段非常方便比如加个业务流水号把任务和业务数据关联起来。失败补救我用了两层第一层是在任务方法内部做try-catch重试适合临时性网络抖动第二层是写一个独立的失败扫描任务每天凌晨扫一遍前一天失败记录自动重试或者钉钉告警。这套兜底逻辑让我即使没有调度中心的自带告警也照样能及时发现问题。5. 落地实操Spring Boot集成Nacos调度模块5.1 服务注册与依赖配置第一步在Spring Boot项目里引入Nacos相关依赖。这里需要注意spring-cloud-alibaba版本和Spring Boot的对应关系我用的版本组合是Spring Boot 2.7.x配合Spring Cloud Alibaba 2021.0.5.0对应的Nacos版本是2.2.x但Nacos服务端我升到了2.5.0兼容性没问题。dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency配置文件里bootstrap.yml负责Nacos连接参数application.yml负责常规业务参数spring: application: name: order-service cloud: nacos: server-addr: 127.0.0.1:8848 username: nacos password: your-password discovery: namespace: dev group: DEFAULT_GROUP config: namespace: dev group: DEFAULT_GROUP file-extension: yaml注册完服务后可以在Nacos控制台的服务列表里看到实例。这个健康实例列表就是我们后续做任务分片和抢占的依据。5.2 动态任务调度器核心代码定时任务要支持动态注册和取消请弃用Component Scheduled的固定写法改用ThreadPoolTaskScheduler把每个任务当成一个ScheduledFuture来管理。Component public class DynamicTaskScheduler { private final ScheduledThreadPoolExecutor executor new ScheduledThreadPoolExecutor(4); private final MapString, ScheduledFuture? taskFutures new ConcurrentHashMap(); public void registerCronTask(String taskName, Runnable task, String cron) { if (taskFutures.containsKey(taskName)) { cancelTask(taskName); } CronTrigger trigger new CronTrigger(cron); ScheduledFuture? future executor.schedule(task, trigger); taskFutures.put(taskName, future); } public void cancelTask(String taskName) { ScheduledFuture? future taskFutures.remove(taskName); if (future ! null) { future.cancel(false); } } }这里的核心点是CronTrigger直接支持标准Cron表达式Nacos配置里怎么写这里就怎么解析。任务重注册时先取消旧的future再创建新的避免重复触发。5.3 任务节点抢占与热更新示例接下来是抢占逻辑的代码版。我用一个配置监听器每次任务配置刷新后重新注册所有任务任务执行前调用NacosLockService抢占锁。Component public class NacosTaskConfigListener { Resource private DynamicTaskScheduler taskScheduler; Resource private NacosLockService lockService; NacosConfigListener(dataId task-config.json, groupId DEFAULT_GROUP, timeout 3000) public void onConfigChange(String content) { ListTaskConfigItem tasks JsonUtils.parseList(content, TaskConfigItem.class); for (TaskConfigItem task : tasks) { if (task.isEnabled()) { taskScheduler.registerCronTask(task.getName(), () - { boolean locked lockService.tryLock(task.getName()); if (locked) { executeTask(task.getBeanName()); } }, task.getCron()); } else { taskScheduler.cancelTask(task.getName()); } } } }lockService的tryLock方法就是前面说的“读版本号-带cas发布锁配置”Service public class NacosLockService { Resource private ConfigService configService; public boolean tryLock(String taskName) { String dataId task-lock- taskName; String group DEFAULT_GROUP; try { String content configService.getConfig(dataId, group, 5000); long version configService.getConfigMeta(dataId, group).getVersion(); String newContent buildLockContent(); boolean publish configService.publishConfigCas(dataId, group, newContent, version); return publish; } catch (Exception e) { log.error(try lock failed, e); return false; } } }代码里的publishConfigCas是Nacos 2.x提供的原子操作接口底层会校验配置版本号只有匹配才能发布成功。如果两个实例同时抢只有一个实例的版本号匹配另一个就会发布失败锁自然就归第一个实例了。这里要说一个实际遇到的细节锁配置的getConfigMeta方法在旧版客户端里没有需要Nacos-client 2.x以上版本。所以客户端依赖一定不要用1.4的老包否则编译期就会卡住。5.4 与Spring Cloud微服务体系的整合细节这个调度模块我放到了单独的common-task-starter依赖里所有需要定时任务能力的业务服务直接引用。服务启动后从Nacos配置中心加载任务配置并根据当前实例是否抢占成功来动态启停任务。多个实例同时在线时只有一个持有锁其他实例虽然是空闲状态但保持着监听一旦持有锁的实例宕机Nacos配置过期不会自动释放锁所以还需要一个锁超时机制。我的做法是在锁配置里写入expireAt时间戳其他实例在执行前会检查锁是否过期如果过期就重新走抢占流程。这算是对纯配置抢占方案的一个重要补充否则实例优雅停机时锁永远不会释放其他实例也永远抢不到任务。再提一下和Nacos服务发现的整合如果你想做分片任务比如一份数据拆成多片每个实例处理一片可以通过NamingService.getAllInstances拿到当前服务的全部在线实例列表然后按序号取模分片。这个能力我们用在了一个批量数据修复任务上效果很理想。6. 常见问题排查与安全加固实录6.1 数据库兼容MySQL 8.4、达梦、PostgreSQL的调整思路先回应一下热词里的“xxl-job适配postgresql”。XXL-JOB默认的SQL脚本是MySQL方言的PostgreSQL需要自己改类型和语法特别是tinyint、datetime、自增主键这些改起来头疼。Nacos方案里没有这个困扰业务自己的任务日志表用什么数据库都行框架层不绑定任何数据库方言。MySQL 8.4.11这个版本比较新连接时有个坑是驱动版本。我用的连接字符串是jdbc:mysql://localhost:3306/nacos?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai驱动依赖必须是mysql-connector-j 8.4.x才能兼容caching_sha2_password认证。有人连接时报“Public Key Retrieval is not allowed”在URL后面加allowPublicKeyRetrievaltrue可以绕过。达梦数据库是另一个话题社区热词里有人问nacos 2.5.4连接达梦数据库实际上是Nacos通过数据源扩展机制支持了达梦驱动。官方默认只带MySQL驱动你需要自己引入达梦的DmJdbcDriver然后在application.properties里修改数据源配置。这个方案可行但建议先在Nacos单机模式下验证配置发布、回滚、监听这些核心功能再去做大范围部署。6.2 Nacos启动与客户端连接高频问题启动报错的排查方向其实很固定。先看logs/start.out如果报“No DataSource set”八成是数据库连接配置没生效检查spring.datasource.platform是否设为mysql。如果在集群模式启动会报连接其他节点超时确认是不是忘了加-m standalone。客户端连接不上优先检查三件事网络到8848和9848端口是否通namespace是否匹配很多人在控制台看到配置但客户端拿不到就是namespace填了名称而不是ID用户名密码是否正确。还有一个隐蔽问题Nacos 2.x的客户端默认走gRPC长连接如果你只放通8848经常出现服务注册正常但配置监听起来不生效的情况把9848端口一起放开就好了。Windows本地启动我也踩过一个坑startup.cmd一闪而过没有任何报错。这种情况通常是JDK环境变量没有配置到系统PATH或者内存参数过大。打开bin目录下的startup.cmd检查JAVA_HOME是否能正确找到jdk路径即可。6.3 安全配置鉴权、默认密钥、SSL证书和命名空间隔离Nacos未授权访问这个问题我在多个项目里都遇到过安全扫描几乎每次扫描报告都会提“nacos namespaces未授权访问”。这说明很多人部署之后没有开鉴权导致任何人都能通过8848/nacos控制台读取配置、管理服务。修复方法很简单在application.properties里开启鉴权同时修改默认密钥。具体参数是nacos.core.auth.enabledtrue nacos.core.auth.system.typenacos nacos.core.auth.plugin.nacos.token.secret.key换成自己生成的至少32位随机字符串 nacos.core.auth.server.identity.key自定义服务标识key nacos.core.auth.server.identity.value自定义服务标识value特别注意很多人只开了鉴权却忘了改默认密钥。Nacos源码里有一个默认的Base64密钥如果被扫描器识别出来可以直接伪造Token绕过鉴权等于没开。再配合方案本身的做法所有业务配置放在独立命名空间下按“环境业务”双重隔离就算有人扫描到端口也无法跨命名空间读取配置。生产环境如果对传输安全有要求可以配置HTTPSNacos支持在配置文件里指定SSL证书和私钥路径启用后控制台和客户端都走HTTPS协议这样配置内容和Token就不会明文跑在网络上。6.4 这套方案什么时候该停用写了这么多优势也该说说它的天花板。如果你的业务依赖调度平台的重试机制、依赖编排工作流、需要精细到每个任务的失败策略管理那这套Nacos轻量方案不够用。它更适合的任务场景是固定频率、固定业务逻辑、对执行结果记录要求不高的定时任务。另外如果你公司已经有生产级调度平台比如阿里云SchedulerX或者自研的调度中心那也没必要为了“轻量”而替换。架构选择永远要服务于团队规模和业务复杂度不要为了优雅而优雅。7. 一点真实体会轻量方案背后的架构取舍折腾完这套方案我最大的体会是架构没有绝对好坏只有合适不合适。XXL-JOB对大规模任务调度场景是好东西但对我们这种每天只有几十个定时任务、团队人数一只手数得过来的项目它就是过度设计。Nacos本来就在我们的基础设施里我把调度逻辑从独立系统折叠到业务进程里省掉的不仅是服务器资源更是长期的运维心智负担。还有个小经验想分享所有通过Nacos动态加载的任务一定要在代码里保留一个本地兜底配置。我们有一次Nacos配置中心短暂抖动任务配置没拉下来结果所有定时任务都停了还好我在本地配置里写了一份默认的任务清单启动时先加载本地兜底配置等Nacos恢复后再用远程配置覆盖。这个兜底习惯帮我扛过了一次线上小故障建议你也加上。最后再说一句很多组员问我为什么不用更“正规”的分布式锁中间件我说Nacos方案能覆盖我们90%的场景剩下10%真出问题的时候我们再去升级也不迟。有时候少一个中间件就是少一个故障源轻量不只是技术选择也是一种运维哲学。
企业数字化 ERP 产品动态
相关推荐
Atlas 300V 24G推理卡部署YOLO模型全流程实战指南 最近在折腾Atlas 300V 24G这张卡,跑了几个YOLO模型做视频流检测,整体流程走下来发现坑不少,但摸清楚之后其实很顺手。如果你也被“atlas部署yolo”这几个词困住——网上资料七零八落,官方文档又写得像天书——那这篇就是给你准备的… · 2026/9/25 5:30:50
智能体+一人公司:六大离钱近方向实操拆解 1. 智能体与一人公司:为什么这个组合突然成了热门话题最近半年,我身边做技术、做内容、做电商的朋友,聊着聊着总会拐到同一个话题上:智能体到底能不能撑起一家“一人公司”。这不是空想,而是实实在在正在发生的事情。所… · 2026/9/25 5:30:50
Shell变量与字符串深度解析:从原理到实战避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:25:01
Win10修改文件默认打开方式全指南:右键、设置、注册表一次说清 不知道你有没有过这种瞬间:双击一个 PDF,结果它跑浏览器里打开了;双击图片,弹出来的是一个从没用过的修图工具;甚至双击 .txt,蹦出来的不是记事本而是某个来路不明的编辑器。我第一次遇到的时候也愣了半天&… · 2026/9/25 6:25:01
从CSDN热榜抓取到技术趋势分析:Python爬虫雷达系统实战 CSDN 的热榜每天刷一遍,十个标题里有八个换新面孔,剩下的两个也变了时间戳。嘴上说着"技术圈日新月异",心里其实一直存个疑问:这些榜单数据背后,到底哪些技术方向是真热,哪些只是昙花一现&#x… · 2026/9/25 6:25:01
脉冲神经网络SNN入门:从LIF神经元到类脑芯片与低功耗计算 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:25:01
从零开始学硬件:用人体解剖学构建硬件系统知识地图 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:24:48
截图固定到屏幕怎么实现?贴图工具原理与Snipaste实操指南 1. 截图固定这件事,比你想的更有讲究很多人第一次听到“把截图固定在电脑页面上”这个需求,脑子里冒出来的第一反应是——截图不就是截完保存成图片文件吗?还能固定在页面上?这听起来像是个小众需求,但只要你真正用过一… · 2026/9/25 6:24:48
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37