1. 项目定位:先搞清楚做的到底是什么
1.1 从标题到产品:financial-services 到底在解决什么问题
我第一次接手这个项目,标题栏里只写了“financial-services”三个词。坦白讲,第一眼有点懵——这范围太大了,银行、保险、证券、支付、信贷,哪个都算 financial services,哪个都不像单靠一个项目能装下的。后来跟需求方对齐了两次,才把重点捞出来:他们真正想做的,是一个面向个人用户的财务健康管理服务,核心功能是帮用户理清“钱从哪来、花到哪去、剩下多少、风险在哪”,而不是做一个传统的交易系统。
这个差别很关键。传统金融系统重的是“钱不能错”,各类账目、清算、风控一整套流程得像流水线一样严密。但个人财务健康服务重的是“用户能不能看明白自己的钱”,核心是数据归集、分类分析、趋势展示和决策建议。设计思路完全不同,后端的数据模型和前端的信息架构也是两套逻辑。
我在实际做的时候,第一件事不是写代码,而是把标题拆成问题清单:
- 面向谁?个人用户,不是企业客户。
- 解决什么痛点?账单散落在各个平台(银行卡、信用卡、微信、支付宝),用户只想一站式看清楚。
- 核心产出是什么?清晰的收支分类、月度/年度报表、消费习惯洞察、财务健康评分。
- 边界在哪?不做放款、不做理财交易,只做分析展示和建议。这样既能控制合规复杂度,也符合大多数个人财务工具的实际定位。
把这些问题列清楚之后,“financial-services”这个标题才算落地。后面所有的技术方案、页面设计、算法模型,都是围绕这四条展开的。
1.2 为什么选“个人财务健康”而不是“记账本”
我见过不少同类项目一上来就做成“高级记账本”,结果用户用了两天就流失。原因很朴素:记账本身是反人性的,每天花时间去记每一笔,大多数人坚持不下来,除非他能立刻看到记这笔账带来的价值。
所以我在这个项目里给产品定了一个核心概念:先分析,后记账。用户授权平台拉取各类账单流水之后,系统自动完成分类、归因、趋势分析,直接给出“你这个月房租占比太高了”“外卖支出环比上月增长 18%”“订阅服务里你有三个会员已经超过 90 天没打开”这类结论。用户不需要录入什么,只需要看结果、偶尔修正分类,就能获得价值。
这个思路在标题里就看得出倾向——“services”是复数,不是“tool”也不是“ledger”。也就是说,这个项目从一开始就定位成“服务”,不是一个本地小工具。既然是服务,就要持续给用户反馈、持续产生洞察,而不是等用户主动来操作。
从技术选型上看,这也决定了我要做的不是单机应用,而是一个典型的云端服务:有数据接入层、清洗层、分析层、展示层,后面还会接定时任务做多月账单归并。听起来好像有点重,但对于这种形态的产品来说是合理投入,因为核心资产就是这份持续更新的财务数据集,把数据底座打过之后,后续做任何扩展都顺手。
1.3 目标用户与核心场景拆解
明确方向之后,我进一步把用户画像收敛成两类:
第一类是“被动型用户”,月收入尚可,但从不主动记账,月底看余额才发现钱没了。这类人需要系统帮他把账自动算清楚,告诉他钱都去哪了,能做的动作也就是偶尔翻一翻报表。第二类是“关注型用户”,已经开始理财,想知道自己每月能结余多少、哪些开销能省,这类人需要趋势分析和预算工具。
针对这两类用户,我列出四个必须做透的场景:
- 账户总览:所有银行卡、信用卡、余额账户在一个页面里可见,月收支、净资产、负债一目了然。
- 分类流水:每一笔交易自动归入餐饮、交通、居住、购物、娱乐等大类,支持用户手动修正。
- 预算管理:按大类设定月度预算,系统实时追踪执行进度,超支时提前预警。
- 财务健康分:基于多个维度算出一个综合评分,用百分比的形式直观展示用户的财务状态。
这四个场景撑起了整个项目的主干。任何模块如果对这四条没有直接帮助,优先级一律靠后。事实上,我在开发过程中收到了很多二次需求,比如“做一个社区”“加一组投资课程”,但通通没有排进去,原因很简单:核心场景还不稳,加东西只会分散精力。
2. 数据结构设计:财务服务的底座
2.1 核心实体:账户、交易、类别、预算,缺一不可
想明白了产品定位之后,接下来的工作就是建模。财务类的数据结构一定要稳,因为数据是这套服务的血液。我的核心实体一共四个:账户(Account)、交易(Transaction)、分类(Category)、预算(Budget)。
账户表不能只存余额,还要存账户类型(借记卡、信用卡、虚拟钱包)、币种、所属机构、是否参与净资产计算。特别注意:信用卡是负债账户,它的余额计算方向和借记卡是反的,所以一定要有“账户方向”这个字段,否则最后算净资产的时候正负号全是乱的。
交易表是整个系统里数据量最大的表,也是最重要的一张表。我建议的字段至少包括:账户 ID、对方名称(比如“美团”“XX超市”)、交易金额(带符号)、币种、交易时间、记账时间、分类 ID、原始备注、商户类型、以及一个业务幂等键。幂等键尤其重要,它是“所属机构 + 对方名称 + 金额 + 日期 + 备注哈希”的组合,用来防止重复导入,具体内容放在后面问题排查章节里详细说。
分类表用两级结构就够了,一级大类十几个,二级细类几十个,不搞三级。分类太碎,用户看不懂,机器学习模型也学不好。我最终的分类方案里设置了 14 个大类,其中“其他”是大类之一,给那些实在分不进去的交易兜底。预算表则按“用户 ID + 分类 ID + 月份”做唯一约束,简单直接,预算周期最小到月就够了,按周和按年预算目前先不做,避免复杂度失控。
2.2 金额精度与多币种:踩过的坑要提前说
金融数据的精度问题,我觉得怎么强调都不过分。刚开始设计表结构的时候,我想都没想就用了 double 存金额,测试阶段没暴露问题,等到接真实账单数据之后,各种幺蛾子就出来了——有的交易在展示层多了 0.001 元,有的汇总报表总和怎么都对不上,后来排查发现就是浮点数计算的锅。
我的最终方案是:金额字段统一用整数类型存“分”。换句话说,用户看到的 123.45 元,在数据库里存的就是 12345 这个整数。所有计算逻辑走整数加法,需要展示的时候再除以 100 转字符串输出。这个做法不会引入精度误差,计算效率也更高。像人民币这种两位小数的币种直接适用,碰到日元这种零小数币种也没问题,转换逻辑统一处理即可。
多币种的问题更麻烦一些。我最初的设计是在交易表里存“交易原币种金额”和“钱包本位币金额”两个字段,但很快发现,如果汇率取数不及时,换算出来的数字会忽高忽低。后来我改成了更务实的方案:交易表里以原币种金额为准,本位币金额只作为展示参考,并且在本位币金额旁边记录“当时使用的汇率值”和“汇率时间戳”。这样即使汇率变动,用户也能看到为什么这笔账在两个时间的展示金额不一样。
说白了就是一套相当稳的方案。
2.3 预算与订阅管理:容易被忽视的隐性支出
预算模块我加了一个很多人忽视的细节:把“订阅服务”(会员费、软件订阅、自动扣费)单独抽出来管理。因为这个项目的数据分析发现,用户的财务问题往往不是某一笔大额消费,而是几十个小额订阅叠加之后形成的隐性流失,大约 20% 的用户每个月在各种订阅上花的钱超过 500 元,但用户自己说不清订了哪些服务。
所以我在分类体系里给订阅单独开了一类,同时开发了一个“订阅地图”页面,把用户绑定的所有周期性扣款列出来,标注每项服务的价格、扣款日期、最近一次使用时间。如果连续三个月没有使用记录,系统会提示“这份订阅可以考虑取消”。这个功能上线之后,用户活跃度比预期高了不少,说明这个需求是真实存在的。
数据模型上,订阅不是独立表,而是交易表里一类特殊交易的聚合视图。每笔交易只要命中“周期性 + 固定金额 + 同类商户”的规则,就会被系统标记为订阅,聚合后在订阅页面呈现。这样设计的好处是:不用额外维护订阅状态表,数据源的任何变更(比如用户取消订阅)都会自动反映到交易流水里,不存在数据不一致的问题。
3. 核心功能实现:从流水到财务健康分
3.1 流水接入链路:解析、清洗、打标三步走
流水数据是整个系统的燃料。我做了三条接入通道:手动导入(CSV/Excel)、API 拉取(部分开放银行接口)、邮件/短信解析(转发给专属邮箱自动归集)。三条通道最终走同一套处理管线,核心步骤是解析、清洗、打标。
解析这步水比较深,因为不同银行的 CSV 模板长得完全不一样。有的导出是“日期,商户,金额”,有的却是“交易时间,对方账户,收支类型,余额”,还有用 Excel 合并单元格的玄学格式。我的处理方式是为每个常用银行做独立解析模板,模板里只配置字段映射关系,代码层面做一个统一的解释器。新增一家银行只需要写一个新的映射配置,不影响主流程。
清洗阶段,重点处理三类脏数据:首尾空格和不可见字符、金额字段里混入的中文逗号/人民币符号、以及同一家商户在不同账单里的名称差异(比如“美团外卖”和“美团”和“美团平台商户”)。清洗规则我用一个配置表维护,启动时加载到内存,处理效率很高,上万条流水秒级完成。
打标阶段就是调用分类引擎,给每笔交易打上分类标签和置信度。分类引擎内部跑了两套逻辑:第一套是规则匹配器,优先处理那些商户名和分类映射关系明确的交易;第二套是机器学习模型,处理规则覆盖不到的长尾交易。规则优先的好处是确定性强,不会出现“麦当劳被分到服装”这种低级错误,模型只做兜底。
3.2 财务健康分的计算逻辑:五个维度怎么组合
财务健康分是这个项目最有辨识度的功能,也是很多用户打开 App 第一个看的指标。我不能在这里给一个拍脑袋的公式,因为它直接影响用户信任,所以必须让每个维度的含义都说得清楚、可解释。
最终我设计了五个维度,加权百分比合成一个 0-100 的总分:
- 结余能力(权重 30%):月度收入减去支出后的盈余比例,衡量“能不能存下钱”。
- 负债水平(权重 25%):月还款额占月收入的比例,越低越健康。
- 应急储备(权重 20%):现金储备能覆盖几个月的生活支出,建议至少在 3 个月以上。
- 消费稳定性(权重 15%):月度支出波动程度,波动越大说明财务越不稳定。
- 预算执行(权重 10%):预算内消费占比,衡量用户自身的计划执行能力。
每个维度算出原始值后,通过一个非线性映射函数换算到 0-100 分。比如结余能力,结余率为 0 时得 0 分,结余率为 30% 时得 80 分,高于 50% 封顶 100 分,中间的曲线不是直线,因为结余从负到正的边际改善比从 30% 到 40% 的改善更重要。这些映射参数不是拍脑袋写的,而是用一小批用户访谈和历史数据拟合出来的,至少保证方向上是合理的。
健康分上线后,我特意挂了埋点统计用户点击“为什么是这个分数”的频次,结果发现大家对这个解释界面特别感兴趣,说明可解释性是这类产品重要的隐性需求。如果用户只看一个神秘数字,很难产生信任感。
3.3 报表和可视化:核心页面的设计逻辑
报表模块我采用的是“三层下钻”的设计:总览页看全部摘要,点击某个分类看该分类的月度趋势,再点某个月份看该分类的明细流水。这个结构和数据分析的“聚合–下钻–明细”思维一脉相承,用户也容易理解。
具体实现上,月度趋势图我建议用柱状图展示收入和支出两个序列,而不是只画支出。因为只看支出曲线,用户会觉得“我又超支了”,但结合收入一起看,他就能理解自己是不是在靠吃老本生活。这个体验上的细节虽然小,但对用户决策很有帮助。
还有一个很容易被忽略的点:可视化组件要处理“空数据”和“极端值”两个边界。空数据的时候不能显示一条空的折线,得展示引导文案。极端值(比如某个月突然有一笔 10 万的医疗支出)会把 Y 轴拉到离谱的范围,导致其他月份的变化几乎看不见。我的处理方式是图表接口里加一个“是否剔除极端值”的参数,默认剔除,用户点开详情才能看到完整数据。
4. 常见问题与排查实录
4.1 排查案例一:重复数据导致月度统计整体失真
项目上线后收到的第一个严重反馈是:月度报表的支出总额比用户自己从银行账单里数出来的多出了几百块。排查过程花了一个下午,最后锁定的原因很典型——同一笔交易被录入了两次。
这个问题的根源在于数据接入管道。用户在银行 App 里导出 CSV 后手动上传,同一天内导了两次,上传工具没有做幂等校验,数据库里直接就插入了两笔一模一样的记录。交易金额是几百块,在几千笔流水的数据集里,肉眼几乎不可能发现。但汇总统计一跑,总数就会多出几百块,用户一眼就看出来不对。
修复方案分三步:第一,立即对已入库的交易做“全字段比对”的去重清理;第二,在上传接口增加幂等键校验,主键或唯一索引直接拦掉重复插入;第三,在数仓层再做一道按“幂等键”的聚合去重,兜住可能绕过接口的异常写入。这套方案在开发阶段就该做,我没做好,算是吃过亏。幂等键我用的是加密工具对“机构编号 + 商户名 + 金额 + 交易日期 + 备注”做哈希,实测下来精准有效。
4.2 排查案例二:分类不准,用户手动改了十几次
分类引擎有个棘手的问题:同一个商户在不同场景下消费分类差异很大。比如同一家连锁超市,买日用品是“购物”,买生鲜是“食品”,但从银行流水里看只有商户名,没有购物明细,根本没法区分。
刚开始规则匹配器把这家超市统一分到“购物”,结果很多用户反映“我伙食费怎么这么低”。后来我换了一个思路:不再强行判断每一笔交易的具体用途,而是对这种“多用途商户”做一个特殊处理——默认分到“食品”,因为生活经验里大家去超市买吃的占比更高。同时这类交易会在前端展示一条提示:“这笔交易被自动分类为食品,你可以手动改为购物”。用户手动修正的结果会汇总回流到样本集,用来迭代后面的模型,形成闭环。虽然分类不是百分之百准,但用户会觉得这个系统“听话、好改”,反而比硬着头皮强行分类的体验好。
4.3 排查案例三:汇率换算让用户对不上账
多币种功能上线后,有用户在论坛直接开帖吐槽:上个月信用卡账单显示消费 112 美元,折合人民币是 797 元,但系统里显示的是 803 元,差了 6 块钱。原因是系统在导入账单当天用实时汇率换算,而银行入账时用的是另一个时间点的结算汇率,两者天然存在差异。
这个问题的核心在于“按什么汇率入账”的一致性。我不可能拿到银行内部的结算汇率,但可以做到最基本的用户体验保障:页面上同时展示两个金额,原币种金额和本位币参考金额,在本位币金额旁边标注“参考汇率 7.17,换算时间 2025-01-15 10:23:05”。用户看到这个标注,再对比银行的账单,就能理解差异来源,不会觉得系统算错了。如果页面只给一个兑换后的数字,任何偏差都会被认为是 bug。这个交互细节很值得其他做多币种功能的团队参考。
4.4 排查案例四:报表查询越来越慢
流水量达到几百万条之后,报表接口开始出现明显延迟。第一次发现是在一个用户只有两年账单、流水大概 40 万笔的账号上,月度汇总查询竟然花了 3 秒多。检查慢查询日志,发现核心问题就在“分类汇总”这一步。我原来的 SQL 写法是在交易明细表上直接 GROUP BY 分类和月份,这样的查询在百万级数据量下效率太低。
优化方案用的是预聚合的思路:建了一张日汇总表,维度是“用户 ID + 日期 + 分类”,数值包含笔数和金额,每天凌晨用定时任务汇总前一天的数据。报表查询直接查汇总表,响应时间从 3 秒降到 200 毫秒以内。同时我保留了对细节流水按条件分页查询的独立接口,用于“点进去看明细”的场景。这是数据产品的老套路,叫“空间换时间”,但这个项目让我彻底体会到,不预聚合的数据分析平台很难支撑好用的大盘展示。
踩过排查的坑之后,我把所有问题整理成了一张速查表,贴在项目文档的最前面,团队小伙伴处理线上问题的时候能少走不少弯路:
| 问题现象 | 根因 | 快速排查方向 | 修复方案 |
|---|---|---|---|
| 月度统计金额偏大 | 重复数据入库 | 按幂等键查是否有重复记录 | 唯一索引拦截 + 清洗任务去重 |
| 分类明显错误 | 规则/模型都没覆盖 | 查看该商户最近 50 条流水的人工修正记录 | 补充规则或纳入模型样本集 |
| 多币种金额对不上 | 换算汇率时间点不一致 | 查看折算汇率标注和银行入账时间 | 页面标注换算时间与参考汇率 |
| 报表加载慢 | 明细表实时聚合开销大 | 抓慢查询SQL定位聚合语句 | 建日汇总表,查询走预聚合 |
| 订阅漏识别 | 周期性交易金额波动 | 查看同类商户历史流水一致性 | 扩宽订阅匹配规则,增加人工标记通道 |
我个人在实际操作中还有一个体会比较深:财务服务的排查和普通业务系统不太一样,它不能带着猜疑去做。因为涉及钱的数字是用户最敏感的部分,任何结论都要有数据支撑,每一步排查都要能拿出证据链。每次处理完一个问题,我都会顺手把这个问题的复现方式和排查过程整理成文档,后续再遇到类似问题直接查文档,不用重新走一遍弯路。这应该是我在这个项目里最值得分享的工作习惯。