news 2026/9/14 2:53:28

Argo CD v3.4 到 v3.5 升级指南:Breaking Changes 全解析与迁移实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Argo CD v3.4 到 v3.5 升级指南:Breaking Changes 全解析与迁移实践

Argo CD v3.4 到 v3.5 升级指南:Breaking Changes 全解析与迁移实践

【免费下载链接】argo-cdDeclarative Continuous Deployment for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/ar/argo-cd

导读

本文基于仓库中的官方升级文档 3.4-3.5.md,系统梳理从 Argo CD v3.4 升级到 v3.5 时的所有破坏性变更(Breaking Changes)、行为改进、API 与安全变化以及弃用项。文中将结合仓库源码(如 util/helm/helm.go、ui/src/app/applications/components/utils.tsx、util/db/repository_secrets.go 等)深入解释变更的底层原理,并给出可直接复制的迁移命令、YAML 配置与操作步骤,帮助运维人员制定一份完整的升级检查清单。


一、破坏性变更(Breaking Changes)总览

v3.5 是一个在 UI、Helm 集成、安全与 API 层面都发生实质变化的大版本。升级前建议按以下顺序逐项核对,避免线上故障:

  1. UI 应用详情页 CSS 类名加了user-app-前缀—— 影响自定义样式选择器;
  2. Helm 升级到 v4.x—— 影响所有使用明文 HTTP(非 HTTPS)OCI 仓库的场景,包括 Chart 依赖仓库;
  3. UI 扩展必须外部化react/jsx-runtime—— 影响基于旧版 Argo CD UI 构建的扩展插件;
  4. 事件列表 gRPC 方法返回值类型变更—— 影响自定义 gRPC 客户端与基于 OpenAPI 生成的 REST 客户端;
  5. GnuPG 签名验证被 Source Integrity 取代—— 涉及signatureKeys弃用与迁移。

下文逐一展开。


二、UI 自定义样式的类名前缀变更

变更内容

在 Argo CD 3.5 中,Application 与 ApplicationSet 详情页会基于应用名称生成一个 CSS 类,供运维人员通过自定义样式针对特定应用做页面定制。3.5 起该类的格式从application-details <appName>改为application-details user-app-<appName>,即统一加上user-app-前缀。

这一改动的动机是:若应用名称恰好与某个内置组件的样式类同名(例如应用叫login),旧格式会渲染出application-details login,从而把登录页面的.login样式错误地加载到应用详情页,导致页面布局损坏(对应 issue #24220)。

源码印证

该逻辑在 ui/src/app/applications/components/utils.tsx 中实现:

export function getApplicationDetailsContainerClass(appName: string): string { return `application-details user-app-${appName}`; }

对应的单元测试 ui/src/app/applications/components/utils.test.tsx 也明确断言了前缀行为:

expect(getApplicationDetailsContainerClass('guestbook')).toBe('application-details user-app-guestbook'); expect(classes).toContain('user-app-login');

迁移操作

如果曾在自定义 CSS 中使用过这个类,需要更新选择器:

/* Before (v3.4 及更早) */ .application-details.my-app { ... } /* After (v3.5) */ .application-details.user-app-my-app { ... }

自定义样式的注入方式本身不受影响,仍是通过argocd-cmConfigMap 的ui.cssurl配置远程 CSS 地址,或将 CSS 文件挂载到 argocd-server 容器后指定路径(详见 custom-styles.md)。


三、Helm v4 升级:明文 HTTP OCI 仓库成为强制显式配置

变更背景

v3.5 将内置 Helm 升级到4.x(文档首节写 4.2.0,Helm Upgraded 一节最终确认升级到4.2.1)。Helm v4 对 OCI 实现做了更严格的约束:如果 registry 不启用 TLS,必须在helm pushhelm registry login以及helm dependency build时显式传入--plain-http

对 Argo CD 用户而言,这意味着所有使用明文 HTTP OCI 仓库的场景都需要在 Argo CD 侧显式声明。主要有两类动作:

1. 为现有 OCI 仓库设置--insecure-oci-force-http

方式一:CLI 显式更新(需使用 v3.5 版本的 CLI):

argocd repo add <repo-url> --type helm --enable-oci --insecure-oci-force-http --upsert # 或针对 type=oci 的仓库: argocd repo add <repo-url> --type oci --insecure-oci-force-http --upsert

方式二:直接编辑 Kubernetes Secret:

stringData: insecureOCIForceHttp: "true"

2. 显式注册明文 HTTP 的依赖仓库

如果 Helm Chart 的Chart.yaml中有以oci://...声明的 OCI 依赖,且这些依赖托管在明文 HTTP registry 上,那么这些依赖仓库现在必须像主仓库一样显式添加到 Argo CD 并设置--insecure-oci-force-http。原因在于:

  • Helm v3 时代:明文 HTTP 的 OCI 依赖仓库可以不注册、透明访问;
  • Helm v4 时代:Argo CD 必须知道哪些依赖仓库使用明文 HTTP,才能在执行helm dependency build时正确传入--plain-http

底层实现:flag 如何传递到 Helm 命令

源码 util/helm/helm.go 中的DependencyBuild逻辑可以印证这一点:Argo CD 会先遍历所有依赖仓库,对启用了 OCI 且有凭据的仓库逐一执行helm registry login(此时每个仓库可独立携带自己的InsecureOCIForceHttp),随后再扫描一次:只要任何一个依赖仓库设置了InsecureOCIForceHttp,整个helm dependency build命令就会追加--plain-http——因为 helm dependency build 不支持按仓库分别指定 TLS 策略。

// util/helm/helm.go 中的关键逻辑(节选) plainHTTP := false for i := range h.repos { if h.repos[i].InsecureOCIForceHttp { plainHTTP = true break } } _, err := h.cmd.dependencyBuild(h.insecure, plainHTTP)

命令构造层面,util/helm/cmd.go 的RegistryLogin会在plainHTTP为 true 时追加--plain-http;而 util/helm/cmd_test.go 中的测试用例plain-httpinsecure and plain-http both set验证了该参数在helm registry loginhelm pullhelm dependency build三条命令上的拼接行为。

--insecure-oci-force-http的合法性校验在 cmd/util/common.go 的ValidateInsecureOCIForceHTTP中实现:该 flag 只能用于type=oci仓库,或type=helm且开启了--enable-oci的仓库,否则报错--insecure-oci-force-http requires --type oci or --enable-oci(对应测试见 cmd/util/common_test.go)。

Secret 中insecureOCIForceHttp键的读写由 util/db/repository_secrets.go 通过updateSecretBool处理,测试 util/db/repository_secrets_test.go 验证了该键与Repository.InsecureOCIForceHttp字段的双向同步。

已知限制:冲突的 TLS flag(Helm v4)

当同一个 Argo CD OCI 仓库同时设置以下两个 flag 时会产生冲突:

  • --insecure-skip-server-verification(传给 Helm 为--insecure-skip-tls-verify
  • --insecure-oci-force-http(传给 Helm 为--plain-http

Helm v4 会静默丢弃--plain-http,因为--insecure-skip-tls-verify具有内部优先级,最终明文 HTTP 操作会以http: server gave HTTP response to HTTPS client失败。受影响的具体场景:

场景失败环节
type=helm+--enable-oci仓库同时挂两个 flagChart 拉取失败
主仓库设--insecure-skip-server-verification,依赖仓库设--insecure-oci-force-httphelm dependency build失败

官方文档明确指出:当同一链路中两种配置都确实需要时,目前没有 workaround。升级前请核查是否命中此限制。

spec.source.helm.version的影响

如果 Application 中曾显式声明 Helm v3:

spec: source: helm: version: v3

该字段在 v3.5 中会被忽略——Argo CD 将只使用 Helm v4 渲染 Chart。无需删除或修改此字段,保留即可,但应了解其不再生效。


四、UI 扩展升级:React 16 → React 19

v3.5 将 Argo CD UI 从 React 16 升级到React 19。基于旧版 UI 构建的 UI 扩展在加载时可能抛出TypeError,界面会呈现如下错误:

Extension <name>.js failed to load: TypeError: Cannot read properties of undefined (reading '<prop>')

解决办法:重新构建扩展,将react/jsx-runtime外部化(externalize)。

完整的修复指南参见 ui-extensions-react-19-upgrading.md。未安装 UI 扩展的用户无需任何操作。


五、事件列表 gRPC 方法返回类型变更(API Changes)

变更内容

Argo CD 3.5 将事件列表相关 gRPC API 的响应类型从 Kubernetes 的k8s.io.api.core.v1.EventList改为 Argo CD 自定义的EventList类型,目的是在不把 Kubernetes protobuf 类型直接暴露到 Argo CD 公共 gRPC 接口的前提下,兼容更新的 Kubernetes protobuf 定义。

受影响的 gRPC 方法:

  • application.ApplicationService/ListResourceEvents
  • applicationset.ApplicationSetService/ListResourceEvents
  • project.ProjectService/ListEvents

对 gRPC 客户端的影响

任何调用上述 RPC 的生成型或自定义 gRPC 客户端,都必须与 Argo CD 3.5 服务端同步重新生成或升级。以下方式不能作为兼容方案:

  • 直接调用受影响 RPC 的程序或脚本,必须同步更新;
  • 使用 grpc-web 协议不是兼容 workaround。

Argo CD 官方 CLI不依赖这些 API,因此不受影响。

对 REST 客户端和 UI 的影响

REST 路径与请求方式完全不变:

  • GET /api/v1/applications/{name}/events
  • GET /api/v1/applicationsets/{name}/events
  • GET /api/v1/projects/{name}/events

JSON 响应体仍是EventList形状的载荷,因此 UI 和以 JSON 方式消费这些端点的 REST 集成不受影响

OpenAPI / Schema 注意点

REST 路径和 JSON 载荷不变,但生成的 OpenAPI schema 中这些端点改用了 Argo CD 的eventsEventList定义(替代io.k8s.api.core.v1.EventList)。如果基于 Argo CD 的 OpenAPI 定义生成 REST 客户端,请将其视为 schema 级别的破坏性变更,升级时需重新生成或更新。


六、行为改进与修复(Behavioral Improvements / Fixes)

1. 模拟身份(Impersonation)扩展到全部 server 操作

启用模拟身份后,3.5 之前只作用于 sync 操作;3.5 起,所有通过 UI 或 API 触发的操作(查看日志、列出事件、删除资源、执行资源动作等)都会使用由 AppProject 的destinationServiceAccounts配置派生的模拟服务账号。

受影响的操作与所需权限:

操作Kubernetes API 调用所需 RBAC verbs
Get resourceGET目标资源get
Patch resourcePATCH目标资源get,patch
Delete resourceDELETE目标资源delete
List resource eventsLISTevents(core/v1)list
View pod logsGETpodspods/logget
Run resource actionGETCREATEPATCH目标资源get,create,patch

上表覆盖内置操作;自定义资源动作(custom resource actions)可能根据其调用的 Kubernetes API 需要额外权限。

升级动作:启用了 impersonation 的用户必须确保destinationServiceAccounts中配置的服务账号具备上述操作的权限。未启用 impersonation 的用户无需操作。

2. 无凭据 SSH 仓库改用argocd-ssh-known-hosts-cm校验主机密钥

Argo CD 将go-git升级到 v5.19.x,其 SSH 主机密钥校验更加严格。为保证主机密钥校验仍能命中 Argo CD 托管的argocd-ssh-known-hosts-cmConfigMap(并避免新版 go-git 下的knownhosts: key mismatch握手失败),Argo CD 现在会为未配置显式凭据的 SSH 仓库自行构建 SSH auth

行为对比:

  • 升级前:无凭据的 SSH 仓库 URL 会走 go-git 的默认 auth builder,基于ssh-agent构建 auth,并从容器内的~/.ssh/known_hosts(或$SSH_KNOWN_HOSTS)读取 known_hosts;
  • 升级后:Argo CD 构建相同的ssh-agent式 auth,但将主机密钥回调指向argocd-ssh-known-hosts-cmConfigMap,与“配置了凭据的仓库”行为保持一致。

升级动作:如果此前依赖 repo-server 镜像/Pod 内自定义的~/.ssh/known_hosts(例如打进自定义镜像或通过 volume 挂载),请把这些主机密钥改加到argocd-ssh-known-hosts-cm。仅使用“配置了凭据的 SSH 仓库”或仅使用 HTTPS 仓库的用户无需操作。

3. GnuPG 签名验证被 Source Integrity 取代

GnuPG 密钥验证功能已被 Source Integrity 取代——后者是更通用的应用源完整性校验子系统。旧配置临时继续生效(会输出 warning)以方便迁移,但建议尽快迁移。


七、安全变更(Security Changes)

本版本的安全侧变化集中在模拟身份(impersonation)覆盖面扩大(见上文第六节)与repo-server 新增 mTLS 支持(见下文第九节)两大块。

另外需注意:由于 Helm v4 对明文 HTTP OCI registry 的严格校验,原先“透明访问”的明文 HTTP 依赖仓库不再被隐式信任,必须显式登记并声明--insecure-oci-force-http(见第三节),这本质上也是一项安全收紧——未经登记的非 TLS 仓库将不再被静默访问。


八、弃用项(Deprecated Items)

1. GnuPG 签名配置弃用

  • argocd proj add-signature-keyargocd proj remove-signature-key命令弃用
  • AppProject 中声明.spec.signatureKeys弃用

应改在 AppProject YAML 中配置sourceIntegrity

2. GnuPG 签名验证结果弃用

  • 从 REST API 读取verifyResult字段弃用
  • 应改为消费结构化字段sourceIntegrityResult

迁移示例

参考 source-integrity-git-gpg.md,要复刻旧版 Argo CD 的验证行为,可移除.spec.signatureKeys并写入:

apiVersion: argoproj.io/v1alpha1 kind: AppProject spec: sourceIntegrity: git: policies: - repos: - url: "*" # 项目内任意仓库 gpg: mode: "head" # 仅验证目标 revision 的 HEAD keys: - "..." # 原 .spec.signatureKeys 中的密钥

迁移说明中还提到:当.spec.sourceIntegrity未定义而.spec.signatureKeys存在时,Argo CD 会在后台做类似转换;但官方建议主动迁移,因为 source integrity 配置更灵活,且.spec.signatureKeys将在未来版本移除。降级(downgrade)时则需反向操作:在 AppProject 中重新引入.spec.signatureKeys并填入.spec.sourceIntegrity.git.policies的全部密钥,再删除.spec.sourceIntegrity段(降级后的旧功能仅支持全部仓库的 "head" 模式)。

未使用 GnuPG 签名验证的用户无需操作。


九、其他变更(Other Changes)

1. 发布物sbom.tar.gz内容调整

普通 GitHub 发布中,sbom.tar.gz仍包含:

  • bom-go-mod.spdx(Go 依赖,spdx-sbom-generator生成的 tag-value SPDX);
  • bom-docker-image.spdx(发布镜像,sigs.k8s.io/bom生成的 tag-value SPDX)。

变化在于 UI 依赖清单:现在是bom-ui-pnpm.spdx.jsonpnpm sbom生成的SPDX 2.3 JSON),取代了原来spdx-sbom-generator./ui目录输出的 tag-value 格式。

升级动作:如果消费该归档的工具只扫描*.spdx文件,请扩展以同时处理bom-ui-pnpm.spdx.json;或改用argocd-sbom.intoto.jsonl验证sbom.tar.gz,避免依赖固定的内部文件清单。

2. repo-server 的 mTLS 支持(新增)

mTLS 是默认关闭的可选能力,未配置的运维人员无需任何操作。

启用方式:创建名为argocd-repo-server-mtls的 Secret 即可,内容如下:

apiVersion: v1 kind: Secret metadata: name: argocd-repo-server-mtls namespace: argocd type: Opaque stringData: client-ca.crt: | <PEM-encoded CA certificate that signed the client certs> client.crt: | <PEM-encoded shared client certificate> client.key: | <PEM-encoded private key for the shared client certificate>

该 Secret 会被自动挂载到每个相关 Pod 的/app/config/reposerver/mtls路径。所有组件默认从该挂载路径读取 cert/key/CA 文件,因此只要 Secret 存在,mTLS 即自动启用——无需修改 ConfigMap、无需 flag 覆盖、无需额外配置。

关键点汇总:

  • 服务端(repo-server)
    • --client-ca-path默认指向/app/config/reposerver/mtls/client-ca.crt(即自动挂载路径),文件存在即启用 mTLS,缺失则静默跳过;
    • 不能与--disable-tls组合使用
    • 服务端 TLS cert/key 从/app/config/reposerver/tls/tls.crttls.key加载。
  • 客户端(argocd-server、application-controller、applicationset-controller、notifications-controller)
    • --repo-server-client-cert-path默认指向/app/config/reposerver/mtls/client.crt,文件不存在则跳过 mTLS 客户端证书;
    • --repo-server-client-cert-key-path默认指向/app/config/reposerver/mtls/client.key,同理;
    • --repo-server-ca-cert-path可选,当 repo-server 的服务端证书由自定义 CA 签发时使用。

运维提示:

  • 启用 mTLS 后,repo-server 会为自身的 liveness 自检自动生成临时客户端证书,无需修改探针;
  • 所有上述配置均有对应的环境变量与argocd-cmd-params-cmConfigMap 键(例如ARGOCD_REPO_SERVER_CLIENT_CA_PATH,详见 mtls.md)。

完整搭建步骤、各组件证书选项、验证方法与排障指南见 mtls.md。

3.--repo-server-strict-tls弃用,改用--repo-server-ca-cert-path

弃用范围--repo-server-strict-tls布尔 flag 在以下组件中全部弃用:

  • argocd-server
  • argocd-application-controller
  • argocd-applicationset-controller
  • argocd-notification

弃用原因:旧 flag 采用隐式行为(自动加载/app/config/server/tls/下的内嵌证书),难以在各组件间保持一致;新方式更显式,且与 Kubernetes Secret 挂载模式对齐。

重要:此弃用不影响TLS 能力本身——仍可用新 flag 配置纯 TLS(不含 mTLS)连接。

迁移指南

方式一:迁移到--repo-server-ca-cert-path(推荐)

# Before (3.4 及更早): argocd-server \ --repo-server-strict-tls # After (3.5): argocd-server \ --repo-server-ca-cert-path=/app/config/server/tls/ca.crt

对应 Kubernetes manifests 的写法变化:

# Before: spec: containers: - name: argocd-server args: - argocd-server - --repo-server-strict-tls volumeMounts: - name: server-tls mountPath: /app/config/server/tls # After: spec: containers: - name: argocd-server args: - argocd-server - --repo-server-ca-cert-path=/app/config/server/tls/ca.crt volumeMounts: - name: server-tls mountPath: /app/config/server/tls

方式二:使用环境变量(备选)

# Before: argocd-server --repo-server-strict-tls # After: export ARGOCD_SERVER_REPO_SERVER_CA_CERT_PATH=/app/config/server/tls/ca.crt argocd-server

方式三:继续使用弃用 flag(不推荐,仅临时)

该 flag 在 3.5 仍可用(会输出弃用警告),且支持与新 flag 同时使用:

# 3.5 中仍可用(会有弃用警告): argocd-server \ --repo-server-strict-tls \ --repo-server-ca-cert-path=/app/config/server/tls/ca.crt
向后兼容性
  • 两个 flag 可同时使用,采用OR 语义:任一为 true 即启用严格校验;
  • 功能无损,TLS 校验能力保持不变;
  • 新 flag 更显式、更易维护。
时间线
  • v3.5:弃用 flag 仍可用(带警告);
  • v3.6+:该 flag 可能被移除,请提前规划迁移。

所有受影响组件(server、application-controller、applicationset-controller、notification-controller)的迁移模式一致。


十、Kustomize / Helm / 自定义健康检查

  • Kustomize:本版本升级文档中未列出 Kustomize 的升级项(无内容);
  • Helm:升级到4.2.1,破坏性变更已在本章第三节详述;
  • Custom Healthchecks:本版本无新增自定义健康检查条目。

十一、升级检查清单(速查)

检查项是否受影响需执行动作
自定义 CSS 中是否使用application-details <app>选择器改为application-details.user-app-<app>
是否存在明文 HTTP 的 OCI 仓库(type=helm + enable-oci / type=oci)为仓库及依赖仓库设置--insecure-oci-force-http --upsert,或 Secret 中加insecureOCIForceHttp: "true"
主仓库/依赖仓库是否同时存在 insecure-skip-server-verification 与 insecure-oci-force-http存在已知限制、无 workaround,需调整架构
Application 是否声明spec.source.helm.version: v3该字段被忽略,无需修改,但应知晓用 Helm v4 渲染
是否安装 UI 扩展按 ui-extensions-react-19-upgrading.md 外部化react/jsx-runtime并重建
是否直接调用事件列表 gRPC / 基于 OpenAPI 生成 REST 客户端重新生成/升级客户端(CLI 与 UI 不受影响)
是否启用 impersonationdestinationServiceAccounts配置 get/patch/delete/list/create 等所需权限
是否依赖无凭据 SSH 仓库的容器内~/.ssh/known_hosts主机密钥改入argocd-ssh-known-hosts-cm
是否使用 GnuPG 签名验证迁移到sourceIntegrity(详见 source-integrity-git-gpg.md)
是否消费sbom.tar.gz且只扫描*.spdx兼容bom-ui-pnpm.spdx.json或改用argocd-sbom.intoto.jsonl
是否配置 repo-server 的--repo-server-strict-tls迁移到--repo-server-ca-cert-path或对应环境变量
是否希望启用 repo-server mTLS可选创建argocd-repo-server-mtlsSecret 即自动启用

十二、参考文档

  • 本文核心来源:docs/operator-manual/upgrading/3.4-3.5.md
  • 升级总览:docs/operator-manual/upgrading/overview.md
  • UI 扩展 React 19 升级指南:docs/operator-manual/upgrading/ui-extensions-react-19-upgrading.md
  • 自定义样式:docs/operator-manual/custom-styles.md
  • 模拟身份同步:docs/operator-manual/app-sync-using-impersonation.md
  • Source Integrity:docs/user-guide/source-integrity.md、GnuPG 迁移:docs/user-guide/source-integrity-git-gpg.md
  • repo-server mTLS:docs/operator-manual/mtls.md

【免费下载链接】argo-cdDeclarative Continuous Deployment for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/ar/argo-cd

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

国产文件传输方案如何解决FTP三大痛点?

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

作者头像 李华
网站建设 2026/9/14 2:50:30

车载CAN-LIN网关OTA升级实战:协议转换与刷写可靠性设计

1. 项目概述&#xff1a;为什么一个车载网关的刷写升级方案值得拆解到毫米级CAN-LIN网关不是一块简单的“翻译器”&#xff0c;它是整车电子电气架构里真正意义上的神经中枢——一边连着高速、高可靠性的CAN总线&#xff08;比如发动机控制单元ECU、ABS模块、仪表盘&#xff09…

作者头像 李华
网站建设 2026/9/14 2:50:22

去中心化微博链上数据模型:以太坊存哈希 + IPFS 存正文的架构实践

简介&#xff1a;基于以太坊的去中心化微博系统设计与实现资料包&#xff0c;面向计算机科学、软件工程、信息工程等专业学生&#xff0c;以及正在入门区块链应用开发的开发者。项目定位为毕业设计与课程设计参考&#xff0c;完整涵盖智能合约、前端界面与设计文档&#xff0c;…

作者头像 李华
网站建设 2026/9/14 2:49:26

Grok与Codex代码理解范式对比:架构差异决定Agent落地成败

1. 项目概述&#xff1a;一场关于“理解力”的硬核实测最近两周&#xff0c;我连续在三套不同规模的开发环境里跑通了 Grok 的本地推理链路——不是调 API&#xff0c;是真刀真枪从模型权重加载、Tokenizer 初始化、KV Cache 管理到响应流式输出全链路手调。标题里那句“强&…

作者头像 李华
网站建设 2026/9/14 2:48:41

同一把 TaoToken Key,在 CC-Switch 里从 Claude 切到 DeepSeek V4

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

作者头像 李华