news 2026/9/30 6:33:12

Jenkins 拉取 GitLab 代码:从凭据配置到部署全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jenkins 拉取 GitLab 代码:从凭据配置到部署全指南

如果你和我一样,第一次在 Jenkins 里填 GitLab 仓库地址的时候卡了半个小时,那你应该能明白:拉代码这件事,看起来只是填一个 URL 和一个账号,真正跑起来之后,问题永远出在你没想到的地方。

这篇文章不打算讲太多的 CI/CD 大概念,就围绕一个非常具体的场景——Jenkins 从 GitLab 拉取代码——把这条链路上最容易出错、也最容易被教程几句话带过的环节拆开讲清楚。内容覆盖环境准备、凭据配置、Git 拉取失败的排查、任务创建以及从代码拉取到自动部署的完整过程。适合刚搭好 Jenkins 正准备接第一个构建任务的人,也适合已经在用、但经常被 “login failed” 和 “无法拉取代码” 折腾的运维和开发同学。

提示:全文以 Jenkins 2.x 版本为基础,文中涉及的插件名称和配置路径,在 Jenkins 2.3 及后续 LTS 版本中基本一致。

1. 动手之前,先想明白 Jenkins 和 GitLab 之间靠什么建立信任

很多人配置失败,不是因为操作不对,而是没搞懂底层逻辑。拉代码不是简单的“填个地址点保存”,Jenkins 要访问 GitLab,本质上要解决两件事:网络通不通,以及GitLab 认不认 Jenkins 这个客户端。前者靠网络配置,后者靠凭据。

1.1 一个被很多人忽略的前提:Jenkins 是“另一台电脑”

先建立一个关键认知:你在浏览器里打开 Jenkins 页面配置东西,但真正执行构建的是 Jenkins 节点,这个节点可能是服务器上一个 Java 进程,也可能是 Docker 容器里的一个 agent,它和你本地的电脑完全是两回事。

这意味着三件事:

  • 你的电脑能访问 GitLab,不代表 Jenkins 服务器能访问。
  • 你在本地生成的 SSH Key,不等于 Jenkins 服务器上有对应的私钥。
  • 你本机配置的 Git 用户信息,和 Jenkins 节点没有任何关系。

所以每次搭建 Jenkins 拉代码之前,第一件事永远是去 Jenkins 所在的机器上自己先试一遍。这个习惯能省掉后面百分之八十的排错时间。

1.2 选对凭据方式:SSH 和 HTTP 的本质区别

从 GitLab 拉代码,协议层面只有两类选择:SSH和HTTP/HTTPS。很多教程会告诉你“两个都行”,但实际项目中这两个的坑完全不同。

HTTP 方式在 Jenkins 里的配置是:仓库地址填http://gitlab.example.com/group/project.git,凭据类型选 “Username with password”,里面填 GitLab 用户名和密码或 Access Token。这种方式的优点是配置直观,缺点是密码或 Token 容易过期,而且每次拉代码都要做一次认证,稍微有点慢。

SSH 方式则是:仓库地址填git@gitlab.example.com:group/project.git,凭据类型选 “SSH Username with private key”,里面填私钥。GitLab 通过公钥识别 Jenkins 的身份,不需要每次输入密码。这种方式更好用,而且在服务器上配置一次后,Git 命令行工具也能复用。

我的建议是:只要是服务器对服务器的场景,一律优先 SSH。原因不只是安全,更重要的是 SSH Key 不会像 Token 一样定期失效,省去的维护成本非常可观。

1.3 GitLab 侧的用户权限模型

GitLab 对仓库的访问权限分几个档位:Guest、Reporter、Developer、Maintainer、Owner。拉取代码至少需要Reporter权限,如果要做 Push 或合入分支操作,才需要更高的 Developer 及以上。

很多人在配置的时候图省事,随手建一个普通用户,然后发现构建报 403 或 401。这不一定是凭据格式写错了,很可能就是权限不够。如果你只是让 Jenkins 拉取代码构建,我建议直接在目标项目里创建一个Deploy Key(部署密钥),只授权这一个项目,权限只读,连额外账号都不用建。这种方式后面会详细讲。

还有一个容易忽略的点:GitLab 在不同版本里对用户名和仓库地址的解析方式有变化。比如老的 GitLab 支持用用户 ID 作为路径前缀,新版本则要求使用用户名或群组名。遇到 “Project not found” 时,先别急着怀疑网络,检查一下仓库路径是不是写成了机器 ID 或数字 ID。

2. 环境准备:把 Jenkins、GitLab 和网络这三件事一次理顺

在配置任何凭据之前,先把环境梳理清楚。我见过太多人,凭据配了半天,最后发现是 Jenkins 服务器少了git命令,或者 DNS 解析不到 GitLab 域名。

2.1 组件安装与版本选择

Jenkins 的安装方式有几种:直接下载 war 包用java -jar启动、用系统包管理器安装、用 Docker 启动。如果是生产环境,我比较推荐用 Docker 或者系统服务方式,方便跟随 LTS 版本升级。

这里有个版本敏感点:Jenkins 2.3 这个版本号本身比较老,但配置思路和现在的 LTS 版本没有本质区别。如果你是用 Docker 装,建议不要用latest标签,直接固定jenkins/jenkins:lts-jdk11或对应 JDK 版本的镜像,避免某天升级后插件不兼容。

GitLab 这边的安装不在本文重点,但如果你用 Docker 装 GitLab,注意端口映射和external_url配置。external_url直接决定 SSH 的 clone 地址格式,如果配的是http://192.168.1.10,那么 Jenkins 里 SSH 方式拉代码的地址就会带着 IP 而不是域名。

2.2 插件安装:Git Plugin、GitLab Plugin 和国内镜像源

Jenkins 拉取 Git 代码依赖两个基础插件:

  • Git Plugin:提供 Git 操作核心能力,源码管理里能选 “Git” 全靠它。
  • GitLab Plugin:提供 GitLab 连接、Merge Request 触发、Webhook 通知等能力。

如果你只打算实现“从 GitLab 拉代码到本地”,那么 Git Plugin 就够了。如果你想让 GitLab 的 Webhook 自动触发 Jenkins 构建,那 GitLab Plugin 也必须装。

这里要特别提一句插件安装源的问题。很多人在安装插件时卡死在 “该 Jenkins 实例似乎已离线”,其实不是真离线,而是默认插件源(updates.jenkins.io)访问不稳定。这时候去清华或华为的开源镜像站,把Update Site地址改成国内镜像,然后Manage Jenkins -> Manage Plugins -> Advanced里点一下 “Check now”,再回到插件列表就能正常下载了。

注意:换源只解决插件下载问题,不解决 GitLab 访问问题。这两个问题千万别混在一起排查。

举一个我实际遇到的例子:某次搭建 Jenkins,插件列表一直刷不出来,控制台提示连接 updates.jenkins.io 超时。我把站点地址换成https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json以后,插件瞬间就装上了。这类问题在服务器上非常常见,属于基础设施问题,不是 Jenkins 的 Bug。

2.3 用 git ls-remote 验证“能不能拉到代码”

配置好插件和环境变量后,别急着去 Jenkins 页面创建任务。先在 Jenkins 服务器上用命令行验证一遍,这一步能隔离掉大部分网络问题。

假设你已经在 GitLab 上创建了一个 SSH Key 对,并且把公钥放到了 GitLab 用户或 Deploy Key 里,那么在服务器上执行:

git ls-remote git@gitlab.example.com:group/project.git

如果输出了一串 refs,说明 SSH 协议和网络都没问题。如果提示Permission denied (publickey),说明公钥没配好或者私钥路径不对。这样可以快速定位问题。

HTTP 方式的验证更简单:

git ls-remote http://gitlab.example.com/group/project.git

按提示输入用户名和密码(或 Access Token),能输出 refs 就说明网络通。关键是这一步做完之后,要记住用的是哪个账号、哪个 Token,后面在 Jenkins 里填的就是这套。

3. 凭据配置:SSH Key 与 Access Token 的正确用法,以及 login failed 的完整排查

凭据是 Jenkins 拉取 GitLab 代码最核心也最容易出问题的一环。基本上所有“拉不到代码”的疑难杂症,最后都能归结到凭据上。

3.1 用 Deploy Key 完成只读拉取

先推荐一种相对来说最干净的方式:Deploy Key。

在 GitLab 项目页面进入Settings -> Repository -> Deploy keys,把 Jenkins 服务器上的公钥内容粘贴进去。这种方式的好处是:

  • 不需要额外创建 GitLab 账号。
  • 只对这一个项目生效,权限范围最小。
  • 可以设置 “Write access allowed” 来决定是否允许推送,日常构建建议不要开启写权限。

Jenkins 服务器上生成 Key 的命令是:

ssh-keygen -t ed25519 -C "jenkins@build-server" -f ~/.ssh/jenkins_gitlab_ed25519

然后把生成的jenkins_gitlab_ed25519.pub内容贴到 GitLab 的 Deploy Keys 里。Jenkins 侧配置时,凭据类型选 “SSH Username with private key”,Username 填git,Private key 直接粘贴私钥文件内容,或者选择 “From the Jenkins master ~/.ssh”。

有一点需要注意:如果你用的是 Docker 方式启动 Jenkins,Jenkins 进程运行在容器里,宿主机的~/.ssh对它是不可见的。这时候要么把私钥内容直接粘贴到凭据里,要么用 volume 把宿主机的.ssh目录挂载进容器。我通常选择前者,因为可维护性更好,换机器也不怕丢。

3.2 用 Access Token 连接 GitLab API

Deploy Key 解决的是 Git 协议层的认证问题。但如果你的 Jenkins 还需要调用 GitLab API(比如创建 Merge Request、读取项目列表、触发 Webhook),那还需要在 GitLab 里生成一个Personal Access Token或Project Access Token。

生成位置在 GitLab 右上角头像 ->Preferences -> Access Tokens(老版本在Settings -> Access Tokens)。生成时勾选api权限,有效期按需设置。

拿到 Token 后,在 Jenkins 的Manage Jenkins -> Configure System -> GitLab里配置连接信息:

  • Connection name:随便填,比如gitlab-prod
  • GitLab host URL:填 GitLab 的外网访问地址,例如http://gitlab.example.com
  • Credentials:选 “GitLab API token”,粘贴 Token

填完之后点 “Test Connection”,正常情况下会显示Success。如果这里就报错,那就是 API Token 或网络问题,和后面任务里的 Git 凭据无关。

3.3 “login failed, check api token or gitlab version” 根因分析

这个报错非常经典,原文一般长这样:

login failed. check api token or gitlab version. log in via git if the version is supported.

先说结论:这个错误出现在Test Connection或任务构建时 GitLab Plugin 调用 GitLab API 的阶段,和 “git 拉取代码”本身是两回事。它说明 Jenkins 的 GitLab 插件尝试用 API Token 访问 GitLab 接口,但失败了。

常见原因按概率排序:

  1. API Token 无效或已过期。重新生成一个 Token,注意勾选api权限。
  2. 网络不通。在 Jenkins 服务器上用curl手动访问 GitLab API 试试,例如:
    curl -H "PRIVATE-TOKEN: 你的token" http://gitlab.example.com/api/v4/version
    如果返回{"version":"xx.x.x"...},说明网络正常,问题在插件侧。
  3. SSL 证书问题。如果 GitLab 是自签名证书,HTTP 访问没问题,但 Jenkins 用 HTTPS 时可能因为证书不受信任而失败。解决方式是让 Jenkins 信任该证书,或者把 GitLab 配置成 HTTP 内网访问。
  4. 插件版本与 GitLab 版本不兼容。老的 GitLab Plugin 调用的 API 接口在新版 GitLab 里被废弃,或反过来。升级插件或 GitLab 时,这个报错很容易突然出现。

排查的时候别一头扎进 Jenkins 设置里翻,要给问题分层:先确认 API 能通,再确认 Token 有效,最后才是插件兼容性。

3.4 从“手动测试”到“填入 Jenkins”的验证顺序

我自己的固定流程是这样的,分享给你作为参考:

  1. 在服务器上用git ls-remote验证 Git 协议层通不通。
  2. 用curl验证 API 通不通。
  3. 在 GitLab 侧确认账号权限和 Token Scope。
  4. 把凭据填入 Jenkins,在Credentials管理页可以点 “Verify” 测试,但注意这里的验证不一定等于构建时的行为。
  5. 创建一个最简单的 “Freestyle project”,只做 “Git 拉取” 这一个动作,跑一次构建,看控制台输出。

我见过最多的翻车现场是:有人跳过了前两步,直接在 Jenkins 里配置,结果报错以后分不清到底是网络、凭据、还是权限问题。先手动验证,再填 Jenkins,这是唯一可靠的方法。

4. 创建构建任务:分支、仓库地址和触发方式的最佳实践

环境通了、凭据也配好了,接下来才是真正的 “从 GitLab 拉代码” 环节。这里的一些参数设置,直接影响构建速度和稳定性。

4.1 自由风格任务还是 Pipeline

实现拉代码这个动作,有两种常见方式:

自由风格任务(Freestyle project)的界面比较直观,适合初学者和简单的构建场景。在 “Source Code Management” 里选 Git,填入 Repository URL,选择凭据,指定 Branches to build,就完成了。优点是上手快,缺点是一旦构建步骤变多,难以维护。

Pipeline则是把整个构建过程写成Jenkinsfile,代码即配置,适合复杂流程。拉代码的 Pipeline 片段大概是这样的:

pipeline { agent any stages { stage('Checkout') { steps { checkout([ $class: 'GitSCM', branches: [[name: env.BRANCH_NAME]], extensions: [], userRemoteConfigs: [[ url: 'git@gitlab.example.com:group/project.git', credentialsId: 'jenkins-gitlab-ssh-key' ]] ]) } } } }

如果你以后要上 “自动化部署”“多分支流水线”,建议直接学 Pipeline,省得以后迁移一次。

4.2 分支参数化与浅克隆

默认情况下,Jenkins 每次构建都会执行一次git fetch,把远端所有分支的引用都拉下来。仓库小还好,仓库一旦大(比如包含多年历史、大量二进制文件的仓库),构建时间和磁盘占用会非常难看。

两种优化手段很实用:

  • 浅克隆(Shallow clone):在 Git Plugin 的 “Additional Behaviours” 里选择 “Advanced sub-modules behaviours” 或直接加 “Shallow clone” 行为,设置depth为 1。这样只拉取最新一次提交,构建速度能快几倍。
  • 指定分支:如果项目固定发布分支为master或main,直接把 Branches to build 写成*/master,不要写成**。

如果你需要“构建时手动选择拉取哪个分支”,可以安装Git Parameter Plugin,在任务参数里加一个 Git 参数,类型选 “Branch”,这样每次构建前端会有一个下拉框,动态读取远端分支列表,选择后作为拉取分支传入。

这一步在很多团队里是刚需:开发要测某个特性分支,测试要部署稳定分支,运维要随时发布 hotfix。没有 Git Parameter 的话,就只能每次改配置里的分支名,或者为每个分支建一个任务,明显太低效。

4.3 触发方式:轮询与 Webhook 怎么选

让 Jenkins 知道“代码更新了”,有两种思路。

轮询(Poll SCM)是最简单的方式:设置一个 cron 表达式,比如H/5 * * * *,每 5 分钟检查一次远端仓库,有变化才触发构建。缺点是检查有延迟,而且每次都会向 GitLab 发请求,仓库多时会给 GitLab 带来压力。

Webhook是 GitLab 在代码提交或合并请求事件发生时,主动通知 Jenkins。在 GitLab 项目里进入Settings -> Webhooks,添加 Jenkins 的 Webhook 地址,例如:

http://jenkins.example.com/project/你的任务名

但如果你的 Jenkins 在 NAT 后面或没有公网 IP,GitLab 无法主动访问,那就只能靠轮询。另外需要注意 Webhook 地址末尾的路径,和 Jenkins 插件版本有对应关系,老版本可能要加/git/notifyCommit,新版本直接用project/任务名即可。

我实际踩过的一个坑是:配了 Webhook 之后,GitLab 那边显示请求成功,但 Jenkins 迟迟不触发。最后发现是 Jenkins 的 “CSRF Protection” 阻止了匿名请求,需要在系统配置里把 GitLab 服务器的 IP 加入白名单,或者在 Webhook 请求里带上 API Token。这个问题很隐蔽,因为 Webhook 日志里看到的可能还是 200 状态码。

4.4 构建环境中的“隐藏变量”

拉取代码成功后,构建脚本经常需要知道自己现在在哪、这次构建是第几次。Jenkins 内置了很多环境变量,下面这几个在“拉代码 -> 构建”场景里最常被使用:

变量名含义典型用途
WORKSPACE工作目录,代码被拉取到的位置构建脚本里切换到代码根目录
JOB_NAME当前任务名日志、通知消息中标识任务
BUILD_NUMBER构建序号产物版本号、镜像标签
GIT_COMMIT拉取到的 commit SHA记录构建对应的代码版本
GIT_BRANCH拉取的分支名区分环境部署分支

建议别在生产构建脚本里写死/var/lib/jenkins/workspace/xxx这种路径,直接引用$WORKSPACE,这样任务迁移或者换节点不会出问题。

5. 把一段构建跑通:从拉取代码到 Java Web 应用自动部署

光拉代码不算完整的自动化,拉完之后能构建、能发布,这条链路才真正跑起来。这一步以一个典型的 Java Web 应用为例,给你一套可直接参考的思路。

5.1 一次标准的构建过程

代码已经通过 Git 插件拉取到WORKSPACE,接下来构建节点上需要准备 JDK 和 Maven 等工具。在自由风格任务的 “Build” 部分,添加一个 “Invoke top-level Maven targets”,填写:

Goals: clean package -DskipTests

如果你用 Pipeline,对应的步骤是:

stage('Build') { steps { sh 'mvn clean package -DskipTests' } }

这里有一个非常容易踩的坑:Jenkins 节点上装的是哪个版本的 JDK,和项目要求的是否一致。很多项目用 Java 8 编译,但构建节点默认 JDK 是 17,结果编译报错。解决方式是安装JDK 参数插件或者在全局工具配置里定义多个 JDK,让不同的任务选不同的版本。

在配置 “Invoke top-level Maven targets” 时,如果下拉列表里没有 Maven,需要在Manage Jenkins -> Global Tool Configuration里先添加 Maven 安装,可以自动下载,也可以指向本机已经装好的 Maven。

5.2 远程发布与部署结果验证

构建产物是target/xxx.war或target/xxx.jar,下一步是把产物发布到目标服务器。

常见的做法是使用Publish Over SSH插件:在系统配置里填好一个 SSH Server,然后在任务 “Post-build Actions” 里添加 “Send build artifacts over SSH”,设置:

  • Source files:target/*.war
  • Remove prefix:target
  • Remote directory:/opt/app
  • Exec command:执行重启脚本,比如systemctl restart tomcat或docker compose up -d

这里有个细节:确认 Jenkins 所在机器能通过 SSH 免密登录目标服务器,如果不行,在 Publish Over SSH 的配置里填好用户名和私钥。目标服务器的目录权限也要提前确认,否则文件传过去却没权限写,日志里只会显示 “Permission denied”。

部署完成后的验证也很重要。发布动作本身只是把文件丢过去,应用是否正常起来是另一回事。建议在 Exec command 里加一段健康检查:

curl -sf http://127.0.0.1:8080/health || exit 1

这样 Jenkins 会认为重启失败,构建标红,不会出现“构建成功但应用挂了”的假象。

5.3 复用与扩展:把 Pipeline 沉淀成模板

当你有多个项目都要走 “拉代码 -> 构建 -> 部署” 的流程时,重复配置自由风格任务会很痛苦。更合理的做法是把构建逻辑写进项目的Jenkinsfile,在代码仓库里维护。

pipeline { agent any options { buildDiscarder(logRotator(numToKeepStr: '10')) disableConcurrentBuilds() } environment { APP_NAME = 'my-web-app' REGISTRY = 'registry.example.com' } stages { stage('Checkout') { steps { checkout scm } } stage('Build') { steps { sh 'mvn clean package -DskipTests' } } stage('Archive') { steps { archiveArtifacts artifacts: 'target/*.war' } } stage('Deploy') { when { branch 'main' } steps { sshPublisher( publishers: [ sshPublisherDesc( configName: 'prod-server', transfers: [ sshTransfer( sourceFiles: 'target/*.war', remoteDirectory: '/opt/app', execCommand: 'sh /opt/app/deploy.sh' ) ] ) ] ) } } } }

这里checkout scm会自动使用任务里配置的仓库地址和凭据,不需要重复写。按分支判断是否部署,是通过when { branch 'main' }控制的,这样同一个 Pipeline 既能测试环境发布特性分支,也能在生产环境只发布主干,逻辑清晰,也不容易改错。

5.4 顺手解决的常见构建问题

最后列几个我在这个过程中真实遇到过、且搜索引擎里经常被搜到的问题:

问题一:git 拉取代码的时候提示 未能顺利退出(退出码 1)

这个报错本质是 Git 命令执行非零退出码。先看控制台日志中 Git 命令是哪个环节失败,重点检查:

  • 凭据是否正确(SSH Key 还是 Token)。
  • 分支名是否存在。
  • 是否把master写成了main。
  • 如果是大仓库 clone 超时,在全局 Git 配置里把http.postBuffer调大,或者直接用 SSH。

问题二:Jenkins 提示找不到 Git 仓库或项目

确认仓库地址写的是“可通过 Jenkins 访问的地址”,而不是你本地浏览器的地址。很多人在办公电脑上测试用的是 NAT 映射的域名,Jenkins 服务器解析不了,就会报 404 或 Host key verification failed。

问题三:GitLab CI/CD 里 Docker 镜像构建时报 daemon 错误

如果你用 GitLab Runner 构建 Docker 镜像,提示error response from daemon: Get ...,一般是 Runner 容器没有挂载 Docker socket,或者 registry 地址网络不通。这在“自动部署”场景下很常见,和 Jenkins 拉代码是两条链路,别混在一起排查。

最后聊两句我的实际体会

如果你问我做 Jenkins 拉代码这件事,最值得记住的一句话是什么,我会说:把“手动验证”变成肌肉记忆。所有配置问题的排查,都从 Jenkins 服务器上用命令行先试一次,而不是在网页上反复保存测试。

另一个经验是,凭据尽量走到“少而稳”:能用 SSH Key 就不用密码,能用 Deploy Key 就不额外建账号,能用一个长期有效的 Token 就不要再造第二个。这样即使团队有人离职或者 GitLab 升级,也不至于让构建一夜之间全红。

Jenkins 拉 GitLab 代码只是整个交付链路的起点,但它也是最基础的地基。希望这篇文章能帮你把这一步走稳,后面无论是做多分支流水线、镜像构建还是自动部署,都会顺手很多。

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

安全回路划分三原则:输入-逻辑-输出责任边界解析

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

作者头像 李华
网站建设 2026/9/30 6:32:41

VirtualBox新手教程:从下载安装到创建虚拟机全流程详解

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

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

Halcon多二维码识别:data code算子与工业读码参数调优

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

作者头像 李华
网站建设 2026/9/30 6:31:39

杭州智能家居方案怎么选?享趣智能全流程拆解

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

作者头像 李华
网站建设 2026/9/30 6:31:21

零基础自学编程避坑指南:从项目实战到正反馈循环

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

作者头像 李华
网站建设 2026/9/30 6:31:11

VMware Ubuntu共享文件夹配置、权限与开机自动挂载全解

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

作者头像 李华