1. 选这个题的逻辑为什么家庭记账系统能成为毕设“常青树”每年到毕设选题季总有人问SSM Vue 是不是过时了现在不是都推荐 Spring Boot 前后端分离吗我自己的看法是如果你想要的是一个稳、能讲清楚、论文好写、功能不容易翻车的题目SSM Vue 的搭配依然是稳妥选择。这也是为什么“家庭记账管理系统”这个命题在毕设里反复出现因为它属于那种“业务场景天然清晰、技术覆盖点足够广”的题目。先说家庭记账这件事本身。它的核心业务无非是用户注册登录、账目分类管理、收入支出记录的增删改查、按月/按分类统计、图表可视化。这些功能看起来简单但它恰好覆盖了 SSM 框架里最常考的几个点——Spring 的依赖注入与事务管理、SpringMVC 的请求流转与会话处理、MyBatis 的动态 SQL 与映射配置。而前端用 Vue又能把数据双向绑定、组件通信、路由跳转这些现代前端概念都用上。一个题同时照顾到后端和前端对于毕设答辩来说是很大的优势老师问后端你能答问前端你也能答。还有一点很实际这套组合的参考资料数量是所有技术栈里最多的几乎没有遇到问题搜不到的情况。不管是环境配置、依赖冲突还是前端跨域社区里都有现成案例。对时间紧的人来说这种“前人踩坑补给充分”的特性能省下大把时间。接下来我会按照整个项目从零到一的过程来讲先讲架构选型背后的考量再讲数据库设计然后分别拆解后端和前端的关键实现最后是避坑记录、论文组织思路和答辩高频考点。全程用我实际开发这套系统的经历来讲希望你能复现而不是看得懂但做不出来。2. SSM 与 Vue 的组合逻辑不只是“老技术”而是分工清晰的稳局2.1 为什么不去追 Spring Boot 潮流很多同学纠结既然 SSM 和 Spring Boot 底层都是 Spring为什么不直接用 Spring Boot省去一堆 XML 配置开发效率不是更高吗实话说如果你是自选题目、没有学校指定框架Spring Boot 确实更高效。但毕设这个场景有个特殊性很多学校的课程体系里SSM 是课堂教学内容老师对它的提问点更熟悉论文的“技术介绍”章节更有东西可写。SSM 需要你手写配置Spring 的 applicationContext.xml、SpringMVC 的 spring-mvc.xml、MyBatis 的 mybatis-config.xml还有 web.xml 里加载容器的配置。这些配置本身就是论文中“系统设计”章节的素材而 Spring Boot 让一切自动配置好之后反而少了体现理解的地方。另一个容易被忽略的点答辩时老师经常会问“Spring MVC 的处理流程是什么”“MyBatis 的 #{} 和 ${} 有什么区别”。这两个问题在 SSM 项目里是直接对你写的配置发问你能结合自己项目里的 mapper XML 和 controller 代码讲得很具体而如果用了 Spring Boot这种问题往往成了纯粹背概念。在答辩场景里手写配置不是负担反而是你证明自己“真做过”的证据。2.2 前端选 Vue 而不是 JSP 的原因传统 SSM 项目的前端一般是 JSP JSTL服务端渲染。但家庭记账系统对交互性要求不低账目列表要实时刷新、统计图表要随日期范围切换、表单校验要即时反馈。用 JSP 做这些功能每改一次筛选条件都要刷新页面体验很糟糕而且前后端代码耦合在同一个工程里维护和调试都别扭。Vue 的核心优势有几条恰好都能用在这个项目上数据双向绑定 v-model记账表单里的金额、分类、备注直接绑定到 data 属性避免了一堆 getElementById 取值写值组件化拆分账目列表、统计图表、分类管理各自独立成组件互不干扰vue-router 前端路由不同页面之间跳转不需要刷新配合后端接口做数据交互就是完整的前后端分离体验。这里要明确一点选择了 Vue 做前后端分离就意味着后端不再返回视图页面而是只返回 JSON 数据。后端的 Controller 方法全部加 ResponseBody前端通过 axios 发起异步请求。这种架构不仅是时代趋势也让系统逻辑更清晰——后端只关心数据和业务规则前端只关心展示和交互分工明确。2.3 这套架构的职责边界我用一张思维导图式的方式总结一下这个项目里每个技术的“岗位职责”技术组件职责典型应用场景Spring对象管理IoC与事务控制AOPService 层 Bean 的装配、记账事务回滚SpringMVC接收前端请求并路由处理 /api/bill/save 等 RESTful 请求MyBatis数据库访问与 SQL 控制账目 CRUD、分类统计查询Vue 2前端页面渲染与交互记账表单、账目列表、图表展示Vue Router前端路由管理登录页、首页、统计页、分类页axios发起 HTTP 请求前端与后端接口的 JSON 数据交互ECharts图表渲染月度收支趋势、分类占比饼图各行其职互不越界。SSM 管数据与业务Vue 管视图与交互这就是这套组合的核心分工逻辑。搞清楚了这一点后面写代码、写论文都会清晰很多。3. 数据地基家庭记账系统的数据库设计与模块边界3.1 表结构设计的核心思路数据库设计决定了整个系统能承载多少功能。我建议先不要一上来就堆表而是从“用户最关心什么”倒推。家庭记账的用户关心三件事钱花哪了、花了多少、还剩多少。围绕这三个问题核心表就出来了。我的设计中最核心的是四张表用户表 t_user - id INT 主键自增 - username VARCHAR(32) 唯一 - password VARCHAR(64) MD5加密存储 - nickname VARCHAR(32) - create_time DATETIME 账目分类表 t_category - id INT 主键自增 - user_id INT 关联用户 - name VARCHAR(32) 分类名餐饮、交通、购物… - type TINYINT 1收入 0支出 - create_time DATETIME 记账明细表 t_bill - id INT 主键自增 - user_id INT 关联用户 - category_id INT 关联分类 - amount DECIMAL(10,2) 金额 - remark VARCHAR(255) 备注 - bill_date DATE 消费日期 - create_time DATETIME 数据统计视图非物理表 - 逻辑上按 bill_date 分组按 category_id 关联分类名 - 用 SQL 统计 SUM(amount)、COUNT(*)有一个细节很关键每张业务表都冗余了 user_id 字段而不是依赖 Session 里的用户 ID 去全局过滤。这是出于两个考虑一是为了查询方便WHERE user_id#{id} 直接过滤数据避免和其他用户的数据混在一起二是为了安全——后端接口获取当前登录用户 ID不是完全信任前端传值而是从 Session 或 Token 中取这样别人就无法越权操作别人的账目。3.2 分类为什么要让用户自建而不是系统写死很多同学做记账系统会把分类写死成“餐饮、交通、购物、娱乐”这种固定枚举然后存到一个字典表里。这样开发快但答辩时很容易被问如果用户需要“宠物”“医疗”这种分类怎么办我的方案是分类归属用户自建。用户注册后系统默认给几套常用分类用户可以在“分类管理”页面自行增加、修改、删除。这个设计的直接好处是业务上更符合真实场景——每个家庭的支出结构不同自建分类才是真正的“家庭记账”功能上多出了一个“分类管理”模块系统功能和论文内容都更丰满答辩时可以说这体现了“用户自定义数据字典”的设计思想。对应的t_bill 表里不直接存分类名称而是存 category_id通过 JOIN 关联分类表查询名称。这样分类一旦改名历史账目显示的名称也会同步变化。这个设计在答辩里属于典型的“数据库规范化设计”考点值得在论文里展开写。3.3 金额字段设计的一个小教训金额字段我一开始用的是 DOUBLE后来实际测试时发现问题当金额经过多次求和统计浮点数精度开始出现误差——比如 0.1 0.2 变成 0.30000000000000004。这在记账系统里是绝对不能接受的因为用户看到的每一分钱都要精确。解决方式是把金额字段统一改成 DECIMAL(10,2)。MySQL 的 DECIMAL 是固定精度数值类型存进去是精确的小数不会出现浮点误差。同时要注意Java 实体类里对应的类型用 BigDecimal而不是 double 或 float因为 BigDecimal 的构造方式能保证从数据库读取到计算不丢失精度。这个改动虽然小但属于“不做就会在统计功能上线后被用户立刻发现”的类型。答辩时主动提到这个设计老师会觉得你考虑到了实际业务中数据精度的问题是个加分项。4. 后端到前端核心功能模块的落地逻辑4.1 记账功能的完整请求链路记一笔账是整个系统最核心的操作。它的流程看起来只是“前端填个表单后存入数据库”但内部链条值得展开因为这正是 SpringMVC 流程的缩影。用户在前端记账页面选择分类、输入金额、填写备注、选择日期点击提交后Vue 组件里的 methods 中调用 axios 发起 POST 请求submitBill(formData) { const param { categoryId: formData.categoryId, amount: formData.amount, remark: formData.remark, billDate: formData.billDate, type: formData.type }; axios.post(/api/bill/save, param, { headers: { Content-Type: application/json } }).then(res { if (res.data.code 200) { this.$message.success(记账成功); this.fetchBillList(); } }); }这里有个容易踩的坑默认情况下axios 的 POST 请求头是 application/json但如果后端 Controller 方法的参数是 POJO 类型SpringMVC 需要 RequestBody 注解才能完成 JSON 反序列化。如果漏了这个注解或 contentType 设置不对前端会收到 415 或 400 错误。后端对应的 Controller 片段Controller RequestMapping(/api/bill) public class BillController { Autowired private BillService billService; ResponseBody RequestMapping(value /save, method RequestMethod.POST) public Result save(RequestBody Bill bill, HttpSession session) { User user (User) session.getAttribute(loginUser); bill.setUserId(user.getId()); int rows billService.addBill(bill); if (rows 0) { return Result.success(); } return Result.error(记账失败); } }注意这里的 userId 不是前端传的而是从 Session 中取出当前登录用户设置进去。这就是前面说的后端不信任任何前端传来的身份信息权限判断必须发生在服务端。这一点在论文的“安全设计”章节中一定要写。Service 层要加事务。我日常写的删除操作是先删明细记录再更新分类统计两步操作必须保证原子性Transactional(rollbackFor Exception.class) public boolean deleteBill(Integer billId, Integer userId) { // 1. 删除 t_bill 中的记录 // 2. 同步更新 t_category 中的统计字段 // 任一步失败则整体回滚 }Transactional 是 Spring 声明式事务的注解rollbackFor Exception.class 表示遇到任何异常都回滚。我现在在项目里默认都写这个参数因为默认情况下 Spring 只对 RuntimeException 回滚如果 Service 里抛出的是受检异常事务不会回滚数据就会不一致。4.2 统计模块后端聚合还是前端计算统计功能是记账系统的亮点模块通常由 ECharts 展示柱状图和饼图。这里有个设计选择题统计数据由谁计算我建议后端计算聚合结果前端只负责渲染。理由很实在前端计算逻辑写在 JS 里不方便调试而且如果数据量大前端计算会卡页面。后端写 SQL 做聚合是一个很自然的事select idgetMonthlySummary parameterTypemap resultTypemap SELECT DATE_FORMAT(bill_date, %Y-%m) AS month, type, SUM(amount) AS totalAmount FROM t_bill WHERE user_id #{userId} AND bill_date BETWEEN #{startDate} AND #{endDate} GROUP BY DATE_FORMAT(bill_date, %Y-%m), type ORDER BY month /select这里 DATE_FORMAT 是 MySQL 的日期格式化函数%Y-%m 格式化为“2026-03”这样的月度字符串。GROUP BY 按月份 收支类型分组SUM 求和。这个 SQL 直接返回 Map 类型前端拿到的 JSON 长这样[ { month: 2026-01, type: 0, totalAmount: 2860.50 }, { month: 2026-01, type: 1, totalAmount: 12000.00 }, { month: 2026-02, type: 0, totalAmount: 3150.80 } ]MyBatis 返回 Map 形式的优点是很灵活不用为聚合结果专门建 VO 类查询结果直接序列化成 JSON 给前端。缺点是 Map 的 key 可能和数据库列名的驼峰/下划线转换相关建议在 mybatis-config.xml 中开启 mapUnderscoreToCamelCasesettings setting namemapUnderscoreToCamelCase valuetrue/ /settings这样数据库字段 bill_date 自动映射为 billDate前端取值更顺手。4.3 前端组件与路由的编排设计前端工程我用 Vue CLI 创建目录结构按模块分包。这里给出一个经过实践验证合理的结构src/ ├── api/ # 接口请求封装 │ ├── bill.js │ ├── category.js │ └── user.js ├── assets/ # 静态资源 ├── components/ # 公共组件 │ ├── Header.vue │ ├── BillTable.vue │ └── ChartPanel.vue ├── router/ │ └── index.js # 路由配置 ├── store/ # Vuex 状态管理 │ └── index.js ├── views/ # 页面组件 │ ├── Login.vue │ ├── Register.vue │ ├── Home.vue │ ├── BillManage.vue │ ├── Statistics.vue │ └── CategoryManage.vue └── App.vue路由配置的要点是需要登录后才能访问的页面要用路由守卫做拦截。Vue Router 提供了 beforeEach 钩子在每次路由切换前校验登录状态const router new VueRouter({ routes: [ { path: /login, component: Login }, { path: /bill, component: BillManage, meta: { requiresAuth: true } }, { path: /statistics, component: Statistics, meta: { requiresAuth: true } } ] }); router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });有了路由守卫用户未登录时直接访问 /bill 会被自动带到登录页。这个机制配合后端接口的 Session 或 Token 校验形成一套双层防护。ECharts 的使用上很多人会直接在每个页面里引入全量 ECharts导致打包体积很大。我建议按需引入import * as echarts from echarts/core; import { BarChart, PieChart } from echarts/charts; import { TitleComponent, TooltipComponent, LegendComponent } from echarts/components; import { CanvasRenderer } from echarts/renderers; echarts.use([BarChart, PieChart, TitleComponent, TooltipComponent, LegendComponent, CanvasRenderer]);实测下来按需引入的打包体积能减小近一半。对毕设而言性能不是核心指标但答辩时讲到“前端性能优化”这个点很加分。5. 避坑实录环境配置、调试手段与浏览器兼容性那点事5.1 环境配置中比例最高的失败原因这个项目涉及 Node.js、Maven、Tomcat、MySQL 四个环境任何一环配置不对都可能导致整个项目跑不起来。我最常遇到的三个坑第一个坑Node 版本与 Vue CLI 版本不匹配。有些同学下载了最新的 Node 20然后安装 Vue CLI 4.x结果启动项目时报错 gyp ERR 或者 node-sass 编译失败。原因很简单旧版本库不兼容新版 Node。我的建议是直接用 Vue CLI 5.x或者干脆手动指定 Node 的 LTS 版本如 16.x 或 18.x。如果是新环境直接装 Node 18 vue/cli 5.0.8实测几乎不会出现问题。第二个坑Maven 依赖下载慢或 jar 包冲突。建议在 settings.xml 中配置阿里云镜像源。SSM 项目依赖通常涉及 spring-webmvc、mybatis-spring、jackson-databind、javax.servlet-api 等版本之间要互相兼容。我实测好用的组合是Spring 5.2.x MyBatis 3.5.x mybatis-spring 2.0.x这几个版本搭档稳定网上排查资料也最全。第三个坑Tomcat 8 vs Tomcat 10 的 javax 和 jakarta 问题。目前很多新教程默认用 Tomcat 10但 Tomcat 10 把包名从 javax.servlet 改成了 jakarta.servlet。SSM 项目基于 javax.servlet 写的老代码在 Tomcat 10 下会报 ClassNotFoundException。如果你用的是 SSM 工程一定要选 Tomcat 8.5 或 9.0否则一启动就挂而且报错信息很容易误导人。5.2 前后端联调时跨域问题的标准解法前后端分离后前端运行在 http://localhost:8081Vue CLI 默认端口后端在 http://localhost:8080Tomcat直接发起 axios 请求会被浏览器拦截报 No Access-Control-Allow-Origin header is present。这就是跨域。毕设阶段我不建议为了做“真正的跨域”去配置 CORS 过滤器更常见也更简单的方案是在 Vue CLI 中配置代理。修改 vue.config.jsmodule.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };这个配置的原理是前端开发服务器接收 /api 开头的请求然后由 dev-server 转发到后端地址。因为转发过程发生在服务器端不涉及浏览器跨域策略所以不会报跨域错误。使用这个方案后前端代码中请求地址只需写相对路径/api/bill/list后端接口的完整路径保持/api/bill/list不变。有个小坑要提醒代理配置只对开发环境npm run serve有效。等最终部署时要么把前端的 dist 目录放进 Tomcat 的 webapps 里由同一个 Tomcat 同时提供静态资源和后端接口这就不用跨域要么给后端加一个 CORS 过滤器。我实际使用后觉得部署阶段把 Vue 打包后的静态文件放到 SSM 项目的 webapp 目录下是最省事的方式。因为毕设答辩通常是单机演示不需要独立的 Web 服务器。5.3 调试技巧与 360 浏览器兼容的两个话题热词里提到“vue项目怎么适配360浏览器”这个我确实遇到过。360安全浏览器默认使用 IE 内核而 Vue 2 依赖 ES6 的 Promise、箭头函数等特性低版本 IE 内核根本不认页面直接白屏。解决方案分两层第一层在 index.html 中添加兼容脚本script srchttps://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js/scriptbabel-polyfill 能在 IE 内核中模拟缺失的 API。但如果网络不稳定外链脚本加载失败会导致白屏建议把它下载到本地后引用。第二层用 babel 做语法转译。在 babel.config.js 中确保 transpile 了 node_modules 里的相关依赖。实际上更简单的办法是演示时直接用 Chrome并在项目里给出一个“推荐使用 Chrome 访问”的提示。答辩环境不是你完全可控的提前准备好 Chrome 便携版是一个实用策略。调试方面我强烈建议安装 Vue Devtools 浏览器插件。它能直接查看 Vue 组件的 data 数据、路由跳转记录、Vuex 状态变化。当你遇到“页面数据没渲染”这类问题打开 Devtools 看组件 data 里有没有值、有没有报错信息比盲改代码高效得多。热词里“vue打debug”就是这个意思——用 Vue Devtools 抓组件状态用浏览器开发者工具 Network 面板抓请求状态双管齐下。6. 论文组织与答辩考点让程序变成高分的呈现逻辑6.1 论文结构的倒推设计很多人写完代码才写论文导致论文是“根据代码回忆出来的流水账”。我更推荐提前规划论文结构让代码开发的过程反向为论文服务。家庭记账管理系统的论文我建议按这个骨架组织论文章节章节内容与程序的对应关系绪论研究背景、国内外现状、选题意义与代码无关但需要系统性的文献与背景调查相关技术SSM框架、Vue.js、MySQL、ECharts把技术栈的介绍写清楚注意结合项目讲不空谈需求分析功能需求、非功能需求、用例分析来自功能模块的拆分代码开发前先画用例图系统设计系统架构图、数据库设计、接口设计对应架构搭建和建表脚本系统实现核心功能界面与业务流程代码这是与代码开发同步推进的章节系统测试功能测试、性能测试、测试用例开发完成后统一补总结与展望总结工作、提出不足与改进方向答辩前写好这个顺序的优点在于论文的每一章都能找到代码工程中的对应物。比如“需求分析”中的用例图对应的就是系统首页的功能菜单“系统设计”中的类图对应的就是 Controller、Service、Mapper 的分层结构。不用临时编造内容论文的每一段话都有真实来源。6.2 答辩时的高频问题与应答思路答辩环节老师最爱围绕这几个方向提问我把它们按优先级列出来对应着你要准备的知识点必问框架流程类。“SpringMVC 处理一个请求的过程是什么”回答要按链路讲前端发送 HTTP 请求DispatcherServlet 接收后通过 HandlerMapping 查找 Controller 方法HandlerAdapter 调用对应处理器Controller 处理完返回结果ViewResolver 解析视图前后端分离时直接返回 JSON最后响应给客户端。结合你项目里的 /api/bill/list 接口讲有实际代码支撑显得真实可信。高频数据库优化类。“统计查询数据量大怎么办”我的应答思路是先讲当前的 SQL 用了 GROUP BY SUM 聚合在数据量小时完全没问题然后指出 t_bill 表的 user_id 和 bill_date 字段在频繁查询中被用作 WHERE 条件应该建联合索引ALTER TABLE t_bill ADD INDEX idx_user_date (user_id, bill_date);联合索引能同时过滤用户和日期范围查询速度提升明显。再加一句“随着数据量进一步增加可以考虑按月份分表或引入缓存”表示你考虑过扩展性。这一套回答下来老师基本没有追问空间。常问安全设计类。“用户密码怎么存的”不要说你用的明文。我这边统一用 MD5 加盐方式。注册时将密码加盐哈希登录时校验哈希值。你还可以提到 Session 超时管理比如用户长时间不操作自动下线这是“非功能需求”中的安全性体现。6.3 可以提前准备的一两个亮点在系统里做一两个超出课程平均水平的亮点往往能拉开分数差距。我建议以下两个方向任选其一亮点一简易的预算提醒。给分类设置月度预算当月支出超过预算时首页弹出提醒。实现上后端每次记账后比较 t_category 表的 budget 字段和当月该分类的 SUM(amount)超过阈值返回提醒标记。这个功能业务价值直观代码量不大但明显超过了“基本增删改查”的水平。亮点二账单导出。用 POI 或 CSV 导出当前月份账单。对很多家庭用户来说记账不只是看图表还要有留存和归档。导出功能让系统更像一个“完整产品”。后端生成 CSV 的代码并不复杂因为 CSV 本质就是带逗号的文本不需要额外引入重依赖。这两个亮点任选一个写进论文的“系统特色”章节答辩介绍环节就会有一个清晰的记忆点不至于让老师在听完一堆 CRUD 功能后觉得平淡。写在最后的一点实在话做毕设这段时间我最深的一个体会是代码本身不是最大的障碍最大的障碍是对“自己为什么要这么做”解释不清楚。家庭记账管理系统之所以适合当毕设就是因为它的每个功能点都能向上追溯到业务需求向下落到具体技术——用 Spring 管理 Bean因为分类和账目之间存在依赖用事务保证一致性因为记账操作可能涉及多次数据库变更用 Vue 双向绑定因为表单交互频繁用 ECharts 做可视化因为用户需要直观看到收支结构。如果时间允许建议你在开发过程中随时把“这个功能为什么这么设计”的思考记录下来无论是写进论文还是用于答辩都是宝贵的素材。毕业后回头看这个系统也许不是最炫的技术但它帮你把开发一套完整业务系统的流程走了一遍这份经历比任何结论都值钱。
