Logo

site iconoldj | 老杰

男,80 后,杭州。阿里 8 年,目前在一个小而美的团队。「妙笔 / WonderPen」「ccReader 」作者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

oldj | 老杰 RSS 预览

AB 工作法

2026-03-25 19:20:00

什么是 AB 工作法

想象一下,你有两个工作台:

A 工作台上摆着你最重要的那个大项目——可能是一个复杂的架构设计,可能是一本书的核心章节,或者是一个需要深度思考的研究课题。这个工作需要你全神贯注,不被打扰。

B 工作台上则堆着其他各种各样的事情——紧急但不复杂的需求、日常维护工作、突发的小任务、需要回复的邮件等等。这些事情重要但不需要长时间的深度投入。

AB 工作法的核心思路是:将各项工作放合适的工作台上,然后你的注意力在这两个工作台之间定期切换。比如上午在 A 工作台专注做深度工作,下午切换到 B 工作台处理各种杂事,或者今天做 A,明天做 B,关键是有节奏地轮换,而不是随机跳跃。

这个方法特别适合那些每天要处理大量不同类型事务的人,——你需要推进重要的长期项目,同时又不能让其他事情堆积成山。如果你的工作是流水线式的,按先来后到依次处理就好,可能用不上这个方法。

它要解决什么问题

我们先来看一个常见的困境。

假设你是一位程序员,你的待处理工作列表很长,每项工作的重要性、紧急程度、耗时都不同。你决定按串行方式处理,每次挑一项最重要的处理,完成之后再挑选下一项。这个策略通常运行良好,但偶尔也会遇到麻烦:某项工作特别耗时,你在上面花了一两周,其他耗时短的小任务就都卡住了,有些原本不紧急的需求因此变得紧急起来。

这背后是两个相互冲突的需求:

一方面,我们需要专注。频繁切换任务的成本很高,因为大脑需要时间重新加载上下文,频繁切换不仅降低效率,还会消耗大量精力,导致精神疲劳。

另一方面,我们又不能长时间陷入一件事。如果完全沉浸在一个大项目中,其他重要但耗时短的任务会被无限期推迟,最终可能演变成紧急问题,甚至引发更大的麻烦。

AB 工作法试图在这两者之间找到平衡。

核心机制

回到“两个工作台”的比喻。AB 工作法的运作方式是:

建立两个工作台

你需要把所有工作分类放到两个工作台上:

  • A 工作台:存放那些需要深度工作的任务——最重要、最复杂、预计耗时较长的工作。这些任务摆在 A 工作台上,等待你在专注时段处理。

  • B 工作台:存放其他所有事务——重要但不太复杂的任务、紧急但耗时短的需求、日常维护工作、突发事项等等。这些任务堆在 B 工作台上,可以在较短时间内逐个处理。

定期轮换

你需要设定一个固定的轮换周期。在 A 时段,你只在 A 工作台工作,专注处理深度任务;在 B 时段,你切换到 B 工作台,灵活处理各种事务。这种有节奏的切换,既保证了主要任务的持续推进,又确保了其他事务不会被长期搁置。

保持纪律

关键是严格遵守“在哪个工作台就做哪个工作台的事”。在 A 工作台时,即使 B 工作台上有看起来很紧急的事,也要记下来留到 B 时段处理。在 B 工作台时,即使突然对 A 任务有了灵感,也要克制住冲动,留到 A 时段再说。

具体怎么做

第一步:给两个工作台分配任务

拿出你的工作列表,开始分类:

哪些任务该放到 A 工作台?

答案是那些“如果不专注投入就很难推进”的工作。对程序员来说,可能是复杂的架构重构;对作家来说,可能是书的核心章节;对研究者来说,可能是关键实验的设计与实施。判断标准是:这个任务需要长时间的连续思考吗?需要进入“心流”状态才能做好吗?如果答案是肯定的,就放到 A 工作台。

哪些任务该放到 B 工作台?

答案是其他所有工作——包括那些重要但不太复杂的任务、紧急但耗时短的需求、日常维护工作、突发事项等等。不用担心 B 工作台上堆得太满太杂,这个工作台本来就是为处理多样化任务设计的。

第二步:设定轮换节奏

根据你的工作性质和个人习惯,选择一个合适的轮换周期:

每半天轮换:上午在 A 工作台,下午在 B 工作台,或者上午在 B 工作台,下午在 A 工作台。这是最常见的节奏,适合工作环境相对安静、任务复杂度高的情况。

每天轮换:今天在 A 工作台,明天在 B 工作台。如果你的深度任务需要更长的连续时间来建立思考状态,这种方式可能更合适。

每个时段轮换:每个工作时段(2-4 小时)切换一次工作台。适合工作环境多变、突发事件较多的情况。

每 2 小时轮换:节奏更快,但不建议更短——太频繁的切换会让上下文切换成本抵消掉收益。

根据效率曲线设置:选择你每天最高效的时间段使用 A 工作台,其他时段使用 B 工作台。比如,如果你在上午 10 点到下午 3 点精力最充沛、思维最清晰,就把这个时段固定为 A 时段,其余时间处理 B 任务。这种方式能最大化利用你的黄金工作时段。

关于时间分配比例

A、B 的时间分配不一定是 1:1。如果你的日常事务相对较少,而深度任务需要大量投入,时间分配可以是 2:1、3:1,甚至可以一周的五个工作日,四天都在 A 工作台,剩下一天在 B 工作台。关键是根据实际工作量来设定合理的比例。

但有一点很重要:一旦定好节奏,就不要轻易改动。频繁调整计划本身就是一种低效的行为,会削弱这个方法的效果。

第三步:严格遵守工作台纪律

这是 AB 工作法成败的关键。

在 A 工作台时:坚决拒绝处理 B 工作台上的事项,即使它们看起来很紧急。你可以把它们记在便签上,但要留到 B 时段再处理。这不是不负责任,而是在保护你的深度工作状态。

在 B 工作台时:不要被 A 工作台上的任务吸引回去,即使你突然有了灵感。把灵感记下来,留到 A 时段再展开。这同样是在保护你的工作模式——B 时段的价值在于灵活机动,如果被大任务拖住,就失去了意义。

这种纪律看似僵化,实际上是在保护两种工作模式的完整性。你可以把自己想象成两个不同的人:A 工作台上的你是专注的深度思考者,B 工作台上的你是灵活的问题解决者。

真正的紧急情况例外

当然,如果遇到真正重要且紧急的突发事件——比如生产系统崩溃、客户的关键问题、或者团队成员急需你的帮助——无论你当前在哪个工作台,都可以立刻停下来去处理。

但需要记住:这种打断应该是罕见的例外,而不是常态。如果你发现自己经常因为“紧急情况”打断工作,可能需要重新审视:这些事情真的都是既重要又紧急吗?还是因为缺乏边界,让所有事情都变成了紧急?

一个简单的判断标准是:如果这件事推迟 2 小时(或推迟到下一个工作台时段)会造成严重后果,那就是真正的紧急情况。如果只是“看起来紧急”但推迟几小时也无妨,那就记下来,留到合适的时段处理。

第四步:动态调整两个工作台

AB 工作法不是一成不变的,需要根据实际情况动态调整:

当 A 工作台清空时:可以从 B 工作台选出最重要、最复杂的任务,把它移到 A 工作台。这样确保你始终有深度任务在推进。

当 B 任务需要升级时:在执行过程中,如果发现某个 B 任务的重要性和复杂度超出了最初的预期,可以把它从 B 工作台移到 A 工作台,作为深度任务来处理。

当 A 任务需要降级时:有时候你可能会发现某个 A 任务其实没那么复杂,或者可以拆分成小块处理,这时也可以把它或者它拆分后的一部分移到 B 工作台。

关键是保持两个工作台的动态平衡,确保 A 工作台上始终有需要深度工作的任务,B 工作台上有足够的机动任务。

特殊情况:B 工作台清空了怎么办?

这是一个有意思的问题。如果到了 B 时段,却发现 B 工作台上的事情都处理完了,你有两个合理的选择:

选择一:转到 A 工作台

既然没有其他事务需要处理,将这段时间用于推进 A 工作台上的任务是很自然的想法,这样可以加快深度任务的进度。

不过需要注意的是,如果你经常这样做,要警惕 AB 工作法是否正在退化成单线程工作模式。偶尔为之没问题,但如果成为常态,可能需要重新审视你的时间分配比例。

选择二:做“元工作”或休息

“元工作”是指那些不属于具体任务,但对工作系统本身有益的活动:整理工作笔记、回顾近期进展、学习新技能、优化工作流程、清理工作环境、或者进行一些前瞻性思考。这些活动往往在忙碌时被忽视,但长期来看对提升工作质量和效率很有帮助。

如果找不到合适的“元工作”,那就干脆休息一下,——散散步、冥想、或者做一些轻松的事情,为下一个 A 时段储备精力。

最重要的是,不要因为 B 工作台暂时清空就感到焦虑或内疚。这恰恰说明你的工作节奏是健康的,——你既在推进重要的长期项目,又及时处理了其他事务。这正是 AB 工作法追求的理想状态。

为什么 AB 工作法有效

AB 工作法的有效性来自于它对人类认知特性的尊重和对工作现实的妥协:

它承认专注的价值

通过为深度任务分配专门的时间块,我们能够进入心流状态。在 A 工作台上,你不需要担心其他事情,可以全身心投入到复杂的思考中。这种不被打扰的专注,是完成复杂工作的必要条件。

它承认灵活的必要

通过 B 工作台的存在,我们不会因为过度专注而忽视其他重要事项。那些看似琐碎但不可或缺的工作——回复邮件、处理临时需求、日常维护——也能得到及时处理,不会堆积成山。

它提供了心理缓冲

知道“我只需要在这个时段专注于 A 工作台,其他事情稍后会有专门时间处理”,这种确定性能够显著降低焦虑。你不会因为“还有很多其他事没做”而分心,也不会因为“一直在做杂事”而焦虑。

它创造了自然的休息点

工作台切换本身就是一种认知上的休息。从深度思考切换到处理多个小任务,或者反过来,都能让大脑的不同区域得到交替使用和恢复。这比连续 8 小时做同类工作要健康得多。

适合谁用

AB 工作法特别适合以下人群:

  • 需要同时推进长期项目和处理日常事务的知识工作者

  • 工作内容多样、优先级复杂的管理者、创业者或独立开发者

  • 需要在创造性工作和事务性工作之间切换的专业人士

  • 容易陷入“只做紧急事,忽视重要事”或“只做重要事,忽视紧急事”两个极端的人

局限性

当然,AB 工作法也有它的局限性:

如果你的工作本质上是单线程的(比如流水线工人、客服人员),这个方法可能过于复杂。如果你的工作环境完全不可控、频繁被打断,可能需要先改善工作环境,再尝试这个方法。如果你正处于某个项目的冲刺期,需要全力以赴,暂时放弃 AB 结构、全力投入也是合理的选择。

一些实践建议

从简单的节奏开始

如果你是第一次尝试 AB 工作法,建议从每天轮换开始,而不是每小时。今天在 A 工作台,明天在 B 工作台。这样更容易建立习惯,也更容易感受到效果。等习惯养成后,再尝试更短的轮换周期。

用工具标记当前工作台

可以使用日历、番茄钟或专门的时间管理工具来标记你当前在哪个工作台。视觉提示能够帮助你更好地保持纪律。有些人会在桌面上放两个不同颜色的便签纸,A 时段翻开绿色,B 时段翻开黄色,这种物理提示也很有效。

每周回顾与调整

每周花 15 分钟回顾一下:A 工作台上的任务推进得如何?B 工作台是否有积压?两个工作台的任务划分是否合理?轮换节奏是否合适?根据实际情况调整策略。这种定期回顾能帮你不断优化自己的工作节奏。

让同事知道你的节奏

如果你在团队中工作,让同事知道你的工作方式。比如在日历上标注“深度工作时段(A 工作台)”,让他们知道这段时间最好不要打扰你。大多数同事会理解并尊重这种安排,因为他们自己可能也有类似的需求。

保持弹性

AB 工作法是一个框架,不是枷锁。真正的紧急情况出现时,当然可以打破规则。关键是这种打破应该是例外,而不是常态。如果你发现自己经常打破规则,可能需要重新审视你的时间分配或任务分类。

扩展:可以有更多工作台吗?

AB 工作法的核心是“两个工作台”,但这不是唯一的可能。如果你的工作情况更复杂,完全可以扩展为三个、四个甚至更多工作台。

什么时候需要更多工作台?

如果你的工作内容跨越多个几乎不相关的领域,每个领域都有自己的深度任务和日常事务,那么多个工作台可能更合适。

比如,假设你同时负责产品开发和市场推广两个领域,你可以采用 ABC 工作法:

  • A 工作台:产品开发方面的深度工作——架构设计、核心功能开发、技术难题攻关

  • B 工作台:市场推广方面的深度工作——营销策略规划、重要内容创作、关键活动策划

  • C 工作台:所有其他日常事务——邮件回复、会议、临时需求、日常维护

然后制定一个轮换策略,比如:周一周二在 A 工作台,周三周四在 B 工作台,周五在 C 工作台。或者每天上午在 A 工作台,下午在 B 工作台,晚上处理 C 工作台的事情。

需要注意的是

工作台越多,轮换的复杂度就越高,上下文切换的成本也会增加。大多数情况下,两个工作台就足够了。只有当你确实需要在多个完全不同的领域之间切换,并且每个领域都有足够的工作量时,才考虑增加工作台数量。

一个简单的判断标准:如果你发现自己在某个“深度工作台”上经常没事可做,或者某两个工作台上的任务其实可以合并处理,那就说明工作台分得太细了,不如简化回两个工作台的结构。

写在最后

AB 工作法的本质,是在专注与灵活、深度与广度、长期与短期之间寻找平衡。它不是什么神奇的生产力秘诀,而是一种务实的工作组织方式。

想象你面前真的有两个工作台。A 工作台干净整洁,只摆着少量需要全神贯注的大项目;B 工作台上堆着各种各样的事情,等待你灵活处理。你在这两个工作台之间有节奏地切换,既不会因为过度专注而忽视其他事务,也不会因为琐事缠身而无法推进重要项目。

在这个信息过载、任务繁杂的时代,我们既需要能够深入思考的专注时光,也需要能够快速响应的灵活机制。AB 工作法提供了一个简单但有效的框架,让这两种看似矛盾的需求能够在同一个人的工作节奏中和谐共存。

如果你也面临着“重要的大项目总是被琐事打断”或“专注于大项目时其他事情都荒废了”的困扰,不妨试试 AB 工作法。给自己两周时间实验,你可能会发现一种全新的工作节奏。

使用 uv 管理 Python 依赖

2025-11-29 15:26:00

我有一个运行了好几年的 Django 项目,之前一直在使用默认的 pip 管理和安装依赖,最近切换到了 uv,感觉还不错,在这儿记录一下。

什么是 uv

根据官网的介绍,uv 是一个 Python 包以及项目管理器,非常快,使用 Rust 开发。

安装和管理依赖只是它的功能之一,除此之外,它还可以创建虚拟环境,即可以取代 pip + virtualenv 的功能。

我测试了一下,uv 确实比 pip 快了很多。在使用相同的镜像源,且都是纯净的 docker 环境下,使用 pip 安装项目的依赖花了约 88 秒,但使用 uv 只用了 13 秒。

不过,安装依赖并不是一个高频操作,多花一点时间一般不是什么痛点,uv 更吸引人的是它简化了很多工作,比如内置了 Python 多版本安装以及虚拟环境管理,且能保证环境的可复现性,这就让 Python 项目的开发和发布工作简单了很多。

uv 的安装和初始化

在 macOS 或 Linux 上,可以使用以下命令安装 uv:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows 上的命令如下:

# On Windows.
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

安装完成之后,可以使用以下命令初始化一个新项目:

uv init my-project

如果你的项目已经存在,也可以直接进入项目根目录,执行以下命令:

uv init

如果项目中已经有 requirements.txt,想改用 uv 进行管理,可以在项目根目录执行以下命令:

uv pip install -r requirements.txt

也可以直接执行:

uv sync

初始化后,uv 会在项目根目录下生成一个 pyproject.toml 文件,其中包含了项目的基本信息以及依赖项。

可以运行以下命令,锁定依赖项:

uv lock

这个命令将会在项目根目录下生成 uv.lock 文件,类似 Node.js 的 package-lock.json,其中包含了项目依赖的各个第三方库以及版本号,确保下次安装时安装的包相同。

虚拟环境

Python 默认是安装在系统中的,使用 pip 安装依赖时,默认也会全局安装,在很多情况下,尤其是需要维护多个项目时,这显然不是我们期望的,此时可以使用虚拟环境。

在 uv 中使用虚拟环境很简单。

首先,可以使用 uv 安装多个不同版本的 Python:

uv python install 3.10 3.11 3.12

然后,可以通过类似下面的命令安装虚拟环境:

uv venv --python 3.12.0

可以直接在项目根目录下执行这个命令,执行成功之后,项目根目录下会生成一个 .venv 文件夹,包含这个环境的所有信息,之后安装的包也会保存在这个文件夹下,记得将这个文件夹添加到 .gitignore 中。

如果你熟悉 Node.js,会发现这个 .venv 文件夹和 Node.js 的 node_modules 文件夹功能类似,且它更进一步,不仅包含依赖,还能包含当前项目所需的 Python 本身。

使用以下命令可以用虚拟环境中的 Python 执行指定脚本:

uv run example.py

如果你在终端中访问项目,可以使用以下命令激活当前 Python 虚拟环境:

source .venv/bin/activate

激活虚拟环境之后,可以直接用类似 python example.py 的方式来运行项目中的脚本。

使用现代 IDE(比如 PyCharm、VSCode 等)打开这个项目时,IDE 一般都能自动识别项目中的 .venv 虚拟环境。

安装依赖

配置好环境后,就可以使用类似下面的命令安装依赖了:

uv add django

这信命令会下载对应的包并安装在 .venv中,安装成功之后,会修改 pyproject.tomluv.lock 文件。

如果你刚将代码从仓库中拉到本地,项目中已经有了 pyproject.tomluv.lock,那么只需执行以下命令即可安装所有依赖:

uv sync

注意,这个命令会根据 pyproject.toml 解析和下载依赖,有可能会改进 uv.lock。如果是在生产环境,你希望严格按照 uv.lock 中的版本安装依赖,可以使用以下命令:

uv sync --frozen

在 docker 中使用 uv

如果你的项目需要使用 docker 发布,还有一些额外需要注意的事项。

如果你的服务器在国内,那么可能需要使用国内 pypi 镜像,uv 中要指定镜像很简单,设置相应的环境变量即可,例如下面设置使用了阿里云的镜像:

ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ENV UV_TRUSTED_HOST=mirrors.aliyun.com

在 docker 中安装依赖时,大体上有两种方式,一种是使用 uv sync 命令,如下所示:

WORKDIR /code/
COPY pyproject.toml uv.lock /code/

# 安装依赖
RUN uv sync --frozen --no-dev

ENV PATH="/code/.venv/bin:$PATH"

这种方式会在当前目录下创建虚拟环境,所有依赖都将安装到 .venv 目录下,因此需要将对应的目录加入 PATH。这种方式安装的依赖将严格遵守 uv.lock 中的版本限制,最为可靠。

或者,也可以选择将依赖直接安装到系统环境中:

RUN uv pip install --no-cache-dir --system .

注意那个 --system 参数,这种方式会将各依赖包安装到全局目录,如果你的 docker 中只有这一个 Python 项目,且不想使用虚拟环境,也可以使用这种方式安装。不过,这种方式安装时虽然也会参考 uv.lock,但并不保证各依赖的版本和 uv.lock 中严格相同。

小结

Python 发布迄今已有三十余年,一开始并没有第三方包的安装和管理工具,这和 Node.js 一发布就自带 npm 不同。如果你使用 Python 的时间较早,可能还会记得曾经有一个叫 easy_install 的工具用于安装 Python 的第三方包。

约 2008 年,pip 发布,随后在 2014 年被 Python 官方集成到 3.4 版中(以及 2.7.9 中),Python 这才有了一个官方的依赖管理工具。不过 pip 并不完美,主要是依赖解析能力较弱,无法保证每次安装后的环境完全相同,同时安装速度也有一些慢,因此后续又出现了一些新的依赖管理工具,比如 poetry、uv 等。

目前,开发 Python 项目的最佳实践是为每个项目创建独立的虚拟环境,将项目所需的依赖安装在该环境中,并通过依赖管理文件记录依赖,以确保隔离、可复现和可移植性。这些工作都可以使用 uv 完成,如果你正在开发或维护一个 Python 项目,不妨试试 uv。

富文本框架体验

2025-06-21 17:31:00

由于项目中要使用富文本编辑器,过去一段时间我深入研究了一下几个知名的富文本框架,在这儿写一下体验。

这不是一个全面的分析,仅代表个人观点。

Quill

我最开始尝试的是 Quill 项目,因为它看起来比较简单同时又足够强大,更重要的是它还有一个 Flutter 版本,可用于移动端应用的富文本开发。

实际体验上,Quill 确实比较容易上手,根据文档很容易就能写出一个基本可用的富文本编辑器来。当然,如果要添加更多复杂的或自定义功能,就要继续深入研究了。

Quill 的数据结构比较特别,它自定义了一个名为 Delta 的 JSON 格式用于描述文档,这种格式概念很简单,比如只有 insertdeleteretain 三种操作,但理论上足以描述任意复杂的文档格式以及修改。

在 Delta 的基础上,Quill 的文档可以被认为是一个“流”,只需一个数字就可以表示文档的任意位置,这让使用代码来查找、修改文档的操作变得非常简单。

一切看起来都很美好,不过当我继续深入,想实现一些更复杂的功能之时,逐渐发现 Quill 存在一些较为严重的不足。

第一个也是最大的不足,是 Quill 缺少“装饰器”设计。

所谓“装饰器”,是给文档中指定内容临时添加一些样式,但又不会影响文档数据的功能。

举例来说,实现关键词搜索时,我们需要把文档中所有匹配的关键词全部高亮显示(比如背景显示为黄色),但这个高亮样式只是临时性的,需要与富文本中原本就有的样式区分开来,添加这种装饰性的样式时,不需要触发文档的 onChange 事件,此时如果获取文档的内容,得到的数据中也不应该包含装饰性的样式。

缺少了装饰器功能,如果想高亮匹配的搜索关键词,就只能直接修改文档数据,在文档的原始数据中为这些关键词添加样式,随后在搜索结束时再去除这些样式。这便需要开发者自行添加和维护这些临时样式的状态,并区分哪些修改是真正的修改可以保存,哪些则只是装饰性修改不需要保存,显然,这会让代码逻辑变得复杂,且由于不是框架底层原生支持,性能上也会差很多。

第二个问题则和 Delta 格式有关。

使用了一段时间之后,我发现 Delta 这种格式用于描述纯文本或简单富文本的修改确实很方便,生成的 JSON 也很容易阅读,但如果要描述带有复杂嵌套结构的 HTML,Delta 反而会让问题复杂化。

同时,应该是实现上还存在 bug,当文档中有一些格式存在重叠、嵌套时,如果对它们再次格式化,可能会让内容变得混乱。对此我在 Quill 的 GitHub 仓库中提交了一个 issue ,不过暂时没有得到回应。

Slate

随后,我又尝试了 Slate ,一个基于 React 的富文本框架。

Slate 非常强大,且相较 Quill 自由度更高,基本上可以用它实现任何想要的功能。它自带装饰器(Decorations),因此,诸如高亮搜索关键词等功能的实现都变得很容易。对应地,它的概念也比 Quill 多了一些,学习成本稍高。

我用 Slate 基本实现了整个需求,原本已经基本准备正式使用它了,不过也许是我对它的理解还不够,遇到了一些始终没能处理好的小问题,虽然不影响主要功能,但却让编辑器在体验上总是差了那么一点。

比如,有时候选中了文档内容,鼠标点在空白处时不会取消选中,要再点一次才行。还有一些中文输入法似乎与它存在兼容问题,一些操作会让整个编辑器崩溃,我研究了很久也找不到修复或者处理方法,只能在外层容器捕获错误,提示用户刷新页面。

ProseMirror

最后,我决定转向 ProseMirror

ProseMirror 是一个老牌富文本框架,它的作者还写过知名的代码编辑器 CodeMirror。ProseMirror 非常强大,但因为学习曲线相对陡峭,因此劝退了很多人,包括之前的我也一度先尝试 Quill、Slate 等方案。

也有一些基于 ProseMirror 的富文本框架,比如 TipTap ,这也从另一个角度展示了 ProseMirror 的强大。

ProseMirror 的文档对新手不太友好,且它有一些自已的概念,比如文档的结构由 Schema 定义和描述,因此需要花一些时间才能上手,不过,上手之后会发现它的文档其实非常详细,基本可以从中找到需要的一切信息。

在使用 ProseMirror 将需求重新实现之后,我发现在使用 Slate 遇到的那些恼人的小问题要么没有了,要么有解决方案,而且在处理有较多装饰器的长文档时,ProseMirror 的性能似乎更好一些。

当然,ProseMirror 也不能说十全十美,不过,在深度使用了几个流行的富文本编辑器框架之后,个人觉得 ProseMirror 是综合来说最强大也最值得学习的。

小结

富文本编辑器是前端开发中最复杂也最难的主题之一,幸运的是多数情况下我们不需要完全从头开始开发,而是可以选择一个基础框架,基于它进行二次开发。

网上有很多富文本编辑器框架的评测,你可以根据自己的需求以及实际情况进行选择。

如果你希望尽快完成项目,且你的文档结构不会很复杂,也没有装饰器等需求,可以考虑 Quill。

如果你的需求比较复杂,且在使用 React,可以考虑 Slate。

最后,如果你的需求很复杂,且你的时间不那么紧张,推荐选择 ProseMirror。

使用函数计算运行定时任务

2025-05-02 15:42:00

上一篇博客提到我在使用 Dokploy 部署网站服务,但 Dokploy 不支持定时任务,于是只能创建普通服务,并在内部使用脚本定时执行命令。最近发现,将这些定时任务放在函数计算中执行可能是更好的选择。

我的服务都跑在阿里云上,下面介绍的也是阿里云函数计算。

创建函数

要创建一个函数,在阿里云函数界面后台,点击创建函数按钮即可,如下图所示:

在随后的界面中,选择“任务函数”类型。

然后,函数代码部分可根据需要选择类型,比如可以使用 ACR 中的 Docker 镜像。

需要注意的是,无论是上传代码还是使用 Docker 镜像,都要确保对应的代码能提供一个 HTTP 服务,因为定时任务执行的入口即是这个 HTTP 服务。

其他还有环境变量等配置,根据你的实际情况填写即可。

函数入口

使用函数计算的定时任务,需要你的代码提供一个 HTTP 服务,定时任务执行时,会以 POST 的方式请求 /invoke 路径,即类似下面这样的请求:

curl -X "POST" "http://localhost:8050/invoke" \
     -H 'Content-Type: application/json' \
     -d $'{
  "payload": "YOUR_PAYLOAD",
  "triggerName": "trigger-name",
  "triggerTime": "2025-04-27T03:12:45Z"
}'

当然,真实的请求还有很多 HTTP 头信息。

你需要在代码中实现 /invoke 接口,并在其中执行定时任务。在函数计算后台,可以设置超时时间等属性。

注意其中的 payload 字段,后面在设置定时触发器时,可以自定义传入的 palyload 信息。

设置定时触发器

添加函数之后,即可在配置界面设置触发器。

函数计算支持多种触发器,在这儿,我们选择定时触发器即可。

其中最后一个字段“触发消息”,其中填写的内容即是上面 payload 参数的值。注意这儿传递的是普通字符串,而不是 JSON,收到之后可根据需要做一个解析。

如果你在同一个函数中有多个用途不同的触发器,可以通过 payload 参数进行区分。

设置好之后,在函数详情界面可以看到类似下面的图示。

如果一切顺利,定时任务就添加成功了,稍后可以在日志页面看到执行记录。

更新函数

如果你的函数使用的是 ACR 中的 Docker 镜像,当推送了新的镜像时,函数计算的版本不会自动更新,需要你登录网站后台手动修改,或者调用函数计算的 API 进行设置。

每次手动修改是一件很麻烦的事,建议使用 API,以便和现有的发布流程结合起来。你可以先安装 aliyun-cli 命令行工具,然后执行类似下面的命令:

aliyun fc PUT /2023-03-30/functions/YOUR_FC --region cn-shanghai --header "Content-Type=application/json;" --body "{\"tracingConfig\":{},\"customContainerConfig\":{\"image\":\"registry-vpc.cn-shanghai.aliyuncs.com/XXX/YYY:1.2.3\"}}"

请注意将其中的参数值替换为你的项目中的值。

小结

有一些定时任务(比如清理老数据、备份用户数据等)比较耗费资源,将它们迁移到函数计算中可以减少主服务器的负担,是一个不错的实践。

函数计算是按量收费的,多数情况下,定时任务使用函数计算应该比专门买一台服务器划算,不过也不要大意,请做好优化,同时注意关注每日的用量。

使用 Dokploy 部署网站服务

2025-04-20 21:41:00

之前的几年我一直在使用 K3s + Rancher 的组合来管理网站服务,不过前段时间迁移到了 Dokploy,在这儿记录一下要点。

为什么迁移?

K3s + Rancher 的组合挺好,几年来一直运行稳定,不过对像我这样的非专业运维来说还是有点太复杂了,事实上几年来,我一直只在使用这个组合的一些最基础的功能。

去年看到有人介绍 Dokploy,了解了一下之后,发现它非常适合我的使用场景,同时又足够简单,于是花了一点时间做了研究,并最终决定迁移到 Dokploy。

除了 Dokploy 之外,还有 Coolify 等产品也不错,而且功能更多一些,读者朋友如果有需要也可以试一试。

云服务还是自托管?

Dokploy 提供了云服务,订阅之后可通过他们的云服务管理自己的服务器。

云服务听起来是个不错的选择,可以减少自己运维的时间成本,我也花了 $4.5 订阅了一个月体验了一番。不过 Dokploy 的云服务在海外,我的服务器在国内,两者之间通讯不畅,因此体验并不是很好。

最后,我选择了自托管服务,将 Dokploy 和网站服务安装在同一个网络中。

安装 Dokploy

Dokploy 的安装很简单,在一台干净的服务器上运行以下命令即可:

curl -sSL https://dokploy.com/install.sh | sh

为了确保 Dokploy 能顺利运行,这台服务器建议至少要 2 CPU + 2 G 内存。

如果你的服务器在国内,安装时可能耗时较长,可以添加国内的 docker 镜像,比如修改 /etc/docker/daemon.json 文件,添加以下内容:

{
  "registry-mirrors": [
    "https://docker.1ms.run"
  ]
}

安装完成之后,即可通过 http://{服务器 IP}:3000 的形式访问 Dokploy 后台。

添加服务器

Dokploy 成功安装后,马上就可以开始创建应用。不过,这时创建的应用会和 Dokploy 安装在同一台服务器上,你也可以在 Dokploy 后台添加新的服务器,并将应用添加到新服务器上。

个人建议用一台服务器专门运行 Dokploy,然后在 Remote Servers 面板中添加其他服务器。

添加服务器之后,还需要在 Actions 菜单中点击 Setup Server,并根据提示进行设置。

其中 Deployments 那个步骤可能耗时会很长,可以考虑点击 Modify Script,将脚本复制到对应的服务器上手动执行。

添加服务

添加完服务器之后,就可以添加项目,随后在项目中添加服务了。

添加服务这儿,最重要的一个设置是 Provider,即设置代码的来源。

Dokploy 支持多种常见的源,比如 Github,配置好之后只需向指定仓库和分支推送代码,Dokploy 就会自动拉取并构建代码,就像 Vercel 一样。

对小项目来说,这样的方式自然是很方便的,不过也可以用 Docker 作为 Provider,并使用第三方镜像服务。这样主要有两个好处:

  1. 镜像的构建工作在第三方执行,不会占用线上服务器资源;
  2. 第三方构建镜像时可以打上版本号 tag,后续回滚操作将会很方便。

我使用的是阿里云的容器镜像服务,填写方式类似下图:

更新服务

Dokploy 提供了丰富的 API,几乎所有操作都可以通过 API 完成。当某个服务需要更新时,可以登录网站手动修改相关值,也可以使用 API 更新。

比如,如果一个服务的 Provider 是 Docker,可以用类似下面的请求进行修改:

curl -X "POST" "https://your-dokploy/api/application.saveDockerProvider" \
     -H 'x-api-key: $YOUR_TOKEN' \
     -H 'Content-Type: application/json' \
     -d $'{
  "applicationId": "$APP_ID",
  "dockerImage": "$DOCKER_URL"
}'

有几个注意点:

  1. 授权头信息是 x-api-key: xxx...,而不是常见的 Authorization: Bearer xxx...
  2. applicationId 的值在 URL 中,在界面上暂时没有显示。

比如某个服务的地址是 https://your-dokploy/dashboard/project/aaa/services/application/bbb,地址最后的 bbb 就是 applicationId

通过 API 的方式,可以很方便地将服务的发布、回滚等操作集中到一处管理,或者与你现有的服务集成。

升级 Dokploy

Dokploy 本身也在不断迭代更新,一段时间之后,你可能需要升级 Dokploy。

Dokploy 后台提供了自助升级服务,不过由于网络原因,在国内服务器上这个升级可能会失败,也可以登录到服务器后使用以下命令升级:

curl -sSL https://dokploy.com/install.sh | sh -s update

使用小结

使用 Dokploy 已经有一段时间了,整体而言还是很满意的,相对其他方案它很容易上手,且足够稳定,可用于生产环境。

不足是暂时还不支持定时任务,不过可以通过启动一个普通服务并在其中运行定时脚本的方式解决。

如果你有类似的需求,不妨也试一试 Dokploy。