Actual 23.3.2 版本解析:Nordigen 银行同步稳定性修复与 Docker 镜像修复实战
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
Actual 23.3.2(发布于 2023-03-13)是本仓库历史版本记录中的一次关键补丁发布,聚焦于两个主题:Docker 镜像构建修复(不再将 Dockerfile 做成符号链接)与Nordigen(GoCardless)银行自动同步的多项数据健壮性修复。本文以该版本发布说明为主体,结合当前仓库中packages/sync-server的源码实现,逐条还原这些 bugfix 的底层逻辑,帮助读者理解 Actual 的银行同步架构、常见数据异常的处理模式,以及如何验证该版本的行为。
版本快照
23.3.2 同时发布了两个组件,两者使用同一个 Docker tag:
| 组件 | 版本 | Docker tag |
|---|---|---|
| Actual(Web 客户端) | 23.3.2 | 23.3.2 |
| Actual Server(自托管服务端) | 23.3.2 | 23.3.2 |
从发布说明的结构可以看出,该版本没有新增大的用户功能,而是一次以修复数据解析正确性与部署可靠性为目的的维护性发布,其中 Nordigen 银行同步相关修复占据了绝大多数条目。
Docker 修复:告别符号链接式 Dockerfile
发布说明首先点明的是一条部署侧修复:"Docker fix: don't make symlink"(对应 actual-server 仓库 #157)。此前 actual-server 的 Docker 构建流程中 Dockerfile 存在被符号链接引用的问题,容易导致不同构建上下文下的行为不一致或镜像打包异常。23.3.2 改为使用真实的 Dockerfile 文件。
在当前仓库中可以看到 Actual 主仓库同样遵循"独立的真实 Dockerfile"约定:Dockerfile 是完整的独立文件(基于node:24-bookworm,安装openssl并声明CMD ["sh", "./bin/docker-start"]),配合根目录的 docker-compose.yml 使用;同步服务器则另有 sync-server.Dockerfile。如果你通过 Docker 自托管 Actual,只需在服务端拉取 tag 为23.3.2的镜像即可获得此修复,无需额外配置。
此外,客户端侧还包含一个与文件处理相关的修复(#738):在尝试解析导入文件之前,先正确设置 filename/filetype。这意味着导入流程中文件类型判定被提前到解析动作之前,避免因扩展名/类型信息缺失导致解析器走错分支。
Nordigen 银行同步:四项数据健壮性修复
23.3.2 的核心工作是围绕 Nordigen(即后来的 GoCardless)银行同步的四个 bugfix。Nordigen 是 Actual 最早接入的欧洲银行数据聚合服务,23.3.0 版本(release-23.3.0)刚以Experimental状态引入账户同步能力,23.3.2 随即针对真实银行返回数据的各种"脏数据"做了补强。下面结合当前仓库packages/sync-server/src/app-gocardless下的源码逐一剖析。
1. 修复-0.00金额交易的"debit"方向误判(#744)
Nordigen 返回的transactionAmount.amount是字符串形式的十进制金额,某些银行对金额为 0 的挂账/冲正交易会返回"-0.00"这样的负零字符串。如果同步代码仅凭字符串前缀的-号判断资金方向,就会把一笔零金额交易误判为支出,进而污染支付方(payee)与分类逻辑。23.3.2 修复了该检测,将方向判定建立在数值语义而非字符串符号上。
从当前源码看,金额处理的正确姿势是先经过amountToInteger这类归一化转换再参与计算。例如 abnamro_abnanl2a.ts 的calculateStartingBalance中,交易金额均通过amountToInteger(transaction.transactionAmount.amount)转成整数分后再做加减;integration-bank.ts 的起始余额计算同样先归一化金额。这也解释了为何字符串层面的"-0.00"必须被单独处理:一旦落入数值运算,负零会带来方向性错误。
2.remittanceInformationUnstructured缺失时回退到数组字段(#745)
部分银行不在单值字段remittanceInformationUnstructured中返回附言,而是只提供数组形态的remittanceInformationUnstructuredArray。23.3.2 增加了从数组版本回退取值的逻辑,确保交易备注(notes)不会被丢空。
当前源码中这一模式已普遍化,典型实现见 abnamro_abnanl2a.ts:
const infoArray = transaction.remittanceInformationUnstructuredArray ?? []; // There is no remittanceInformationUnstructured, so we'll make it editedTrans.remittanceInformationUnstructured = infoArray.join(', ');而基类 integration-bank.ts 在组装notes时按notes→remittanceInformationUnstructured→remittanceInformationUnstructuredArray.join(' ')的优先级逐级回退,同时还会把两个数组字段序列化到交易对象上供 UI 映射使用。如果你自建银行集成,建议同样遵循"单值字段缺失时回退数组字段"的容错模式。
3.valueDate缺失时回退到bookingDate(#743)
Nordigen 交易对象中bookingDate(记账日)与valueDate(起息日)并不总是同时存在,部分银行只返回其中一个。23.3.2 修复了当valueDate未设置时交易被错误丢弃的问题,改为回退使用bookingDate。
这正是基类normalizeTransaction中的日期选择链(integration-bank.ts):
const date = trans.date || transaction.bookingDate || transaction.bookingDateTime || transaction.valueDate || transaction.valueDateTime;若所有日期字段均缺失,该交易会被过滤掉(返回null),等待银行后续处理完成后再同步。此外,有些银行只提供valueDateTime(时间戳形态),例如 abnamro_abnanl2a.ts 通过(transaction.valueDateTime ?? '').slice(0, 10)截取日期部分。日期最终统一格式化为yyyy-MM-dd,保证进入 Actual 预算数据后的一致性与可排序性。
4. 链接账户前先检查服务器状态(#742)
Nordigen 授权流程需要客户端与自托管服务器配合完成:客户端引导用户跳转到银行授权页,银行回调后由服务器侧完成 requisition 的创建与关联。若服务器尚未完成 GoCardless 密钥配置,整个流程会静默失败。23.3.2 要求在链接账户之前先检查服务器状态,提前暴露配置缺失问题。
当前仓库中对应的能力是服务器端新增的/status端点(app-gocardless.ts):
app.post('/status', async (req, res) => { res.send({ status: 'ok', data: { configured: goCardlessService.isConfigured(), }, }); });其判定逻辑位于 gocardless-service.ts:isConfigured()返回secretId与secretKey是否均已通过密钥服务配置完毕。也就是说,23.3.2 之后客户端在发起链接前可先查询/status,若configured: false则提示用户先去服务器配置 GoCardless 凭据,避免在授权中途失败。
客户端修复:#247 聚合查询的已删除交易过滤
除了 Nordigen 相关修复,客户端还包含一条交易数据正确性修复(#247):在交易分组模式下,将聚合查询路由到正确的数据层,以剔除已删除的交易。该问题源于分组视图(按支付方/分类聚合)走的查询路径绕过了"软删除标记过滤",导致已删除交易仍出现在聚合结果中。修复后聚合查询统一经过过滤层,与明细列表的删除语义保持一致。
Actual Server 侧改动详解
服务端在 23.3.2 中同样获得了一个新特性与四个修复:
新增 status 端点(#162)
即上文提到的/status端点,用于向客户端暴露 GoCardless 服务的配置与可用状态,是"链接前先检查服务器状态"(#742)的服务端配套能力。实现位于 app-gocardless.ts 的app.post('/status', ...)路由。
重新生成 Nordigen token(#156)
Nordigen 的访问令牌是短期 JWT,过期后所有 API 调用都会失败。23.3.2 修复了令牌过期后无法自动刷新的问题。当前源码中这一逻辑已经演化为setToken(gocardless-service.ts):先解析 JWT 的exp声明判断是否过期(Date.now() / 1000 >= payload.exp),过期则调用client.generateToken()重新获取,并把密钥按内容哈希缓存在clientsMap 中以复用客户端实例。所有对外方法(getInstitutions、getRequisition、initSession等)都会先经过setToken确保令牌有效。
打开/nordigen/link路径时关闭窗口(#160)
Nordigen 授权页在银行侧完成后会重定向回服务器,服务器再引导回客户端。23.3.2 让/nordigen/link路径直接返回一个自动关闭的页面,避免残留空白标签页。该逻辑在当前源码中保留为 app-gocardless.ts 的LINK_PAGE_HTML:页面加载即执行window.close(),并提示"如果什么都没发生,可以手动关闭窗口",同时附有"Please wait..."提示文案。
账户名追加货币(#163)
此前通过 Nordigen 链接的账户名不含货币信息,多币种账户在 UI 中难以区分。23.3.2 将货币代码追加到账户名中。对应实现见基类normalizeAccount(integration-bank.ts):账户名由name/displayName/product、格式化后的 IBAN(printIban)与currency三部分拼接而成,例如"Daily account ·PL00 ·EUR"的形态。
Dockerfile 去符号链接(#157)与杂项维护
服务端的 Dockerfile 不再以符号链接形式存在(与客户端 Docker 修复对应);此外 23.3.2 还包含 README 更新(#161)与 LICENSE 年份移除(#140/#665)等维护性改动,不涉及运行时行为。
从 23.3.0 到 23.3.2:Nordigen 同步能力的演进脉络
要完整理解 23.3.2 的定位,可以回看 23.3.0(release-23.3.0)引入的功能基础:Nordigen 账户同步(客户端 #457 + 服务端 #74/#145)、可编辑的交易过滤器、服务端自动下发 server URL、Safari 大批量同步修复等。23.3.2 正是在这一新功能上线后,针对各银行真实数据形态差异做的第一轮集中打磨——四个 Nordigen bugfix 全部属于"银行返回的数据不符合预期"这一类问题,而不是架构性改动。
当前仓库中该功能的最终形态比 23.3.2 更加成熟:
- 模块已更名为 GoCardless(
packages/sync-server/src/app-gocardless),Nordigen 品牌已由 GoCardless 承接; - 通过 bank-factory.ts 的
BankFactory(institutionId)按机构 ID 分派到具体的银行适配器(如abnamro_abnanl2a.ts、mbank_retail_brexplpw.ts等),未匹配的机构回退到IntegrationBank基类; - 每条交易经过
normalizeTransaction→sortTransactions→calculateStartingBalance的标准化流水线,最终以booked(已记账)、pending(待处理)、all(合并排序)三组返回客户端(gocardless-service.ts)。
如果你要基于这套架构排查银行同步问题,建议按以下顺序验证:① 服务端/status是否configured: true;② 服务器日志中 requisition 是否处于LN(已链接)状态;③ 交易对象中日期、金额、附言字段是否符合预期(参考 app-gocardless/README.md 中给出的normalizeAccount/sortTransactions/calculateStartingBalance的调试日志样例);④ 检查是否命中了该银行专属的适配器。
总结
Actual 23.3.2 是一次"小而关键"的补丁发布:Docker 镜像的符号链接问题直接影响自托管用户的部署一致性;而四项 Nordigen 修复分别覆盖了金额方向误判、附言字段缺失、日期字段缺失、链接前状态检查四个真实场景,大幅提升了欧洲银行自动同步的可用性。透过当前仓库源码可以看到,这些修复所确立的容错模式——单值字段回退数组字段、日期字段多级回退、金额先归一化再计算、操作前先查状态——已成为后续所有银行适配器共同遵循的规范,也是理解 Actual 银行同步架构的最佳切入点。
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考