Actual Budget 25.4.0 版本解析:Pluggy.ai 巴西银行同步、内嵌同步服务器与移动端预算体验升级

原创2026-09-10 19:38:311,914 阅读
文章标签:金融科技后端前端桌面应用移动开发

Actual Budget 25.4.0 版本解析:Pluggy.ai 巴西银行同步、内嵌同步服务器与移动端预算体验升级

Actual Budget(本地优先的个人财务管理应用)于 2025 年 4 月 3 日发布了 25.4.0 版本,Docker 镜像标签为 25.4.0。本版本围绕三大主线展开:一是通过 Pluggy.ai 为巴西银行提供实验性的银行同步能力,二是将同步服务器(sync-server)实验性地嵌入 Electron 桌面应用,三是围绕移动端预算页、交易列表与日期国际化做了一系列体验增强。本文以官方发布文档(packages/docs/blog/2025-04-03-release-25-4-0.md)为骨架,结合仓库源码逐项拆解这些能力的实现细节与使用方式。

核心特性(Features)

1. Pluggy.ai:巴西银行同步的实验性支持

25.4.0 首次引入了面向巴西银行的 Pluggy.ai 银行同步(PR #4049)。这是一项实验性能力,需要在同步服务器侧完成凭据配置后才能使用。

从源码结构看,Pluggy.ai 集成位于 packages/sync-server/src/app-pluggyai/ 目录,包含两个文件:

  • app-pluggyai.js:负责定义 Express 路由(/status、/accounts、/transactions),并完成权限校验、文件访问控制与数据归一化;
  • pluggyai-service.js:封装对 pluggy-sdk 的调用,负责创建 Pluggy 客户端、拉取账户与交易。

凭据配置与三级路由设计

pluggyai-service.js 中的 getCredentialSource(fileId) 定义了凭据的两种来源:

function getCredentialSource(fileId) {
  if (!!fileId && hasCredentials(fileId)) {
    return 'per-budget-file';
  }
  if (hasCredentials()) {
    return 'global';
  }
  return null;
}
  • per-budget-file:为单个预算文件单独配置的 Pluggy 凭据(clientId、clientSecret、itemIds);
  • global:全局共享的 Pluggy 凭据,适用于所有预算文件。

hasCredentials() 要求三个密钥(pluggyai_clientId、pluggyai_clientSecret、pluggyai_itemIds)同时存在才认为已配置,这些密钥通过 secretsService 读写,并缓存在内存中的 pluggyClients Map 中(以文件 ID 与凭据序列化为缓存键)。itemIds 以逗号分隔存储,解析时会被拆分、去空格并过滤空项。

三个路由的职责与鉴权

app-pluggyai.js 挂载了 requestLoggerMiddleware、express.json() 与 validateSessionMiddleware,并统一从请求头 X-Actual-File-Id 读取文件 ID,通过 canAccessFile()(管理员或对该文件有访问权的用户)校验权限,非法文件 ID 返回 400 invalid-file-id,无权限返回 403 file-access-denied:

  • POST /status:返回当前文件是否已配置 Pluggy 及凭据来源(configured 与 source);
  • POST /accounts:遍历所有 itemIds 调用 pluggyaiService.getAccountsByItemId() 聚合账户列表;若未配置凭据返回 400 not-configured;
  • POST /transactions:接收 { accountId, startDate },拉取交易并归一化为 Actual 的转账格式,同时返回起始余额(startingBalance)与余额对象(balances)。

交易归一化的关键逻辑

/transactions 路由中的归一化逻辑值得注意:

  • 交易按 booked(已入账)与 pending(待入账)分组,判断依据是 trans.status === 'PENDING';
  • 早于 startDate 且非沙箱的交易会被跳过;
  • 信用卡(account.type === 'CREDIT')账户的金额与余额会被取反,以符合 Actual 的记账方向;
  • 交易日期通过 getTransactionDateCorrected() 修正:若交易携带信用卡分期元数据(creditCardMetadata.installmentNumber),则基于 purchaseDate 按月数递推分期日期,并使用 addMonthsClamped() 防止 31 日跨月溢出;
  • 交易对象经 flattenObject() 扁平化后与归一化字段合并;
  • 收款方(payee)名称优先取 merchant.name / merchant.businessName,其次按借贷方向取 paymentData.receiver 或 paymentData.payer 的名称或证件号(documentNumber.value);
  • 所有交易按 sortOrder(交易日期的时间戳)降序排列,返回 all、booked、pending 三组结果。

沙箱账户的兼容处理

pluggyai-service.js 中 getTransactionsByAccountId() 对 Pluggy 沙箱账户做了特殊处理:沙箱数据不会自动推移日期,交易往往超过 90 天,因此当账户 owner 为 John Doe 时,将 startDate 强制设为 2000-01-01 并给交易打上 sandbox: true 标记,以便测试时能取回全部历史数据。25.4.0 还修复了 Pluggy.ai 信用卡交易金额反相(#4628)与外币交易(#4712)等问题。

2. 账户新增 "上次核对时间"(last reconciled)

PR #4459 为账户模型增加了 last_reconciled 时间戳字段。对应的数据库迁移文件为 packages/loot-core/migrations/1740506588539_add_last_reconciled_at.sql:

BEGIN TRANSACTION;

ALTER TABLE accounts ADD COLUMN last_reconciled text;

COMMIT;

在服务端,packages/loot-core/src/server/accounts/app.ts 的账户查询中新增了 last_reconciled 字段,并将其纳入账户实体的返回结果(dbAccount.last_reconciled ?? null),同时支持在账户更新时回写该字段。桌面客户端侧,账户头部(Header.tsx)、对账弹窗(AccountReconcileModal.tsx)与对账组件(Reconcile.tsx)均读取该字段,用于展示账户最近一次完成对账的时间,帮助用户跟踪对账节奏。

3. 实验性:将同步服务器嵌入桌面应用

PR #4526 是 25.4.0 的另一项重要实验:将 sync-server 直接嵌入 Electron 桌面应用,使用户无需单独部署 Actual Server 即可在同一进程体系中获得完整的服务端能力。

从 packages/desktop-electron/index.ts 的实现看,桌面应用通过 utilityProcess.fork() 启动 sync-server 的构建产物(@actual-app/sync-server/package.json 所在目录下的 build/app.js),并通过环境变量完成配置注入:

const envVariables: Env = {
  ...process.env, // required
  ACTUAL_PORT: `${syncServerConfig.port}`,
  ACTUAL_HOSTNAME: `${syncServerConfig.hostname}`,
  ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`,
  ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`,
  ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`,
  ACTUAL_WEB_ROOT: webRoot,
};

其中 ACTUAL_SERVER_DATA_DIR 是 sync-server 的数据根目录,服务端文件(server-files)与用户文件(user-files)均在其下;ACTUAL_WEB_ROOT 指向 @actual-app/web 的构建目录,由 web 客户端提供界面。桌面端通过 IPC 暴露了 start-sync-server、stop-sync-server、is-sync-server-running 三个通道(见 preload.ts),分别用于启动、停止与查询内嵌服务状态,子进程的 stdout/stderr 会转发到主窗口日志中。该功能在开发模式下还会为 fork 的子进程附加 --inspect 以便调试。

使用方式与前置条件:该特性属于实验性能力,需自行构建或使用支持此功能的桌面端版本;内嵌服务的数据目录、端口等可通过环境变量(ACTUAL_DATA_DIR、ACTUAL_PORT 等)控制,与独立部署 sync-server 时一致。同一版本还配套修复了 sync-server.Dockerfile 未正确构建客户端的问题(#4677)。

体验增强(Enhancements)

日期与日期选择器的国际化翻译

PR #4211 为日期与日期选择器(datepicker)增加了按所选语言翻译的能力,将日期控件从硬编码英文切换为 i18n 驱动的本地化文案。25.4.0 同时收录了 PR #4635,为规则(rules)与字段增加了更多翻译条目,规则编辑器中的条件、操作与字段名在切换语言后能够同步本地化。

移动端预算页的 Banner 提示

PR #4643(重做)为移动端预算表引入了 Banner 机制,用于主动提醒用户各类预算待办事项。实现位于 packages/desktop-client/src/components/mobile/budget/BudgetPage.tsx,共实现三类 Banner:

  • OverbudgetedBanner(超预算警告,type="critical"):当 toBudget 金额为负时显示 "You have budgeted more than your available funds",提供 Cover 按钮打开 cover 弹窗,从其他类别或 "To Budget" 中移入资金覆盖超预算部分;
  • OverspendingBanner(超支类别警告,type="critical"):通过 useOverspentCategories() 统计超支类别,显示超支类别数量,提供覆盖(cover-overspending)与类别选择弹窗(category-autocomplete),且支持显示隐藏的超支类别(#4680);
  • UncategorizedTransactionsBanner(未分类交易提醒,type="warning"):统计未分类交易条数与总额,提供 Categorize 按钮跳转至 /categories/uncategorized。

Banner 的配色随主题变量变化(errorBackground、warningBackground、noticeBackground),在无相关事项时返回 null 不占位,避免干扰正常浏览。

移动端交易与账户操作增强

  • 子交易搜索(#4471):移动端支持搜索子交易(split 中的条目),方便在拆分交易中快速定位明细;
  • 拖拽排序(#4476、#4482):移动端支持拖拽重排账户列表与预算页的收入类别,目前仅支持 Chromium 内核浏览器;
  • 标记转账(#4511):小屏设备上允许将交易标记为转账(Transfer);
  • 拆分交易(#4536):当拆分中不存在空条目时,交易编辑按钮显示 "Add New Split" 文案;
  • 目标与预算信息展示(#4546):移动端展示类别的目标(goal)与预算信息;
  • 收入类别余额跳转(#4621):点击收入类别余额可查看对应交易列表;
  • 通知滑动关闭(#4606):左右滑动即可关闭通知提醒;
  • 搜索/过滤时拆分交易折叠(#4600):带搜索或过滤条件时支持折叠/展开拆分交易。

银行同步相关增强

  • 最后同步时间人性化(#4523):银行同步的最后时间以自然语言(如 "2 hours ago")展示,取代原始时间戳;
  • 有限历史银行名单扩充(#4416):将 INDUSTRIEL_CMCIFRPAXXX、QONTO_QNTOFRP1 加入 BANKS_WITH_LIMITED_HISTORY 常量(这些银行仅提供约 90 天交易历史);同期 #4595 将捷克 AirBank 加入同类名单;
  • 删除交易不再被重复导入(#4644):新增配置项,控制银行同步时是否重新导入已删除的交易;
  • 机构列展示(#4646):关联账户弹窗中的账户名称旁增加机构(institution)列;
  • 外部账户记忆化(#4540):对银行同步弹窗中的外部账户列表做 memoize,减少重复请求;
  • 同步摘要月度平均改进(#4302):改进汇总报表(summary report)的月度平均计算方式。

关键 Bugfix 一览

25.4.0 修复了大量与同步服务器、OpenID、移动端交互相关的问题,值得运维与重度用户关注:

同步服务器与 OpenID 配置

  • ACTUAL_OPENID_DISCOVERY_URL 新变量(#4530)与 ACTUAL_OPENID_ENFORCE 强制启用修复(#4533);
  • 生产构建缺失 convict 与 pluggy-sdk 依赖(#4534)、默认数据目录配置(#4535)、ACTUAL_PORT / PORT 变量处理(#4537)、config 文件中的对象类型支持(#4688)、OpenID 响应缺失 iss 参数(#4626);
  • 这些修复与 packages/sync-server/src/load-config.js 中基于 convict 的配置体系(ACTUAL_* 环境变量、config.json 加载、tokenExpiration 自定义格式校验)直接相关。

银行同步

  • mBank 波兰交易日期修复(#4493)、SimpleFIN 批量同步超时与调试日志(#4515)、银行同步 API 错误收集修复(#4689)、GoCardless 机构结构差异导致 SelectLinkedAccounts 失效(#4667)。

规则与交易

  • 排程无 payee 时自动补充 "payee is nothing" 规则条件(#4267)、规则创建拆分交易失败(#4623)、scheduleIsRecurring 空值判断(#4627)、带模板规则保存失败(#4686)、转账交易过滤(#4519)。

移动端与界面

  • iOS 上预算金额聚焦(#4651)、移动端远程预算文件切换(#4596)、自定义报表 "any" 条件保存(#4652)、报表仪表盘现金流组件过滤(#4683)、service worker 缓存上限提升至 5MB(#4665)。

维护与架构演进(Maintenance)

25.4.0 背后包含大量架构级维护工作,这些改动虽不直接面向用户,但为后续版本奠定了更清晰的分层结构:

  • Redux Toolkit 迁移第二阶段(#4114、#4119、#4126、#4127、#4128):budgets、modals、notifications、preferences、users 等 slice 相继迁移至 Redux Toolkit;
  • 服务端 handler 拆分(#4442、#4443、#4649、#4661):将预算类别、payee、spreadsheet、同步相关的服务端 handler 从庞大的 main.ts 中抽取到独立的 server/*/app.ts,并统一使用实现函数类型,减少类型重复(#4650);
  • 组件库收敛(#4554、#4556、#4563、#4568):图标文件、Select、Input、useResponsive 等组件与 Hook 迁入 @actual-app/components 组件库,移除废弃的 MenuButton、Button 组件;
  • 依赖与构建治理(#4592、#4609、#4610、#4641、#4654、#4656、#4717):loot-core 路径解析迁移至 yarn workspaces、数据模型导入统一走 barrel 文件、yarn 升级至 4.7.0、axios 升至 1.8.3、tar-fs 升至 2.1.2、构建禁用名称混淆(#4532);
  • 类型系统强化(#4248、#4249、#4542、#4524):db.first、db.firstSync、db.all 泛型化,预算表组件类型细化;
  • 发布流程自动化(#4664):新增 release note 生成器,后续版本发布说明可由脚本自动生成,这正是本文所依据的发布文档的产出渠道之一。

升级与验证建议

  • 使用 Docker 部署 Actual Server 的用户可拉取 actualbudget/actual:25.4.0 镜像完成升级;last_reconciled 字段通过迁移脚本自动添加,无需手动改库;
  • 尝试 Pluggy.ai 巴西银行同步的用户,需在同步服务器中配置 pluggyai_clientId、pluggyai_clientSecret、pluggyai_itemIds 三组密钥(支持全局或按预算文件),并通过 POST /status 验证配置是否生效;
  • 关注 OpenID 配置的运维人员,本版本对 ACTUAL_OPENID_DISCOVERY_URL、ACTUAL_OPENID_ENFORCE、ACTUAL_PORT 等环境变量的行为做了多项修复,建议升级后回归验证登录与端口绑定;
  • 桌面端用户可在设置中体验内嵌同步服务器(实验性),注意其数据目录与独立部署版本相互独立,迁移数据时需分别处理。

25.4.0 是 Actual Budget 在"银行同步生态拓展 + 桌面端架构收敛 + 移动端体验打磨"三线并进的一次集中发布:Pluggy.ai 的实验性接入填补了巴西银行同步的空白,内嵌同步服务器为单机用户提供了更完整的本地优先方案,而预算 Banner、日期翻译与大量移动端交互改进则显著降低了日常记账的摩擦成本。

登录后查看全文
actual