从零开发个人记账应用:全栈技术选型与微信小程序实践
1. 为什么放着现成的记账App不用非要自己造一个说实话市面上能打的记账App其实不少随手记、鲨鱼记账、钱迹我都用过一阵子。但用得越久越觉得别扭免费版塞满了开屏广告想导出自己的账单数据得先开会员云端同步的数据到底存哪儿、会不会被拿去喂推荐算法心里完全没底。对于一个每天都要记账的重度用户来说这些点慢慢积累成了真正的痛点。后来转念一想我自己就是写代码的为什么不干脆自己写一个于是就有了这个从零开始的个人记账项目——一个网页端加微信小程序端的记账工具。做下来的体会是它不只是给自己造了个计算器更是把前端、后端、数据库、部署、小程序审核这条链路完整走了一遍收获比想象中大得多。这篇文章会把整个构建过程、技术选型思路、踩到的坑全部拆开讲。适合两类人一是想练手全栈开发、需要一个真实项目来驱动学习的朋友二是不想把个人财务数据交给第三方、想自己掌控数据的隐私敏感型用户。当然如果你只是想快速记账直接买现成App的会员可能更省事——自己造轮子的账得用时间和精力来付。1.1 我的记账需求清单动手之前我先把需求写在了纸上避免写着写着开始画蛇添足这是个人项目最常见的失控方式。整理下来核心需求其实就四条快速记账打开就能记输入金额、选分类、写备注三步以内完成一笔。月度统计每月支出、收入总额、分类占比、和上月的对比最好有图表能直观看到。多端同步电脑上摸鱼时记一笔回家路上用手机小程序再记一笔数据得是同一份。数据可控所有数据存在自己手里随时能导出、能备份、能删干净。除了这四条其他像多人共享账本、预算提醒、周期记账这些我统统没做。个人项目最忌讳的就是需求膨胀——每加一个功能后面就是成倍的数据结构改动和调试成本。比如周期记账看起来很简单但牵扯到提醒推送方式是否要生成账单草稿节假日顺延规则这些一堆细节真做起来两周打底。所以我从一开始就把边界定死只做最核心的闭环。1.2 网页端和小程序端的定位差异一个有意思的问题是既然要做两个端它们各承担什么角色我的定位是网页端是管理后台小程序端是随手记账入口。网页端屏幕大、键盘输入方便适合看报表、导数据、修改历史账单小程序端则追求极致的快打开即记用完即走不需要在小程序里看太多分析图表因为手机小屏上看柱状图本来就是受罪。这个定位直接决定了后面两端的页面设计取舍网页端重点做统计报表和账单管理小程序端重点做记账流程和体验优化而不是两个端做一模一样的镜像功能。事实证明这个决定帮我省掉了大量重复开发成本——统计页那套图表在小程序里根本不用做也顺便避开了小程序包体积过大的问题。2. 技术选型一套代码还是两套代码这是整个项目里我纠结最久的问题。要做网页和小程序摆在我面前的路其实是三条路线做法优势劣势A. 完全分离网页用 Vue/React小程序用原生各自体验最优无框架束缚两套前端代码维护成本直接翻倍B. uni-app 跨端一套 Vue 代码编译出 H5 和小程序一次开发两端跑生态成熟小程序端有兼容坑复杂交互受限制C. Taro 跨端一套 React 代码编译出多端适合 React 技术栈对框架版本要求严格周边生态比 uni-app 小我最后选了uni-app理由是我本身对 Vue 更熟而且 uni-app 从 Vue 2 到 Vue 3 的支持已经相当成熟社区里记账类、工具类小程序的开源案例很多遇到问题基本都有现成答案。如果你是 React 党Taro 也是稳妥选择没必要为了技术之争去改自己顺手的方向。跨端框架的本质是用框架的约定换开发效率选哪个不重要重要的是别在项目中途反复横跳。2.1 后端与数据库别一上来就上重武器后端这块我见过太多个人项目一上来就是 Spring Boot MySQL Redis 三件套结果部署的时候光是配环境就劝退了。记账这种低频个人应用数据量撑死几万条根本不需要分布式那套东西。我用的是Node.js Express SQLite。为什么这么选Node.js和前端同语言前后端可以共享一些校验逻辑和数据格式定义心智负担最小。Express轻量、生态成熟写几个 RESTful 接口绰绰有余。SQLite单文件数据库不需要单独装数据库服务备份就是复制一个文件对个人项目来说简直完美。如果你更熟悉 Python换成FastAPI SQLite完全没问题逻辑是一样的。核心思路是个人项目的后端能简单就简单别把生产级架构的惯性带进来。我见过有人为了记账项目上了 Kafka 消息队列理由是以后数据量大了要用——这种预判式设计在个人项目里通常只是给自己加戏。提示SQLite 有个著名的并发写入限制同一时刻只有一个写事务。但个人记账这种单用户、低频写入的场景这个限制几乎不会触发。真要多人共用再换 PostgreSQL 也不迟接口层封装好了迁移成本并不高。2.2 小程序端的特殊约束域名与 HTTPS小程序端有一个网页端不需要考虑的硬性约束小程序正式环境要求所有请求域名必须是 HTTPS并且必须在小程序管理后台配置合法域名白名单。这意味着你得有一个备案过的域名国内服务器加备案微信团队只认这个。域名必须配置有效的 SSL 证书。开发调试时可以用不校验合法域名的开关绕过但真机预览和发布就必须走正式域名。这个约束直接影响了部署方案的选择我后面会用专门一节来细讲。这里想先提醒一句很多人开发小程序时图方便把接口直接放在云开发环境里域名问题确实省了但同时也被云开发平台绑死了。我个人的取舍是宁可多花点钱和时间搞定自己的域名和服务器也不希望核心数据落在自己控制不了的平台上。3. 数据模型与接口设计把地基打牢记账应用的灵魂不在界面而在数据模型。我反复改了三版表结构才最终定下来这里直接说最终方案以及每一步背后的理由。3.1 账单表结构一张核心表transactions外加一张categories分类表。交易表设计如下CREATE TABLE transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL CHECK(type IN (expense, income, transfer)), amount_cents INTEGER NOT NULL, -- 金额单位分 category_id INTEGER NOT NULL, account_id INTEGER, -- 账户默认现金 note TEXT DEFAULT , trans_date TEXT NOT NULL, -- 记账日期格式 YYYY-MM-DD created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)), updated_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) );几个关键设计决定金额用整数分存储而不是浮点数。这是财务系统的基本常识但个人项目里太容易忽视。浮点数的 0.1 0.2 不等于 0.3二进制表示会有误差如果金额用 FLOAT 存月底对账的时候小数点后面会莫名多出一串 99999。用整数分前端展示时再除以 100彻底避开精度坑。这个坑对记账应用来说是致命的——用户的每一分钱都要准确。type字段区分支出、收入、转账。很多记账软件把收入和支出做成两个独立表查询时还要做 UNION很麻烦。用一张表加类型字段统计时一条GROUP BY type就完事。这里的转账比较特殊它本质上本金不变、位置变了所以转账记录不参与收支统计单独用接口处理。日期存YYYY-MM-DD文本而不是时间戳。记账关心的是哪天花的钱而不是具体几点几分几秒时间戳按天聚合还得做时区换算文本格式直接GROUP BY trans_date就按天分组了配合 SQLite 的日期函数非常顺手。存文本看起来不专业但在这种场景下反而是最实用的方案。3.2 分类体系预置还是自定义分类我走了预置 自定义的混合路线预置支出分类餐饮、交通、购物、居住、娱乐、医疗、教育、社交、其他一共 9 个够用且不繁琐。预置收入分类工资、奖金、理财、兼职、其他共 5 个。自定义入口用户也就是我自己可以增删改但每个分类绑定type方向收入分类不能用于支出。分类为什么不能太多这是记账坚持率的关键。心理学上有个概念叫选择过载——选项越多完成操作的心理成本越高。我统计过自己的使用情况九成以上的记账都落在三四个分类里预置 9 个支出分类已经足够覆盖日常生活。如果你给用户 30 个分类他每次记账都要花 5 秒去想要选哪个用不了两周就放弃了。3.3 API 设计清单后端接口我按 RESTful 风格拆了下面这些实际用下来刚好覆盖全部需求接口方法用途/api/transactionsGET按日期范围、分类、类型筛选账单支持分页/api/transactionsPOST新增一笔账单/api/transactions/:idPUT修改账单摘要、金额、分类/api/transactions/:idDELETE删除账单/api/categoriesGET获取全部分类/api/stats/summary?month2024-05GET月度收支汇总、分类占比、环比/api/budgetsGET/PUT读取、设置每月预算后期加的功能统计接口单独做不在前端边拉边算原因是统计逻辑分类占比、环比、日均属于后端业务前端只负责渲染。这样前端代码干净而且以后如果要加小程序之外的新端比如桌面客户端统计逻辑不用重写。我见过有人把统计全部写到前端代码里等第二端接入时才发现同样的逻辑要复制一遍改一处忘另一处非常痛苦。4. 网页端实现先让它完整跑起来网页端我用 uni-app 编译成 H5 来做的。这里有个很多人不知道的点uni-app 编译出的 H5 就是一个标准的 Vue 应用可以直接部署到任意静态服务器不要求必须使用 uni 的云服务。所以网页端本质上是在用 Vue 的思路开发只是部分 API 用了 uni 的封装。4.1 页面结构规划网页端我规划了四个页面对应底部导航的四个 Tab首页仪表盘本月总收入、总支出、结余三大数字卡片加最近 7 天支出趋势折线图。记账页金额输入框自动弹出数字键盘、分类九宫格、日期选择器、备注输入框、保存按钮。账单列表按月份切换的历史账单支持下拉刷新加载更多每条记录可点击进入编辑。统计页月度分类占比饼图、收支对比柱状图、近 12 个月趋势外加 CSV 导出按钮。四个页面看似简单但每个页面都有值得单拎出来说的细节。下面挑三个最影响体验的说。4.2 金额输入框被忽略的交互细节记账应用里最重要的交互是金额输入。直接用一个普通的input typenumber是最省事的做法但体验很差手机上弹出的是全键盘容易误触而且小数点和数字键布局混乱。我最终的做法是网页端用一个大号输入框加自定义的数字键盘数字 0-9、小数点、删除、完成。金额输入框实时格式化输入1234自动显示1,234.00但存储时只存数字字符串提交时再转成整数分。限制最多两位小数输入小数时自动处理12.这种中间态避免用户输到一半被格式校验打断。这个自定义键盘的代码量不大一个 Vue 组件但对记账体验的提升非常明显。我做了一个小实验用系统键盘输入一笔 12.5 元的记账平均要 6 秒用自定义键盘只要 2 秒多。记账这种高频动作单次省 4 秒一个月下来就是省了几分钟操作时间这种细节才是留存率的关键。4.3 图表渲染轻量方案优先图表库我对比过 ECharts 和 uCharts。ECharts 功能最全但对移动端不友好打包体积 400 多 KBuCharts 是 uni-app 生态里的图表库带lime-echart插件体量更小。实际用下来网页端H5的图表我直接用 EChartsPC 浏览器加载体积问题不大且交互能力最强缩放、tooltip、图例切换都是现成的。小程序端不设统计页面所以不涉及图表在小程序里的渲染问题——这也呼应了我在 1.2 里定下的两端定位差异决策。这个取舍其实很现实图表库的体积在小程序包体里属于实打实的负担ECharts 全量引进小程序大概要占掉 400 KB 以上的空间还会拖慢首屏加载。为了一个看起来我也都有的统计页让两个端都背上这个包袱非常不划算。功能定位的差异化最终在设计技术方案时帮了大忙。4.4 CSV 导出数据自有的最后一块拼图前面说数据可控落到功能上就是导出。我在统计页放了一个导出 CSV按钮后端接口直接把筛选出的账单流式写入 CSV 文件返回前端下载。导出的 CSV 列包含日期、类型、分类、金额元、备注。这里有一个细节——CSV 文件头要加 UTF-8 BOM否则用 Excel 打开中文会乱码。这个问题坑了我两次一次是导出记账数据一次是导出用户反馈表都是同样的原因。加 BOM 的代码只有几行但忘了它就会被用户追着问为什么你的导出文件是乱码。const BOM \uFEFF; const csvContent BOM rows.map(r r.join(,)).join(\n);5. 小程序端适配与隐藏的坑小程序端的页面我砍到三个 Tab记账、账单、我的。网页端最重的统计页在小程序里被简化成月度小结卡片放在我的页面顶部。实际开发中小程序和 H5 的同源逻辑很好复用uni-app 的编译机制但有几个坑是纯小程序场景才有的。5.1 登录态的坑别学大型应用的强制登录个人记账工具最让用户反感的就是一上来就要手机号授权登录。我自己做的小程序一开始也天真地想做个完整的注册登录体系后来想通了记账数据是低频、偏向私人的东西没必要做账号密码体系。记账的爽点在于开箱即记任何一步多余的身份验证都在劝退用户。小程序支持静默登录通过uni.login拿到 code后端用 code 换 openid把 openid 当成唯一用户标识自动创建一个游客账本。如果用户真的需要多设备同步再加一个简单的绑定邮箱 设置同步密码的引导而不是强制。实际效果用户打开小程序 0 门槛进入记账界面同步功能按需启用。这个决策让留存体验好了非常多——对个人工具类小程序来说降低首屏操作成本比完整账号体系重要十倍。别被必须做登录的惯性思维绑架先想清楚你的产品到底需不需要它。5.2 日期组件的差异H5 和微信小程序行为不同uni-app 的picker组件在 H5 和微信小程序上的行为有差异。在 H5 上picker modedate实际渲染的是浏览器的原生日期选择器样式完全不可控且每款浏览器长得都不一样在小程序里则是微信自带的滚动选择器样式统一、交互符合手机用户习惯。我最初为了统一代码在两端都用了picker。结果网页端在 Chrome 上弹的是日历弹窗在 Safari 上弹的是转轮体验割裂。最终的处理方案小程序端保留picker组件微信原生日期选择器体验足够好。H5 端改用自己封装的弹层日期选择器组件基于一个轻量的日期面板库确保各浏览器表现一致。这也算是我这次实践里最典型的一套代码两端体验矛盾案例。跨端框架能帮你编译帮不了你的体验一致性关键交互场景还是得做平台差异适配越早意识到这一点越好。5.3 小程序包体与静态资源微信小程序主包限制是 2MB不含分包这在实际开发中比你想象的更容易撞线。我一开始把分类图标、字体文件、图表库一股脑塞进主包结果编译出来 2.4MB直接超限。我的瘦身过程图标不用图片改用iconfont字体文件并在 App.vue 里按需引入节省几百 KB。统计图表依赖库移到分包里——反正统计页面不在主包 Tab 上可以合理分包。所有图片资源压缩能用 CSS 绘制的装饰元素绝不用图片。做完这三步主包从 2.4MB 降到 1.4MB加载速度也明显提升。这里建议所有做小程序的朋友开发第一天就查一下打包体积别等到审核或真机预览时才发现超了返工成本非常高。微信开发者工具的详情 → 基本信息里能看到主包和分包大小编译后也有提示养成随时看一眼的习惯。5.4 关于反编译小程序一个必须泼的冷水搜索微信小程序相关热词时我发现很多人对反编译小程序抓包小程序这类技术非常感兴趣。我在这里多说一句不要对别人的商用小程序做逆向分析和抓包。从技术上小程序代码确实存在被反编译的可能性抓包也相对容易很多老版本小程序甚至没做证书校验但这不等于你有权利这么做。从合规上未经许可破解、抓取他人应用的数据流量可能涉及违反相关法律法规个人开发者尤其要注意边界。这类操作不仅可能惹上官司在一些场景下还涉及数据安全和个人信息保护问题。对学习技术的人来说更健康的路径是去 GitHub 上找开源小程序项目阅读源码、跑通 demo或者像我一样从零写一个小程序。逆向别人的作品既难学到系统的架构思维又有法律风险得不偿失。我在开发过程中的全部调试用的都是自己小程序的开发者工具自带的网络面板和模拟器完全不需要也不应该去碰他人的流量。这一点务必记住。6. 部署上线从本地到公网的全流程小程序要发布必须先有正式的 HTTPS 接口域名。所以部署这一步其实是整个链路里最绕不开的关卡我单独拎出来重点讲。6.1 部署方案对比方案适合场景成本缺点腾讯云开发 CloudBase小程序 云函数 云数据库一体化有免费额度超出后按量付费平台绑定后续迁移成本高云服务器 Nginx Node.js需要灵活控制的完整环境低配服务器约 60-100 元/月要自己维护环境、证书、进程守护静态托管Vercel/GitHub Pages只有 H5 网页免费不能用于小程序接口家庭服务器 内网穿透极客玩家硬件成本稳定性差不推荐承载个人数据我最终选了**云服务器 Nginx Node.jsPM2 守护进程**的方案。原因很直接我想完整掌握部署链路包括 Nginx 反代、HTTPS 证书配置、进程守护这些基本功这些在云开发里全是黑盒学不到东西。如果只是想快速跑起来CloudBase 确实最省心往编辑器里点几下就部署完成免费额度对个人记账也完全够用。6.2 服务器配置的关键步骤这里不贴完整教程网上一搜一大把只说我踩过、后来发现极容易忽略的三个点第一务必配置 PM2 进程守护。Node.js 应用裸跑在服务器上一旦进程崩溃整个接口就断了。PM2 一键守护崩溃自动重启还能看日志是个人项目的最低成本保障。我见过很多人部署完 Node 接口没挂 PM2半夜内存溢出之后整个小程序瘫痪第二天才被人提醒。第二Nginx 反代时要注意超时配置。如果前端用了轮询或者想做实时刷新Nginx 默认的 60 秒超时会造成接口偶尔变慢的假象。我在location /api/里显式配置了proxy_read_timeout 120s;问题立刻消失。排查这个问题的过程很典型接口直接访问正常走 Nginx 反代就超时最后定位到是代理层超时设置。第三HTTPS 证书用 Lets Encrypt 的免费证书加 certbot 自动续期。很多人以为 HTTPS 一定要买付费证书其实个人项目完全可以用免费证书每月自动续期一条 cron 就能搞定。我见过有人嫌配置麻烦直接用 HTTP结果小程序根本发布不了——微信强制要求 HTTPS这条绕不过去。6.3 域名与备案别忽视时间成本小程序正式环境的请求域名必须 HTTPS而在国内服务器上部署 HTTPS 又绕不开 ICP 备案。备案的坑是它不是即时生效的整个流程顺利的话也要 1-2 周中途还可能因为资料不齐全被驳回重提。所以如果你打算做小程序备案这件事要最先启动和开发并行进行。我就是先买了服务器、提交了备案然后才开始写接口代码等代码写完备案也下来了无缝衔接。反过来如果代码写好了再备案干等两周项目热情基本被磨没了。身边有朋友吃过这个亏开发完兴致勃勃要发布结果发现备案还没下来足足等了两周才上线。小程序管理后台配置合法域名时只需要在开发管理 → 开发设置 → 服务器域名里把 HTTPS 接口域名填进去。改完配置大概等几分钟到几十分钟生效不用重新上传代码。这里有个细节如果你后续要加文件下载或上传功能还得单独配 downloadFile 和 uploadFile 域名域名数量也不是无限的规划时要留出余量。7. 实测与体验一个月的真实使用记录部署上线后我自己连续用了一个月每天记账 3-5 笔。这个阶段暴露的问题比开发阶段更多也更有价值——真实使用数据是最好的产品经理。7.1 统计数据暴露的问题一个月后我第一次认真看统计页发现几件反直觉的事其他分类占比高达 18%。预置分类虽有 9 个但很多小额支出买瓶水、停车费、骑共享单车记的时候图省事全选了其他。这说明分类设计还是不够贴近实际使用习惯。我后来把其他拆成了日用杂项和其他日常小额支出的归类意愿明显提升。网页端访问量极低小程序端占了 95% 以上。这验证了随手记账场景天然属于移动端网页端更多承担的是每周一次的对账管理角色和我当初对两端的定位判断基本一致。这也说明做多端产品前先想清楚每端的主要使用场景有多重要。月度环比意义不大周环比更有参考价值。工资日前后收入异常波动看月度环比反而掩盖了真实消费节奏。我后来加了自定义对比周期功能比如对比上周和本周的开销比看这个月比上个月多花多少更能反映真实变化。7.2 数据备份个人项目最容易忽视的环节数据存在自己手里是好事但也意味着备份责任全在自己。我习惯每周五晚上跑一条备份命令直接把 SQLite 数据库文件连同journal和wal文件打包上传到对象存储sqlite3 ~/bookkeep.db .backup /tmp/bookkeep-$(date %F).db zip -r /tmp/backup.zip /tmp/bookkeep-$(date %F).db ossutil cp /tmp/backup.zip oss://my-private-bucket/bookkeep/SQLite 官方提供.backup命令可以在数据库运行中做一致性备份比直接复制文件更安全。为什么连journal和wal文件一起备份因为 SQLite 的预写日志里可能有未合并的数据漏掉它们可能在极端情况下造成备份恢复后少了最近几条记录。这里给所有自托管数据的人一个忠告备份要自动化别依赖我周五手动记得备份。人是最不可靠的定时器我给自己配置的是 cron 定时任务加成功通知到微信做到了备份失败我也知道。数据丢失这种事一次就够你后悔半年。8. 后续迭代方向与个人体会到这里从零构建记账网页加小程序的核心流程已经完整走了一遍。最后说几个我接下来想做的事以及这趟折腾下来最深的几点体会。8.1 值得做的后续功能账单导入支付宝和微信支付都支持导出 Excel/CSV 格式的交易流水做一个解析器把历史账单导入进来能把记账的冷启动成本降到最低。不然新用户第一个月要补录大几十条记录留存率很难看。我下一个版本就会做这个功能优先级排在所有新功能最前面。预算预警每月设置分类预算进度超过 80% 时在小程序端弹一条轻提醒。提醒的文案要克制天天弹只会让人忽略。家庭共享账本如果要做多人共享就需要引入权限体系、邀请机制和操作日志这会让架构上一个台阶适合作为下一个阶段练手的目标。8.2 踩坑后的三条经验第一个人项目也要先写数据模型再写界面。我一开始跳过了数据设计直接画页面结果记账页面做完才发现分类和交易的关系没定清楚返工改了两版表结构。界面是表结构的下游产物地基没打好上面的楼必然歪。哪怕只有你自己用表结构的合理性也会直接影响你后续加功能的心情。第二跨端框架的一次编写到处运行是理想不是现实。uni-app 确实帮我把共享业务逻辑API 请求、数据格式化、状态管理复用了 80%但剩下的 20% 平台差异日期选择器、键盘、分享、支付回调才是决定体验高低的关键。越早做平台差异适配越优雅——拖到后期再回头补适配复杂度会成倍上升。第三功能边界比功能数量重要。记账工具做得再花哨用户每天打开它的时间也不超过 30 秒。与其堆功能不如把一个快速记账的体验做到极致。我当初很兴奋地做了账单 AI 分析功能后来发现又慢又消耗算力准确率也拼不过现有统计图表最后忍痛砍掉了。用户要的从来不是更多功能而是更短的操作路径。这趟从零开始的项目最值钱的部分不是那个能用的记账工具而是过程中对数据模型、前后端协作、部署链路、小程序生态边界这些事的完整认知。如果你也打算拿一个真实需求练手我的建议是选一个你自己真会天天用的东西来做然后尽快上线让真实使用数据来告诉你下一步该改进什么。自己做的工具哪怕丑、哪怕糙每天打开它记一笔账的时候那种这是我自己造的的踏实感是任何现成 App 都给不了的。