1. 为什么选记账系统当毕设:题目价值与需求边界
1.1 一个“小而不单薄”的业务闭环
这几年陆续接过不少毕设指导的活儿,被问得最多的一个问题是:能不能帮我看下这个题目可不可以做?我的判断标准一直很固定——题目能不能在一两个月内靠一个人独立跑完,技术上能不能把设计理由说清楚。基于Spring Boot的个人记账系统,是我很愿意推荐的备选之一。它听起来不大,但涵盖了用户鉴权、收支记录、分类管理、统计报表、预算提醒这些Web开发的基础功,恰好能把Spring Boot的核心能力展示完整,又不会把新手拖进微服务、消息队列这些深水区。
很多同学会问:记账系统不是早被人做烂了吗?这个问题要看站在什么角度。站在商业产品角度,它确实不新鲜;站在毕业设计角度,评判标准从来不是“有没有人做过”,而是“你能不能把完整的技术链路走通,并且解释清楚每一步为什么这么设计”。记账系统天然是个闭环:用户产生收支行为,系统记录流水,再把流水加工成统计图表,最后用预算控制反哺用户行为。这个闭环覆盖了前端交互、后端接口、数据库建模、复杂查询、权限控制,足够把相关技术点串起来。相比图书管理系统只做增删改查,记账系统多了一个“统计”维度,论文里可以写的东西立刻多了一倍;相比商城系统动辄涉及订单状态机、库存扣减和并发问题,它的业务复杂度对新手又友好得多。
1.2 把“记账”需求拆成可落地的功能列表
需求分析是论文第一章的重头戏,也是后面开发工作的底盘。这里建议把它拆成两个角色、六大功能模块。两个角色是普通用户和管理员,毕设里的管理员往往只承担分类数据兜底维护,核心都在用户侧。
六大功能模块先列个清单:
- 注册登录与密码修改:保证每个用户只能看到自己的数据。
- 账户管理:钱包、银行卡、现金等记账主体的余额维护。
- 账单流水管理:支持新增、编辑、删除、按时间范围和分类查询。
- 收支分类管理:收入类、支出类分开维护,支持二级分类嵌套。
- 统计报表:月度收支汇总、分类占比、月度趋势图。
- 预算管理:按月给支出分类设置限额,超支后要有提醒。
把上面列完,初版需求文档基本就成型了。每个模块再往下写用例,比如“新增账单”的用例要写清楚主流程、异常流程和边界条件。这里特别强调边界条件,是因为它既是论文素材,也是答辩时被提问的重灾区:金额为负数怎么办,分类不存在怎么办,账户余额不够支付怎么办,删除已关联数据的分类怎么办——提前把这些写进需求分析,后面写代码和测试都有据可依。
2. 技术栈与分层架构:不炫技但很稳妥的选型
2.1 Spring Boot版本和JDK怎么搭
技术选型的第一原则是稳。基础组合建议用Spring Boot 2.7.x加JDK 8或JDK 17。原因很现实:学校实验室和老教程大多围绕这套版本,遇到问题搜得到答案,第三方依赖很少踩版本坑。Spring Boot 3.x也不是不能用,但它要求JDK 17以上,一些旧版依赖需要同步升级,对时间紧的毕设来说容易处处碰壁。
如果学校明确要求高版本,就选Spring Boot 3.2.x加JDK 17,注意把MyBatis-Plus、数据库驱动这些依赖的版本对齐到官方兼容列表,不要图省事直接在网上复制老代码。我的习惯是先把依赖清单锁死,再写业务代码,避免“写一半发现依赖冲突只好返工”的局面。项目里尽量用Maven或Gradle管理依赖,把版本号放在配置文件的统一属性里,升级时可以一次调整到位。
2.2 持久层选型:MyBatis-Plus是我推荐的默认选项
持久层在MyBatis-Plus和Spring Data JPA之间摇摆的情况最多。我个人的偏好很明确:毕设默认用MyBatis-Plus。理由有三个:第一是条件构造器对动态SQL的支持非常直观,按用户名、日期、分类筛选不需要手拼一长串XML;第二是分页插件开箱即用,统计列表的分页不用自己造轮子;第三是代码生成器可以连同实体、Mapper、Service一键生成,省出来的时间可以留给调试和文档。
| 对比项 | MyBatis-Plus | Spring Data JPA |
|---|---|---|
| 动态查询 | 条件构造器,上手快 | 需要Specification,复杂 |
| 分页插件 | 内置分页拦截器 | 可用Pageable |
| 聚合统计 | SQL逻辑可控 | JPQL/原生SQL,门槛略高 |
| 新手友好度 | 高 | 中 |
JPA也不是不行,它的Hibernate自动建表在原型阶段确实省事,但遇到聚合统计查询,还是得写JPQL或者原生SQL,对没接触过ORM的新手来说,反而比直接写SQL更难排查。选型要在论文里写清楚理由,不要只写“用了某某框架”,要写“因为动态查询多、分页频繁,选择MyBatis-Plus,它在XX方面更贴合本项目需求”。
2.3 前端方案:前后端分离是答辩的加分项
前端有两条路线。一条是传统的Thymeleaf加Bootstrap,项目打包成一个可运行的工程,部署简单,但前后端代码揉在一起,答辩时不太好展示接口设计。另一条是前后端分离,Vue3加Element Plus加Axios加Vite,后端只做REST接口,前端单独一套工程。我更推荐后者,因为分离架构正好可以多讲一个跨域问题怎么解决,而且Element Plus的表格和图表在视觉上比Bootstrap模板专业得多。
需要提前处理的是跨域。后端写一个CORS配置类允许前端地址访问,或者在Vite里配置代理转发,二选一即可。两个都配也没问题,但答辩时要能说清楚哪个是开发环境用的、哪个是生产环境用的。另外建议统一接口返回结构,比如统一为“code、message、data”三层结构,前端拦截器根据code判断成功失败。这一步虽小,却能减少大量重复代码,也让接口风格显得专业。
3. 数据库是第一优先级:表结构、索引与数据流向
3.1 六张核心表的模型关系
建表前先把数据流向画清楚。用户登录后,在某个账户下记录一条收支流水,流水挂在某个分类下面,同一分类当月可以设置预算,需要更细划分还可以加标签。最少六张表:用户表、账户表、分类表、流水表、预算表、标签表。
流水表显然是最核心的一张表,字段参考如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 用户ID,冗余便于统计 |
| account_id | bigint | 账户ID |
| category_id | bigint | 分类ID |
| type | tinyint | 1收入,0支出 |
| amount | decimal(10,2) | 金额 |
| record_date | datetime | 记账日期 |
| remark | varchar(255) | 备注 |
| deleted | tinyint | 软删除标记 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
这里冗余了user_id,看起来有点重复,但几乎所有核心查询都以用户为第一条件,加上它之后索引和过滤都方便得多。账户表记录账户名称、类型、当前余额、初始余额。分类表记录父分类id、名称、类型、排序号。预算表记录用户id、分类id、月份、预算金额,建议用“年+月”字符串做唯一约束,防止同一个月同一个分类出现两条预算。
3.2 金额字段必须用Decimal,别用Double
记账系统里最容易让老师抓到把柄的地方,就是金额字段的类型。如果用了double或者float,累计几次小数运算就会出现0.1加0.2不等于0.3这类尴尬问题。编程里这叫浮点数精度损耗,日常买菜按分计价,任何一位都不能差。所以金额字段统一用decimal(10,2),Java侧对应BigDecimal,而不是double或者float。
还有两个块容易被忽略:软删除和审计字段。流水删除建议做逻辑删除,用一个deleted字段标记,保留历史数据;create_time和update_time在插入和更新时自动填充,MyBatis-Plus里可以用字段填充注解实现。这样设计后,就算做错一次修改还有数据可追,答辩时也能讲出这套设计的道理。
3.3 统计查询需要的索引设计
索引不是越多越好,多了拖慢写入,少了统计慢到没法演示。以流水表为例,最核心的查询是“某个用户在某个时间段内的收支汇总”,所以建议建组合索引“用户id加记账日期加类型”。分类筛选是另一个高频场景,但数据量不大,单列索引即可。预算表按“用户id加月份”定位,也需要一个组合索引。其他字段不要乱加索引,否则插入数据时索引维护开销反而成为负担。
演示数据量到几千条时,索引效果就已经能看出来;将来真跑生产,这个设计也扛得住百万级以内的读写。如果数据量再大,那要考虑的就不是索引而是分表了,毕设阶段不用想那么远,但论文里可以提一句“落大数据量场景的进一步优化方向”。
4. 核心模块的实现逻辑:记账、统计与预算提醒
4.1 新增账单的完整链路:参数校验、事务与账户余额联动
新增账单是写入频率最高的操作,最容易出问题。完整的处理顺序是:参数校验,检查金额是否大于0、分类是否属于当前用户、账户是否存在;装配数据,确定收入还是支出;更新账户余额,支出扣减、收入累加;插入流水记录;统一提交事务。只要中间任何一步失败,整个操作都要回滚,否则会出现账上余额改了但流水没记,或者流水记了但余额没动的数据不一致问题。
具体实现上,Service方法加上@Transactional注解,余额更新用原子更新操作,而不是“先查后改”。所谓原子更新,就是直接在SQL里写update acc_account set balance = balance - #{amount} where id = #{accountId},而不是先查一次余额到Java里算好再更新回去。后者在高并发场景有覆盖更新的风险,虽然毕设并发量小,但这种写法体现出来的意识,答辩时很加分。
4.2 月度统计:SQL聚合和代码组装的取舍
月度统计报表是整个系统最值得花时间的模块,也是论文里能写出深度的部分。两种做法:一种是在数据库端用SQL聚合,按日期或分类分组,对金额求和;另一种是把当月流水全部查出来,在Java内存里循环分类求和。
我的建议是,固定格式的月度汇总和分类占比用SQL,因为一个select就能返回需要的数据,性能好,也容易解释清楚。SQL写出来类似select category_id, type, sum(amount) from biz_record where user_id = ? and record_date between ? and ? group by category_id, type。图表需要的分类名称,再用一次查询把分类字典组装进去,或者用连表查询直接带出来。
内存计算不是不能用,在小数据量下甚至更灵活,新增一种统计维度不用改SQL。但答辩时老师很容易追问一句“数据量大了怎么办”,与其到时候圆场,不如一开始就用SQL聚合,把JOIN、GROUP BY、SUM这些数据库基本功亮出来。趋势图的数据也可以用SQL按天分组,返回日期和金额两个字段,前端拿到后直接描点连线。
4.3 预算超支提醒:两种实现方式分别什么时候用
预算提醒有两条路。一是实时联动,用户在新增流水后,立刻查询当月该分类下的累计支出,再和预算比较,超了就返回一个提醒字段或者弹出提示。这种方式直观,演示效果好,也不需要额外的调度任务,毕设阶段推荐这种。二是定时扫描,用Spring的@Scheduled注解每天或每小时扫一遍预算表,把超支记录写进提醒表。优点是可以通过页面展示历史提醒记录,但代码量会多一个调度器和提醒表。
做的时候注意一个边界:累计支出是否应该包含被删除的流水。如果做了软删除,SQL里要记得过滤deleted标记;如果新增时直接把用户当月分类支出余额缓存起来,删除流水后还要同步更新缓存,这里非常容易漏。我的习惯是先统一过滤条件,再处理余额联动逻辑,避免“统计和流水对不上”的尴尬。
5. 从启动到答辩:部署调试与演示环境的准备
5.1 三类启动失败和它们的排查顺序
项目写完后最常翻车的就是环境。按我遇到过的频率排序:数据库连不上、端口被占、跨域失败。
数据库连不上的表现是启动日志刷出一串通信链路异常。排查顺序固定为:数据库服务是否启动;账号密码是否正确;连接地址里的库名是否存在;时区是否导致连接拒绝或日期错位。JDBC连接串里加上serverTimezone=Asia/Shanghai和useSSL=false能解决大部分本地问题。驱动版本和数据库版本差距太大也会报错,把依赖版本调到对应兼容档即可。
端口被占是另一种常见问题,Spring Boot默认端口8080,本机有时候被其他程序占着。Windows可以用netstat -ano找占用进程,Linux用lsof -i:8080,找到进程后杀进程,或者改配置里的server.port,二选一。前后端分离项目还要检查前端访问后端时地址和端口是否写对,别前端配的是localhost,后端实际监听在局域网IP上,这样请求会直接失败。
5.2 预置演示数据:图表好不好看,全看这步
答辩现场临时录入数据很危险,一是手速不可控,二是录入的数据无法覆盖“支出多、收入少、某分类超支”这些演示需要的典型场景。正确做法是写一个演示数据脚本,生成至少三个月的模拟流水,每天两三条,包含工资收入、餐饮、交通、购物、娱乐等分类,并保证某个月某个分类刚好超预算,这样预算提醒和图表都有素材。
脚本里的数据不要一个模式复制到底,要带点起伏,比如周末支出偏高、月末预算逼近上限。这样月度趋势图看起来才有真实感,而不是一条直线。演示数据单独放在一个初始化脚本里,和正式建表脚本分开。开发环境可以反复执行,答辩前重置数据库再跑一遍,确保效果稳定。
5.3 代码和文档怎么配合:边开发边留素材
论文不要在写完代码后再硬凑,建议边开发边截图。需求分析里的用例图、功能结构图,数据库设计的ER图,系统实现里的界面截图和核心代码,测试章节里的测试用例表和运行结果截图,这些素材积累起来后,写作就是填空。界面截图要成对贴:填写前、保存后、列表刷新后,最好还要有边界值测试,比如金额填负数被拦截的提示,这样测试章节才有说服力。
答辩演示建议写成三五分钟的脚本:登录,看首页统计概览;新增一笔收入,验证账户余额增加;新增一笔支出,验证图表变化;进入预算页,确认超支提醒被触发;最后演示一次删除和软删除的效果。演示脚本里每一步都要写清楚“我的操作为什么能证明功能正确”,而不是单纯点点点。
6. 答辩前夜的检查清单:踩过坑后总结的几个动作
6.1 把数据库、后端、前端的启动顺序固化成文档
答辩前一周,我会让学生反复练习冷启动:从关机状态,按三步启动数据库、后端、前端。每一步的启动命令、端口、验证方法都写进一张操作卡。很多人答辩现场慌,问题就出在只练业务功能没练环境启动,换一台电脑就不知道该点什么。固化启动顺序还有个好处,就是文档里能贴一张“环境启动说明”,这也是不少评审老师会翻的部分。
6.2 提前准备两套演示数据
我建议准备两套演示数据。一套是完整版,三个月流水加预算超支记录,用于正常功能演示;一套是干净版,只有初始分类和基础账户,用于展示“用户从零开始记账”的流程。答辩时如果时间紧张,可以先用干净版演示新增,再切到完整版看统计效果。两套数据都放到脚本里,切换用一条命令完成,比现场手工操作可靠得多。
6.3 最后再分享一个我自己检查代码的习惯
每次交项目前,我会把所有测试用的输出、注释掉的调试代码、临时代码清理一遍,然后把统一返回结构里的消息文案全部改成用户能看懂的说法。这些小地方不会写在功能清单里,但评审老师随便点开一个接口,看到返回消息还是调试时随手写的“test123”,印象分会差很多。项目做到最后拼的就是细节,尤其是可以当场演示、当场翻代码的场景,整洁的代码和清晰的注释比功能数量更能说明你真正理解了整个系统。