Spring Boot整合Elasticsearch 7.6实战:简单查询与高亮分词搜索落地
1. 项目背景与整体设计思路1.1 为什么要在Spring Boot里整合Elasticsearch做过搜索功能的人都有一个体会用数据库的LIKE %关键词%做模糊查询数据量一上来基本就废了。几百万条数据里做全表扫描响应时间从几百毫秒飙到几秒甚至十几秒用户体验直接崩盘。更别提还要做分词、高亮、相关性排序这些进阶需求传统关系型数据库根本扛不住。Elasticsearch后面简称ES就是来解决这个问题的。它底层基于Lucene天生为全文检索设计倒排索引的结构让它在海量数据里查关键词快到飞起。而Spring Boot作为目前Java后端开发的主流框架生态成熟、配置简洁把两者整合起来就能用很低的成本搭建一套可用的搜索服务。这个项目的核心目标很明确在一个Spring Boot应用里接入ES 7.6实现两类查询——简单查询按字段精确匹配、范围查询、分页列表和高亮分词查询对文本内容分词后检索并把命中的关键词高亮返回给前端。适合有一定Java基础、正在做搜索功能的后端开发者参考也适合刚接触ES想找个完整落地案例的朋友。1.2 版本选型的那些坑为什么偏偏是ES 7.6这里有几个现实考量。ES 7.x是一个大版本分水岭7.0之后彻底移除了TransportClient官方推荐使用RestHighLevelClient。而7.6这个版本相对稳定社区资料多和Spring Boot 2.x的兼容性也经过大量验证。Spring Boot这边我建议用2.3.x到2.5.x之间的版本。太新的Spring Boot 3.x默认要求JDK 17而且对ES客户端的依赖管理有变化容易踩坑。整合方式上市面上有两种主流做法一种是直接用官方的RestHighLevelClient手动封装另一种是用spring-boot-starter-data-elasticsearch。前者灵活、可控后者省事但封装太厚遇到复杂查询反而不好调。我的建议是如果你的查询逻辑不复杂用Spring Data的Repository方式能省不少代码但一旦涉及高亮、聚合、自定义分词还是老老实实用RestHighLevelClient把控制权握在自己手里。1.3 整体架构分层整个项目的结构其实不复杂我按职责拆成四层Controller层对外暴露REST接口接收查询参数返回统一格式的JSON。Service层业务逻辑核心负责组装ES的查询请求SearchRequest处理返回结果。ES客户端层封装RestHighLevelClient的初始化和通用查询方法避免每次都在Service里重复写连接代码。配置层管理ES连接地址、超时时间、连接池等参数。这样分层的好处是查询逻辑和连接管理解耦后面要换ES版本或者加新的索引改动范围可控。数据流向也很清晰前端传关键词 → Controller接收 → Service组装查询条件 → 客户端发请求到ES → ES返回命中结果 → Service解析高亮字段 → 返回前端渲染。2. 环境准备与核心依赖配置2.1 依赖引入的正确姿势先看pom.xml里需要加什么。核心就两个ES的官方客户端和Spring Boot的Web依赖。dependency groupIdorg.elasticsearch.client/groupId artifactIdelasticsearch-rest-high-level-client/artifactId version7.6.2/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency这里有个极易踩的坑Spring Boot的父POM里默认管理了ES的版本如果你不显式指定它可能会拉一个和你ES服务端不匹配的客户端版本。比如你服务端装的是7.6.2客户端却被拉成了7.9.x虽然小版本通常兼容但遇到序列化问题时排查起来很痛苦。所以务必在properties里锁定版本或者像上面那样在依赖里直接写死。另外如果你引入了spring-boot-starter-data-elasticsearch它会自带一个旧版本的客户端可能和你手动引入的冲突。两个二选一别都留着。2.2 客户端连接配置ES 7.x推荐用RestHighLevelClient初始化代码如下Configuration public class ElasticsearchConfig { Value(${elasticsearch.host:127.0.0.1}) private String host; Value(${elasticsearch.port:9200}) private int port; Bean(destroyMethod close) public RestHighLevelClient restHighLevelClient() { RestClientBuilder builder RestClient.builder( new HttpHost(host, port, http) ); builder.setRequestConfigCallback(requestConfigBuilder - requestConfigBuilder .setConnectTimeout(5000) .setSocketTimeout(60000) ); return new RestHighLevelClient(builder); } }几个参数值得说道说道。connectTimeout设5秒是建立TCP连接的超时socketTimeout设60秒是等待响应的超时。为什么socket超时给这么长因为ES在做大数据量聚合或者复杂高亮时响应时间可能超过默认的10秒设太短会频繁抛超时异常。当然也不能无限长60秒是个比较平衡的值。注意destroyMethod close这个一定要加否则应用关闭时连接不会释放反复重启可能耗尽连接资源。2.3 索引结构设计假设我们要做一个文章搜索索引名叫article_index。Mapping设计如下{ settings: { number_of_shards: 1, number_of_replicas: 0 }, mappings: { properties: { id: { type: long }, title: { type: text, analyzer: ik_max_word, search_analyzer: ik_smart }, content: { type: text, analyzer: ik_max_word, search_analyzer: ik_smart }, author: { type: keyword }, createTime: { type: date } } } }这里的关键点是分词器的选择。ik_max_word在写入时会把文本切得尽可能细比如中华人民共和国会切成中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国等而ik_smart在查询时切得粗一些做最粗粒度划分。写入细、查询粗能兼顾召回率和准确率这是中文搜索里非常经典的搭配。author字段用keyword类型因为它不需要分词做精确匹配和聚合更合适。createTime用date类型方便做时间范围查询和排序。3. 简单查询的实现细节3.1 精确匹配与范围查询简单查询主要覆盖三类场景按ID查详情、按作者精确筛选、按时间范围过滤。先看按ID查询这是最基础的public MapString, Object getById(Long id) throws IOException { GetRequest getRequest new GetRequest(article_index, String.valueOf(id)); GetResponse response restHighLevelClient.get(getRequest, RequestOptions.DEFAULT); if (!response.isExists()) { return Collections.emptyMap(); } return response.getSourceAsMap(); }GetRequest走的是实时GET直接按文档ID取性能最好。但要注意如果你刚写入数据还没刷新refresh可能查不到。ES默认每秒自动refresh一次如果对实时性要求极高写入时可以指定RefreshPolicy.IMMEDIATE但会牺牲写入性能。范围查询用RangeQueryBuilderSearchSourceBuilder sourceBuilder new SearchSourceBuilder(); RangeQueryBuilder rangeQuery QueryBuilders.rangeQuery(createTime) .gte(2024-01-01) .lte(2024-12-31) .format(yyyy-MM-dd); sourceBuilder.query(rangeQuery); sourceBuilder.from(0); sourceBuilder.size(20); sourceBuilder.sort(createTime, SortOrder.DESC);from和size就是分页from是偏移量。这里有个深分页的坑当from size超过index.max_result_window默认10000时ES会直接报错。如果你的数据量大别用from/size翻到很后面改用search_after游标方式性能也好得多。3.2 多条件组合查询实际业务里很少只有一个条件通常是标题包含关键词 且 作者是某人 且 时间在某范围内。这时候用BoolQueryBuilderBoolQueryBuilder boolQuery QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.matchQuery(title, keyword)); boolQuery.filter(QueryBuilders.termQuery(author, author)); boolQuery.filter(QueryBuilders.rangeQuery(createTime).gte(startTime));must和filter的区别很关键。must参与相关性打分filter不参与打分但会被缓存性能更好。所以像作者、时间这种不需要算相关性的条件一律用filter。should则是或的关系满足越多分越高。实操心得term查询用于keyword类型字段match查询用于text类型字段。用错了要么查不到要么报类型异常。我见过不少人对着text字段用term查中文结果因为分词对不上死活查不出数据。3.3 结果解析与分页封装查询返回后需要把SearchHits解析成业务对象SearchResponse response restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits response.getHits(); ListMapString, Object list new ArrayList(); for (SearchHit hit : hits.getHits()) { MapString, Object source hit.getSourceAsMap(); source.put(score, hit.getScore()); list.add(source); } long total hits.getTotalHits().value;注意getTotalHits().value在7.x里返回的是long而且默认情况下track_total_hits是10000超过就只返回10000。如果需要精确总数得在SearchSourceBuilder里设置trackTotalHits(true)但这会带来性能开销数据量大时慎用。分页信息封装成一个通用的PageResult对象包含list、total、pageNum、pageSize前端拿到就能直接渲染分页组件。4. 高亮分词查询的完整实现4.1 高亮查询的核心原理高亮查询是搜索功能里体验感最强的一环。用户搜Spring Boot结果里所有出现Spring Boot的地方都标红一眼就能看到命中位置。ES的高亮原理是在返回结果时对命中的字段重新分析把匹配到的词用指定的HTML标签包起来。实现高亮需要三样东西一个HighlightBuilder、指定要高亮的字段、以及前后缀标签。核心代码如下HighlightBuilder highlightBuilder new HighlightBuilder(); highlightBuilder.field(title); highlightBuilder.field(content); highlightBuilder.preTags(em classhighlight); highlightBuilder.postTags(/em); highlightBuilder.fragmentSize(200); highlightBuilder.numberOfFragments(3); highlightBuilder.requireFieldMatch(false);fragmentSize是每个高亮片段的字符数numberOfFragments是最多返回几个片段。requireFieldMatch(false)这个参数很关键——它允许高亮那些没有参与查询匹配的字段。比如你只在title里搜但想让content里出现的关键词也高亮就得设成false。4.2 分词查询与高亮结合把查询条件和高亮组装到一起SearchSourceBuilder sourceBuilder new SearchSourceBuilder(); MultiMatchQueryBuilder multiMatch QueryBuilders.multiMatchQuery(keyword, title, content) .type(MultiMatchQueryBuilder.Type.BEST_FIELDS) .tieBreaker(0.3f); sourceBuilder.query(multiMatch); sourceBuilder.highlighter(highlightBuilder); sourceBuilder.from(0).size(10); SearchRequest searchRequest new SearchRequest(article_index); searchRequest.source(sourceBuilder); SearchResponse response restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);multiMatch允许一个关键词同时搜多个字段。BEST_FIELDS策略是取匹配最好的那个字段的分数作为最终分tieBreaker是给其他匹配字段的一点加权避免完全忽略。比如标题命中的权重通常比正文高这个策略就很合适。4.3 高亮结果的解析与替换拿到结果后高亮内容不在source里而在highlight字段里for (SearchHit hit : response.getHits().getHits()) { MapString, Object source hit.getSourceAsMap(); MapString, HighlightField highlightFields hit.getHighlightFields(); if (highlightFields.containsKey(title)) { source.put(title, highlightFields.get(title).getFragments()[0].string()); } if (highlightFields.containsKey(content)) { StringBuilder sb new StringBuilder(); for (Text fragment : highlightFields.get(content).getFragments()) { sb.append(fragment.string()).append(...); } source.put(content, sb.toString()); } list.add(source); }这里有个细节高亮返回的是片段数组不是完整原文。如果你前端需要展示完整内容得自己决定是展示片段还是原文。我的做法是列表页展示高亮片段详情页展示原文各取所需。注意高亮标签里的em是HTML前端如果用Vue或React直接渲染记得用v-html或dangerouslySetInnerHTML否则标签会被转义成文本显示出来。但这也带来XSS风险所以标签内容一定要确保是ES返回的、经过转义的别把用户输入直接拼进去。5. 常见问题与排查技巧实录5.1 连接与超时类问题问题一启动时报Connection refused。八成是ES服务没起来或者端口不对。先用浏览器访问http://127.0.0.1:9200确认服务正常。如果ES装在远程检查防火墙和elasticsearch.yml里的network.host配置默认只监听本地回环地址。问题二查询偶尔超时。先看是不是查询条件太宽泛导致扫描数据量过大。可以加timeout参数限制单次查询时间同时优化查询条件尽量用filter缩小范围。另外检查socketTimeout是不是设得太短。5.2 查询结果异常类问题问题三明明有数据却查不到。排查顺序是这样的先确认索引名对不对ES索引名区分大小写再确认字段类型和查询方式是否匹配text用matchkeyword用term最后检查分词器用_analyze接口看看关键词被切成了什么。GET /article_index/_analyze { field: title, text: Spring Boot实战 }问题四高亮不生效。最常见的原因是requireFieldMatch设成了true默认值导致只有参与查询的字段才高亮。另一个原因是查询用了term精确匹配而字段是text类型分词对不上自然没有高亮。5.3 性能与稳定性问题问题五深分页报Result window is too large。前面提过from size不能超过10000。解决方案有两个一是调大index.max_result_window不推荐治标不治本二是改用search_after用上一页最后一条的排序值作为下一页的起点。问题六写入后查不到。ES是近实时搜索默认1秒refresh。如果业务要求写入立即可查可以在写入请求里设置RefreshPolicy.IMMEDIATE但高并发写入时慎用会拖慢整体吞吐。下面这张表把常见问题做了个速查问题现象可能原因排查方向连接被拒绝服务未启动/端口错误浏览器访问9200端口验证查询超时条件过宽/超时设置短优化查询条件调整socketTimeout有数据查不到字段类型与查询方式不匹配检查mapping用_analyze验证分词高亮不显示requireFieldMatch为true设为false检查查询类型深分页报错fromsize超10000改用search_after游标写入后查不到未refresh设置RefreshPolicy或等待1秒5.4 几个独家避坑技巧第一个技巧索引别名。生产环境别直接用索引名查询用别名。这样重建索引时可以无缝切换不用改代码。比如建一个article_alias指向article_index_v1重建时把别名指向v2即可。第二个技巧查询日志。在开发阶段把SearchSourceBuilder的toString()打出来能看到最终发给ES的JSON长什么样。很多查询问题看一眼这个JSON就明白了比盲猜高效得多。第三个技巧批量写入用Bulk。如果你要初始化大量数据别一条条index用BulkRequest批量提交每批500到1000条性能能提升几十倍。第四个技巧中文分词器要装对版本。IK分词器的版本必须和ES版本严格对应7.6.2的ES就得用7.6.2的IK版本不一致会导致ES启动失败。装完后记得重启ES并在mapping里正确引用。6. 从查询到落地的经验补充6.1 查询性能的优化思路搜索功能上线后性能优化是个持续过程。我的经验是分三步走先看慢查询日志找出耗时最长的查询再分析是不是查询条件设计不合理比如该用filter的用了must该限制范围的没限制最后考虑索引层面的优化比如调整分片数、增加副本、优化mapping。分片数不是越多越好。单个分片建议控制在20GB到50GB之间分片太多会增加集群管理开销太少又影响并行查询能力。对于中小规模数据1到3个分片足够了。6.2 高亮体验的细节打磨高亮标签的样式要和前端设计对齐别用默认的em改成带class的标签前端好统一控制颜色和字体。片段长度也要调太短看不全上下文太长又显得啰嗦200字符左右是个比较舒服的值。另外如果关键词在标题里命中列表页优先展示标题高亮如果只在正文命中就展示正文片段。这种智能摘要的处理能让用户一眼看到最有价值的信息。6.3 后续可扩展的方向这套基础框架搭好后能扩展的方向很多。比如加拼音搜索让用户输入拼音也能搜到中文加同义词把手机和移动电话关联起来加搜索建议用户输入一半就提示候选词加聚合统计按作者、时间做分组计数。再往深了走可以引入搜索排序的个性化根据用户历史行为调整权重。不过这些都属于进阶玩法建议先把基础查询和高亮做扎实再逐步叠加。我在实际项目里踩过的最大一个坑是早期没做索引别名后来要重建索引时只能停服操作影响了好几个小时。从那以后所有ES索引我都坚持用别名重建时零停机切换这个习惯帮我省了太多麻烦。另外就是分词器的选择一开始图省事用了默认的standard分词器中文被切成一个个单字搜索效果惨不忍睹换成IK之后召回率和准确率都上了一个台阶。这些细节看着小但真正影响搜索功能的可用性值得在项目初期就规划好。

相关新闻

多智能体系统触达管理:从探测到调度的全链路实践

多智能体系统触达管理:从探测到调度的全链路实践

做Agent系统做到第三个月,我被一个看似简单的问题卡住了:智能体明明在线,任务却迟迟没有响应;服务状态一切正常,消息却石沉大海。排查到最后,问题全部指向同一个环节——触达。从网关到智能体之间那条链路&…

2026/10/15 11:40:20 阅读更多 →
Matlab声波时域差分方程模拟:核心原理与实操指南

Matlab声波时域差分方程模拟:核心原理与实操指南

1. 整体设计与思路拆解1.1 声波数值模拟的核心需求这阵子好几个朋友问我声波时域差分怎么做,我干脆把自己一套经验整理出来,顺便把关键坑位标出来。所谓“利用matlab编程实现的声波的时域差分方程”,说白了就是用Matlab把描述声波传播的偏微分…

2026/10/15 11:40:20 阅读更多 →
从数据盘点到治理核验:数据治理四项工作拆解

从数据盘点到治理核验:数据治理四项工作拆解

不少企业把数字化转型当成一次采购:买套平台,接上系统,以为就转型了。平台跑起来了,报表里的数仍旧对不上。问题通常出在更前面的地方——数据散在各业务系统里,命名、编码、口径各不相同;数据质量参差不齐…

2026/10/15 11:40:20 阅读更多 →

最新新闻

S变换在配电网混合线路故障选线中的应用

S变换在配电网混合线路故障选线中的应用

1. 项目背景与核心价值在配电网系统中,电缆-架空线混合线路的故障选线一直是困扰运维人员的难题。传统基于工频量的选线方法在面对高阻接地、间歇性电弧等复杂故障时,往往会出现误判。而S变换作为一种时频分析工具,能够同时保留信号的时间与频…

2026/10/15 13:58:46 阅读更多 →
基于S变换的配电网混合线路故障选线方法

基于S变换的配电网混合线路故障选线方法

1. 项目背景与核心价值在配电网系统中,电缆-架空线混合线路的故障选线一直是个棘手问题。传统基于工频量的选线方法在面对这类混合线路时,往往会因为线路参数差异大、故障特征复杂而失效。我在某地区电网的实际运维中就遇到过这样的案例:一条…

2026/10/15 13:58:46 阅读更多 →
1688图片搜索API技术内幕:从特征提取到向量匹配的工程实现

1688图片搜索API技术内幕:从特征提取到向量匹配的工程实现

图片搜索这件事,表面上看是"传一张图,返回一堆相似商品",但真正落到工程实现上,它是一条从像素到向量的完整流水线。1688的图片搜索API背后,核心要解决的是"如何让机器理解一张商品图里到底有什么&…

2026/10/15 13:58:46 阅读更多 →
乱抛垃圾行为识别监测系统

乱抛垃圾行为识别监测系统

城市公共街道、小区园区、公园广场、环卫责任片区、垃圾桶周边等公共区域,是市容环境治理与文明城市建设的核心场景。日常公共环境管理中,市民随手乱扔纸屑、果皮、包装袋、废弃杂物,垃圾桶旁乱堆乱放垃圾等不文明行为屡禁不止,不…

2026/10/15 13:58:46 阅读更多 →
AI辅助开发App全流程:从技术选型到上架发布的个人开发者实战指南

AI辅助开发App全流程:从技术选型到上架发布的个人开发者实战指南

1. 从零到一:AI辅助开发一款App的整体思路拆解1.1 为什么现在个人开发者也能独立完成App放在五年前,一个没有移动端经验的后端程序员想独立做出一款能上架的App,基本等于痴人说梦。UI设计、原生开发、后端接口、应用商店审核,每一…

2026/10/15 13:58:46 阅读更多 →
微信小程序预约挂号系统:号源并发扣减与排班逻辑实战

微信小程序预约挂号系统:号源并发扣减与排班逻辑实战

简介:这份资源是面向计算机专业毕业设计场景的微信小程序预约挂号系统完整论文资料,适合正在准备毕设选题、需要参考系统分析与设计写作范式的学生,以及希望了解SSM框架与微信小程序结合开发思路的开发者。压缩包共1个doc文件,约2…

2026/10/15 13:57:45 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →