news 2026/9/10 22:07:03

Actual 23.4.2 版本解析:按命名计划自动预算、侧边栏浮动优化与服务端密码重置脚本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Actual 23.4.2 版本解析:按命名计划自动预算、侧边栏浮动优化与服务端密码重置脚本

Actual 23.4.2 版本解析:按命名计划自动预算、侧边栏浮动优化与服务端密码重置脚本

【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual

Actual 是一款本地优先(local-first)的个人财务管理应用,23.4.2 是其 2023 年 4 月发布的一个维护型版本,涵盖客户端(Actual)与服务端(Actual Server)两侧的改动。本文以官方发布说明 packages/docs/blog/2023-04-22-release-23.4.2.md 为主线,结合当前仓库源码,深入讲解「按命名计划(named schedules)自动写入预算模板」「侧边栏自动浮动行为改进」「撤销快捷键修复」「零值对账修复」「自动补全依赖清理」以及服务端「reset-password密码重置脚本」与「Nordigen 错误信息增强」等核心变更的实现原理与使用方式,帮助读者理解这些更新背后的工程逻辑并直接上手实践。

版本概览:一个客户端与服务端同步发布的维护版本

23.4.2 同时发布了客户端与服务端两个组件,Docker 镜像标签为23.4.2

组件版本变更类型
Actual(客户端)23.4.21 个功能(Feature)、1 个增强(Enhancement)、2 个缺陷修复(Bugfix)、2 项维护(Maintenance)
Actual Server(服务端)23.4.21 个功能、1 个增强

整体上这是一次小版本迭代:客户端聚焦预算模板与交互体验的打磨,服务端则补上了一个此前缺失的运维能力——命令行重置密码。下文分别展开。

客户端(Actual 23.4.2)变更详解

按命名计划自动生成预算模板金额(Feature #885)

本版本最重要的功能更新是:预算模板(Budget Template)新增了schedule关键字,可以按「命名计划」(named schedule)自动计算出当月应预算的金额,该功能由社区贡献者 pole95 提交。

在 Actual 的预算模板体系中,每个模板行由类型(type)、指令(directive)、优先级(priority)等字段构成。schedule类型的模板行并不直接写死金额,而是通过name(或内部使用的scheduleId)关联到一条已有的计划(Schedule),由预算引擎在运行时解析出金额。相关实现位于 packages/loot-core/src/server/budget/schedule-template.ts,入口函数为runSchedule

从源码可以看到其核心计算流程:

  1. 按名称/ID 匹配计划:通过template.scheduleId(优先,避免重命名破坏关联)或template.name(兼容旧数据)查询schedules表中tombstone = 0的活跃计划,再经由getRuleForSchedule拿到该计划关联的规则,从中提取日期条件与金额条件。
  2. 计算单次金额:从规则条件中提取金额,若金额条件是区间(isbetween),则取两端平均值;若模板配置了adjustment(调整量)与adjustmentType,还会按百分比(percent)或固定值(fixed)对金额做修正——例如adjustment: 10, adjustmentType: 'percent'表示在原金额基础上上浮 10%。
  3. 区分「当月付清」与「沉没分摊」:对于本月到期的计划(pay-month-of,即num_months === 0且频率与间隔满足条件,如每月一次、每周间隔 ≤ 4、每天间隔 ≤ 31),直接预算全额;对于更远的未来计划(如年度账单),则按getMonthlyBaseContribution/getSinkingContributionBreakdown进行跨月分摊(sinking fund),将目标金额按月拆解,并优先用已有余额覆盖最近到期的计划。
  4. 处理边界情况:已完成的计划(completed)会被排除;已经过去的非重复计划会记录Schedule xxx is in the Past错误;周/日频率且间隔较大的计划按跨月月数折算。

对应的单元测试覆盖了这些行为,见 packages/loot-core/src/server/budget/schedule-template.test.ts,例如:

  • 月度重复计划直接预算全额(金额为负时取正,如-10000→ 预算10000);
  • 年度计划配合已有余额时仅预算当月分摊额($1200的年度账单当月只需$1000);
  • full: true(到期月一次性付清)时不为未来月份预存资金;
  • percent/fixed两种调整方式分别得到1100010500的结果;
  • 多计划并存时按到期日排序,让已有余额优先覆盖最早的账单。

使用方式:在预算模板中新增一行,type 设为schedule,name 填写要关联的计划的名称(或使用内部 scheduleId),引擎即会在每次运行模板时自动按计划金额与日期计算应预算值,无需手工维护金额,计划金额变动时模板自动跟随。

改进侧边栏自动浮动行为(Enhancement #868)

该版本优化了侧边栏的「自动浮动」(auto-floating)表现。在桌面端中,侧边栏支持浮动模式(floating sidebar),相关组件位于 packages/desktop-client/src/components/sidebar,其中FloatableSidebar在 packages/desktop-client/src/components/FinancesApp.tsx 中被挂载。

侧边栏的浮动行为同时受两个因素控制(见 packages/desktop-client/src/components/Titlebar.tsx):

  • 用户偏好floatingSidebar(通过useGlobalPref读取);
  • sidebar.alwaysFloats(来自 packages/desktop-client/src/components/sidebar/SidebarProvider.tsx 的上下文状态)。

改进后的自动浮动逻辑让侧边栏在窗口宽度变化或用户展开/收起操作时,能更平稳地在「固定」与「浮动」两种形态之间切换,减少跳动与遮挡,提升多窗口、窄屏场景下的使用体验。这是交互层的小步优化,不影响数据与预算逻辑。

修复撤销键盘快捷键失效问题(Bugfix #926)

此前在某些场景下,Ctrl+Z(macOS 为Cmd+Z)撤销快捷键会被忽略,本版本修复了该问题。

Actual 的撤销机制是「本地优先 + 服务端同步」架构下的关键能力:撤销/重做并非简单操作 DOM 或组件状态,而是通过向核心层发送undo/redo消息、回滚底层数据表变更实现的。相关实现见:

  • packages/desktop-client/src/undo/index.ts:核心入口,_undo = throttle(() => send('undo'), 100)undo消息做了 100ms 节流,并通过_undoEnabled标志在特定场景下临时禁用撤销;
  • packages/desktop-client/src/hooks/useUndo.ts:将撤销能力以 hook 形式注入页面组件;
  • packages/desktop-client/src/global-events.ts:监听undo-event,将撤销事件与标签(undoTag)状态路由到各页面监听器。

修复前,当焦点位于某些输入控件或撤销状态被节流窗口吞掉时,快捷键事件可能无法正确触发undo();修复后撤销快捷键在更多交互场景下都能稳定响应,配合界面右下角的撤销提示条(undo notification)使用体验更一致。

修复零值预算的对账问题(Bugfix #915)

该修复针对「对一个零值预算执行对账(reconcile)」时出现的异常。对账是 Actual 核对账户余额与账单一致性的核心功能,其实现位于 packages/loot-core/src/server/accounts/app.ts,其中reconcileTransactions负责将银行同步/导入的交易与现有交易重新匹配(相关逻辑也有测试覆盖,见 packages/loot-core/src/server/accounts/sync.test.ts)。

修复前,当对账涉及的金额合计为零(例如收支相抵或全部金额为 0 的批量操作)时,会触发除零或空值比较等边界问题;修复后引擎能正确处理零值场景,保证对账过程正常完成、余额状态一致。

自动补全依赖清理(Maintenance #916 / #924)

两个维护性 PR 完成了对旧自动补全(autocomplete)组件的收尾:

  • #916:移除@jlongster/lively依赖,重构旧的自动补全组件使其不再依赖该库,并禁用了新的自动补全;
  • #924:进一步移除react-select及新自动补全组件。

这说明 23.4.2 处在自动补全组件的新旧交替期:旧实现被彻底清理,新实现(由后续版本基于组件库 packages/component-library 中的Select等组件重写)暂时禁用。对普通用户而言,此次清理减少了打包体积与运行时依赖,属于纯工程性改进,不改变功能行为。

服务端(Actual Server 23.4.2)变更详解

新增npm run reset-password脚本(Feature #186)

这是本版本服务端最重要的运维能力补充:通过一条命令即可设置或重置服务器登录密码,解决了此前忘记密码后只能手工操作数据库的痛点。

使用方式(在 sync-server 目录下执行):

npm run reset-password

脚本会自动判断当前状态并走两条分支:

  • 尚未设置过密码(首次初始化):提示It looks like you don't have a password set yet. Let's set one up now!,交互式输入新密码后完成初始化;
  • 已设置过密码:提示It looks like you already have a password set. Let's reset it!,交互式输入新密码后完成重置,并提醒所有已登录的浏览器与设备需要使用新密码重新登录

脚本实现见 packages/sync-server/src/scripts/reset-password.js,它通过needsBootstrap()判断是否首次初始化,分别调用 packages/sync-server/src/accounts/password.js 中的bootstrapPasswordchangePassword

除了npm run reset-password,服务端 CLI 还提供了等价的命令行参数形式(见 packages/sync-server/bin/actual-server.js):

actual-server --reset-password

bin/actual-server.js通过 Node 内置的parseArgs解析--reset-password布尔参数,命中后先动态加载../src/scripts/reset-password.js执行重置,随后process.exit(),不会启动同步服务。

底层实现原理:密码并非明文存储,而是以 Argon2id 哈希保存。hashPassword使用如下参数(见 packages/sync-server/src/accounts/password.js,参数遵循 OWASP 密码存储建议):

const ARGON2_OPTIONS = { type: argon2.argon2id, // Argon2id 变体 memoryCost: 47104, // 内存成本 46 MiB timeCost: 1, // 迭代次数 parallelism: 1, // 并行度 };

同时兼容旧数据:verifyPassword会先判断哈希前缀,$argon2开头走 Argon2 校验,否则回退到 bcrypt 比较(bcrypt.compare),保证升级前用 bcrypt 存储的密码仍可正常登录;登录成功时还会将旧 bcrypt 哈希原地升级为 Argon2id 哈希(见loginWithPassword中的isLegacyHash分支)。

两个关键安全细节:

  1. 空密码被拒绝isValidPassword拒绝nullundefined与空字符串,bootstrapPasswordchangePassword对空密码统一返回{ error: 'invalid-password' }
  2. OpenID-only 实例保护:如果实例启用了 OpenID 认证(没有 password 认证方法),changePassword会返回{ error: 'no-password-method' }而不是静默成功,且不会禁用已存在的 OpenID 方法——避免误操作破坏已有认证配置。这两点均有测试验证,见 packages/sync-server/src/accounts/password.test.js。

服务端 CLI 的完整参数表(见 packages/sync-server/README.md)如下:

参数说明
-h/--help打印帮助信息并退出
-v/--version打印版本号并退出
--config指定配置文件路径
--reset-password设置或重置服务器密码

注意:@actual-app/sync-servernpm 包要求Node.js v22 或更高版本(见 packages/sync-server/README.md)。

更清晰地报告 Nordigen 请求异常(Enhancement #189)

Nordigen(现 GoCardless Bank Account Data)是 Actual 对接银行账户数据聚合的渠道之一。此前当 Nordigen 请求返回非预期状态码时,报错信息较为模糊,难以定位问题;本版本增强了错误报告,让失败请求的状态码与上下文信息更明确,便于用户在银行同步失败时快速判断是凭证失效、账户被移除还是 API 侧限流。

仓库中 Nordigen/GoCardless 相关实现位于 packages/sync-server/src/app-gocardless(共 80 余个 TS 文件),涉及账户授权、交易拉取、银行元数据等完整流程。该增强不改变同步功能本身,只提升可观测性,属于服务端的可维护性改进。

小结

Actual 23.4.2 是一个「稳中有进」的版本:

  • 客户端:为预算模板带来按命名计划自动预算的能力(schedule模板关键字),配合计划金额调整(percent/fixed)与沉没分摊逻辑,让「计划驱动的预算」真正自动化;同时打磨了侧边栏浮动交互,修复了撤销快捷键与零值对账两个影响日常使用的缺陷,并清理了旧的自动补全依赖;
  • 服务端:补齐了reset-password运维能力(npm 脚本 +--reset-password参数双入口),基于 Argon2id 哈希并保持 bcrypt 向后兼容,还内置了对 OpenID-only 实例的保护;Nordigen 异常报告也得到增强。

对于自托管用户,升级到23.4.2后最值得立刻上手的功能就是npm run reset-password——从此忘记密码不再需要动数据库。对于开发者,schedule模板的实现(schedule-template.ts)与密码管理实现(password.js)都是理解 Actual 预算引擎与认证体系的上佳入口,配套的 schedule-template.test.ts 与 password.test.js 则直接给出了各种边界行为的行为契约。

【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

Hypervisor2在智能汽车虚拟化中的关键技术与实践

1. Hypervisor2与现代智能汽车系统的技术耦合 在汽车电子架构从分布式向集中式演进的浪潮中,Hypervisor技术正经历着从基础虚拟化到功能安全的质变。作为第二代虚拟化方案的典型代表,Hypervisor2通过Type-1型架构直接运行在硬件层上,相比传统…

作者头像 李华
网站建设 2026/9/10 22:02:44

CANN/ge Tiling下沉设计

Tiling 下沉(Tiling Sink)特性分析 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型…

作者头像 李华