ARTICLE · 1100788
记账软件终于不用联网了

一个把「信封预算法」做成软件的记账工具,数据存在你自己机器上,多设备同步靠端到端加密的 CRDT 而不是某个云账号——而且它对所有人免费。
先说结论
国内流行的记账 App 基本走同一条路:注册账号、数据上云、基础功能免费、报表和导出要开会员。你输入的是自己最敏感的数据之一——每笔钱的去向——但这份数据存在别人的服务器上,导出格式是私有的,停服或涨价你只能接受。
Actual Budget 是另一条路。它是 local-first(本地优先) 的:数据默认存在你自己的设备上,同步是可选功能而不是前提条件。它完全开源免费,没有付费墙。
值得关注,但也要提前说清楚一件事:它是一套基于「信封预算」的方法论工具,不是「导入支付宝账单自动分类」的傻瓜记账本。 你得愿意花点时间理解它的思路。如果你只是想随手记两笔,它可能有点重。
它到底是什么
Actual 的核心定位是 envelope budgeting(信封预算法)。这个方法本身很老,来自现金时代:你把每个月的钱分装进若干个信封,每个信封对应一类开支(房租、吃饭、交通)。信封里的钱花完了,那个类别这个月就不能再花——除非你从别的信封里挪一点过来。
它的价值在于强制你提前做取舍。普通的记账是「事后记录」,你知道钱花在哪了,但已经花完了。信封预算逼你在月初就分配好,花超了必须显式地从别处调——这个「必须做决定」的动作才是控制支出的关键。
Actual 把这个方法做成了软件,几个关键设计:
• 本地优先:桌面端(Windows/macOS/Linux)、手机 App 都能独立运行,文件在本地。同步是可选的。 • 同步用 CRDT 而不是「服务器说了算」。 • 整份数据是一个文件,可以自己备份、自己搬走。 • 四种部署方式,从完全不懂技术到自建服务器都覆盖。
这是它 README 里放的界面图——左边是预算分类,右边是每个分类分配到的金额:

界面本身很朴素,但你注意两件事:一是每个分类有「已分配 / 已花 / 剩余」三个数字,这就是信封的三个状态;二是超支的分类会被高亮——它不隐藏你超支的事实,这是这个方法的重点。
核心机制:多设备同步为什么不冲突
这是 Actual 最值得看的技术决定。
普通记账 App 的同步逻辑是:你的手机把修改发给服务器,服务器是唯一权威。你必须联网才能改数据,改了之后等服务器确认。断网的时候要么不能改,要么本地先改然后合并——而合并经常出问题。
Actual 的做法不一样。它的同步层是一个独立的包 @actual-app/crdt,客户端和服务器共用同一套核心逻辑。README 里明确写了这个包的定位:
This package contains the core CRDT logic that enables Actual's syncing. It is shared between the client and server.
CRDT 是 Conflict-free Replicated Data Type(无冲突复制数据类型) 的缩写。它的核心思想是:让数据结构本身就具备「合并不会冲突」的数学性质。每个设备都可以独立地修改自己的副本,等设备之间能通信的时候,把它们合并起来,结果必然收敛到同一个状态——不需要有人来判断「谁是对的」。
这对记账场景特别合适。因为记账的操作天然是可交换的:你在手机上把「午餐」记成 35 元,我在电脑上把「交通」记成 6 元,这两个操作顺序互换,最终结果完全一样。CRDT 就是为这种场景设计的。
对用户来说,这个决定的直接好处是:
1. 离线真的能用。 飞机上、地铁里,你照常记,联网之后自动合并。 2. 不存在「服务器把本地改动覆盖掉」。 因为没有哪个副本是权威的。 3. 服务器可以很轻。 它主要是帮你把变更转达给别的设备,不需要跑复杂的业务逻辑来裁决冲突。
代价也要说清楚:
• CRDT 的数据结构比「一张表」复杂得多。 每条记录要带额外的元数据(比如逻辑时钟、设备标识)才能判断合并顺序。这意味着存储开销更大,调试也更难。 • 有些操作本质上不可交换,CRDT 只能选一种策略而不是「都保留」。 比如同一笔交易的金额被两台设备同时改了,最后会按某个确定性规则挑一个(通常是后写的赢或按设备 ID 定序),而不是提示你冲突。你通常不会察觉,但也不该对着两台设备同时改同一笔。 • 历史无法真正删除,只能标记。 分布式环境下「删除」需要留下墓碑(tombstone),否则删掉的记录会被另一台还记得它的设备重新合并回来。这是分布式系统的通用代价。
另一个有意思的工程细节在同一个 README 里:他们用 protobuf 把消息编码成二进制再传输,而不是用 JSON。原因是省带宽——同步频繁发生在手机上、可能有几百个变更要一起推。
但 README 里专门警告了一个坑,这段值得单独拎出来:
// protobuf 编译器默认生成的代码长这样:var global = (function() { return this || window || global || self || Function('return this')(); }).call(null);// 必须手工改成:var global = globalThis;为什么?因为第一行用了 Function('return this')()——在运行时动态构造并执行一段代码。这在浏览器的 CSP(Content Security Policy,内容安全策略) 下会被直接拒绝:如果一个页面的 CSP 禁止 unsafe-eval,任何形式的动态代码求值都会抛错。
这是很多项目会忽略的细节:protobuf 自动生成的 JS 在 Node.js 下跑得好好的,打包进前端就炸了,而且报错信息通常很不直观。Actual 团队选择每次生成后手工替换成 globalThis(globalThis 是标准化的全局对象引用,不需要任何动态求值),并把这个改动写进 README 提醒后来者。
这种「生成工具的默认输出不符合安全要求,必须在生成后打补丁」的坑,几乎每个用代码生成器的项目都会遇到。把它显式写进文档,比默默改完然后让下一个人重新踩一遍要好。
真实场景怎么用
它给了四种部署方式,从最省事到最自主:
1. 一键托管(约 $2/月) —— 通过 PikaPods。README 明确标注这是 recommended for non-technical users(推荐给非技术用户)。
2. 托管(约 $1.5/月) —— 通过 Fly.io 自己部署,官方有文档。
3. 自建(自托管 Docker) —— 有自己的 NAS 或服务器就走这条路:
docker run -d \ --name actual \ -p 5006:5006 \ -v /你的路径/actual-data:/data \ actualbudget/actual-server:latest跑起来之后浏览器访问 http://你的IP:5006,第一次会让你设置一个密码(这个密码是用来加密同步数据的,丢了就打不开数据,一定要记牢)。
4. 纯本地 App —— 官网直接下载 Windows / macOS / Linux 客户端,完全不联网也能用。如果你只有一台设备、不需要多端同步,这是最省事的选择。
上手流程建议这样走:
1. 先装纯本地 App,不要一上来就搞同步。把预算跑起来、熟悉信封预算的用法。 2. 建账户(银行卡、现金、信用卡分别建),录入余额。 3. 建分类(categories)和分类组(category groups),比如「固定支出 / 生活 / 储蓄」。 4. 用 Budget 页面给每个分类分配这个月的额度。这就是「往信封里装钱」。 5. 记录交易。在 Accounts 里手动加,或者用 CSV 导入——大多数银行都能导出 CSV,这是最实用的批量录入方式。 6. 用 Bank Sync 自动同步账单(如果支持你所在地区的银行)。 7. 看 Reports 了解趋势。
导入和对账的界面是这样:

它是深色主题,因为 Actual 跟随系统——这也算 local-first 的一个小体现:界面偏好存在本地,不用登录也不影响。
预算分配页则长这样,你能一眼看到「可用资金 / 超支 / 已预算 / 下月」四个数:

「可用资金」这个数字是这个方法的核心反馈。 它等于你手上真实可动用的钱,减去你已经分配给信封的部分。如果它是负数,说明你分配的钱比实际有的多——这个信号比任何报表都直接。
几个具体的实操点:
# CSV 导入的关键是列映射# 你的 CSV 里通常有:日期、金额、备注# Actual 会让你指定哪一列对应哪个字段# 建议先在 Excel / Numbers 里把负号、千分位、日期格式统一# 日期格式不一致是最常见的导入失败原因# 对账(reconcile)的用法# 每月收到账单后,把银行卡余额填进去# 差额会让你去查那笔漏掉的交易# 这是发现「忘了记账」最有效的办法注意事项:
• 不要两台设备同时改同一笔交易。 前面说过,CRDT 会替你选一个结果而不是提示冲突。 • 备份要定期做。 因为是「一份文件」,备份本身极简(导出文件 / 拷走数据目录即可),但正因如此丢了就是全丢。 • 同步密码丢了数据就解不开了,这是端到端加密的必然代价。 • CSV 导入前先清理格式。金额里的 ¥、千分位逗号、日期格式不统一,都会导致导入报错或数值错乱。• 如果从别的记账软件迁移,官方文档有专门的迁移指南,先看一眼再动手,别直接对着 CSV 硬导。
和同类方案比有什么不同
YNAB(You Need A Budget) 是信封预算法最知名的商业产品,方法论和 Actual 同源,界面打磨更成熟、银行对接更广、教程和社区更大。但它是订阅制(每年几十美元),数据在 YNAB 的服务器上,导出能力受限。如果你愿意付钱换省心,选 YNAB;如果你在意数据归属权和长期成本,选 Actual。
Firefly III 是另一个流行的开源自托管记账工具,功能极其丰富,支持复式记账、多币种、规则引擎。但它更偏「财务记录系统」而不是「预算方法」——它的预算功能不如 Actual 原生成熟,而且它是纯 Web 应用、没有本地优先这套架构,同步靠服务器。
原版 Actual 项目一度停止维护、被社区接手,这段历史说明了一件事:开源 + 数据在你手里,确实能对冲「项目死了」的风险,社区能把它接过来继续做。这是闭源订阅服务做不到的。
Mint 之类的免费记账 App 走的是广告和推荐金融产品的商业模式,本质上你是产品。
Actual 的优势总结起来:
1. 真正的 local-first。 不同步也能用,同步是增强而不是前提。 2. 没有付费墙。 全部功能免费开源,托管费用只是替你出服务器钱。 3. 方法论成熟。 信封预算是被验证几十年的方法,不是产品经理拍脑袋的功能列表。 4. 数据可携带。 一份文件,随时搬走。
局限也要讲:
• 银行对接(Bank Sync)覆盖有限,很多国家和地区需要靠 CSV 手动导入。 • 移动端体验不如商业产品,中文支持一般。 • 信封预算有学习成本,而且要持续投入精力维护。买了不用,它就是负担。 • 两端同时改同一笔会静默合并,不适合多人同时编辑同一份预算(虽然有协作功能)。
总结
记账工具的选择,本质上是在回答一个更根本的问题:你把自己的财务数据当作谁的东西?
Actual 的答案是「你的」,并且它把这个答案落实到了架构里——local-first 意味着不联网也是完整功能,CRDT 意味着不需要一个权威服务器来裁决,一份文件意味着迁移和备份不依赖任何人的配合。这几条加起来,才让「数据属于你」不只是一句宣传语。
另外值得一提的是它的 loot-core / desktop-client / desktop-electron 三层分包结构。核心业务逻辑和平台实现被彻底分开,所以同一套 core 能跑在浏览器、Electron 桌面端和移动端上——这也是它能同时提供「本地 App」和「自托管服务」两种形态的架构基础。
如果你现在的记账 App 要你开会员才能看报表,而你已经积累了两年的数据——那正好是考虑迁移的时机:数据越早拿回来,成本越低。