Java开发实践MCP:用Spring Boot把Function Call接进LLM工作流
1. Java 后端接 MCP 的真实痛点Function Call 写三遍模型换一家全白干如果你是一名 Java 后端最近大概率被两件事同时夹击一边是产品经理催着「给系统加个 AI 助手能查订单、能算报价」另一边是你打开文档发现 Function Call 的定义方式每家模型都不一样。OpenAI 一套 JSON SchemaClaude 一套 tool_use 结构国产模型又是另一套字段名。你辛辛苦苦在 Spring Boot 里写好的工具方法换一个模型供应商就得重写一遍适配层测试用例全部推倒重来。MCPModel Context Protocol模型上下文协议要解决的就是这个事。你可以把它理解成「工具调用的 USB-C 标准」以前每个模型厂商自己定接口现在大家约定一套协议你的 Java 服务只要按 MCP 规范暴露工具任何支持 MCP 的客户端都能直接调用。对 Java 开发者来说最直接的收益是——工具逻辑只写一次模型侧通过统一通道接入换模型不用动业务代码。这篇文章面向的是有 Spring Boot 基础、想把 Function Call 真正接进 LLM 工作流的后端开发者。我会带你从零搭一个 MCP Server把计算器工具注册进去再用 MCP Client 打通调用链最后通过 TaoToken 统一 Key 和 API 通道完成模型侧接入与验证。全程可复制命令和配置都给你踩过的坑我也会标出来。核心检索词先明确Java MCP 服务端开发、Spring Boot Function Call 接入 LLM、MCP Client 本地联调。这三个词贯穿全文你跟着做就能跑通。2. TaoToken 前置准备统一 Key 与 API 通道别让模型接入拖后腿在写代码之前先把模型侧的通道理顺。很多 Java 开发者卡在第一步不是不会写 Spring Boot而是不知道该用哪家的 API、Key 怎么管、base-url 填什么。我的建议是工具逻辑归工具逻辑模型接入归模型接入两者解耦。TaoToken 在这里扮演的就是「统一通道」的角色——你拿一个 Key配一个 base-url后面换模型只改 model 字段不用动 MCP 那套代码。先说清楚它是什么TaoToken 提供统一的 API 通道兼容 OpenAI 风格的接口协议。对 Spring Boot 项目来说这意味着你可以直接用spring-ai-starter-model-openai这个 starter把base-url指向 TaoToken 的 API 地址api-key填你申请的 Key就能调用背后的模型。适合谁适合不想在多个模型厂商之间反复注册、反复改配置的后端团队尤其是做原型验证和内部工具的阶段。具体操作路径第一步访问官网了解通道能力地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 进去后先看文档里支持的模型列表和接口格式。第二步进控制台创建 API Key。控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。创建后把 Key 复制出来注意别提交到 Git后面我们用环境变量注入。第三步如果你只是想先验证模型通不通可以用模型对话页面直接试https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。输入一句话看有没有正常返回确认 Key 有效。第四步API Key 管理页可以随时查看和轮换https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这里有个关键点TaoToken 的 API 地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数配置到base-url里就用这个。很多同学把带参数的链接填进去结果请求 404就是这里出的问题。注意Key 只创建一次就够不要每个环境建一个。用 Spring 的 profile 或者环境变量区分 dev/prodKey 本身复用。如果你后面要做长期编码或者 Agent 类项目可以考虑 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。不过本文的重点是 MCP 联调先用按量调用验证链路即可。前置准备做完你手里应该有三样东西一个可用的 API Key、base-url 是https://taotoken.net/api、以及确认模型能正常返回。接下来进入编码环节。3. 可复制配置Spring Boot 搭 MCP Server把计算器工具注册进去这一节是全文技术核心篇幅会重一些。我们分两步先写 MCP Server暴露工具再写 MCP Client调用工具。环境要求 JDK 17、Spring Boot 3.3 及以上这是 Spring AI MCP 的硬性门槛低于这个版本会缺类。3.1 MCP Server 依赖与工具定义新建一个 Spring Boot 项目pom.xml里加核心依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp/artifactId /dependency如果你用的是 Spring AI 的 BOM 管理版本记得在dependencyManagement里引入对应的 BOM否则版本号对不上会报NoClassDefFoundError。然后写工具类。这里定义一个计算器工具用Tool注解暴露给 LLMimport org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; public class ToolService { Tool(name calculator, description 简单的计算器工具支持加减乘除) public String calculator( ToolParam(description 第一个数字 a) Double a, ToolParam(description 第二个数字 b) Double b, ToolParam(description 计算操作符支持 - * /) String operation) { double result switch (operation) { case - a b; case - - a - b; case * - a * b; case / - a / b; default - throw new IllegalArgumentException(未知操作: operation); }; return String.format(计算结果: %s %s %s %s, a, operation, b, result); } }注意ToolParam里的 description 不是写给人看的是写给模型看的。描述越清楚模型选工具、填参数越准。我试过把 description 写成「参数1」「参数2」模型经常把 a 和 b 填反改成「第一个数字」「第二个数字」之后就正常了。3.2 注册工具并打包在启动类或者配置类里注册ToolCallbackProviderimport org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class McpConfig { Bean public ToolCallbackProvider calculatorTools(ToolService toolService) { return MethodToolCallbackProvider.builder() .toolObjects(toolService) .build(); } }打包命令mvn clean package -DskipTests打包完成后你会得到一个 jar比如target/spring-mcp-server.jar。这个 jar 后面要用 stdio 模式启动所以启动参数里要关掉 Web 容器和 banner否则控制台输出会污染 stdio 通道导致 MCP 握手失败。3.3 MCP Client 依赖与 application.yaml再建一个项目作为 Client依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency /dependenciesapplication.yaml配置这里就是 TaoToken 统一通道发挥作用的地方spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: deepseek-chat mcp: client: enabled: true name: demo-client version: 1.0.0 type: sync request-timeout: 20s stdio: servers-configuration: classpath:/mcp-server-config.json logging: level: io.modelcontextprotocol: debug三件套对齐一下Base URL 是https://taotoken.net/apiKey 用环境变量TAOTOKEN_API_KEY注入Model ID 这里填deepseek-chat你可以按文档换成其他支持的模型。这三个字段是接入的核心缺一个都跑不通。3.4 mcp-server-config.json 配置在src/main/resources下建mcp-server-config.json{ mcpServers: { demo: { command: java, args: [ -Dspring.ai.mcp.server.stdiotrue, -Dspring.main.banner-modeoff, -Dspring.main.web-application-typenone, -Dserver.port7002, -Dlogging.pattern.console, -jar, /你的路径/spring-mcp-server/target/spring-mcp-server.jar ], env: {} } } }路径要换成你本机打包出来的绝对路径。Windows 用户注意command可能要写成cmdargs前面加/c这是 stdio 启动方式在 Windows 上的差异很容易踩坑。3.5 启动类与调用链import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import io.modelcontextprotocol.client.McpSyncClient; import java.util.List; import java.util.Scanner; SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } Bean public CommandLineRunner predefinedQuestions( ChatClient.Builder chatClientBuilder, ListMcpSyncClient mcpSyncClients) { return args - { var chatClient chatClientBuilder .defaultSystem( 你是个人小助手。 如果用户问计算问题请使用 demo 相关工具回答 如果问题与计算无关提醒用户问题不在处理范围内。 ) .defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients)) .build(); for (McpSyncClient client : mcpSyncClients) { System.out.println(已连接 MCP Server: client.getServerInfo().name()); } try (Scanner scanner new Scanner(System.in)) { while (true) { System.out.print(\n用户: ); String input scanner.nextLine(); System.out.println(\nASSISTANT: chatClient.prompt(input).call().content()); } } }; } }到这里配置部分就完整了。你可以看到MCP Server 负责暴露工具MCP Client 负责连接 Server 并把工具注册给 ChatClient模型侧通过 TaoToken 统一通道调用。三层解耦换模型只改 yaml 里的 model 字段。4. 验证请求本地联调跑通计算器调用链配置写完接下来是验证。这一步别跳过很多问题都是启动阶段暴露的。先启动 MCP Server 的 jar确认它能独立跑起来java -Dspring.ai.mcp.server.stdiotrue \ -Dspring.main.banner-modeoff \ -Dspring.main.web-application-typenone \ -Dlogging.pattern.console \ -jar target/spring-mcp-server.jar如果控制台没有任何输出别慌这是正常的——stdio 模式下我们把日志关掉了就是为了不污染通道。你可以临时把-Dlogging.pattern.console去掉看到启动日志后再加回来。然后启动 MCP Client 项目。启动日志里应该能看到类似这样的输出已连接 MCP Server: demo这说明 Client 成功通过 stdio 连上了 Server并且拿到了 Server 的工具列表。如果这一步没打印出来说明mcp-server-config.json里的路径不对或者 jar 启动失败。接着在控制台输入测试问题用户: 帮我算一下 128 乘以 47 等于多少预期返回ASSISTANT: 128 乘以 47 等于 6016。如果模型返回的是「我无法计算」或者直接给了一个错误答案说明工具没有被正确注册。这时候打开 debug 日志看io.modelcontextprotocol包下有没有工具列表的打印。正常情况下你会看到calculator这个工具被注册进去了。再测一个边界情况用户: 今天天气怎么样预期返回ASSISTANT: 问题不在处理范围内。我目前可以帮你做计算相关的操作。这个测试是为了验证 system prompt 里的能力边界有没有生效。如果模型硬答天气说明 system prompt 没起作用检查defaultSystem有没有正确设置。验证模型侧通道是否走通还有一个简单办法把base-url临时改错看请求是否报错。如果报 401 或者连接失败说明确实在走 TaoToken 通道如果还能正常返回说明你本地有缓存或者配置没生效。验证完记得改回来。实测下来整个链路跑通后从输入问题到返回结果大概 2-4 秒取决于模型响应速度。工具调用本身的开销很小主要耗时在模型推理。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节把我踩过的坑和社区里高频报错整理出来对照着排查能省不少时间。报错一401 Unauthorized这是最常见的。原因通常是 Key 没注入或者注入错了。检查application.yaml里的api-key: ${TAOTOKEN_API_KEY}确认环境变量真的设置了。在 IDEA 里跑的话Run Configuration 的 Environment variables 里要手动加。命令行跑的话export TAOTOKEN_API_KEY你的Key之后再启动。还有一种情况是 Key 复制时带了空格肉眼看不出来建议重新复制一次。报错二local proxy failed / Connection refused这个报错通常出现在 MCP Client 连不上 Server 的时候。检查mcp-server-config.json里的 jar 路径是不是绝对路径Windows 下路径分隔符要不要转义。另外确认 Server 的 jar 能独立启动如果 Server 本身启动就报错Client 自然连不上。端口冲突也会导致这个问题-Dserver.port7002换一个没被占用的端口试试。报错三reading choices / choices is null这个报错说明请求发出去了但返回体里没有choices字段。常见原因是base-url填错了比如填成了带 UTM 参数的链接或者填成了官网首页。正确地址是https://taotoken.net/api注意结尾没有斜杠。还有一种可能是 model 字段填了一个不支持的模型名返回体结构不一样。对照文档确认模型 ID。报错四OAuth / authentication failed如果你在配置里误开了某些需要 OAuth 的选项会报这个。MCP Client 的 stdio 模式不需要 OAuth检查application.yaml里有没有多余的认证配置。另外 Spring AI 某些版本对spring.ai.mcp.client.type的取值敏感sync和async要写对。报错五NoClassDefFoundError: org/springframework/ai/mcp/...这是版本不匹配。Spring AI MCP 的版本要和 Spring Boot 3.3 对齐BOM 没引入或者版本号写错都会报这个。检查pom.xml里的 BOM 配置确保spring-ai-mcp和spring-ai-starter-mcp-client版本一致。报错六工具被调用但参数为空模型选了工具但参数没填进去。检查ToolParam的 description 是否清晰参数类型是否用了包装类型Double而不是double基本类型在某些版本下会导致参数解析失败。注意排查顺序建议从外到内——先确认 Key 和 base-url 对再确认 MCP Server 能独立启动最后看工具注册日志。大部分问题出在前两步。如果你用的是 Claude Code 或者 Cline 这类客户端接 MCP配置格式和上面类似但字段名可能不同。Cline 的 MCP 配置里transportType要写stdioClaude Code 的配置在 settings 里。核心三件套不变Base URL、Key、Model ID。6. 继续往下走把 MCP 接进真实业务与统一通道收尾跑通计算器只是起点。真实业务里你会把订单查询、库存校验、报价计算这些方法都用Tool暴露出去让 LLM 通过 MCP 调用。这时候有几个实践建议。第一工具粒度别太细。一个「查询订单」工具比「查订单号」「查订单状态」「查订单金额」三个工具更好用模型选择成本低参数也少。第二工具返回值要结构化别返回一大段自然语言模型解析起来费劲。第三给工具加超时和降级LLM 调用工具失败时要有兜底不能让整个对话卡死。模型侧的统一通道建议就用 TaoToken 这一套。Base URL 固定https://taotoken.net/apiKey 走环境变量Model ID 按需切换。这样你的 MCP Server 代码完全不用感知模型变化换模型只改一行配置。需要验证新模型时去模型对话页面快速试一下https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 遇到字段问题先查文档。如果你后面要做长期编码助手或者 Agent 工作流Coding Plan 的入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 适合需要稳定调用额度的场景。最后说一个我踩过的坑MCP Server 打包成 jar 后如果依赖里有 Web 容器一定要在启动参数里关掉web-application-typenone否则 stdio 通道会被 HTTP 服务的日志干扰表现为 Client 连上了但工具列表为空。这个坑排查了我一个下午希望你别再踩。代码写到这里链路已经完整。你可以把计算器换成任何业务方法MCP 那层不用动。这就是协议标准化的价值——工具归工具模型归模型中间用统一通道连起来。

相关新闻

MCU跑RTOS还是SoC跑Linux?嵌入式选型核心逻辑与实战经验

MCU跑RTOS还是SoC跑Linux?嵌入式选型核心逻辑与实战经验

1. 从一块控制板和一块开发板的选型分歧说起如果你在嵌入式行业待过几年,一定遇到过这样的场景:团队要做一个新项目,硬件工程师画完框图,软件负责人看了一眼主控型号,脱口而出——“这颗MCU跑RTOS就行”,或…

2026/10/12 16:47:02 阅读更多 →
MCU跑RTOS、SoC跑Linux:嵌入式系统选型核心逻辑与实战解析

MCU跑RTOS、SoC跑Linux:嵌入式系统选型核心逻辑与实战解析

1. 从一颗芯片的“性格”说起如果你在嵌入式行业待过几年,一定遇到过这种场景:一个刚入行的朋友拿着两块开发板问你——“这块STM32的板子跑的是FreeRTOS,那块树莓派的板子跑的是Linux,为什么不能反过来?让STM32跑Linu…

2026/10/10 23:55:09 阅读更多 →
嵌入式操作系统核心机制与实战:任务调度、内存管理及常见问题排查

嵌入式操作系统核心机制与实战:任务调度、内存管理及常见问题排查

1. 嵌入式操作系统到底管什么很多人第一次接触嵌入式开发,脑子里都有一个问号:我裸机写个while(1)循环跑得也挺好,为什么非要塞一个操作系统进去?这个问题我当年也问过自己,后来在做一个多传感器数据采集的项目时被现实…

2026/10/9 12:32:14 阅读更多 →

最新新闻

Service层命名规范:从见名知意到代码可维护性

Service层命名规范:从见名知意到代码可维护性

接手一个业务系统,我最怕看到的不是复杂算法,而是那种一打开就让人头晕的 Service 实现类。2000 行起步,方法名字从 process 到 handle 再到 doSomething,翻遍整个类也猜不出某个方法是干嘛的。命名这种事,看起来不影响…

2026/10/13 17:55:28 阅读更多 →
Windows内核模块枚举与隐藏原理深度解析

Windows内核模块枚举与隐藏原理深度解析

1. 这不是“找模块”,而是在内核内存里做一次精准外科手术“枚举和隐藏内核模块”——这八个字在安全研究、驱动开发或系统调试场景中,绝不是一句轻飘飘的技术术语。它背后是一整套对Windows内核运行时状态的深度观测与主动干预能力。我第一次在某高校实…

2026/10/13 17:55:28 阅读更多 →
Hibernate二级缓存Provider详解:从原理到选型与排坑

Hibernate二级缓存Provider详解:从原理到选型与排坑

被人突然问起“Hibernate的caching provider是什么”,我第一反应是:能问出这个词的人,多半已经看到配置文件里那一堆以hibernate.cache开头的键,但又没搞懂这些键到底在切换什么。这篇文章就把这个词拆开讲清楚,重点解…

2026/10/13 17:55:28 阅读更多 →
电能质量复合扰动识别:混沌集成决策树实战解析

电能质量复合扰动识别:混沌集成决策树实战解析

简介:这是一份面向电能质量复合扰动识别研究的MATLAB完整源程序包,对应知网论文《基于混沌集成决策树的电能质量复合扰动识别》,可作为电气工程、信号处理及机器学习方向的毕业设计或课题研究参考,帮助复现算法并开展对比实验。程…

2026/10/13 17:55:28 阅读更多 →
CentOS局域网DNS搭建:BIND9多域名权威解析实战

CentOS局域网DNS搭建:BIND9多域名权威解析实战

1. 为什么在CentOS上自建局域网DNS不是“多此一举”,而是刚需落地的第一步很多人看到“CentOS搭建局域网DNS”第一反应是:不就是改个/etc/resolv.conf?内网查个主机名用/etc/hosts不就完事了?我试过——在某高校实验室的物联网教学…

2026/10/13 17:55:28 阅读更多 →
ActiveMQ consumerCount精准获取与实战调优指南

ActiveMQ consumerCount精准获取与实战调优指南

1. 项目概述:为什么“获取消费者数量”是 ActiveMQ 运维与调优的黄金指标在某公司消息中间件团队的实际运维中,我接手的第一个紧急故障就和consumerCount直接相关——一个订单履约服务突然延迟飙升,下游库存扣减平均耗时从200ms暴涨到8秒。排…

2026/10/13 17:54:25 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →