Spring Boot项目修改内置Tomcat版本:原理、实战与避坑指南
1. 项目缘起为什么需要修改Spring Boot内置的Tomcat版本在Spring Boot项目里Tomcat就像你买精装房时开发商预装好的那套卫浴。开箱即用省心省力绝大多数情况下它都能完美工作。但干过几年开发的同行肯定都遇到过这样的场景项目要上线了运维团队跑过来说“咱们生产环境统一用的是Tomcat 9.0.80你们这个Spring Boot 2.7.x默认带的是9.0.68版本不一致有安全策略和性能调优参数对不上得统一一下。” 或者你在集成某个第三方SDK时它明确要求Tomcat版本必须≥9.0.70否则会有类加载或Servlet API兼容性问题。这时候你就不得不面对“修改内置Tomcat版本”这个看似基础实则藏着不少细节的操作。我最初接触这个问题是在一个老项目升级Spring Boot 2.3到2.7的过程中。新版本默认的Tomcat带来了更好的HTTP/2支持和线程池管理但同时也引入了一个老项目里某个Filter不兼容的改动。直接升级Spring Boot大版本风险太高退而求其次只升级其内置的Tomcat小版本就成了一个更稳妥的“手术式”解决方案。这个需求背后其实反映了Spring Boot“约定优于配置”哲学的另一面当“约定”不符合你的“配置”这里指环境或特定需求时你必须有清晰、可靠的手段去覆盖它。从技术角度看Spring Boot通过spring-boot-starter-web依赖将特定版本的Tomcat作为“传递性依赖”打包进来。修改它本质上就是告诉Maven或Gradle“别用你默认带来的那个用我指定的这个。” 听起来简单但实操中版本号怎么写、依赖怎么排除、不同构建工具如何处理、升级后如何验证每一步都有讲究。网上教程很多但往往只给命令不说原理更不提那些只有踩过坑才知道的“副作用”。今天我就结合多次实战经验把这套操作掰开揉碎了讲清楚让你不仅能“做到”更能“懂得”。2. 核心原理Spring Boot是如何管理Tomcat依赖的要修改内置组件首先得知道它从哪来、怎么被管理的。我们直接从项目的依赖树看起。创建一个最简单的Spring Boot Web项目它的pom.xml里通常会有这么一段dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency这个starter-web就像一个“礼包”它自己并不包含代码而是声明了一组依赖。通过Maven的mvn dependency:tree命令我们可以清晰地看到这个礼包拆开后的样子[INFO] com.example:demo:jar:0.0.1-SNAPSHOT [INFO] - org.springframework.boot:spring-boot-starter-web:jar:2.7.18:compile [INFO] | - org.springframework.boot:spring-boot-starter:jar:2.7.18:compile [INFO] | - org.springframework.boot:spring-boot-starter-json:jar:2.7.18:compile [INFO] | - org.springframework:spring-webmvc:jar:5.3.31:compile [INFO] | - org.springframework:spring-web:jar:5.3.31:compile [INFO] | \- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.18:compile [INFO] | - org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.68:compile [INFO] | - org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.68:compile [INFO] | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.68:compile关键点就在spring-boot-starter-tomcat这个子starter里。它定义了三个核心的嵌入式Tomcat组件tomcat-embed-core: Tomcat的核心引擎。tomcat-embed-el: 表达式语言支持。tomcat-embed-websocket: WebSocket支持。Spring Boot父工程spring-boot-dependencies的BOMBill Of Materials文件里已经为每个Spring Boot版本预定义好了这些Tomcat组件的版本号。例如在Spring Boot 2.7.18的BOM里你可能会找到类似tomcat.version9.0.68/tomcat.version的属性定义。这就是“约定”的来源。所以修改版本的核心思路就明确了我们需要覆盖Spring Boot父工程中预定义的Tomcat版本属性。这通常有两种主流做法属性覆盖推荐在项目的pom.xml中直接定义一个同名的属性如tomcat.versionMaven会优先使用项目中定义的值。依赖排除与显式引入先排除掉starter里传递过来的Tomcat依赖再手动引入指定版本的依赖。第一种方法更简洁是Spring Boot官方推荐的方式。第二种方法则更“暴力”和直接通常在需要引入Tomcat的某个特殊变体比如测试版或者遇到复杂的依赖冲突时使用。我们接下来会详细讲解第一种并补充说明第二种的应用场景。3. 实战操作基于Maven项目的版本修改全流程假设我们的需求是将Tomcat从Spring Boot 2.7.18默认的9.0.68升级到9.0.82。下面是一步一步的操作和背后的思考。3.1 第一步确认当前版本与目标版本动手前先知己知彼。运行mvn dependency:tree | findstr tomcat-embedWindows或mvn dependency:tree | grep tomcat-embedLinux/Mac确认当前真实的Tomcat版本。然后去Apache Tomcat官网或Maven中央仓库确认目标版本如9.0.82是稳定发布版并且与你当前的Spring Boot版本兼容。一个简单的兼容性判断原则是大版本号9必须一致小版本号0.82建议选择比Spring Boot内置版本更高但仍是稳定版的版本。Spring Boot社区通常不会在补丁版本中引入不兼容的Tomcat升级。3.2 第二步在pom.xml中覆盖版本属性这是最核心的一步。在你的项目pom.xml的properties标签内添加Tomcat版本属性。properties java.version11/java.version !-- 覆盖Spring Boot默认的Tomcat版本 -- tomcat.version9.0.82/tomcat.version /properties为什么在这里加properties是Maven定义变量的地方。当Spring Boot的父BOM定义了tomcat.version时你在子项目中重写它Maven在解析依赖时会使用你的定义。这比直接去dependencyManagement里写要简洁得多。添加后强烈建议立即刷新Maven项目在IDE中点击Maven刷新按钮或执行mvn clean compile -U。-U参数强制更新快照依赖能确保版本信息被正确拉取。3.3 第三步验证依赖变更刷新后再次执行mvn dependency:tree | findstr tomcat-embed。你应该能看到输出中的版本号全部变成了9.0.82[INFO] | - org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.82:compile [INFO] | - org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.82:compile [INFO] | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.82:compile这步验证至关重要它能确认你的配置生效了。我见过有同事改了属性但没刷新或者属性名拼写错误比如写成tomcat.version少了个点导致修改无效排查了半天。3.4 第四步启动测试与基础功能验证版本改好了不代表万事大吉。启动应用进行基础功能测试启动日志观察应用启动日志Tomcat初始化信息中应该会显示版本号例如Starting Servlet engine: [Apache Tomcat/9.0.82]。端点检查访问/actuator/env如果开启了Actuator或/actuator/info查看server.tomcat相关的信息确认运行时版本。基础请求写一个简单的RestController接口发送HTTP GET/POST请求确保基本的MVC功能正常。WebSocket测试如果你的项目用到了WebSocket务必进行连接测试因为tomcat-embed-websocket是独立组件。3.5 第五步处理可能出现的依赖冲突这是最容易踩坑的地方。升级了Tomcat可能会“拔出萝卜带出泥”引发其他依赖的版本冲突。常见的有Servlet APITomcat 9对应的是Servlet 4.0规范。如果你项目中其他地方比如某个古老的工具包直接引入了javax.servlet:servlet-api的旧版本如2.5可能会引发NoSuchMethodError或ClassNotFoundException。使用mvn dependency:tree查找所有servlet-api或javax.servlet相关的依赖通过exclusions排除掉旧的或者统一在dependencyManagement中管理版本。EL表达式语言实现tomcat-embed-el是Tomcat自带的EL实现。如果项目里还有org.glassfish:jakarta.el之类的其他EL实现也可能冲突。通常保留Tomcat自带的即可。一个排查依赖冲突的实用命令是mvn dependency:tree -Dverbose。它会显示所有依赖并标注出因为版本冲突而被忽略的依赖显示为omitted for conflict。重点关注这些被忽略的依赖看它们是不是必要的。4. 进阶场景与深度避坑指南上面的标准流程能解决90%的问题。但实际企业级项目更复杂下面这些场景你可能也会遇到。4.1 场景一需要降级Tomcat版本是的有时候不是升级而是降级。比如一个为Tomcat 9.0.60优化的本地缓存组件在9.0.68上出现了性能回退经过评估决定暂时回退到9.0.60。操作方法和升级一模一样只需将tomcat.version属性值设为9.0.60即可。但降级需要格外小心安全风险旧版本可能包含已知的安全漏洞。降级前必须经过安全团队评估并确认是否有其他补偿性安全措施。功能回退确认新版本中你依赖的某个特性或Bug修复在旧版本中不存在是否会影响业务。仔细阅读Tomcat的版本变更日志。Spring Boot兼容性极端情况下Spring Boot的某个自动配置可能依赖了高版本Tomcat的某个特定方法。降级后启动报NoSuchMethodError的概率比升级要大。务必进行全面测试。4.2 场景二使用dependencyManagement进行精细控制对于多模块项目或者项目中有其他库也传递依赖了Tomcat虽然不常见你可能需要在父POM或顶层POM的dependencyManagement节中显式声明Tomcat依赖的版本以确保所有模块统一。dependencyManagement dependencies dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-core/artifactId version9.0.82/version /dependency dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-el/artifactId version9.0.82/version /dependency dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-websocket/artifactId version9.0.82/version /dependency /dependencies /dependencyManagement注意这种方式需要声明所有三个embed组件比只覆盖一个tomcat.version属性要繁琐。它通常用于解决更复杂的、属性覆盖无法解决的依赖冲突。4.3 场景三排除默认依赖并引入指定版本“硬替换”这是最彻底但也最麻烦的方法。在spring-boot-starter-web依赖中排除掉Tomcat starter然后手动引入你想要的Tomcat依赖。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency !-- 手动引入指定版本的Tomcat -- dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-core/artifactId version9.0.82/version /dependency dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-el/artifactId version9.0.82/version /dependency dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-websocket/artifactId version9.0.82/version /dependency什么时候用这个方法当你需要引入的“Tomcat”根本不是官方的tomcat-embed而是某个深度定制的版本或者像网络热词中提到的“国产中间件宝蓝德”这类替换实现时这里仅作技术可能性探讨具体替换需参考对应中间件文档。此时属性覆盖和依赖管理都不起作用必须彻底排除默认实现引入新的jar包。4.4 重大陷阱版本号变量名并非一成不变这是一个超级大坑tomcat.version这个属性名并不是Spring Boot亘古不变的约定。在Spring Boot 3.x版本中由于Tomcat 10的包名从javax.servlet迁移到了jakarta.servlet为了区分Spring Boot 3.x的BOM中可能使用了不同的属性名例如tomcat-embed-core.version。如何确定正确的属性名最可靠的方法查看你所使用的Spring Boot版本对应的官方文档中关于“Build System”或“Dependency Versions”的部分。工程方法在IDE中按住Ctrl键点击你的spring-boot-starter-parent或spring-boot-dependencies的版本号跳转到它的POM文件然后搜索tomcat找到它实际定义的属性名是什么。如果你在Spring Boot 2.7.x的项目里错误地使用了tomcat-embed-core.version你的覆盖是不会生效的。这个细节很多教程都不会提但一旦出错排查起来非常耗时。5. 基于Gradle构建项目的修改策略虽然国内Maven仍是主流但使用Gradle的项目也越来越多。其核心逻辑相通但语法不同。在Gradle的build.gradle或build.gradle.kts文件中你需要通过覆盖ext中的属性或使用resolutionStrategy来修改版本。对于build.gradle(Groovy DSL)ext[tomcat.version] 9.0.82或者更现代的方式是直接在dependencies块中强制指定版本dependencies { implementation(org.springframework.boot:spring-boot-starter-web) { // 如果需要排除可以在这里排除 // exclude group: org.springframework.boot, module: spring-boot-starter-tomcat } // 显式引入指定版本Gradle的高级冲突解决策略会优先使用这个版本 implementation org.apache.tomcat.embed:tomcat-embed-core:9.0.82 implementation org.apache.tomcat.embed:tomcat-embed-el:9.0.82 implementation org.apache.tomcat.embed:tomcat-embed-websocket:9.0.82 }对于build.gradle.kts(Kotlin DSL)extra[tomcat.version] 9.0.82或者dependencies { implementation(org.springframework.boot:spring-boot-starter-web) // 强制所有配置中的Tomcat组件使用指定版本 implementation(org.apache.tomcat.embed:tomcat-embed-core) { version { strictly(9.0.82) } } }Gradle的依赖解析机制比Maven更灵活也稍复杂。修改后运行./gradlew dependencies --configuration runtimeClasspath来查看最终的依赖树确认版本已变更。6. 修改后的全面验证清单版本修改完成并成功启动只是第一步。在生产环境部署前建议执行一个完整的验证清单我称之为“Tomcat版本变更健康检查”基础功能回归测试所有核心业务接口的CRUD操作。会话Session测试如果用了Tomcat的Session管理测试登录态保持、Session超时等。文件上传下载测试特别是大文件测试max-http-post-size等配置是否依然有效。SSL/TLS连接测试如果启用了HTTPS验证证书加载和握手过程。AJP连接器测试如果使用了AJP与前置代理如Nginx通信测试连通性。内存与线程监控通过JMX或/actuator/metrics端点观察线程池tomcat.threads、内存使用等指标与旧版本进行对比确保没有异常增长。压测对比在测试环境进行简单的压力测试如使用Apache Bench对比关键接口的QPS和平均响应时间确保性能没有劣化。查看日志级别确认logging.level.org.apache.tomcat的日志级别没有输出大量不必要的DEBUG或TRACE信息避免生产日志暴涨。7. 从修改版本到理解版本管理最佳实践思考经过上面这一通操作我们成功修改了Tomcat版本。但这件事带给我们的价值不应该止步于“会改”。更深层的收获是建立起对项目依赖管理的敏感度。版本锁定与可重复构建对于企业级项目建议在父POM中不仅锁定Tomcat版本而是通过dependencyManagement锁定所有核心组件的版本形成一份明确的“物料清单”确保任何人在任何时间构建出的产物都是一致的。关注版本差异每次升级或修改版本花10分钟阅读一下两个版本之间的官方Release Notes。重点看Bug Fixes和Security Fixes。这能帮你预判可能的风险并理解这次变更的价值。例如从9.0.68到9.0.82可能修复了某个特定的内存泄漏问题。建立升级流程将版本修改操作标准化。可以是一个简单的Checklist或一个内部Wiki页面记录着“改属性 - 刷新依赖 - 查依赖树 - 启动验证 - 功能测试 - 性能基线对比”的完整步骤避免后续人员重复踩坑。考虑使用Spring Boot的版本管理除非有强制的环境要求或特定的Bug规避否则尽量跟随Spring Boot官方推荐的Tomcat版本。Spring Boot团队在集成测试上投入巨大他们选择的版本组合通常是经过充分兼容性验证的。自行升级小版本是安全的但跨大版本如从Tomcat 9到10则需要同步升级Spring Boot大版本这属于系统性的升级工程。修改一个内置组件的版本就像给一台精密仪器更换一个指定型号的螺丝。你知道要换也知道怎么换但真正的功夫在于清楚这颗螺丝为什么必须是这个型号兼容性知道用什么工具能最顺手的换上Maven/Gradle换完之后如何测试仪器是否运转如初验证清单以及从这次更换中总结出保养整个仪器的方法论依赖管理实践。把这个过程想透、做熟你在应对其他类似问题比如处理Netty、Jetty、数据库驱动版本时就能举一反三游刃有余了。

相关新闻

【Bug已解决】Analyze bfloat16 support status in cpu and cuda EP 解决方案

【Bug已解决】Analyze bfloat16 support status in cpu and cuda EP 解决方案

【Bug已解决】Analyze bfloat16 support status in cpu and cuda EP 解决方案 一、现象长什么样 用 bfloat16(bf16) 精度的模型在 ONNX Runtime 上跑时,行为在不同 EP 之间不一致: # CPU EP:某些 op 没有 bf16 内核&am…

2026/10/21 1:20:52 阅读更多 →
PKCS5与PKCS7填充模式辨析:跨平台AES加解密避坑指南

PKCS5与PKCS7填充模式辨析:跨平台AES加解密避坑指南

1. 从一次“解密失败”的排查说起:为什么填充模式如此关键最近在做一个跨平台的数据交换项目,涉及到Java后端和C#客户端之间的AES加密通信。后端用的是JDK 1.8自带的加密库,而客户端用的是.NET Framework的System.Security.Cryptography。测试…

2026/10/21 7:26:28 阅读更多 →
MacOS下Docker部署Dify完整指南与优化实践

MacOS下Docker部署Dify完整指南与优化实践

1. MacOS环境准备与Docker安装在MacOS上部署Dify之前,需要先确保基础环境配置正确。我推荐使用Docker作为容器化解决方案,这能有效避免环境依赖冲突问题。以下是经过实测的完整配置流程:1.1 系统版本兼容性检查首先确认你的MacOS版本符合Dock…

2026/10/21 5:29:44 阅读更多 →

最新新闻

精锐系列驱动安装排查指南:位宽、签名与设备管理器

精锐系列驱动安装排查指南:位宽、签名与设备管理器

简介:精锐(elite)系列加密狗驱动安装包V4.0.0.4,面向需要部署或维护精锐加密狗驱动的软硬件集成商、设备管理员及后期维护人员。驱动包同时提供64位与32位版本,适配Windows 7、Windows 10等主流系统,可解决…

2026/10/21 7:44:05 阅读更多 →
Web Fuzzer性能优化实战:并发模型、连接复用与表达式系统改造

Web Fuzzer性能优化实战:并发模型、连接复用与表达式系统改造

1. Web Fuzzer想解决的痛点与优化目标1.1 一个请求包能玩出多少花样做安全测试的人几乎每天都在跟请求包打交道,无论是挖漏洞还是做合规检测,本质上都是在做同一件事:找到一个可以变形的请求,然后观察服务端在不同变形下的反应。W…

2026/10/21 7:44:05 阅读更多 →
WASM推理性能优化全攻略:从内存布局到SIMD与多线程

WASM推理性能优化全攻略:从内存布局到SIMD与多线程

从第一次在浏览器里跑起一个中等规模的图像分类模型,看到白屏转了几秒钟才出结果的时候,我心里对 WebAssembly(WASM)推理的滤镜就碎了一地。那时 WAM 的卖点还是“接近原生性能”,可实测下来,同一个模型在原…

2026/10/21 7:44:05 阅读更多 →
MySQL字段长度过长报错全解析:从varchar到索引的避坑指南

MySQL字段长度过长报错全解析:从varchar到索引的避坑指南

当初第一次看到“mysql字段长度过长”这个报错时,我整个人是懵的。字段长度还能过长?我明明只写了一个varchar(1000),这也能算长?后来在业务里接连被“Column length too big”“Row size too large”“Specified key was too lon…

2026/10/21 7:44:05 阅读更多 →
FCA-RL框架:用多智能体强化学习保障网约车动态市场效率

FCA-RL框架:用多智能体强化学习保障网约车动态市场效率

我是在出行平台算法团队做调度优化的。上个月组里把一项关于动态市场效率保障的工作投中了ECML-PKDD ’25,也就是FCA-RL框架那套东西。趁着这几天评审意见整理完,把整个方案的设计思路、实验过程和踩过的坑完整写下来。这篇文章更偏向工程复盘&#xff0…

2026/10/21 7:44:05 阅读更多 →
Terraform

Terraform

一、IaC基础概念 基础设施即代码 (IaC):使用配置文件而非图形用户界面来管理基础设施 优势: 自动化和可重复:同一配置可多次部署,确保环境一致。版本控制:基础设施配置可以像代码一样审查、回滚。可集成 CI/CD 流程&am…

2026/10/21 7:43:04 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →