news 2026/9/7 14:55:39

从本地到上线:Python项目Docker化部署实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从本地到上线:Python项目Docker化部署实战指南

带了几年项目,我越来越觉得“本地能跑”和“能上线”完全是两码事。Python 写业务逻辑确实快,但一旦要部署给别人用、放到服务器上长期跑,各种环境问题就会接踵而至:Python 版本对不上、系统少了个底层库、依赖装到一半报错、换台机器直接崩。后来我把项目切到 Docker 容器化部署之后,这些问题基本从根上消失了。这篇我就用一套完整的 Python 项目部署流程,把 Docker 从安装到 Compose 编排一次讲透,适合刚学 Python、想了解工程化部署的同学,也适合已经写了一段时间脚本、想把手头项目规范化的开发者。

1. 为什么Python项目一定要学会容器化部署

1.1 “在我电脑上是好的”到底是怎么来的

先讲个特别常见的场景。你写了个 Flask 接口,在本机跑得好好的,同事 clone 下来一跑就报ModuleNotFoundError,仔细一看,同事的 Python 是 3.8,你是 3.11,依赖版本冲突,系统还没装libpq这个底层库。这种问题的根源在于:传统部署方式把“代码”和“运行环境”完全绑死在了一台机器上,而每台机器的系统和软件状态都不一样。

这就好比你在家厨房炒了一盘菜,味道很好,但你把菜打包给朋友时,朋友得把自己家厨房也装修成和你一模一样,才能复现那个味道。听上去很荒谬,但在没有容器化的时候,服务器部署就是这么干的。

Docker 的思路则简单得多:把“菜谱、食材、锅具、火候”全部打成一个标准化的盒子,谁拿到这个盒子,打开就能吃到一模一样的菜。放到项目里,就是镜像。

1.2 容器化到底解决了什么

用容器化部署 Python 项目,核心价值其实就四条。

第一是环境隔离。每个容器有自己的文件系统、Python 版本、依赖库,互不干扰。同一个服务器上可以同时跑 Python 3.8 的老项目和 Python 3.12 的新项目,不会打架。

第二是交付一致性。开发环境、测试环境、生产环境用的是同一个镜像,行为完全一致。这直接消灭了“我这边没问题啊”这句经典台词。

第三是快速扩容。容器比虚拟机轻得多,启动只要几秒钟。遇到流量涨了,直接再多开几个容器实例就完事,不用重新配置环境。

第四是团队协作。Dockerfile 本身就是一份“环境即代码”的文档,新人按流程一跑就能复现整个项目,上手成本大幅降低。

1.3 一条完整的部署主线

这篇博文的实操主线很明确:先配好 Python 和 Docker 环境,再写一个 Python 小项目,用 Dockerfile 把它构建成镜像,接着用 Docker Compose 把 Web 服务、MySQL、Redis 编排起来,最后把常见的部署坑过一遍。整个过程跟着做,你就能拥有一个真正能交付、能迁移、能上线的 Python 项目。

我建议你一边读一边动手,不要只看不敲。Docker 这东西光看命令是学不会的,多踩几个坑之后,你对整个部署链路会有完全不一样的理解。

2. 环境准备:Python、Docker与开发工具一次配齐

2.1 Python安装与版本管理的选择

现在做 Python 开发,第一步不是去官网下个安装包那么随意了。我的建议是装一个版本管理工具,比如pyenvconda,原因特别简单:你在公司可能维护两三个项目,A 项目用 Python 3.9,B 项目用 Python 3.11,如果只装一个全局 Python,切来切去就是灾难。

Windows 上最省事的做法是装 Anaconda 或者 Miniconda,然后用conda create -n myenv python=3.11创建独立环境。macOS 和 Linux 上则可以用pyenv,安装好之后pyenv install 3.11.8就能拉起一个指定版本。

不过要提醒一句:如果你后面铁了心用 Docker 做部署,宿主机上的 Python 其实只需要满足“本地开发调试”这个需求,不用纠结版本多精确。真正干活的运行环境是容器里的 Python,那才是标准环境。

检查安装是否成功的命令永远是这两条:

python --version pip --version

看到版本号正常输出,就说明基础环境没问题。如果提示pip找不到,Windows 上可能需要把 Python 的 Scripts 目录加入 PATH,或者在终端里用python -m pip代替。

2.2 Docker安装:Windows、macOS、Linux 三个平台一次说清

Docker 的安装是很多人最开始卡住的地方。先说结论:Windows 和 macOS 用户直接装 Docker Desktop,Linux 用户用命令行装 Docker Engine。

Windows

Windows 装 Docker Desktop 之前,建议先把 WSL2 装好,因为 Docker Desktop 默认就是靠 WSL2 后端跑的,性能比老旧的 Hyper-V 方案更稳。装完 WSL2 之后再去官网下 Docker Desktop 安装包,一路 Next 就行。

装完之后打开 Docker Desktop,等右下角鲸鱼图标变成稳定状态,就代表 Docker 引擎已经在跑了。这里有个新手特别容易困惑的地方:Docker Desktop 只是个管理界面,真正执行命令的 Docker 引擎在 WSL2 的虚拟机里,所以终端里能直接敲docker命令,前提是 Docker Desktop 在后台运行。

macOS

macOS 同样装 Docker Desktop,唯一要注意的是苹果芯片的 Mac 装的是 ARM 版本,有些老镜像可能没有 ARM 架构支持,但近几年主流镜像基本都补齐了。万一拉取时遇到架构不兼容的提示,可以在镜像名前加--platform linux/amd64参数。

Linux

Linux 下不需要 Desktop,直接装引擎就够了。以 Ubuntu 为例,用 apt 安装前先更新索引,然后装docker.io或官方源里的docker-ce。国内服务器如果访问官方源慢,可以直接用阿里云的镜像脚本,装完再配置镜像加速。

装完先验证一下:

docker --version docker ps

docker ps能正常列出空表格,就代表 Docker 服务在运行。如果报Cannot connect to the Docker daemon,Linux 上通常是当前用户不在 docker 用户组里,执行sudo usermod -aG docker $USER后重新登录即可。

2.3 用VSCode配置Python开发环境

编辑器这边,我推荐 VSCode,免费、插件生态齐全,配置 Python 开发环境非常顺滑。装完之后需要做三件事。

第一是安装 Python 扩展,这会提供语法高亮、智能提示、代码补全和调试功能。第二步是安装 Docker 扩展,它能让你在编辑器里直接看到镜像、容器、日志,不用来回切终端。第三步是关联解释器,按Ctrl+Shift+P打开命令面板,输入Python: Select Interpreter,选择你当前环境的 Python 路径。

如果你用的是 PyCharm,配置方式大同小异:在 Settings 里找到 Project Interpreter,选择已经建好的 conda 环境或系统 Python,顺手把 Docker 插件在 Plugins 里启用就行。两个编辑器选一个用顺手即可,我个人的建议是 VSCode,因为它在写 Python 和看容器日志之间的切换最省力。

2.4 先让Docker跑起来:从hello-world到第一个Python脚本

环境装完之后,别急着写项目,先跑一个最经典的健康检查命令:

docker run hello-world

这条命令会去镜像仓库拉一个hello-world镜像,然后创建容器、执行输出、退出。如果你能看到一段英文说明文字,恭喜,Docker 已经通了。

接着试试在容器里跑 Python,感受一下“环境隔离”是什么意思:

docker run --rm -it python:3.11-slim python

这行命令的含义是:用官方python:3.11-slim镜像启动一个临时容器,在交互模式里执行 Python 命令。进去之后随便打两句:

print("hello from container")

看到输出再敲exit()退出。你刚刚已经在容器里用 Python 了,而你宿主机上甚至都没装 3.11。这个体验,就是容器化最直观的震撼。

顺手再演示一个稍微有趣点的脚本,把经典的“李白打酒”问题在容器里跑一下。题目大意是李白出门喝酒,遇店加一倍,见花喝一斗,三次遇店和花之后壶中酒喝光了,问原有多少酒。反向推导是最简单的:

wine = 0 for action in ["flower", "shop", "flower", "shop", "flower", "shop"]: if action == "flower": wine += 1 else: wine /= 2 print(f"壶中原有酒: {wine:.4f} 斗")

可以把这段代码保存成wine.py,然后用一条命令直接放到容器里执行:

docker run --rm -v "$PWD":/app -w /app python:3.11-slim python wine.py

这里-v "$PWD":/app是把当前目录挂载进容器,-w /app指定工作目录。看到结果的那一刻,你就会明白挂载卷这个东西在容器里有多常用。

3. Dockerfile编写与镜像构建:把Python项目装进容器

3.1 一个最小可用的Dockerfile

环境跑通之后,就该正式碰核心内容了:写 Dockerfile。这是整个容器化部署的灵魂文件,它的作用就是告诉 Docker:你要构建一个什么样的镜像。

我先用一个最简单的 FastAPI 项目举例。项目目录长这样:

my_project/ ├── app.py ├── requirements.txt └── Dockerfile

app.py是一个最简单的 Web 服务:

from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"message": "hello docker"}

requirements.txt里只有一行:

fastapi uvicorn

对应的 Dockerfile 是这个样子:

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt COPY . . EXPOSE 8000 CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

逐行解释一下意图。FROM指定基础镜像,相当于你做饭的锅,选python:3.11-slim是因为它体积小、够用。WORKDIR设定容器里的工作目录,后面所有命令默认在这个目录下执行。COPY把文件从宿主机复制进容器。RUN在构建阶段执行安装命令。CMD是容器启动时默认执行的命令。

这五行指令,就是绝大多数 Python 项目镜像的骨架。

3.2 镜像分层机制:为什么要求先把依赖复制进去

初学者最容易忽略的是指令顺序。同样是 COPY 两个文件,顺序不同,构建效率天差地别。

Docker 镜像是一层一层堆叠的,每次RUNCOPY都会生成一个新层。只要某一层的内容没变,Docker 在下次构建时就会直接用缓存层,大幅加速构建过程。

所以我上面把COPY requirements.txt .放在复制全部代码之前,是有意的。这样一来,当你改了一行app.py再重新构建时,Docker 发现 requirements.txt 没变,就跳过RUN pip install那层的缓存重建,只有后面代码层会重新拷贝。如果你把代码全部先复制进去,那么每次改代码都会导致依赖重装一遍,构建过程动辄几分钟,体验非常糟糕。

换句话说:把“不常变的东西”放在 Dockerfile 上方,“经常变的东西”放在下方,这是写 Dockerfile 最重要的缓存优化原则。

3.3 构建与运行:亲手把项目跑起来

确认 Dockerfile 和项目文件就位之后,在项目目录下执行:

docker build -t my-fastapi-app .

-t是给镜像打个标签,方便后续引用。最后的.表示构建上下文是当前目录,Docker 会把当前目录的所有文件打包发送给守护进程。所以如果你项目里有大文件、虚拟环境目录、缓存文件,一定要建一个.dockerignore文件把它们排除掉,否则构建过程会又慢又臃肿:

__pycache__ *.pyc .venv venv .git .env

构建完成后看一眼镜像列表:

docker images docker run -d -p 8000:8000 --name my-app my-fastapi-app

-d表示后台运行,-p 8000:8000把容器的 8000 端口映射到宿主机的 8000 端口。启动后访问http://localhost:8000,能看到{"message":"hello docker"}就说明整个链路通了。

这时候再执行docker logs my-app能看到 uvicorn 的启动日志,执行docker exec -it my-app /bin/bash可以进入容器内部查看文件结构。容器已经成为一个独立的小机器在为你服务了。

3.4 镜像加速与镜像仓库的基础认知

构建和运行都通了,还有一个绕不开的问题:镜像拉取慢。尤其是第一次执行docker run python:3.11-slim或者docker build时,经常卡在下载基础镜像这一步。

解决办法是配置镜像加速器。Docker Desktop 用户在设置里找到 Docker Engine,在配置文件中加入镜像源地址,Linux 用户则修改/etc/docker/daemon.json

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.mirrors.ustc.edu.cn" ] }

改完重启 Docker 服务再拉镜像,速度会明显提升。需要注意,公共加速源的稳定性会时不时变化,如果某个地址失效,换一个可用的即可。

至于镜像仓库,你可以把docker build出来的镜想象成一个压缩包。要给这个压缩包“上传到服务器”,需要先打标签再推送:

docker tag my-fastapi-app yourname/my-fastapi-app:v1.0 docker push yourname/my-fastapi-app:v1.0

把镜像推到仓库之后,任何一台装有 Docker 的机器都能通过docker pull拉取并运行,这也是团队协作交付的标准姿势。

4. 用Docker Compose编排多容器项目:Web + MySQL + Redis

4.1 为什么要上Compose

一个正经项目很少只有一个服务。常见组合是 Web 应用 + MySQL 存数据 + Redis 做缓存。如果全靠docker run一条条命令启动,你要记下每个容器的端口、数据卷、网络,管理成本非常高。

Docker Compose 就是来解决这个问题的。它允许你用一份 YAML 文件定义所有服务,一条命令全部启动。我实际用的最多的场景是:本地开发一键起依赖环境(MySQL、Redis),以及 CI 环境里一键起整套服务跑测试。

用 Compose 之前,先确认一下版本:

docker compose version

Docker Desktop 和近几年的 Docker Engine 都自带 Compose v2,直接用docker compose子命令就能调用,不需要单独安装。

4.2 一个完整的docker-compose.yml示例

下面这个配置,我实际用在很多中小型 Python 项目里,包括了 Web 应用、MySQL 8.0 和 Redis。你可以直接拷贝修改:

version: "3.8" services: app: build: . ports: - "8000:8000" environment: - DB_HOST=mysql - DB_PORT=3306 - DB_USER=root - DB_PASSWORD=123456 - REDIS_HOST=redis - REDIS_PORT=6379 volumes: - .:/app depends_on: mysql: condition: service_healthy redis: condition: service_started mysql: image: mysql:8.0 container_name: project-mysql restart: always environment: MYSQL_ROOT_PASSWORD: "123456" MYSQL_DATABASE: "myproject" ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p123456"] interval: 5s retries: 10 redis: image: redis:7-alpine container_name: project-redis restart: always ports: - "6379:6379" volumes: - redis_data:/data volumes: mysql_data: redis_data:

重点解释几个细节。build: .表示 app 服务的镜像直接由当前目录的 Dockerfile 构建,和之前手动 build 是一回事。depends_on控制启动顺序,MySQL 还加了健康检查,防止 app 启动时数据库还没就绪,代码一连就超时。容器之间通过服务名(mysql、redis)互相访问,不需要写 IP 地址,这是 Compose 网络自动处理好的。

MySQL 8.0 这个镜像有个最常见的坑:官方镜像默认使用 caching_sha2_password 认证插件,老版本的客户端驱动连不上。如果你用的 Python 库是pymysql,通常没问题,如果是老代码用的老驱动,就需要在启动命令里加--default-authentication-plugin=mysql_native_password

4.3 数据卷、端口与环境变量的设置逻辑

新手最容易踩的坑,就是容器一删数据全没了。原因很简单:容器本身是无状态的,写在容器可写层的数据会随容器销毁而消失。所以 MySQL 和 Redis 这种需要持久化的服务,必须挂载数据卷。

上面配置里mysql_data:/var/lib/mysql就是把 MySQL 的数据目录映射到 Docker 管理的卷里。这样即使你执行docker compose down,卷里的数据也还在,下次启动时原样恢复。

端口映射这块,要理解一个概念:容器内部的服务都运行在一个隔离的网络空间里,外部是访问不到的。"3306:3306"的意思是把容器内 3306 端口映射到宿主机 3306 端口。如果你本机已经装了一个 MySQL 占了 3306,那么改成"3307:3306",外部连接就用 3307。

环境变量的使用也要养成习惯。不要把密码写死在代码里,而是通过environment传给容器,代码里用os.getenv("DB_PASSWORD")读取。更规范的做法是使用.env文件配合 Compose 的${VAR}变量替换,这个后面进阶再深入。

4.4 Compose的常用操作命令与日常使用

把上面这段配置保存为docker-compose.yml,然后在项目目录下执行:

docker compose up -d

-d表示后台启动。第一次执行会先构建镜像、拉取依赖镜像,然后再逐个创建容器。执行完之后,用docker compose ps看一眼状态,三个服务的状态应该都是 Up。

日常开发中最常用的还有这几条:

# 查看所有服务日志,-f 表示持续跟踪 docker compose logs -f # 查看某个服务的日志 docker compose logs app # 进入某个服务容器内部 docker compose exec app /bin/bash # 停止并删除所有容器,-v 会连同数据卷一起删,慎用 docker compose down

我个人的经验是:写代码过程中只用docker compose up -d把 MySQL 和 Redis 拉起来,本地用 VSCode 直接跑 Python 调试;等代码稳定了再docker compose up -d --build app重新构建应用容器做联调。这样既保证了开发效率,又能在最终环节验证容器内的真实行为。

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

5.1 镜像下载慢、超时的解决方案

这个问题每个用 Docker 的人都遇到过。基本思路是三步走。

第一步,检查是否配置了镜像加速源,方法上面已经说了。第二步,如果是构建过程中 pip 下载慢,可以在 Dockerfile 的RUN pip install后面加-i https://pypi.tuna.tsinghua.edu.cn/simple指定国内源,比配置 Docker 加速源影响更直接。第三步,如果是访问 GitHub 等资源慢导致构建失败,就得想办法把对应资源预下载到本地,再通过 COPY 复制进镜像。

这里要特别提醒:基础镜像尽量选-slim-alpine版本,比如python:3.11-slim比完整版python:3.11体积小一半以上。体积小不仅下载快,而且攻击面小,上线也更安全。

5.2 “pip install 装不上”的排查思路

这个报错我会分四类排查。第一类是网络问题,表现是下载超时或连接重置,换 pip 源基本能解决。第二类是 Python 版本不兼容,比如有些旧包不支持 Python 3.12,这时候要么调整基础镜像的版本,要么给包指定兼容版本。第三类是缺少编译工具,典型的如lxmlpandasopencv-python这类带 C 扩展的包,在python:3.11-slim里编译失败,就需要先安装gccg++和相关系统库:

RUN apt-get update && apt-get install -y gcc g++ libffi-dev

第四类是包名本身不对,有些包在 PyPI 上的名字和 import 时的名字不一样,比如安装的是opencv-python,导入时用的却是cv2。还有最近比较多人问的 COMFYUI 相关报错,提示缺少节点时,本质就是 Python 环境里缺依赖包,用pip install -U补装就好,思路是一样的。

5.3 容器启动后几秒就自动退出的问题

这是新手最常问的问题之一。容器和虚拟机不一样,虚拟机跑一个系统,容器只是跑一个进程。如果这个主进程结束了,容器也就结束了。很多人启动容器后一看容器停了,就怀疑是不是 Docker 出问题,其实不是。

解决方法第一步是看日志:

docker logs 容器名或ID

第二步是检查主进程是否在前台运行。比如直接用CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"],就是前台进程。如果你在 CMD 里写了nohup ... &或者用了某些会把进程放到后台的写法,容器就会秒退。不要手动把进程后台化,让容器进程保持前台执行。

第三步,如果日志显示模块找不到,进入容器逐个排查:

docker run -it myproject /bin/bash python -c "import fastapi; print(fastapi.__version__)"

现场确认依赖有没有装上,比反复重建镜像快得多。

5.4 端口冲突与连接被拒绝的排查

启动 MySQL 容器时报port is already allocated,说明宿主机 3306 被占了,最常见的本机已经装了 MySQL 或者上次的容器没删掉。先执行docker ps -a看容器状态,再docker compose ps看到底是谁占用了端口。排查之后把端口改为3307:3306即可。

连接被拒绝则是另一个性质的问题。如果代码里连接数据库的地址写的是localhost:3306,在容器内部根本连不通。因为在容器里 localhost 指向容器自己,不是宿主机,更不是 MySQL 容器。Compose 网络里服务之间的正确访问方式是用服务名,比如mysql:3306;宿主机访问则用映射后的端口。这个坑,我见过太多人踩了。

5.5 容器重启后数据丢失的问题

这个前面提到过,核心原因是没挂数据卷。MySQL 数据存在容器可写层里,docker compose down之后容器删除,数据跟着没了。

如果之前没有挂载卷,还有救:直接用docker cp把文件从旧容器里拷出来:

docker cp project-mysql:/var/lib/mysql ./mysql_backup

然后把备份目录挂载回新容器即可。不过这些都是补救方案,正确做法是一开始就给数据库、缓存、日志目录都配置好数据卷。记住一条经验:所有要持久化的内容,必须显式挂载 volume 或 bind mount,不要依赖容器内部存储。

5.6 Windows下Docker Desktop的几个特殊坑

Windows 上跑 Docker,最常遇到的是文件挂载性能差的问题。源代码在 Windows 文件系统,容器在 WSL2 里,跨文件系统读写非常慢。解决办法是把代码放到 WSL2 的文件系统下,比如直接用 VSCode 的 WSL 远程开发插件打开 Ubuntu 里的项目目录,这样读写性能会好很多。

另一个坑是端口占用。Windows 上很多程序会随机占用端口,导致容器映射失败。排查命令我建议用netstat -ano | findstr "8000"找到占用进程,再决定改哪个映射端口。

Docker Desktop 本身偶尔会卡在启动界面,尤其是 Windows 更新后。最常用的恢复手段是找到右下角鲸鱼图标,右键选择 Restart,或者直接重启电脑。大多数情况下能恢复。

写在最后的一点个人经验

聊了这么多,如果你真的动手把从环境安装到 Compose 编排走了一遍,会发现容器化部署并不是什么高不可攀的技术,它只是把“保证环境一致”这件事固化成了标准操作。我个人实际项目中的体会是:Docker 带来的不是某个功能,而是一整套思维方式的改变。以前部署一个新环境,我要做一堆手工操作,现在就是docker compose up -d一条命令的事。

最后再分享一个实用小技巧:新建 Python 项目时,尽量把requirements.txtDockerfiledocker-compose.yml作为项目标配直接放进仓库,从一开始就按容器化的标准来写代码。等你经历过一次“新机器拉代码、起服务、跑通”的顺畅流程,就再也不想回到手动部署的老路上去了。

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

软考中级系统集成项目管理工程师300集精讲:零基础备考全攻略

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

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

免费开源公文排版工具:批量处理与AI内容自动重排

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

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

屏幕点不亮别急着送修:先查信号链路与硬件接触这两处

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

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

Webpack核心机制与构建优化:从依赖分析到性能调优

在社区答疑和面试辅导里被问得最多的前端工具,Webpack绝对排前三。不是因为它有多难,而是因为它太底层了——几乎所有现代前端项目都跑在它上面,但大多数人只在报错的时候才想起它。这篇算是我这几年在项目里反复折腾Webpack的一个汇总&#…

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

技术人如何用接口设计思维提升职场协作效率

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

作者头像 李华