家庭理财记账系统:Spring Boot 3 + Vue 3 数据统计与可视化
2026/9/7 3:37:14 网站建设 项目流程

最近又在帮人看毕设选题了,其中出现频率特别高的一个组合是:Spring Boot 3 + Vue 3 做一个家庭理财记账系统。说实话,这个题目第一眼看上去很常规,无非就是记账、分类、统计、图表、登录注册这几件事。真正动手做的时候你会发现,这个项目麻烦的地方根本不在增删改查,而是在数据统计口径、可视化图表的字段对齐,以及前后端联调时那些说不清道不明的小问题。

这篇文章我会按一个真实课设/毕设项目推进的顺序来写,从需求判断、技术选型、表结构设计、接口设计、ECharts 可视化,一直写到联调阶段的常见坑和排查路径。整个过程里,我会把“为什么要这样设计”和“实际落地时通常会遇到什么”放在比“怎么敲代码”更重要的位置。很多同学做完这个项目,数据库表建了,接口也写了,图表也出来了,但被老师一问“这个统计口径是什么”“这个月结余怎么算的”就答不上来——这说明项目只是跑起来了,并没有真正理解。

1. 先搞清楚这套系统真正要解决的痛点是什么

家庭理财记账系统这个题目,表面上是“管钱”,本质上是“管数据的沉淀和再消费”。如果只做一个记账本,每天记一笔收入和支出,那这个系统一点竞争力都没有,和 Excel 表格没有任何区别。它真正有价值的部分在于:把一笔一笔的流水数据,转换成家庭成员能看懂的财务状况结论。

1.1 记账只是入口,统计和可视化才是价值

你可以先问自己一个问题:用户为什么要记账?不只是“记录一下”,而是想知道钱花到哪里去了、每个月能不能存下钱、哪一类开支超出了预算。这就决定了系统的核心模块不是“新增一笔账单”这个表单页面,而是统计分析。

所以在设计阶段,就要把系统分成三层:

  • 数据层:负责把流水、分类、用户、预算这些基础数据存下来。
  • 服务层:负责算清楚总收入、总支出、结余、分类占比、月度趋势这些统计口径。
  • 展示层:负责用 ECharts 把统计结果变成饼图、折线图、柱状图。

很多同学一上来就怼着页面写,结果项目做完,发现统计接口返回的数据 ECharts 根本没法直接用,又要回头改接口。这是最常见的返工原因。

1.2 个人记账、家庭记账和财务记账的差异

这个系统叫“家庭理财记账系统”,和“个人记账”的区别在于:家庭记账至少要考虑多成员、多账户、共享分类。也就是说,数据模型不能只为一个用户服务。

通常会这样设计:

  • 用户表:家庭成员账号。
  • 账本表:一个家庭可以有多个账本,比如“日常开销”“旅行专款”。
  • 流水表:每笔账单归属某个账本、某个分类、某个记账人。
  • 分类表:收入和支出分类,例如餐饮、交通、工资、理财收益。

如果嫌表太多,可以把账本表去掉,一个家庭一个账本,但至少要保留用户和账单之间的关联关系。否则“家庭”这两个字体现不出来。

注意:如果你的项目定位是单人记账,就不要强行加多成员逻辑,先把单人流程做完整,比半吊子的多成员方案好讲得多。毕设答辩时,一个能完整演示的单账本流程,强过一个只能新增不能统计的多账本框架。

2. Spring Boot 3 + Vue 3 这套组合,为什么适合做完整项目

技术选型是开题报告里一定会写的部分。很多同学选 Spring Boot 3 + Vue 3,理由是“新”。但你要能讲清楚它新在哪里,以及它带来的代价是什么。

2.1 Spring Boot 3 的核心变化不只是版本号

Spring Boot 3 是一个比较大的版本跳跃,原因是它基于 Spring Framework 6,并且把 Java 基线升到了 Java 17。这意味着:

  • 本地 JDK 必须是 17 或以上,否则项目都启动不了。
  • 很多老版本的第三方依赖需要升级,尤其是 mybatis-plus、druid 这类常用库,要选支持 Spring Boot 3 的版本。
  • 如果之前习惯用 Java 8 写代码,现在可以开始用recordvarswitch表达式这些新语法。

对课设来说,Spring Boot 3 最大的好处是项目结构清晰:controller 负责接收请求,service 负责业务逻辑,mapper 负责数据库访问,配置集中在application.yml里。这种分层很符合答辩时“讲架构”的需求。

一个典型的最小依赖集合大概是:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency>

具体版本号我这里不写死,因为你的本地环境和选用的模块可能与我不一样。落地前先确认一件关键的事:所有依赖是否都支持 Spring Boot 3。最容易出问题的就是数据库连接池和分页插件。

2.2 Vue 3 + Vite + ECharts:前端技术栈的关键权衡

Vue 3 带来了 Composition API、<script setup>语法和更灵活的响应式系统。对做课设的同学来说,<script setup>最大的体验提升是代码量明显变少,逻辑也更集中。

至于要不要上 TypeScript,我的建议是:如果你对 TS 不熟,就别硬上。真的,课设阶段用 JavaScript 把功能做完,比用 TS 写了一堆类型报错然后卡住更实际。但如果你的项目定位是给自己的简历加分,那就用 TS,并且把类型定义写得清楚一些,这是面试时能聊的点。

前端项目的常规结构:

src/ api/ // axios 请求封装 views/ // 页面,比如登录、账单列表、统计报表 components/ // 复用组件,比如账单表单、图表卡片 router/ // 路由配置 store/ // 状态管理,选 Pinia

ECharts 在 Vue 3 项目里的使用方式,和 Vue 2 时代不太一样。现在通常不会用vue-echarts封装组件,而是直接在组件里引入 ECharts,然后通过ref拿到 DOM 容器,在onMounted里初始化图表实例。原因很简单:ECharts 本身是一个独立的图表库,它和 Vue 没有耦合,直接使用反而更可控。

3. 从表结构到统计接口:记账系统的核心是口径问题

这里我想重点展开讲一下。很多人设计表结构的时候只想着“能存数据”,没有想“数据取出来之后要怎么算”。这导致后面统计接口写得特别别扭。

3.1 一套常见且够用的表结构设计

以单账本、多用户、多分类的方式为例,最少需要四张表:

用户表(sys_user)

字段说明
id主键
username登录名
password加密后的密码
nickname昵称

分类表(category)

字段说明
id主键
name分类名称,如餐饮、交通
type类型,0 支出、1 收入
user_id所属用户,空表示系统默认分类

账单流水表(bill)

字段说明
id主键
user_id记账人
category_id关联分类
amount金额
bill_type0 支出、1 收入
record_date记账日期
remark备注

预算表(budget)

字段说明
id主键
user_id用户
category_id分类
month预算月份,如 2025-06
amount预算金额

这套表结构的核心设计点在于:分类和账单分离。不要在每个账单里直接存一个字符串分类名,因为这样统计分类占比时,你会被字符串匹配玩死。分类独立成表,账单只存分类 id,统计时再关联查出分类名称。

金额字段类型怎么选?如果只是课设,用DecimalBigDecimal。如果你用 MySQL,字段类型建议decimal(10,2),不要用double。因为浮点数在累计求和时会出精度问题,比如 0.1 加 0.2 变成 0.30000000000000004。这种问题在演示统计报表时被老师发现,是非常尴尬的。

3.2 统计接口设计的三个关键点

设计统计接口时,最怕的不是不会写 SQL,而是统计口径不一致。我见过很多项目,首页的总支出显示的是一个数字,折线图里月度支出加起来又是另一个数字,饼图里的分类占比加起来不等于 100%。这就是口径没有统一。

建议统一用三个统计接口覆盖所有展示需求:

第一个:按月收支统计接口

返回月份、总收入、总支出。这个接口用于折线图和柱状图。

第二个:按分类支出统计接口

返回分类名称、分类支出总额。这个接口用于饼图。

第三个:月度汇总接口

返回指定月份的总收入、总支出、结余、预算使用率。

这三个接口在 service 层要使用同一个查询逻辑,比如都从bill表按record_datebill_type过滤。不要一个接口自己写 SQL,另一个接口用 Java 内存计算,口径很容易跑偏。

一个需要注意的点是:收支不要用正负号区分。很多新手会把支出记为负数,收入记为正数,然后统计时直接SUM(amount)。这样做的确能做出一张总余额,但会导致分类统计很麻烦:支出分类里出现负数,饼图没法画。更合理的方案是:bill_type字段单独表示收支类型,amount字段永远存正数,统计时用SUM(CASE WHEN bill_type = 0 THEN amount ELSE 0 END)这种方式分别汇总。

4. ECharts 可视化的本质是让数据“被看见”

ECharts 是这个项目的重点加分项,也是答辩时最好展示的部分。但这里有一个普遍的误区:把 ECharts 当成一个“画图插件”,文档翻到哪个配置就复制哪个配置,完全没有思考图表和数据之间的关系。

4.1 三种图表分别解决什么问题

家庭记账系统里,最常用的是三种图:

折线图:展示最近 6 个月或 12 个月的收支趋势。折线图的 X 轴是月份,Y 轴是金额,两条折线分别对应收入和支出。它能直观回答“这个月比上个月花得多还是少”。

饼图:展示当月支出分类占比。饼图的 data 需要{ name: '餐饮', value: 1234.56 }这种格式。它回答“钱主要花在了哪里”。

柱状图:展示每月支出或分类金额对比。柱状图能同时展示类目和数值,适合做分类对比。

这三种图表的共同点是:它们的 input 都是统计接口返回的数据。所以正确的工作流是:

  1. 先设计接口返回结构。
  2. 在接口里把数据整理成 ECharts 可以直接使用的格式。
  3. 前端拿到数据后直接塞给图表配置。

4.2 ECharts 在前端的最小接入流程

在 Vue 3 项目里使用 ECharts,核心步骤是稳定的:

npm install echarts

然后在需要展示图表的组件里:

<script setup> import * as echarts from 'echarts' import { ref, onMounted, onBeforeUnmount } from 'vue' const chartRef = ref(null) let chartInstance = null onMounted(() => { chartInstance = echarts.init(chartRef.value) // 这里调用接口拿到数据后 setOption }) onBeforeUnmount(() => { chartInstance && chartInstance.dispose() }) </script> <template> <div ref="chartRef" style="height: 400px;"></div> </template>

这里有几个关键点:

  • chartRef是模板里的 DOM 引用,echarts.init必须在这个元素渲染完成后调用,所以写在onMounted里。
  • 组件销毁时一定要dispose,否则会报 “There is a chart instance already initialized on the dom” 的警告。
  • setOption可以重复调用,所以数据刷新时不需要重新init,直接用同一个实例更新配置。

4.3 一个常见的数据格式坑

ECharts 的饼图数据格式是[{ name: '餐饮', value: 100 }, { name: '交通', value: 30 }],而后端统计接口返回的可能是[{ categoryName: '餐饮', total: 100 }]。你需要在接口返回时或者在前端做一次 map:

const pieData = res.data.map(item => ({ name: item.categoryName, value: item.total }))

这个转换看起来很简单,但恰恰是很多人卡住的地方:接口返回的字段名和后端实体字段对应不上,undefined 混进图表,饼图就变成空白。所以前端在接到接口数据后,先console.log看一眼数据,再决定要不要转换。

建议:每个图表组件只做一件事——接收数据、渲染图表。不要在前端组件里写复杂的统计逻辑。统计逻辑放后端,前端只负责把后端的统计结果呈现出来。这样分工,答辩时也好讲。

5. 前后端联调最容易踩的坑,以及标准排查链路

这个项目里,前端和后端各自开发的时候都很顺,一联调就出问题。这是所有前后端分离项目的通病,也是课设阶段最能拉开差距的地方。下面四类问题是我见过出现频率最高的。

5.1 跨域问题

前端在 5173 端口,后端在 8080 端口,前端直接请求后端接口会被浏览器拦截。这是最常见的第一个联调问题。

处理方式有两种:

  • 后端配置 CORS:允许指定来源访问。
  • 前端配置 Vite 代理:在vite.config.js里设置 proxy,让请求转发到后端。

对课设来说,方式二更接近生产环境,因为部署时前端和后端往往经过同一个网关或同一个 Web 服务器。一个常见写法:

// vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

这样前端请求/api/bill/list会被转发到http://localhost:8080/api/bill/list,浏览器看到的是同源请求,不会触发跨域。

5.2 时间格式和金额类型的坑

后端返回的时间是2025-06-01T12:00:00这种 ISO 格式,前端要显示成2025-06-01,这就需要在后端统一配置 JSON 序列化格式。如果是 Spring Boot 3,常用做法是配置 Jackson:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

金额在后端是BigDecimal,返回给前端的可能是100.00,也可能是1.0E+2这种科学计数法。为了避免这个问题,金额字段在后端序列化为字符串,或者确保数据库是decimal(10,2)并且在 Java 里统一用BigDecimal且保留两位小数。

5.3 完整排查链路

当你发现前端页面白屏、图表不渲染、列表为空时,按这个顺序查:

  1. 打开浏览器 F12,看 Network 面板里接口请求是否发出。
  2. 看请求 URL 是否正确,是否走代理。
  3. 看请求是否返回 200,如果没有,去后端看日志,查跨域、路径、token 鉴权。
  4. 如果返回 200 但是数据为空,看后端 SQL 是否查到了数据,可以在 Navicat 里直接执行一下。
  5. 如果数据有值但页面不显示,把后端返回的 JSON 打出来,看字段名跟前端代码里取的是否一致。
  6. 如果是图表不渲染,先写一个写死的setOption看图表是否能渲染,如果能,说明是数据格式问题;如果图表本身都不能渲染,查容器高度是否被压缩为 0。

这条链路的核心思想是分层定位:先确定问题在网络层、数据处理层还是展示层。不要一上来就改代码,先看现象,再动手。

5.4 登录鉴权的边界

课设项目一般都会做登录功能。常见方案是 JWT:登录成功后后端返回一个 token,前端存在 localStorage 里,之后每个请求都在 header 里带上 token。

在 axios 里统一处理:

// request.js import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config })

这里要提醒一点:JWT 的过期时间、密钥、token 传输方式,在课设里可以简化,但你必须能讲清楚它的工作流程,因为这是答辩常问点。另一个容易被忽略的地方是:后端的 Spring Security 或拦截器要放行登录接口、验证码接口和静态资源,其他接口统一校验 token。如果你用的是拦截器,注意排除路径要写全,否则登录页都会请求失败。

6. 从“能跑”到“能讲清楚”:课设项目里真正值得投入的四个环节

很多同学的项目做到最后是“能跑”,但问细节就说不清楚。如果你希望这个项目成为简历上的亮点,下面四个环节值得认真打磨。

6.1 日志:给系统装上记录仪

后端至少要配置日志输出。Spring Boot 自带 Logback,在application.yml里可以配置日志级别和文件输出路径。请求进来时有入参日志,处理完有响应日志,出错时有堆栈信息。答辩时如果老师问“万一接口出错了怎么排查”,你完全可以当场演示:查看日志文件,定位异常,修复,重启。

前端也要有基础的错误提示。接口请求失败时,用 Element Plus 的 ElMessage 提示用户,而不是直接白屏。

6.2 统一返回结果和全局异常处理

后端接口不要直接返回裸数据,建议统一返回结构,例如:

{ "code": 200, "message": "success", "data": {} }

配合@RestControllerAdvice做全局异常处理,这样校验异常、业务异常、未知异常都能返回可控的格式,而不是一堆堆栈信息。这个设计一旦养成习惯,整个项目的代码风格会提升一大截。

6.3 参数校验不只是形式

写一个账单新增接口时,前端要校验金额是否大于 0、日期不能为空、分类不能为空,后端也要用@Valid+@NotBlank这些注解再做一次校验。前端校验是为了用户体验,后端校验是为了数据安全。课设答辩中这题是为了确认:你真的理解为什么后端校验必不可少。

一个最小实现:

// BillDTO.java public class BillDTO { @NotNull(message = "金额不能为空") @DecimalMin(value = "0.01", message = "金额必须大于0") private BigDecimal amount; @NotNull(message = "分类不能为空") private Long categoryId; }

Controller 里加@Valid注解,校验失败时会抛MethodArgumentNotValidException,再由全局异常处理器统一转换。

6.4 部署:本地跑通和线上可访问是两回事

课设最后一般都需要演示。本地跑没问题,最怕到演示机器上起不来。建议至少提前一天做一次干净的部署演练:克隆新代码、配置数据库、启动后端、启动前端、走一遍核心流程。

前端部署可以选择构建后放到 Nginx 里,也可以直接用npm run dev跑开发服务。后者更简单,但生产演示时不如前者专业。如果选择 Nginx,需要注意把前端路由的 history 模式配置好,否则刷新页面会 404。一个常见的 Nginx 配置:

location / { try_files $uri $uri/ /index.html; }

7. 如果要从这个课设里提炼一个可复用的方法论

做完这个项目,你会发现它其实是一个很经典的“业务系统”样本:有认证、有 CRUD、有统计、有可视化、有前后端联调。它几乎覆盖了一个中小型 Web 系统的所有关键环节。

这里面最值得沉淀的经验,我认为是一个三步法:

  1. 先定义统计口径,再设计表结构。没有统计需求,你无法知道账单表需要哪些索引、需要按什么维度聚合。
  2. 先跑通一条最小链路,再扩展批量功能。比如从“新增一笔账单”开始,到“查询账单列表”,到“按月统计”,最后到“图表展示”。链路完整了,再考虑批量导入、预算管理等附加功能。
  3. 先能稳定复现,再谈优化。遇到问题时,先确认问题的触发条件是否稳定复现,再做修改。项目里最容易翻车的不是代码写不出来,而是改了一行代码,不知道哪块被影响到了。

这三个步骤看起来简单,但它的价值在于:把一次课设从“写功能”升级为“做工程”。你在简历上用这个项目时,面试官大概率也会问“这个项目你遇到最大的难点是什么”。如果你回答“跨域问题”“ECharts 数据格式不对”,然后能完整讲清楚排查链路和最终方案,这个项目就可以成为你的加分项;如果你的回答是“项目功能都做完了,没什么难点”,那面试官就不知道该问什么了。

回到最开始那个判断:家庭理财记账系统的核心不是记账,而是数据从产生、存储、统计到可视化呈现的整个链路。把这个链路打通,它就不只是一份课设作业,而是一个你真正做过、理解过、能讲明白的完整项目。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询