news 2026/10/15 1:13:13

Firewalld从入门到实战:区域、服务与富规则深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Firewalld从入门到实战:区域、服务与富规则深度解析

1. 从零吃透Firewalld:为什么它值得你花时间

很多人第一次接触Linux安全加固,都是从iptables开始的。四条表五条链背得滚瓜烂熟,结果一到实际生产环境,面对频繁变动的业务端口和临时调试需求,手敲命令敲到怀疑人生。Firewalld就是为了解决这个痛点而生的——它不是要取代iptables,而是在iptables之上加了一层动态管理的外壳,让你不用每次改规则都重建整条链。

我刚开始用Firewalld的时候也犯过嘀咕:这玩意儿到底跟iptables什么关系?改了Firewalld的规则,iptables里怎么也能看到?后来把它的架构扒清楚才明白,Firewalld本质上是一个规则管理器,底层依然调用iptables(或者nftables)来干活,但它引入了区域(zone)和运行时/永久配置分离这两个核心概念,把防火墙管理从“手工活”变成了“策略活”。

这篇文章适合谁看?如果你是刚接触Linux运维的新手,想搞明白服务器防火墙到底怎么配才既安全又不把自己锁在外面;或者你已经在用iptables但被频繁的规则变更搞得头大,想找一个更优雅的管理方式——那这篇内容就是给你准备的。我会从核心概念讲起,把区域、服务、端口、富规则这些关键点一个个拆开,配上可以直接复现的实验步骤,最后再聊聊我在实际项目里踩过的坑和总结出来的排查套路。

整个内容分两大块:理论部分帮你建立心智模型,搞清楚Firewalld的设计哲学和核心组件;实验部分带你从零搭建一套可用的防火墙策略,包括区域切换、服务放行、端口转发、富规则编写这些高频操作。理论加实验,两条腿走路,学完就能上手干活。

2. Firewalld核心架构与设计思路拆解

2.1 为什么需要Firewalld:iptables的痛点在哪里

要理解Firewalld的价值,得先看清楚iptables在实际使用中的几个硬伤。

第一个问题是规则变更的原子性。iptables的规则是一条条追加的,当你需要修改某条规则时,通常的做法是找到对应行号,插入新规则,再删除旧规则。这个过程如果中间出错,防火墙可能处于一个“半成品”状态,规则集不完整,安全策略出现真空。更麻烦的是,如果你用的是远程SSH连接,一条错误的iptables命令可能直接把你挡在外面,只能去机房接显示器救火。

第二个问题是规则组织的混乱。iptables的规则是平铺的,所有规则混在一起,来源IP、目标端口、协议类型全部揉在一条条命令里。当规则数量超过几十条之后,可读性急剧下降,想搞清楚“到底哪些端口对外开放”需要逐条排查,维护成本很高。

第三个问题是缺乏动态感知能力。iptables是静态的,规则写死了就是写死了。但实际业务中,服务可能需要临时开放某个端口做调试,调试完再关掉。用iptables做这件事,你得手动加规则、手动删规则,容易遗漏。Firewalld的运行时配置和永久配置分离机制,恰好解决了这个场景——临时改动放在运行时,重启后自动恢复,不会污染永久配置。

Firewalld的设计思路可以概括为:用区域做策略分组,用服务做端口集合,用运行时/永久分离做变更管理。这三板斧下来,防火墙管理从“面向命令”变成了“面向策略”,效率提升非常明显。

2.2 区域(Zone):Firewalld最核心的抽象

区域是Firewalld里最重要的概念,没有之一。你可以把它理解成一套预设的安全策略模板,每个区域定义了默认放行哪些服务、哪些端口、如何处理转发和伪装。网卡或者源地址被绑定到某个区域后,就自动继承该区域的策略。

Firewalld内置了九个区域,按信任程度从低到高排列:

区域名称默认行为适用场景
drop丢弃所有入站包,不回应最高安全级别,完全静默
block拒绝所有入站包,返回拒绝消息明确拒绝,让对端知道被拒
public放行ssh和dhcpv6-client公共网络,服务器默认区域
external放行ssh,开启伪装路由器外网接口
internal放行ssh、mdns、samba-client、dhcpv6-client内部网络
dmz放行ssh隔离区,有限对外开放
work放行ssh、dhcpv6-client工作环境
home放行ssh、mdns、samba-client、dhcpv6-client家庭网络
trusted放行所有流量完全信任,慎用

实际工作中,public和trusted是用得最多的两个。public是默认区域,适合对外提供服务的服务器;trusted用于完全可信的内网环境,比如数据库集群之间的通信。其他区域可以根据业务场景选用,但没必要死记硬背,理解“信任程度递增”这个逻辑就够了。

区域绑定的方式有两种:基于网卡和基于源地址。基于网卡是最常见的做法,比如eth0绑定public区域,eth1绑定internal区域。基于源地址则更灵活,可以把来自特定网段的流量引导到指定区域,适合复杂的网络拓扑。

注意:一个网卡只能绑定一个区域,但一个区域可以绑定多个网卡。如果流量同时匹配网卡区域和源地址区域,源地址区域的优先级更高。

2.3 运行时配置与永久配置:动态管理的精髓

Firewalld把配置分成两套:Runtime和Permanent。Runtime是当前生效的配置,存在内存里,重启Firewalld服务或者重载配置后就会丢失。Permanent是持久化配置,存在/etc/firewalld/目录下的XML文件里,重启后依然生效。

这个设计的妙处在于:你可以用Runtime做临时调试,确认没问题后再把配置写入Permanent。比如临时开放一个端口给同事调试,用--runtime-to-permanent之前,这个改动只影响当前会话,重启就恢复原状,不会留下安全隐患。

两套配置之间的操作命令需要特别注意--permanent参数。不加这个参数,命令默认修改Runtime;加了之后修改Permanent,但需要执行firewall-cmd --reload才会生效。这个“改完要重载”的机制,新手很容易忘,导致“明明改了配置怎么不生效”的困惑。

我个人的习惯是:所有正式变更都直接操作Permanent,改完reload;临时调试才用Runtime,调试完要么转永久要么重载丢弃。这样能避免Runtime和Permanent不一致带来的排查困难。

2.4 服务(Service):端口集合的优雅封装

服务是Firewalld对“一组端口和协议”的命名封装。比如ssh服务,实际上对应的是tcp协议的22端口;http服务对应tcp的80端口;https对应tcp的443端口。用服务名代替端口号,好处是语义清晰,而且一个服务可以包含多个端口和协议,管理起来更方便。

Firewalld预定义了大量常见服务,存放在/usr/lib/firewalld/services/目录下,每个服务一个XML文件。你也可以在/etc/firewalld/services/下自定义服务,格式很简单:

<?xml version="1.0" encoding="utf-8"?> <service> <short>MyApp</short> <description>My custom application service</description> <port protocol="tcp" port="8080"/> <port protocol="tcp" port="8443"/> </service>

自定义服务的好处是,当你的应用需要开放多个端口时,不用逐个端口去加规则,定义一个服务,一次性放行。后续如果端口有变动,改XML文件就行,不用动防火墙规则本身。

提示:自定义服务的文件名必须和short名称一致,比如myapp.xml对应short为MyApp。文件名不匹配会导致服务无法识别。

3. 核心组件与实操要点深度解析

3.1 区域操作:查看、切换与绑定

先看怎么查看当前区域状态。最常用的命令是:

firewall-cmd --get-active-zones

这条命令会列出所有活跃区域及其绑定的网卡或源地址。输出类似:

public interfaces: eth0 trusted sources: 192.168.1.0/24

意思是eth0绑定在public区域,来自192.168.1.0/24网段的流量走trusted区域。

查看默认区域用:

firewall-cmd --get-default-zone

修改默认区域:

firewall-cmd --set-default-zone=internal

这条命令会同时修改Runtime和Permanent,不需要额外reload。

给网卡绑定区域:

firewall-cmd --permanent --zone=internal --add-interface=eth1 firewall-cmd --reload

给源地址绑定区域:

firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24 firewall-cmd --reload

这里有个实操细节:修改区域绑定后必须reload才能生效,因为区域绑定属于Permanent配置。如果你只是临时测试,可以不加--permanent,直接操作Runtime,立即生效,重启后恢复。

注意:给网卡绑定新区域之前,确保该网卡没有绑定在其他区域,否则会报错。可以用--remove-interface先解绑。

3.2 服务放行:最常用的日常操作

放行一个服务是最常见的防火墙操作。以放行http为例:

firewall-cmd --permanent --zone=public --add-service=http firewall-cmd --reload

查看某个区域已放行的服务:

firewall-cmd --zone=public --list-services

移除服务:

firewall-cmd --permanent --zone=public --remove-service=http firewall-cmd --reload

这里的关键点是区域的选择。如果你的服务器有多张网卡,分别绑定不同区域,那放行服务时要明确指定区域。比如eth0是公网口绑定public,eth1是内网口绑定trusted,那http服务只需要在public区域放行,内网口不需要额外放行(trusted默认全通)。

我见过不少新手犯的错误是:不指定区域,直接用firewall-cmd --add-service=http,这条命令操作的是默认区域。如果默认区域不是你想要的那个,规则就加错地方了。所以养成习惯,操作时始终带上--zone参数,哪怕当前默认区域就是目标区域,明确指定也能避免后续默认区域变更带来的混乱。

3.3 端口放行:服务之外的灵活补充

有些应用没有预定义服务,或者你不想定义服务,直接放行端口也行:

firewall-cmd --permanent --zone=public --add-port=8080/tcp firewall-cmd --reload

放行端口范围:

firewall-cmd --permanent --zone=public --add-port=8000-9000/tcp firewall-cmd --reload

查看已放行端口:

firewall-cmd --zone=public --list-ports

端口放行和服务放行的区别在于:服务是一组端口的集合,端口是单个或一段端口。如果某个应用只用一两个端口,直接放行端口更简单;如果应用涉及多个端口且经常变动,定义服务更优雅。

提示:放行端口时务必指定协议(tcp或udp),不指定的话默认是tcp。UDP端口需要显式写/udp。

3.4 富规则(Rich Rule):精细控制的利器

富规则是Firewalld最强大的功能,允许你用接近自然语言的语法编写复杂的防火墙规则。基本语法结构是:

rule [family="ipv4|ipv6"] source address="源地址" destination address="目标地址" service name="服务名" | port port="端口" protocol="协议" accept | reject | drop [log] [audit] [limit value="速率"]

举几个实际例子。

限制某个IP访问SSH:

firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" source address="10.0.0.100" service name="ssh" reject' firewall-cmd --reload

允许特定网段访问数据库端口:

firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" source address="192.168.1.0/24" port port="3306" protocol="tcp" accept' firewall-cmd --reload

限制SSH连接速率,防止暴力破解:

firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" service name="ssh" limit value="10/m" accept' firewall-cmd --reload

这条规则的意思是:每分钟最多允许10个新建SSH连接,超出的丢弃。对于防暴力破解有一定效果,但不能完全替代fail2ban这类专业工具。

带日志记录的拒绝规则:

firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" source address="10.0.0.0/8" port port="8080" protocol="tcp" log prefix="DENY-8080: " level="info" reject' firewall-cmd --reload

这条规则会在拒绝的同时记录日志,prefix会出现在日志里,方便排查。

富规则的优先级高于普通规则。也就是说,如果一条富规则拒绝某个流量,即使普通规则放行了该流量,最终结果也是拒绝。这个特性可以用来做精细的例外控制。

3.5 端口转发与伪装:NAT场景的核心配置

Firewalld支持端口转发和地址伪装,这在做网关或者反向代理时非常有用。

开启IP伪装(SNAT):

firewall-cmd --permanent --zone=external --add-masquerade firewall-cmd --reload

伪装的作用是把内网流量的源地址改成网关的外网地址,让内网机器能访问外网。这个操作通常在网关服务器上做,external区域是默认开启伪装的,但如果你用的是其他区域,需要手动开启。

端口转发:

firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=8080:toaddr=192.168.1.100 firewall-cmd --reload

这条规则的意思是:访问本机80端口的流量,转发到192.168.1.100的8080端口。注意,端口转发需要先开启伪装:

firewall-cmd --permanent --zone=public --add-masquerade

注意:端口转发只在同一区域内生效,跨区域的转发需要额外配置。另外,转发目标地址必须是可达的,否则转发会失败。

4. 完整实操流程:从零搭建一套防火墙策略

4.1 实验环境说明与初始状态检查

假设我们有一台服务器,两张网卡:eth0对外提供Web服务,eth1对内提供数据库服务。需求是:eth0只开放80和443端口,eth1只允许192.168.1.0/24网段访问3306端口,其他全部拒绝。

先检查初始状态:

# 查看Firewalld运行状态 systemctl status firewalld # 查看当前活跃区域 firewall-cmd --get-active-zones # 查看默认区域 firewall-cmd --get-default-zone # 查看所有区域配置 firewall-cmd --list-all-zones

如果Firewalld没启动,先启动并设置开机自启:

systemctl start firewalld systemctl enable firewalld

初始状态下,eth0和eth1可能都绑定在默认区域(通常是public)。我们需要重新规划区域绑定。

4.2 区域规划与网卡绑定

根据需求,我们规划两个区域:public用于eth0,internal用于eth1。

# 创建internal区域(如果不存在) firewall-cmd --permanent --new-zone=internal # 将eth1绑定到internal区域 firewall-cmd --permanent --zone=internal --add-interface=eth1 # 确保eth0绑定在public区域 firewall-cmd --permanent --zone=public --add-interface=eth0 # 重载配置 firewall-cmd --reload # 验证绑定结果 firewall-cmd --get-active-zones

预期输出:

internal interfaces: eth1 public interfaces: eth0

这里有个细节:如果eth1之前绑定在其他区域,需要先解绑。可以用--remove-interface操作。另外,--new-zone创建的区域默认没有任何规则,需要手动添加。

4.3 服务与端口放行配置

接下来配置public区域,只放行80和443:

# 放行http和https服务 firewall-cmd --permanent --zone=public --add-service=http firewall-cmd --permanent --zone=public --add-service=https # 移除public区域默认放行的ssh(如果不需要远程管理) firewall-cmd --permanent --zone=public --remove-service=ssh # 重载 firewall-cmd --reload # 查看public区域配置 firewall-cmd --zone=public --list-all

配置internal区域,只允许192.168.1.0/24访问3306:

# 添加富规则 firewall-cmd --permanent --zone=internal --add-rich-rule=' rule family="ipv4" source address="192.168.1.0/24" port port="3306" protocol="tcp" accept' # 重载 firewall-cmd --reload # 查看internal区域配置 firewall-cmd --zone=internal --list-all

这里有个关键点:internal区域默认可能放行了ssh、mdns等服务,如果不需要,应该移除。另外,富规则的accept只是放行匹配的流量,其他流量依然走区域的默认策略(internal区域默认拒绝)。

4.4 富规则进阶:限制访问来源与速率

假设我们还需要限制SSH访问,只允许管理网段10.0.0.0/24连接,并且每分钟最多5次:

firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" source address="10.0.0.0/24" service name="ssh" limit value="5/m" accept' firewall-cmd --permanent --zone=public --add-rich-rule=' rule family="ipv4" service name="ssh" reject' firewall-cmd --reload

注意规则顺序:先放行管理网段的SSH,再拒绝其他所有SSH。Firewalld会按顺序匹配,第一条匹配后就不再往下走。所以放行规则要写在拒绝规则前面。

提示:富规则的匹配顺序是按照添加顺序来的,可以用--list-rich-rules查看当前顺序。如果需要调整顺序,只能先删除再重新添加。

4.5 配置验证与持久化确认

配置完成后,需要验证规则是否生效。可以从另一台机器测试端口连通性:

# 测试80端口 curl -I http://服务器IP # 测试3306端口(从192.168.1.0/24网段) telnet 服务器IP 3306 # 测试SSH(从10.0.0.0/24网段) ssh 服务器IP

同时查看Firewalld的运行时配置和永久配置是否一致:

# 查看运行时配置 firewall-cmd --zone=public --list-all # 查看永久配置 firewall-cmd --permanent --zone=public --list-all

如果两者不一致,说明有些改动只操作了Runtime没有写入Permanent。可以用--runtime-to-permanent把当前运行时配置全部转为永久:

firewall-cmd --runtime-to-permanent

这个命令在调试完成后非常有用,能把调试过程中验证过的规则一次性固化下来。

5. 常见问题与排查技巧实录

5.1 规则不生效的排查思路

规则不生效是最常见的问题,排查思路可以按以下顺序进行。

第一步:确认Firewalld是否运行。systemctl status firewalld,如果没运行,规则自然不会生效。有些系统默认用iptables而不是Firewalld,需要确认当前用的是哪个。

第二步:确认规则加在了正确的区域。firewall-cmd --get-active-zones查看流量实际走哪个区域。如果网卡绑定的区域和你想的不一样,规则就加错地方了。

第三步:确认是否reload。加了--permanent的规则需要firewall-cmd --reload才生效。不加--permanent的规则立即生效但重启丢失。很多人忘了reload,以为规则没生效。

第四步:确认规则优先级。富规则优先于普通规则,拒绝规则优先于放行规则(在富规则内部按顺序)。如果有一条拒绝规则在前面,后面的放行规则就不会生效。

第五步:查看日志。Firewalld的日志默认在/var/log/messages或journalctl里。可以用journalctl -u firewalld查看。如果规则里带了log,日志里会有记录。

5.2 把自己锁在门外的急救方案

这是最危险的情况:配置防火墙时不小心拒绝了SSH,导致远程连接断开。如果服务器在本地,直接接显示器操作就行。如果是远程服务器,有几种急救方案。

方案一:通过带外管理。很多服务器有IPMI或iDRAC等带外管理接口,可以通过它登录系统修改防火墙。

方案二:重启进入单用户模式。在GRUB菜单里选择单用户模式,系统启动时不加载防火墙,进去后修改配置。

方案三:提前设置定时任务。这是一个预防性技巧:在修改防火墙之前,先设置一个定时任务,比如10分钟后自动停止Firewalld。如果修改导致断连,等10分钟防火墙自动停止,就能重新连上。确认没问题后再取消定时任务。

# 设置10分钟后停止firewalld echo "systemctl stop firewalld" | at now + 10 minutes # 确认没问题后取消 atrm 任务号

这个技巧我用了很多次,救过好几次命。强烈建议在远程修改防火墙前都加上这层保险。

5.3 区域与网卡绑定的常见坑

坑一:网卡绑定多个区域。一个网卡只能绑定一个区域,如果尝试绑定到第二个区域会报错。需要先解绑再绑定。

坑二:默认区域变更影响范围大。--set-default-zone会同时修改Runtime和Permanent,而且影响所有未明确绑定区域的网卡。变更前要确认哪些网卡在用默认区域。

坑三:源地址区域和网卡区域冲突。如果流量同时匹配网卡区域和源地址区域,源地址区域优先。这个特性可以用来做精细控制,但也容易导致“规则加在网卡区域却不生效”的困惑。

坑四:区域配置文件位置。Permanent配置在/etc/firewalld/zones/目录下,每个区域一个XML文件。直接编辑XML文件也可以,但需要reload才生效。不建议直接编辑,用命令行更安全。

5.4 富规则语法错误的快速定位

富规则语法比较严格,写错了会报错。常见错误包括:引号不匹配、family没指定、service和port同时出现等。

排查方法:先用--add-rich-rule不加--permanent测试,如果语法错误会立即报错。确认无误后再加--permanent。

另外,富规则里的换行和缩进不影响解析,但为了可读性,建议每条规则分行写。如果规则很长,可以写成多行,用单引号包裹。

提示:firewall-cmd --check-config可以检查配置文件语法是否正确,在reload之前跑一下,能避免因语法错误导致防火墙启动失败。

5.5 性能考量与规则数量控制

Firewalld底层是iptables,规则数量多了之后性能会下降。虽然对于大多数场景,几百条规则对性能的影响可以忽略,但如果规则数量上千,就需要考虑优化。

优化思路:合并规则。比如多个端口可以用一条富规则的多端口语法(如果支持),或者用ipset把多个IP合并成一个集合。Firewalld支持ipset,可以用--new-ipset创建,然后在富规则里引用。

另一个思路是减少区域数量。区域越多,流量匹配的层级越多。如果业务简单,两三个区域就够了,没必要搞七八个。

最后,定期清理无用规则。业务下线后,对应的防火墙规则要及时删除,避免规则集膨胀。

6. 理论到实验的闭环:我的实操体会

把Firewalld的理论和实验串起来看,核心逻辑其实很清晰:区域定策略,服务/端口定放行,富规则定例外,运行时/永久定变更管理。这四句话概括了Firewalld的日常使用。

我在实际项目里最深的体会是:不要试图用Firewalld做太复杂的事情。它擅长的是主机层面的访问控制,对于应用层的防护、入侵检测、流量分析这些,应该交给专业工具。Firewalld的定位是“把该开的门开对,该关的门关死”,做到这一点就够了。

另外,文档化很重要。每次变更防火墙规则,都应该记录:改了什么、为什么改、什么时候改的、谁改的。我见过太多因为规则变更没有记录,导致后来排查问题时完全不知道某条规则是谁加的、干什么用的。可以用简单的文本文件记录,也可以用配置管理工具做版本控制。

最后分享一个实用技巧:用--list-all-zones定期审计。这条命令会列出所有区域的完整配置,包括服务、端口、富规则、转发、伪装等。每个月跑一次,看看有没有不该开的端口、有没有过期的规则,能及时发现安全隐患。

防火墙配置这件事,理论懂了只是第一步,真正的手感是靠一次次实验和踩坑积累出来的。建议你在虚拟机里多练几遍,把区域切换、服务放行、富规则、端口转发这些操作都走一遍,遇到报错就查文档、看日志,慢慢就形成肌肉记忆了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 1:13:10

IFRS国际会计准则核心要点与实务应用解析

1. 国际财务报告准则&#xff08;IFRS&#xff09;学习背景解析在哈萨克斯坦国立大学&#xff08;KazNU&#xff09;系统学习IFRS的经历&#xff0c;让我深刻体会到这套全球通用会计准则对现代商业社会的塑造力。作为全球120多个国家采用的财务语言&#xff0c;IFRS正在重塑企业…

作者头像 李华
网站建设 2026/10/15 1:13:00

XCP协议实战指南:嵌入式标定通信的底层原理与工程落地

/* 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 1:12:28

布隆过滤器原理与生产实践:从误判机制到高并发落地

/* 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 1:12:12

基于MP2642GR-Z与STM32F405RG的双节串联锂电池主动均衡设计

/* 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 1:11:59

PCM音频采样数据处理:从原理到Python实战

/* 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 1:11:38

接口幂等性设计全解析:从重复请求防护到Redis与状态机实践

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

作者头像 李华