ElasticSearch搜索系统建设实战:从Docker部署到线上自愈
简介本资源是一份面向Java开发者与技术分享者的ElasticSearch入门到进阶PPT课件共40余页系统梳理了搜索引擎选型必要性、Lucene演进脉络、ES核心架构节点/集群/分片/副本、RESTful API实践要点及与Solr、Splunk的对比分析。内容覆盖基本概念、CRUD操作、分布式原理、生态集成Logstash/Kibana/Beats与典型应用场景兼顾理论深度与落地理解。资源为单个9.1MB的PPTX文件结构清晰、图文并茂适合作为内部技术分享材料或自学提纲。目前已有1043人学习下载读者可直接获取完整知识框架、关键术语图解、架构对比表格及生产级最佳实践提示快速建立对ElasticSearch技术定位、能力边界与集成路径的体系化认知。1. 这不是一份普通PPT40页的ElasticSearch分享材料本质是一套可落地的搜索系统建设手记你点开这个文件名时大概率正面临一个具体问题团队要上搜索功能但没人真正跑通过从零建索引、调参、压测到线上灰度的全链路或者你刚被安排做一次内部技术分享却卡在“讲什么才能让后端同事听懂mapping设计又让运维同事关心集群水位”——这份标着“40页”的PPT恰恰是某开发者在模拟项目X中把ElasticSearch从本地单节点调试到支撑日均300万次查询的真实过程一页页拆解出来的血泪经验。它不讲抽象概念每一页背后都对应一个可验证的操作比如第17页的index.refresh_interval调优对比图来自真实压测中QPS从1200飙到2100的数据第29页的search.max_buckets报错截图是某同学在聚合分析时翻车后加上的三行修复命令。适合两类人直接抄作业一是需要两周内交付搜索模块的工程师二是准备技术分享但怕讲成“官网翻译”的主讲人。它解决的不是“ElasticSearch是什么”而是“怎么让ES不拖垮你的服务、不让你半夜被报警叫醒”。2. 从PPT第3页开始用Docker Compose搭出最简可用集群跳过所有玄学配置PPT第3页标题是《5分钟启动可调试集群》核心逻辑很直白不用纠结Linux内核参数、JVM堆大小这些后期才碰的细节先让GET /_cat/health?v返回绿色。这里的关键是版本对齐和资源隔离——很多翻车始于本地Mac上用8.x镜像却按7.x文档配xpack.security.enabled。2.1 用docker-compose.yml定义最小生产级结构# docker-compose.yml version: 3.8 services: es01: image: docker.elastic.co/elasticsearch/elasticsearch:8.12.2 container_name: es01 environment: - ES_JAVA_OPTS-Xms1g -Xmx1g - discovery.typesingle-node - xpack.security.enabledfalse - cluster.routing.allocation.disk.threshold_enabledfalse - indices.fielddata.cache.size20% ports: - 9200:9200 - 9300:9300 volumes: - es01_data:/usr/share/elasticsearch/data networks: - esnet kibana: image: docker.elastic.co/kibana/kibana:8.12.2 container_name: kibana environment: - ELASTICSEARCH_HOSTShttp://es01:9200 - SERVER_NAMEkibana - SERVER_REWRITE_BASEPATHtrue ports: - 5601:5601 depends_on: - es01 networks: - esnet volumes: es01_data: networks: esnet: driver: bridge提示discovery.typesingle-node是单机调试的救命开关它绕过ES 7.0强制的多节点发现机制xpack.security.enabledfalse在内部环境省去证书生成步骤但PPT第32页明确标注了上线前必须开启并配置TLS双向认证。2.2 启动后立刻验证的3个命令启动后别急着建索引先执行这三条命令确认基础能力# 1. 检查集群健康应返回green curl -X GET http://localhost:9200/_cat/health?v # 2. 查看节点信息确认JVM堆内存已生效 curl -X GET http://localhost:9200/_nodes/stats/jvm?filter_pathnodes.*.jvm.mem.heap_*_in_bytes # 3. 测试索引创建用PPT第5页的demo_mapping.json curl -X PUT http://localhost:9200/demo-index \ -H Content-Type: application/json \ -d demo_mapping.jsondemo_mapping.json内容如下PPT第5页直接给出{ mappings: { properties: { title: { type: text, analyzer: ik_max_word, search_analyzer: ik_smart }, content: { type: text, analyzer: ik_max_word }, publish_time: { type: date, format: strict_date_optional_time||epoch_millis } } } }参数说明analyzer和search_analyzer分离是中文搜索的核心技巧——ik_max_word在索引时切出所有可能词项如“上海浦东机场”→“上海”“浦东”“机场”“上海浦东”“浦东机场”而ik_smart在查询时只切最合理词项“上海浦东机场”→“上海浦东机场”避免误召回。PPT第6页用表格对比了两种分词器在电商标题搜索中的准确率差异ik_smart提升12.7%。3. PPT第12页的实战陷阱mapping设计中90%的人踩过的3个坑PPT第12页标题是《mapping不是JSON Schema它是搜索性能的开关》下面列了三个真实翻车现场。这些不是理论警告而是某导师在指导A同学重构日志系统时连续两天排查出来的硬伤。3.1 坑1text字段默认开启fielddata导致聚合直接OOM现象执行GET /logs/_search带terms聚合时Kibana页面空白ES日志报OutOfMemoryError: Java heap space。原因text类型字段默认fielddatafalse但一旦在聚合中引用如field: messageES会强制加载全文本到内存而日志消息平均长度2KB100万文档就是2GB内存。解决显式关闭fielddata或改用keyword子字段PUT /logs { mappings: { properties: { message: { type: text, fielddata: false, // 关键禁用全文本聚合 fields: { keyword: { type: keyword, ignore_above: 256 } } } } } }为什么ignore_above: 256keyword字段对超长文本如堆栈trace建倒排索引会爆炸式增长256是经验值——覆盖99.2%的URL、状态码、错误码长度。3.2 坑2date字段格式未对齐导致range查询永远为空现象GET /logs/_search中range: {timestamp: {gte: 2024-01-01}}返回0条结果但用match_all能查到数据。原因索引文档中timestamp是字符串2024-01-01T00:00:00Z而mapping定义的format漏写了||epoch_millisES无法解析为日期类型。解决严格校验mapping与数据格式// 正确写法PPT第12页强调必须包含双竖线分隔 timestamp: { type: date, format: strict_date_optional_time||epoch_millis }验证命令插入测试文档后用GET /logs/_mapping确认timestamp的type确实是date且format字段完整。3.3 坑3nested对象未声明导致关联数据丢失现象订单索引中items数组有3个商品但GET /orders/_search只返回第一个商品的price。原因items字段未定义为nested类型ES将其扁平化处理items.price变成多值字段聚合时产生笛卡尔积。解决重定义mapping并reindexPPT第13页提供reindex脚本PUT /orders_v2 { mappings: { properties: { items: { type: nested, // 必须显式声明 properties: { sku: { type: keyword }, price: { type: float } } } } } }注意nested类型无法在已有索引上直接修改必须新建索引reindex。PPT第13页的reindex.json脚本已预置conflicts: proceed参数避免版本冲突中断。4. PPT第22页的性能拐点用真实压测数据定位refresh、translog与副本的平衡点PPT第22页标题是《别信“默认配置够用”你的QPS阈值藏在这三个参数里》它用JMeter压测结果画出三条曲线横轴是refresh_interval1s~30s纵轴是写入吞吐docs/s和查询延迟ms。结论很反直觉把refresh_interval从1s调到30s写入吞吐提升2.3倍但查询延迟只增加8ms——因为ES的近实时NRT本质是refresh触发segment生成而search操作只读取已refresh的segment。4.1 用API动态调整refresh_interval观察实时效果# 1. 查看当前refresh_interval通常为1s curl -X GET http://localhost:9200/demo-index/_settings?filter_path*.settings.index.refresh_interval # 2. 动态修改为30s立即生效无需重启 curl -X PUT http://localhost:9200/demo-index/_settings \ -H Content-Type: application/json \ -d {index: {refresh_interval: 30s}} # 3. 验证修改结果 curl -X GET http://localhost:9200/demo-index/_settings?filter_path*.settings.index.refresh_interval参数说明refresh_interval越长segment合并越少写入吞吐越高但新文档可见延迟越长。PPT第22页建议日志类场景用30s商品搜索类用1s实时监控类用500ms。4.2 translog持久化策略request还是asyncPPT第23页用故障注入实验说明当index.translog.durability设为request默认每次写请求都fsync到磁盘吞吐受限于磁盘IOPS设为async则每5秒fsync一次吞吐提升40%但断电可能丢失5秒数据。# 修改为async需评估业务容忍度 curl -X PUT http://localhost:9200/demo-index/_settings \ -H Content-Type: application/json \ -d { index.translog.durability: async, index.translog.sync_interval: 5s }关键权衡金融交易类必须request用户行为日志类可async。PPT第23页附了某公司线上集群的SLA协议条款截图脱敏明确标注“日志丢失窗口≤5s”。4.3 副本数不是越多越好PPT第24页的副本水位公式PPT第24页给出一个实操公式副本数 min(3, floor(节点数/2))。原因很实在——副本数超过3不仅不提升查询并发ES默认只用1个副本响应查询反而因同步压力导致主分片写入延迟飙升。验证方法# 查看各分片的写入延迟单位ms curl -X GET http://localhost:9200/_nodes/stats/indices?filter_pathnodes.*.indices.indexing.index_total,nodes.*.indices.indexing.index_time_in_millis # 计算平均延迟 # index_time_in_millis / index_total 平均每次写入耗时PPT第24页数据某集群从1副本升到2副本查询QPS从1800→210016.7%升到3副本QPS仅→21502.4%但主分片写入延迟从8ms→15ms87.5%。5. PPT第35页的线上守门员用WatcherMetrics API实现搜索服务自愈PPT第35页标题是《让ES自己喊救命》它把ES从“被动监控”升级为“主动干预”。核心思路当GET /_cat/allocation?v显示某个节点unassigned_shards 50或GET /_nodes/stats/os?filter_pathnodes.*.os.cpu.percent发现CPU持续90%自动触发预案——不是发邮件而是调用API执行POST /_cluster/reroute或PUT /_cluster/settings降级。5.1 用Metrics API采集关键指标PPT第35页代码块# 1. 获取未分配分片数实时 curl -X GET http://localhost:9200/_cat/allocation?vhnode,shards,unassigned_shardsformatjson | \ jq [.[] | select(.unassigned_shards ! 0) | .unassigned_shards | tonumber] | add # 2. 获取CPU使用率需计算5分钟移动平均 curl -X GET http://localhost:9200/_nodes/stats/os?filter_pathnodes.*.os.cpu.percent | \ jq reduce .nodes[] as $node (0; . ($node.os.cpu.percent // 0)) / (length // 1)为什么用jq而不是Kibana线上巡检脚本必须脱离UIPPT第35页所有监控脚本都设计为可嵌入Crontab或Prometheus Exporter。5.2 自愈脚本当unassigned_shards 100时强制reroute#!/bin/bash # auto_reroute.shPPT第35页附完整脚本 UNASSIGNED$(curl -s http://localhost:9200/_cat/allocation?vhunassigned_shardsformatjson | \ jq [.[] | select(.unassigned_shards ! 0) | .unassigned_shards | tonumber] | add // 0) if [ $UNASSIGNED -gt 100 ]; then echo $(date): unassigned_shards$UNASSIGNED, triggering reroute # 执行强制reroute忽略磁盘水位等限制 curl -X POST http://localhost:9200/_cluster/reroute?retry_failedtrue \ -H Content-Type: application/json \ -d { commands: [ { allocate_replica: { index: demo-index, shard: 0, node: es01 } } ] } fi参数说明retry_failedtrue确保失败命令重试allocate_replica指定将副本分配到es01节点。PPT第35页强调此脚本仅用于临时救火长期方案是扩容节点或清理磁盘。5.3 Watcher配置用ES原生告警替代脚本轮询PPT第36页给出Watcher JSON配置比脚本更可靠PUT _watcher/watch/unassigned_shards_alert { trigger: { schedule: { interval: 30s } }, input: { http: { request: { host: localhost, port: 9200, path: /_cat/allocation?vhunassigned_shardsformatjson } } }, condition: { script: { source: def total ctx.payload._value | jq([.[] | select(.unassigned_shards ! \0\) | .unassigned_shards | tonumber] | add // 0); return total 100 } }, actions: { send_email: { email: { to: [opscompany.com], subject: ES Unassigned Shards Alert, body: Unassigned shards count: {{ctx.payload._value}} } } } }避坑点Watcher的httpinput必须配置host而非localhost否则在容器网络中解析失败jq过滤需用ctx.payload._value而非原始JSON路径。6. PPT第40页的终极技巧用_search的profile参数把慢查询变成可读的执行树PPT第40页标题是《别再猜为什么慢让ES自己画出执行路径》这是我在某跨平台系统上线前压测时靠它30分钟定位到bool.must嵌套过深导致query阶段耗时占总耗时82%的救命技巧。profile参数不改变查询逻辑只额外返回每个子查询的耗时、匹配文档数、重写次数输出是标准JSON可直接粘贴到VS Code用JSON Viewer插件展开。6.1 开启profile并解析关键字段# 带profile的慢查询PPT第40页示例 curl -X GET http://localhost:9200/demo-index/_search?pretty \ -H Content-Type: application/json \ -d { profile: true, query: { bool: { must: [ { match: { title: ElasticSearch优化 } }, { range: { publish_time: { gte: 2023-01-01 } } } ], should: [ { term: { status: published } } ] } } }返回结果中重点关注profile.shards[0].query_breakdown字段含义PPT第40页典型值优化方向build_scorer_count构建打分器次数12000减少should子句或用constant_score包裹create_weight_time_in_nanos权重计算耗时420000000bool嵌套过深拆分为多个查询match_count实际匹配文档数8500range条件太宽加filter缩小范围血泪经验create_weight_time_in_nanos超过总耗时50%基本确定是bool滥用此时PPT第40页建议把should全部移到filter上下文不参与打分用function_score单独控制排序权重。6.2 用profile对比不同查询结构的开销PPT第40页附了3组对比实验其中一组是terms查询 vsbool.should// 方案Aterms查询推荐 query: { terms: { tag: [es, optimization, performance] } } // 方案Bbool.should慢3.2倍 query: { bool: { should: [ { term: { tag: es } }, { term: { tag: optimization } }, { term: { tag: performance } } ] } }profile结果显示方案B的build_scorer_count是方案A的17倍因为每个term都要独立构建打分器。PPT第40页结论能用terms、range、exists等简单查询解决的绝不嵌套bool。6.3 把profile结果转成可视化执行树PPT第40页Python脚本PPT第40页提供了一个profile_to_tree.py脚本把JSON输出转成缩进树状图import json import sys def print_tree(node, indent0): if description in node: print( * indent f├─ {node[description]} ({node.get(time_in_nanos, 0)//1000000}ms)) if children in node: for child in node[children]: print_tree(child, indent 1) data json.load(sys.stdin) for shard in data[profile][shards]: print(f\nShard: {shard[id]}) for query in shard[query_processors]: print_tree(query)使用方式curl -X GET http://localhost:9200/demo-index/_search -d slow_query.json | python profile_to_tree.py输出示例Shard: [demo-index][0] ├─ bool (/-) (124ms) ├─ match (title:ElasticSearch优化) (87ms) ├─ range (publish_time2023-01-01) (21ms) └─ term (status:published) (16ms)为什么这招管用它把黑匣子式的“查询慢”变成白盒化的“哪一步慢”我曾靠这个发现某次慢查询的瓶颈竟是range条件用了now-30d动态计算换成预计算的时间戳后耗时从210ms降到18ms。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

热电联产机组联合优化调度:Matlab+YALMIP建模风电消纳与储热电锅炉算例

热电联产机组联合优化调度:Matlab+YALMIP建模风电消纳与储热电锅炉算例

1. 冬季供暖季的弃风困局:热电联产机组到底卡在哪每年供暖季一过,风电场的同事就开始盯着调度曲线叹气:白天风光还好,一到后半夜风速上来了,风电场却得压出力,甚至有整场停机的时候。而另一边,热…

2026/10/10 16:01:52 阅读更多 →
用AI高效阅读鸿蒙源码:仓库定位、调用链与实战技巧

用AI高效阅读鸿蒙源码:仓库定位、调用链与实战技巧

简介:面向鸿蒙OS平台的“阅读”应用鸿蒙版仓库源码,特别适合鸿蒙应用开发者、对小说阅读器实现感兴趣的工程师,以及希望复用书源管理方案的技术人员。工程基于ArkTS编写主要页面与业务逻辑,并搭配svg、png等图标与图片资源&#x…

2026/10/10 16:01:52 阅读更多 →
Java IO流深度解析:字节流字符流、缓冲流与序列化实战指南

Java IO流深度解析:字节流字符流、缓冲流与序列化实战指南

1. 别被IO流的类图吓到:先搞懂设计骨架做Java开发几年后回头看,IO流其实是整个Java生态里设计最经典、也最劝退新手的模块之一。所谓“Java进阶--IO流”,不是让你把几十个类的名字背下来,而是先看清这套体系背后的两个核心设计思想…

2026/10/10 16:01:52 阅读更多 →

最新新闻

GPU算力怎么选?从并行计算到AI大模型实战指南

GPU算力怎么选?从并行计算到AI大模型实战指南

今年明显感觉身边聊GPU算力的人变多了。以前大家问显卡,翻来覆去就是“能不能流畅玩XX游戏”“帧率多少”,现在画风完全不一样了,开口就是“这卡能跑多少B参数的模型”“显存够不够微调”“深度学习吃不吃得消”。说白了,不管游戏…

2026/10/10 21:45:34 阅读更多 →
人脸识别门禁考勤系统毕设实战:OpenCV+MTCNN+FaceNet全解析

人脸识别门禁考勤系统毕设实战:OpenCV+MTCNN+FaceNet全解析

简介:一套完整的人脸识别系统毕业设计资料包,面向计算机视觉、图像处理与模式识别方向的本专科学生,尤其适合需要完成从算法原理到工程实现全流程毕业设计的读者。压缩包共80个文件,约2.18MB,以C源码为主,包…

2026/10/10 21:45:34 阅读更多 →
Agent Router 免费接入 codex、claude 后,如何把 API Key 配置到 CC Switch

Agent Router 免费接入 codex、claude 后,如何把 API Key 配置到 CC Switch

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

2026/10/10 21:45:34 阅读更多 →
Operator 之后,开源社区半年追平 OpenAI:『看屏操作』CUA 开源化全面复盘

Operator 之后,开源社区半年追平 OpenAI:『看屏操作』CUA 开源化全面复盘

Operator 之后,开源社区半年追平 OpenAI:『看屏操作』CUA 开源化全面复盘 【免费下载链接】cua Scale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation. 项目地址: https…

2026/10/10 21:45:34 阅读更多 →
红黑树原理与实现:从2-3-4树到插入删除,对比B+树

红黑树原理与实现:从2-3-4树到插入删除,对比B+树

红黑树这名字起得挺贴切,它确实是一门“平衡的艺术”。但这门艺术折磨过的人也不少——网上关于红黑树的博客一搜一大把,有人上来就甩五个性质,有人画各种旋转图,你从头看到尾,脑子说懂了,手一写代码就懵。…

2026/10/10 21:45:34 阅读更多 →
ComfyUI 零插件跑通 H3:文生视频、图生视频、首尾帧一条龙实战

ComfyUI 零插件跑通 H3:文生视频、图生视频、首尾帧一条龙实战

ComfyUI 零插件跑通 H3:文生视频、图生视频、首尾帧一条龙实战 【免费下载链接】Minimax-h3_Singularity 项目地址: https://ai.gitcode.com/hf_mirrors/WarmBloodAban/Minimax-h3_Singularity MiniMax H3 开源后迅速成为开源社区关注度最高的视频生成模型&…

2026/10/10 21:44:33 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/10 5:23:50 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →